From 3a55c8cc0ab1530c38ddee42c7efea956612c0fe Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Fri, 10 Mar 2017 17:17:37 +0000 Subject: [PATCH 1/7] Building components for battle centre --- src/app/components/BattleCentre.jsx | 98 +++++++ src/app/components/Cargo.jsx | 102 +++++++ src/app/components/EngagementRange.jsx | 123 +++++++++ src/app/components/Fuel.jsx | 96 +++++++ src/app/components/Pips.jsx | 358 +++++++++++++++++++++++++ src/app/components/SvgIcons.jsx | 18 ++ src/app/i18n/Language.jsx | 22 +- src/app/pages/OutfittingPage.jsx | 10 +- src/less/app.less | 1 + src/less/outfit.less | 8 + src/less/pips.less | 29 ++ 11 files changed, 846 insertions(+), 19 deletions(-) create mode 100644 src/app/components/BattleCentre.jsx create mode 100644 src/app/components/Cargo.jsx create mode 100644 src/app/components/EngagementRange.jsx create mode 100644 src/app/components/Fuel.jsx create mode 100644 src/app/components/Pips.jsx create mode 100755 src/less/pips.less diff --git a/src/app/components/BattleCentre.jsx b/src/app/components/BattleCentre.jsx new file mode 100644 index 00000000..fc6297ea --- /dev/null +++ b/src/app/components/BattleCentre.jsx @@ -0,0 +1,98 @@ +import React from 'react'; +import TranslatedComponent from './TranslatedComponent'; +import { Ships } from 'coriolis-data/dist'; +import Slider from '../components/Slider'; +import Pips from '../components/Pips'; +import Fuel from '../components/Fuel'; +import Cargo from '../components/Cargo'; +import EngagementRange from '../components/EngagementRange'; + +/** + * Battle centre allows you to pit your current build against another ship, + * adjust pips and engagement range, and see a wide variety of information + */ +export default class BattleCentre extends TranslatedComponent { + static PropTypes = { + ship: React.PropTypes.object.isRequired + }; + + static DEFAULT_OPPONENT = { ship: Ships['anaconda'] }; + + /** + * Constructor + * @param {Object} props React Component properties + * @param {Object} context React Component context + */ + constructor(props, context) { + super(props); + + const { ship } = this.props; + const opponent = BattleCentre.DEFAULT_OPPONENT; + + this.state = { }; + } + + componentWillReceiveProps(nextProps) { + // Rather than try to keep track of what changes our children require we force an update and let them work it out + this.forceUpdate(); + return true; + } + + /** + * Triggered when pips have been updated + */ + _pipsUpdated(sys, eng, wep) { + console.log('Pips are now ' + sys + '/' + eng + '/' + wep); + } + + /** + * Triggered when fuel has been updated + */ + _fuelUpdated(fuel) { + console.log('Fuel is now ' + fuel); + } + + /** + * Triggered when cargo has been updated + */ + _cargoUpdated(cargo) { + console.log('Cargo is now ' + cargo); + } + + /** + * Triggered when engagement range has been updated + */ + _engagementRangeUpdated(engagementRange) { + console.log('Engagement range is now ' + engagementRange); + } + + /** + * Render + * @return {React.Component} contents + */ + render() { + const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context; + const { formats, translate, units } = language; + const { against, expanded, maxRange, range, totals } = this.state; + const { ship } = this.props; + const shipUpdated = this._shipUpdated; + const pipsUpdated = this._pipsUpdated; + const fuelUpdated = this._fuelUpdated; + const cargoUpdated = this._cargoUpdated; + const engagementRangeUpdated = this._engagementRangeUpdated; + + return ( + +

{translate('battle centre')}

+
+ +
+
+ + + +
+
+ ); + } +} diff --git a/src/app/components/Cargo.jsx b/src/app/components/Cargo.jsx new file mode 100644 index 00000000..1e7ddbaf --- /dev/null +++ b/src/app/components/Cargo.jsx @@ -0,0 +1,102 @@ +import React from 'react'; +import TranslatedComponent from './TranslatedComponent'; +import { Ships } from 'coriolis-data/dist'; +import Slider from '../components/Slider'; + +/** + * Cargo slider + * Requires an onChange() function of the form onChange(cargo), providing the cargo in tonnes, which is triggered on cargo level change + */ +export default class Cargo extends TranslatedComponent { + static PropTypes = { + ship: React.PropTypes.object.isRequired, + onChange: React.PropTypes.func.isRequired + }; + + /** + * Constructor + * @param {Object} props React Component properties + * @param {Object} context React Component context + */ + constructor(props, context) { + super(props); + + const ship = this.props.ship; + + this.state = { + cargoCapacity: ship.cargoCapacity, + cargoLevel: 1, + }; + } + + /** + * Update the state if our ship changes + * @param {Object} nextProps Incoming/Next properties + * @return {boolean} Returns true if the component should be rerendered + */ + componentWillReceiveProps(nextProps) { + const { cargoLevel, cargoCapacity } = this.state; + const nextCargoCapacity = nextProps.ship.cargoCapacity; + + if (nextCargoCapacity != cargoCapacity) { + // We keep the absolute cargo amount the same if possible so recalculate the relative level + const nextCargoLevel = Math.min((cargoLevel * cargoCapacity) / nextCargoCapacity, 1); + + this.setState({ cargoLevel: nextCargoLevel, cargoCapacity: nextCargoCapacity }); + + // Notify if appropriate + if (nextCargoLevel * nextCargoCapacity != cargoLevel * cargoCapacity) { + this.props.onChange(Math.round(nextCargoLevel * nextCargoCapacity)); + } + } + return true; + } + + /** + * Update cargo level + * @param {number} cargoLevel percentage level from 0 to 1 + */ + _cargoChange(cargoLevel) { + const { cargoCapacity } = this.state; + // We round the cargo level to a suitable value given the capacity + cargoLevel = Math.round(cargoLevel * cargoCapacity) / cargoCapacity; + + if (cargoLevel != this.state.cargoLevel) { + this.setState({ cargoLevel }); + this.props.onChange(Math.round(cargoLevel * cargoCapacity)); + } + } + + /** + * Render cargo slider + * @return {React.Component} contents + */ + render() { + const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context; + const { formats, translate, units } = language; + const { cargoLevel, cargoCapacity } = this.state; + + return ( + +

{translate('cargo carried')}: {formats.int(cargoLevel * cargoCapacity)}{units.T}

+ + + + + + +
+ +
+
+ ); + } +} diff --git a/src/app/components/EngagementRange.jsx b/src/app/components/EngagementRange.jsx new file mode 100644 index 00000000..adfee2cd --- /dev/null +++ b/src/app/components/EngagementRange.jsx @@ -0,0 +1,123 @@ +import React from 'react'; +import TranslatedComponent from './TranslatedComponent'; +import { Ships } from 'coriolis-data/dist'; +import Slider from '../components/Slider'; + +/** + * Engagement range slider + * Requires an onChange() function of the form onChange(range), providing the range in metres, which is triggered on range change + */ +export default class Range extends TranslatedComponent { + static PropTypes = { + ship: React.PropTypes.object.isRequired, + onChange: React.PropTypes.func.isRequired + }; + + /** + * Constructor + * @param {Object} props React Component properties + * @param {Object} context React Component context + */ + constructor(props, context) { + super(props); + + const ship = this.props.ship; + + const maxRange = this._calcMaxRange(ship); + + this.state = { + maxRange: maxRange, + rangeLevel: 1, + }; + } + + /** + * Update the state if our ship changes + * @param {Object} nextProps Incoming/Next properties + * @return {boolean} Returns true if the component should be rerendered + */ + componentWillReceiveProps(nextProps) { + const { rangeLevel, maxRange } = this.state; + const nextMaxRange = this._calcMaxRange(nextProps.ship); + + if (nextMaxRange != maxRange) { + // We keep the absolute range amount the same if possible so recalculate the relative level + const nextRangeLevel = Math.min((rangeLevel * maxRange) / nextMaxRange, 1); + + this.setState({ rangeLevel: nextRangeLevel, maxRange: nextMaxRange }); + + // Notify if appropriate + if (nextRangeLevel * nextMaxRange != rangeLevel * maxRange) { + this.props.onChange(Math.round(nextRangeLevel * nextMaxRange)); + } + } + return true; + } + + /** + * Calculate the maximum range of a ship's weapons + * @param {Object} ship The ship + * @returns {int} The maximum range, in metres + */ + _calcMaxRange(ship) { + let maxRange = 1000; + for (let i = 0; i < ship.hardpoints.length; i++) { + if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) { + const thisRange = ship.hardpoints[i].m.getRange(); + if (thisRange > maxRange) { + maxRange = thisRange; + } + } + } + + return maxRange; + } + + /** + * Update range + * @param {number} range percentage level from 0 to 1 + */ + _rangeChange(rangeLevel) { + const { maxRange } = this.state; + // We round the range to an integer value + rangeLevel = Math.round(rangeLevel * maxRange) / maxRange; + + if (rangeLevel != this.state.rangeLevel) { + this.setState({ rangeLevel }); + this.props.onChange(Math.round(rangeLevel * maxRange)); + } + } + + /** + * Render range slider + * @return {React.Component} contents + */ + render() { + const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context; + const { formats, translate, units } = language; + const { rangeLevel, maxRange } = this.state; + + return ( + +

{translate('engagement range')}: {formats.int(rangeLevel * maxRange)}{translate('m')}

+ + + + + + +
+ +
+
+ ); + } +} diff --git a/src/app/components/Fuel.jsx b/src/app/components/Fuel.jsx new file mode 100644 index 00000000..eb5d98c6 --- /dev/null +++ b/src/app/components/Fuel.jsx @@ -0,0 +1,96 @@ +import React from 'react'; +import TranslatedComponent from './TranslatedComponent'; +import { Ships } from 'coriolis-data/dist'; +import Slider from '../components/Slider'; + +/** + * Fuel slider + * Requires an onChange() function of the form onChange(fuel), providing the fuel in tonnes, which is triggered on fuel level change + */ +export default class Fuel extends TranslatedComponent { + static PropTypes = { + ship: React.PropTypes.object.isRequired, + onChange: React.PropTypes.func.isRequired + }; + + /** + * Constructor + * @param {Object} props React Component properties + * @param {Object} context React Component context + */ + constructor(props, context) { + super(props); + + const ship = this.props.ship; + + this.state = { + fuelCapacity: ship.fuelCapacity, + fuelLevel: 1, + }; + } + + /** + * Update the state if our ship changes + * @param {Object} nextProps Incoming/Next properties + * @return {boolean} Returns true if the component should be rerendered + */ + componentWillReceiveProps(nextProps) { + const { fuelLevel, fuelCapacity } = this.state; + const nextFuelCapacity = nextProps.ship.fuelCapacity; + + if (nextFuelCapacity != fuelCapacity) { + // We keep the absolute fuel amount the same if possible so recalculate the relative level + const nextFuelLevel = Math.min((fuelLevel * fuelCapacity) / nextFuelCapacity, 1); + + this.setState({ fuelLevel: nextFuelLevel, fuelCapacity: nextFuelCapacity }); + + // Notify if appropriate + if (nextFuelLevel * nextFuelCapacity != fuelLevel * fuelCapacity) { + this.props.onChange(nextFuelLevel * nextFuelCapacity); + } + } + return true; + } + + /** + * Update fuel level + * @param {number} fuelLevel percentage level from 0 to 1 + */ + _fuelChange(fuelLevel) { + this.setState({ fuelLevel }); + this.props.onChange(fuelLevel * this.state.fuelCapacity); + } + + /** + * Render fuel slider + * @return {React.Component} contents + */ + render() { + const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context; + const { formats, translate, units } = language; + const { fuelLevel, fuelCapacity } = this.state; + + return ( + +

{translate('fuel carried')}: {formats.f2(fuelLevel * fuelCapacity)}{units.T}

+ + + + + + +
+ +
+
+ ); + } +} diff --git a/src/app/components/Pips.jsx b/src/app/components/Pips.jsx new file mode 100644 index 00000000..b6ecd59a --- /dev/null +++ b/src/app/components/Pips.jsx @@ -0,0 +1,358 @@ +import React from 'react'; +import TranslatedComponent from './TranslatedComponent'; +import { Ships } from 'coriolis-data/dist'; +import ShipSelector from './ShipSelector'; +import { nameComparator } from '../utils/SlotFunctions'; +import { Pip } from './SvgIcons'; +import LineChart from '../components/LineChart'; +import Slider from '../components/Slider'; +import * as ModuleUtils from '../shipyard/ModuleUtils'; +import Module from '../shipyard/Module'; + +/** + * Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area. + * Requires an onChange() function of the form onChange(sys, eng, wep) which is triggered whenever the pips change. + */ +export default class Pips extends TranslatedComponent { + static PropTypes = { + ship: React.PropTypes.object.isRequired, + onChange: React.PropTypes.func.isRequired + }; + + /** + * Constructor + * @param {Object} props React Component properties + * @param {Object} context React Component context + */ + constructor(props, context) { + super(props); + const ship = props.ship; + const pd = ship.standard[4].m; + + this._keyDown = this._keyDown.bind(this); + + let pipsSvg = this._renderPips(2, 2, 2); + this.state = { + sys: 2, + eng: 2, + wep: 2, + sysCap: pd.getSystemsCapacity(), + engCap: pd.getEnginesCapacity(), + wepCap: pd.getWeaponsCapacity(), + sysRate: pd.getSystemsRechargeRate(), + engRate: pd.getEnginesRechargeRate(), + wepRate: pd.getWeaponsRechargeRate(), + pipsSvg + }; + } + + /** + * Add listeners after mounting + */ + componentDidMount() { + document.addEventListener('keydown', this._keyDown); + } + + /** + * Remove listeners before unmounting + */ + componentWillUnmount() { + document.removeEventListener('keydown', this._keyDown); + } + + /** + * Update values if we change ship + * @param {Object} nextProps Incoming/Next properties + * @returns {boolean} Returns true if the component should be rerendered + */ + componentWillReceiveProps(nextProps) { + const { sysCap, engCap, wepCap, sysRate, engRate, wepRate } = this.state; + const ship = nextProps.ship; + const pd = ship.standard[4].m; + + const nextSysCap = pd.getSystemsCapacity(); + const nextEngCap = pd.getEnginesCapacity(); + const nextWepCap = pd.getWeaponsCapacity(); + const nextSysRate = pd.getSystemsRechargeRate(); + const nextEngRate = pd.getEnginesRechargeRate(); + const nextWepRate = pd.getWeaponsRechargeRate(); + if (nextSysCap != sysCap || + nextEngCap != engCap || + nextWepCap != wepCap || + nextSysRate != sysRate || + nextEngRate != engRate || + nextWepRate != wepRate) { + this.setState({ + sysCap: nextSysCap, + engCap: nextEngCap, + wepCap: nextWepCap, + sysRate: nextSysRate, + engRate: nextEngRate, + wepRate: nextWepRate + }); + } + + return true; + } + + /** + * Handle Key Down + * @param {Event} e Keyboard Event + */ + _keyDown(e) { + switch (e.keyCode) { + case 37: // Left arrow == increase SYS + e.preventDefault(); + this._incSys(); + break; + case 38: // Up arrow == increase ENG + e.preventDefault(); + this._incEng(); + break; + case 39: // Right arrow == increase WEP + e.preventDefault(); + this._incWep(); + break; + case 40: // Down arrow == reset + e.preventDefault(); + this._reset(); + break; + } + } + + /** + * Reset the capacitor + */ + _reset() { + let { sys, eng, wep } = this.state; + if (sys != 2 || eng != 2 || wep != 2) { + sys = eng = wep = 2; + this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) }); + this.props.onChange(sys, eng, wep); + } + } + + /** + * Increment the SYS capacitor + */ + _incSys() { + let { sys, eng, wep } = this.state; + + const required = Math.min(1, 4 - sys); + if (required > 0) { + if (required == 0.5) { + // Take from whichever is larger + if (eng > wep) { + eng -= 0.5; + sys += 0.5; + } else { + wep -= 0.5; + sys += 0.5; + } + } else { + // Required is 1 - take from both if possible + if (eng == 0) { + wep -= 1; + sys += 1; + } else if (wep == 0) { + eng -= 1; + sys += 1; + } else { + eng -= 0.5; + wep -= 0.5; + sys += 1; + } + } + this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) }); + this.props.onChange(sys, eng, wep); + } + } + + /** + * Increment the ENG capacitor + */ + _incEng() { + let { sys, eng, wep } = this.state; + + const required = Math.min(1, 4 - eng); + if (required > 0) { + if (required == 0.5) { + // Take from whichever is larger + if (sys > wep) { + sys -= 0.5; + eng += 0.5; + } else { + wep -= 0.5; + eng += 0.5; + } + } else { + // Required is 1 - take from both if possible + if (sys == 0) { + wep -= 1; + eng += 1; + } else if (wep == 0) { + sys -= 1; + eng += 1; + } else { + sys -= 0.5; + wep -= 0.5; + eng += 1; + } + } + this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) }); + this.props.onChange(sys, eng, wep); + } + } + + /** + * Increment the WEP capacitor + */ + _incWep() { + let { sys, eng, wep } = this.state; + + const required = Math.min(1, 4 - wep); + if (required > 0) { + if (required == 0.5) { + // Take from whichever is larger + if (sys > eng) { + sys -= 0.5; + wep += 0.5; + } else { + eng -= 0.5; + wep += 0.5; + } + } else { + // Required is 1 - take from both if possible + if (sys == 0) { + eng -= 1; + wep += 1; + } else if (eng == 0) { + sys -= 1; + wep += 1; + } else { + sys -= 0.5; + eng -= 0.5; + wep += 1; + } + } + this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) }); + this.props.onChange(sys, eng, wep); + } + } + + /** + * Handle a click + */ + onClick(which) { + if (which == 'SYS') { + this._incSys(); + } else if (which == 'ENG') { + this._incEng(); + } else if (which == 'WEP') { + this._incWep(); + } else if (which == 'RST') { + this._reset(); + } + } + + /** + * Set up the rendering for pips + * @param {int} sys the SYS pips + * @param {int} eng the ENG pips + * @param {int} wep the WEP pips + * @returns {Object} Object containing the rendering for the pips + */ + _renderPips(sys, eng, wep) { + const pipsSvg = {}; + + // SYS + pipsSvg['SYS'] = []; + for (let i = 0; i < Math.floor(sys); i++) { + pipsSvg['SYS'].push(); + } + if (sys > Math.floor(sys)) { + pipsSvg['SYS'].push(); + } + for (let i = Math.floor(sys + 0.5); i < 4; i++) { + pipsSvg['SYS'].push(); + } + + // ENG + pipsSvg['ENG'] = []; + for (let i = 0; i < Math.floor(eng); i++) { + pipsSvg['ENG'].push(); + } + if (eng > Math.floor(eng)) { + pipsSvg['ENG'].push(); + } + for (let i = Math.floor(eng + 0.5); i < 4; i++) { + pipsSvg['ENG'].push(); + } + + // WEP + pipsSvg['WEP'] = []; + for (let i = 0; i < Math.floor(wep); i++) { + pipsSvg['WEP'].push(); + } + if (wep > Math.floor(wep)) { + pipsSvg['WEP'].push(); + } + for (let i = Math.floor(wep + 0.5); i < 4; i++) { + pipsSvg['WEP'].push(); + } + + return pipsSvg; + } + + /** + * Render pips + * @return {React.Component} contents + */ + render() { + const { formats, translate, units } = this.context.language; + const { ship } = this.props; + const { sys, eng, wep, sysCap, engCap, wepCap, sysRate, engRate, wepRate, pipsSvg } = this.state; + + const onSysClicked = this.onClick.bind(this, 'SYS'); + const onEngClicked = this.onClick.bind(this, 'ENG'); + const onWepClicked = this.onClick.bind(this, 'WEP'); + const onRstClicked = this.onClick.bind(this, 'RST'); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
  {pipsSvg['ENG']} 
 {pipsSvg['SYS']}{translate('ENG')}{pipsSvg['WEP']}
 {translate('SYS')}{translate('RST')}{translate('WEP')}
{translate('capacity')} ({units.MJ}){formats.f1(sysCap)}{formats.f1(engCap)}{formats.f1(wepCap)}
{translate('recharge')} ({units.MW}){formats.f1(sysRate * (sys / 4))}{formats.f1(engRate * (eng / 4))}{formats.f1(wepRate * (wep / 4))}
+ ); + } +} diff --git a/src/app/components/SvgIcons.jsx b/src/app/components/SvgIcons.jsx index af119679..4593f173 100644 --- a/src/app/components/SvgIcons.jsx +++ b/src/app/components/SvgIcons.jsx @@ -708,6 +708,24 @@ export class Switch extends SvgIcon { } } +/** + * Pip + */ +export class Pip extends SvgIcon { + /** + * Overriden view box + * @return {String} view box + */ + viewBox() { return '0 0 200 200'; } + /** + * Generate the SVG + * @return {React.Component} SVG Contents + */ + svg() { + return ; + } +} + /** * In-game Coriolis Station logo */ diff --git a/src/app/i18n/Language.jsx b/src/app/i18n/Language.jsx index f22c0d42..e8d81da0 100644 --- a/src/app/i18n/Language.jsx +++ b/src/app/i18n/Language.jsx @@ -58,21 +58,21 @@ export function getLanguage(langCode) { }, translate, units: { - CR: {translate('CR')}, // Credits - kg: {translate('kg')}, // Kilograms - kgs: {translate('kg/s')}, // Kilograms per second - km: {translate('km')}, // Kilometers - Ls: {translate('Ls')}, // Light Seconds - LY: {translate('LY')}, // Light Years - MJ: {translate('MJ')}, // Mega Joules - 'm/s': {translate('m/s')}, // Meters per second - '°/s': {translate('°/s')}, // Degrees per second - MW: {translate('MW')}, // Mega Watts (same as Mega Joules per second) + CR: {translate('CR')}, // Credits + kg: {translate('kg')}, // Kilograms + kgs: {translate('kg/s')}, // Kilograms per second + km: {translate('km')}, // Kilometers + Ls: {translate('Ls')}, // Light Seconds + LY: {translate('LY')}, // Light Years + MJ: {translate('MJ')}, // Mega Joules + 'm/s': {translate('m/s')}, // Meters per second + '°/s': {translate('°/s')}, // Degrees per second + MW: {translate('MW')}, // Mega Watts (same as Mega Joules per second) mps: {translate('m/s')}, // Metres per second ps: {translate('/s')}, // per second pm: {translate('/min')}, // per minute s: {translate('secs')}, // Seconds - T: {translate('T')}, // Metric Tons + T: {translate('T')}, // Metric Tons } }; } diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index 0e50fc63..fb0ffbd3 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -20,13 +20,11 @@ import MovementSummary from '../components/MovementSummary'; import EngineProfile from '../components/EngineProfile'; import FSDProfile from '../components/FSDProfile'; import JumpRange from '../components/JumpRange'; -import DamageDealt from '../components/DamageDealt'; -import DamageReceived from '../components/DamageReceived'; +import BattleCentre from '../components/BattleCentre'; import PowerManagement from '../components/PowerManagement'; import CostSection from '../components/CostSection'; import ModalExport from '../components/ModalExport'; import ModalPermalink from '../components/ModalPermalink'; -import Slider from '../components/Slider'; /** * Document Title Generator @@ -373,11 +371,7 @@ export default class OutfittingPage extends Page {
- -
- -
- +
diff --git a/src/less/app.less b/src/less/app.less index ccff47d7..25d4de4a 100755 --- a/src/less/app.less +++ b/src/less/app.less @@ -19,6 +19,7 @@ @import 'shipselector'; @import 'sortable'; @import 'loader'; +@import 'pips'; html, body { height: 100%; diff --git a/src/less/outfit.less b/src/less/outfit.less index 8d5e7f0e..7b792542 100755 --- a/src/less/outfit.less +++ b/src/less/outfit.less @@ -198,6 +198,14 @@ }); } + &.twothirds { + width: 67%; + + .smallTablet({ + width: 100% !important; + }); + } + .smallScreen({ .axis.x { g.tick:nth-child(2n + 1) text { diff --git a/src/less/pips.less b/src/less/pips.less new file mode 100755 index 00000000..d1b09f43 --- /dev/null +++ b/src/less/pips.less @@ -0,0 +1,29 @@ +// The pips table - keep the background black +.pipstable { + background-color: @bgBlack; + color: @primary; +} + +// A clickable entity in the pips table +.pipsclickable { + cursor: pointer; +} + +// A full pip +.fullpip { + stroke: @primary; + fill: @primary; +} + +// A half pip +.halfpip { + stroke: @primary-disabled; + fill: @primary-disabled; +} + +// An empty pip +.emptypip { + stroke: @primary-bg; + fill: @primary-bg; +} + From 02db800c7b781155640e679aaec583245c2bac84 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sat, 11 Mar 2017 13:28:24 +0000 Subject: [PATCH 2/7] Link in components --- src/app/components/BattleCentre.jsx | 45 ++++++++++----- src/app/components/Cargo.jsx | 12 ++-- src/app/components/Movement.jsx | 85 +++++++++++++++++++++++++++++ src/app/shipyard/Calculations.js | 79 +++++++++++++++++++++++++++ src/app/shipyard/Ship.js | 48 ++++++++++++++++ src/less/app.less | 1 + src/less/movement.less | 26 +++++++++ 7 files changed, 276 insertions(+), 20 deletions(-) create mode 100644 src/app/components/Movement.jsx create mode 100644 src/less/movement.less diff --git a/src/app/components/BattleCentre.jsx b/src/app/components/BattleCentre.jsx index fc6297ea..c5187ff5 100644 --- a/src/app/components/BattleCentre.jsx +++ b/src/app/components/BattleCentre.jsx @@ -5,6 +5,7 @@ import Slider from '../components/Slider'; import Pips from '../components/Pips'; import Fuel from '../components/Fuel'; import Cargo from '../components/Cargo'; +import Movement from '../components/Movement'; import EngagementRange from '../components/EngagementRange'; /** @@ -29,7 +30,23 @@ export default class BattleCentre extends TranslatedComponent { const { ship } = this.props; const opponent = BattleCentre.DEFAULT_OPPONENT; - this.state = { }; + this._cargoUpdated = this._cargoUpdated.bind(this); + this._fuelUpdated = this._fuelUpdated.bind(this); + this._pipsUpdated = this._pipsUpdated.bind(this); + this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this); + + this.state = { + // Pips + sys: 2, + eng: 2, + wep: 2, + // Fuel + fuel: ship.fuelCapacity, + // Cargo + cargo: ship.cargoCapacity, + // Engagement range + engagementRange: 1500, + }; } componentWillReceiveProps(nextProps) { @@ -42,28 +59,28 @@ export default class BattleCentre extends TranslatedComponent { * Triggered when pips have been updated */ _pipsUpdated(sys, eng, wep) { - console.log('Pips are now ' + sys + '/' + eng + '/' + wep); + this.setState({ sys, eng, wep }); } /** * Triggered when fuel has been updated */ _fuelUpdated(fuel) { - console.log('Fuel is now ' + fuel); + this.setState({ fuel }); } /** * Triggered when cargo has been updated */ _cargoUpdated(cargo) { - console.log('Cargo is now ' + cargo); + this.setState({ cargo }); } /** * Triggered when engagement range has been updated */ _engagementRangeUpdated(engagementRange) { - console.log('Engagement range is now ' + engagementRange); + this.setState({ engagementRange }); } /** @@ -73,24 +90,22 @@ export default class BattleCentre extends TranslatedComponent { render() { const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context; const { formats, translate, units } = language; - const { against, expanded, maxRange, range, totals } = this.state; + const { sys, eng, wep, cargo, fuel, engagementRange, totals } = this.state; const { ship } = this.props; - const shipUpdated = this._shipUpdated; - const pipsUpdated = this._pipsUpdated; - const fuelUpdated = this._fuelUpdated; - const cargoUpdated = this._cargoUpdated; - const engagementRangeUpdated = this._engagementRangeUpdated; return (

{translate('battle centre')}

- +
- - - + + + +
+
+
); diff --git a/src/app/components/Cargo.jsx b/src/app/components/Cargo.jsx index 1e7ddbaf..f03ae01e 100644 --- a/src/app/components/Cargo.jsx +++ b/src/app/components/Cargo.jsx @@ -58,12 +58,14 @@ export default class Cargo extends TranslatedComponent { */ _cargoChange(cargoLevel) { const { cargoCapacity } = this.state; - // We round the cargo level to a suitable value given the capacity - cargoLevel = Math.round(cargoLevel * cargoCapacity) / cargoCapacity; + if (cargoCapacity > 0) { + // We round the cargo level to a suitable value given the capacity + cargoLevel = Math.round(cargoLevel * cargoCapacity) / cargoCapacity; - if (cargoLevel != this.state.cargoLevel) { - this.setState({ cargoLevel }); - this.props.onChange(Math.round(cargoLevel * cargoCapacity)); + if (cargoLevel != this.state.cargoLevel) { + this.setState({ cargoLevel }); + this.props.onChange(Math.round(cargoLevel * cargoCapacity)); + } } } diff --git a/src/app/components/Movement.jsx b/src/app/components/Movement.jsx new file mode 100644 index 00000000..3cf1dc8e --- /dev/null +++ b/src/app/components/Movement.jsx @@ -0,0 +1,85 @@ +import React from 'react'; +import cn from 'classnames'; +import TranslatedComponent from './TranslatedComponent'; + +/** + * Movement + */ +export default class Movement extends TranslatedComponent { + static PropTypes = { + ship: React.PropTypes.object.isRequired, + eng: React.PropTypes.number.isRequired, + fuel: React.PropTypes.number.isRequired, + cargo: React.PropTypes.number.isRequired + }; + + /** + * Constructor + * @param {Object} props React Component properties + */ + constructor(props) { + super(props); + + this._toggleBoost = this._toggleBoost.bind(this); + + this.state = { boost: false }; + } + + /** + * Toggle the boost feature + */ + _toggleBoost() { + let { boost } = this.state; + boost = !boost; + this.setState({ boost }); + } + + /** + * Render movement + * @return {React.Component} contents + */ + render() { + const { ship, eng, cargo, fuel } = this.props; + const { language } = this.context; + const { formats, translate, units } = language; + const { boost } = this.state; + + // + return ( + + + // Axes + + + + // End Arrow + + // Axes arcs and arrows + + + + + + + + + + + + + + + + // Speed + {formats.int(ship.calcSpeed(eng, fuel, cargo, boost))}m/s + // Pitch + {formats.f1(ship.calcPitch(eng, fuel, cargo, boost))}°/s + // Roll + {formats.f1(ship.calcRoll(eng, fuel, cargo, boost))}°/s + // Yaw + {formats.f1(ship.calcYaw(eng, fuel, cargo, boost))}°/s + + + ); + } +} diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js index 47b74b7f..e5b56dc0 100644 --- a/src/app/shipyard/Calculations.js +++ b/src/app/shipyard/Calculations.js @@ -173,3 +173,82 @@ function normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, bas res * (1 - (engpip * 1)), res]; } + +function calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base, engpip, eng) { + const xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass)); + const exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass))); + const ynorm = Math.pow(xnorm, exponent); + const mul = minMul + ynorm * (maxMul - minMul); + const res = base * mul; + + return res * (1 - (engpip * (4 - eng))); +} + +export function calcSpeed(mass, baseSpeed, thrusters, engpip, eng, boostFactor, boost) { + // thrusters might be a module or a template; handle either here + const minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; + const optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass; + const maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass; + const minMul = thrusters instanceof Module ? thrusters.getMinMul('speed') : (thrusters.minmulspeed ? thrusters.minmulspeed : thrusters.minmul); + const optMul = thrusters instanceof Module ? thrusters.getOptMul('speed') : (thrusters.optmulspeed ? thrusters.minmulspeed : thrusters.minmul); + const maxMul = thrusters instanceof Module ? thrusters.getMaxMul('speed') : (thrusters.maxmulspeed ? thrusters.minmulspeed : thrusters.minmul); + + let result = calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseSpeed, engpip, eng); + if (boost == true) { + result *= boostFactor; + } + + return result; +} + +export function calcPitch(mass, basePitch, thrusters, engpip, eng, boostFactor, boost) { + // thrusters might be a module or a template; handle either here + let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; + let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass; + let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass; + let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul); + let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul); + let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul); + + let result = calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, basePitch, engpip, eng); + if (boost == true) { + result *= boostFactor; + } + + return result; +} + +export function calcRoll(mass, baseRoll, thrusters, engpip, eng, boostFactor, boost) { + // thrusters might be a module or a template; handle either here + let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; + let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass; + let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass; + let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul); + let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul); + let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul); + + let result = calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseRoll, engpip, eng); + if (boost == true) { + result *= boostFactor; + } + + return result; +} + +export function calcYaw(mass, baseYaw, thrusters, engpip, eng, boostFactor, boost) { + // thrusters might be a module or a template; handle either here + let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; + let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass; + let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass; + let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul); + let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul); + let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul); + + let result = calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseYaw, engpip, eng); + if (boost == true) { + result *= boostFactor; + } + + return result; +} + diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index f1325dee..b8d11ade 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -194,6 +194,54 @@ export default class Ship { return Calc.speed(this.unladenMass + fuel + cargo, this.speed, this.standard[1].m, this.pipSpeed); } + /** + * Calculate the speed for a given configuration + * @param {Number} eng Number of pips in ENG + * @param {Number} fuel Amount of fuel carried + * @param {Number} cargo Amount of cargo carried + * @param {boolean} boost true if boost is applied + * @return {Number} Speed + */ + calcSpeed(eng, fuel, cargo, boost) { + return Calc.calcSpeed(this.unladenMass + fuel + cargo, this.speed, this.standard[1].m, this.pipSpeed, eng, this.topBoost / this.topSpeed, boost); + } + + /** + * Calculate the pitch for a given configuration + * @param {Number} eng Number of pips in ENG + * @param {Number} fuel Amount of fuel carried + * @param {Number} cargo Amount of cargo carried + * @param {boolean} boost true if boost is applied + * @return {Number} Pitch + */ + calcPitch(eng, fuel, cargo, boost) { + return Calc.calcPitch(this.unladenMass + fuel + cargo, this.pitch, this.standard[1].m, this.pipSpeed, eng, this.topBoost / this.topSpeed, boost); + } + + /** + * Calculate the roll for a given configuration + * @param {Number} eng Number of pips in ENG + * @param {Number} fuel Amount of fuel carried + * @param {Number} cargo Amount of cargo carried + * @param {boolean} boost true if boost is applied + * @return {Number} Roll + */ + calcRoll(eng, fuel, cargo, boost) { + return Calc.calcRoll(this.unladenMass + fuel + cargo, this.roll, this.standard[1].m, this.pipSpeed, eng, this.topBoost / this.topSpeed, boost); + } + + /** + * Calculate the yaw for a given configuration + * @param {Number} eng Number of pips in ENG + * @param {Number} fuel Amount of fuel carried + * @param {Number} cargo Amount of cargo carried + * @param {boolean} boost true if boost is applied + * @return {Number} Yaw + */ + calcYaw(eng, fuel, cargo, boost) { + return Calc.calcYaw(this.unladenMass + fuel + cargo, this.yaw, this.standard[1].m, this.pipSpeed, eng, this.topBoost / this.topSpeed, boost); + } + /** * Calculate the recovery time after losing or turning on shields * Thanks to CMDRs Al Gray, GIF, and Nomad Enigma for providing Shield recharge data and formulas diff --git a/src/less/app.less b/src/less/app.less index 25d4de4a..22417f53 100755 --- a/src/less/app.less +++ b/src/less/app.less @@ -20,6 +20,7 @@ @import 'sortable'; @import 'loader'; @import 'pips'; +@import 'movement'; html, body { height: 100%; diff --git a/src/less/movement.less b/src/less/movement.less new file mode 100644 index 00000000..bfd43bac --- /dev/null +++ b/src/less/movement.less @@ -0,0 +1,26 @@ + +#movement { + svg { + width: 100%; + height: 100%; + stroke: @primary-disabled; + fill: @primary-disabled; + + text { + stroke: @primary; + } + } + + button { + font-size: 1.4em; + background: @primary-bg; + color: @primary; + border: 1px solid @primary; + &.boost { + // Shown when boost is enabled + background: @primary; + color: @primary-bg; + } + } + +} From 3a271e4b7b3fec8b44884e0f60c3a619a5f52ea9 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sat, 11 Mar 2017 17:57:03 +0000 Subject: [PATCH 3/7] Add ship picker --- src/app/components/BarChart.jsx | 2 +- src/app/components/BattleCentre.jsx | 32 +++-- src/app/components/Cargo.jsx | 2 +- src/app/components/CostSection.jsx | 2 +- src/app/components/DamageDealt.jsx | 2 +- src/app/components/DamageReceived.jsx | 2 +- src/app/components/DefenceSummary.jsx | 2 +- src/app/components/EngagementRange.jsx | 2 +- src/app/components/EngineProfile.jsx | 2 +- src/app/components/FSDProfile.jsx | 2 +- src/app/components/Fuel.jsx | 2 +- src/app/components/JumpRange.jsx | 2 +- src/app/components/LineChart.jsx | 2 +- src/app/components/Movement.jsx | 2 +- src/app/components/MovementSummary.jsx | 2 +- src/app/components/OffenceSummary.jsx | 2 +- src/app/components/Pips.jsx | 2 +- src/app/components/PowerManagement.jsx | 2 +- src/app/components/ShipPicker.jsx | 143 ++++++++++++++++++++ src/app/components/ShipSelector.jsx | 2 +- src/less/app.less | 1 + src/less/shippicker.less | 180 +++++++++++++++++++++++++ 22 files changed, 361 insertions(+), 31 deletions(-) create mode 100644 src/app/components/ShipPicker.jsx create mode 100755 src/less/shippicker.less diff --git a/src/app/components/BarChart.jsx b/src/app/components/BarChart.jsx index 8ab59851..801e9f88 100644 --- a/src/app/components/BarChart.jsx +++ b/src/app/components/BarChart.jsx @@ -44,7 +44,7 @@ export default class BarChart extends TranslatedComponent { unit: '' }; - static PropTypes = { + static propTypes = { colors: React.PropTypes.array, data: React.PropTypes.array.isRequired, desc: React.PropTypes.bool, diff --git a/src/app/components/BattleCentre.jsx b/src/app/components/BattleCentre.jsx index c5187ff5..88ce19eb 100644 --- a/src/app/components/BattleCentre.jsx +++ b/src/app/components/BattleCentre.jsx @@ -1,24 +1,23 @@ import React from 'react'; import TranslatedComponent from './TranslatedComponent'; import { Ships } from 'coriolis-data/dist'; -import Slider from '../components/Slider'; -import Pips from '../components/Pips'; -import Fuel from '../components/Fuel'; -import Cargo from '../components/Cargo'; -import Movement from '../components/Movement'; -import EngagementRange from '../components/EngagementRange'; +import Slider from './Slider'; +import Pips from './Pips'; +import Fuel from './Fuel'; +import Cargo from './Cargo'; +import Movement from './Movement'; +import EngagementRange from './EngagementRange'; +import ShipPicker from './ShipPicker'; /** * Battle centre allows you to pit your current build against another ship, * adjust pips and engagement range, and see a wide variety of information */ export default class BattleCentre extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired }; - static DEFAULT_OPPONENT = { ship: Ships['anaconda'] }; - /** * Constructor * @param {Object} props React Component properties @@ -34,18 +33,17 @@ export default class BattleCentre extends TranslatedComponent { this._fuelUpdated = this._fuelUpdated.bind(this); this._pipsUpdated = this._pipsUpdated.bind(this); this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this); + this._targetShipUpdated = this._targetShipUpdated.bind(this); this.state = { // Pips sys: 2, eng: 2, wep: 2, - // Fuel fuel: ship.fuelCapacity, - // Cargo cargo: ship.cargoCapacity, - // Engagement range engagementRange: 1500, + targetShip: Ships['anaconda'] }; } @@ -83,6 +81,13 @@ export default class BattleCentre extends TranslatedComponent { this.setState({ engagementRange }); } + /** + * Triggered when target ship has been updated + */ + _targetShipUpdated(targetShip, targetBuild) { + this.setState({ targetShip, targetBuild: targetBuild }); + } + /** * Render * @return {React.Component} contents @@ -90,12 +95,13 @@ export default class BattleCentre extends TranslatedComponent { render() { const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context; const { formats, translate, units } = language; - const { sys, eng, wep, cargo, fuel, engagementRange, totals } = this.state; + const { sys, eng, wep, cargo, fuel, engagementRange } = this.state; const { ship } = this.props; return (

{translate('battle centre')}

+
diff --git a/src/app/components/Cargo.jsx b/src/app/components/Cargo.jsx index f03ae01e..25ec985a 100644 --- a/src/app/components/Cargo.jsx +++ b/src/app/components/Cargo.jsx @@ -8,7 +8,7 @@ import Slider from '../components/Slider'; * Requires an onChange() function of the form onChange(cargo), providing the cargo in tonnes, which is triggered on cargo level change */ export default class Cargo extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, onChange: React.PropTypes.func.isRequired }; diff --git a/src/app/components/CostSection.jsx b/src/app/components/CostSection.jsx index 2df44855..f0dc5bfd 100644 --- a/src/app/components/CostSection.jsx +++ b/src/app/components/CostSection.jsx @@ -12,7 +12,7 @@ import TranslatedComponent from './TranslatedComponent'; */ export default class CostSection extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, code: React.PropTypes.string.isRequired, buildName: React.PropTypes.string diff --git a/src/app/components/DamageDealt.jsx b/src/app/components/DamageDealt.jsx index bb4bd484..c9bd714e 100644 --- a/src/app/components/DamageDealt.jsx +++ b/src/app/components/DamageDealt.jsx @@ -50,7 +50,7 @@ export function weaponComparator(translate, propComparator, desc) { * Damage against a selected ship */ export default class DamageDealt extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, chartWidth: React.PropTypes.number.isRequired, code: React.PropTypes.string.isRequired diff --git a/src/app/components/DamageReceived.jsx b/src/app/components/DamageReceived.jsx index 294e4fbc..ec1799ed 100644 --- a/src/app/components/DamageReceived.jsx +++ b/src/app/components/DamageReceived.jsx @@ -45,7 +45,7 @@ export function weaponComparator(translate, propComparator, desc) { * Damage received by a selected ship */ export default class DamageReceived extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, code: React.PropTypes.string.isRequired }; diff --git a/src/app/components/DefenceSummary.jsx b/src/app/components/DefenceSummary.jsx index 21896b90..ba6ac4e5 100644 --- a/src/app/components/DefenceSummary.jsx +++ b/src/app/components/DefenceSummary.jsx @@ -7,7 +7,7 @@ import { DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons'; * Defence summary */ export default class DefenceSummary extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired }; diff --git a/src/app/components/EngagementRange.jsx b/src/app/components/EngagementRange.jsx index adfee2cd..c09b56ec 100644 --- a/src/app/components/EngagementRange.jsx +++ b/src/app/components/EngagementRange.jsx @@ -8,7 +8,7 @@ import Slider from '../components/Slider'; * Requires an onChange() function of the form onChange(range), providing the range in metres, which is triggered on range change */ export default class Range extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, onChange: React.PropTypes.func.isRequired }; diff --git a/src/app/components/EngineProfile.jsx b/src/app/components/EngineProfile.jsx index b8f874c3..0ff10850 100644 --- a/src/app/components/EngineProfile.jsx +++ b/src/app/components/EngineProfile.jsx @@ -13,7 +13,7 @@ import * as Calc from '../shipyard/Calculations'; * Engine profile for a given ship */ export default class EngineProfile extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, chartWidth: React.PropTypes.number.isRequired, code: React.PropTypes.string.isRequired diff --git a/src/app/components/FSDProfile.jsx b/src/app/components/FSDProfile.jsx index 1fa92289..b5138f9c 100644 --- a/src/app/components/FSDProfile.jsx +++ b/src/app/components/FSDProfile.jsx @@ -13,7 +13,7 @@ import * as Calc from '../shipyard/Calculations'; * FSD profile for a given ship */ export default class FSDProfile extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, chartWidth: React.PropTypes.number.isRequired, code: React.PropTypes.string.isRequired diff --git a/src/app/components/Fuel.jsx b/src/app/components/Fuel.jsx index eb5d98c6..c3f9cc6b 100644 --- a/src/app/components/Fuel.jsx +++ b/src/app/components/Fuel.jsx @@ -8,7 +8,7 @@ import Slider from '../components/Slider'; * Requires an onChange() function of the form onChange(fuel), providing the fuel in tonnes, which is triggered on fuel level change */ export default class Fuel extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, onChange: React.PropTypes.func.isRequired }; diff --git a/src/app/components/JumpRange.jsx b/src/app/components/JumpRange.jsx index d197ff04..e8929a58 100644 --- a/src/app/components/JumpRange.jsx +++ b/src/app/components/JumpRange.jsx @@ -13,7 +13,7 @@ import * as Calc from '../shipyard/Calculations'; * Jump range for a given ship */ export default class JumpRange extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, chartWidth: React.PropTypes.number.isRequired, code: React.PropTypes.string.isRequired diff --git a/src/app/components/LineChart.jsx b/src/app/components/LineChart.jsx index 41021ff2..dbd18b16 100644 --- a/src/app/components/LineChart.jsx +++ b/src/app/components/LineChart.jsx @@ -17,7 +17,7 @@ export default class LineChart extends TranslatedComponent { colors: ['#ff8c0d'] }; - static PropTypes = { + static propTypes = { width: React.PropTypes.number.isRequired, func: React.PropTypes.func.isRequired, xLabel: React.PropTypes.string.isRequired, diff --git a/src/app/components/Movement.jsx b/src/app/components/Movement.jsx index 3cf1dc8e..f61843df 100644 --- a/src/app/components/Movement.jsx +++ b/src/app/components/Movement.jsx @@ -6,7 +6,7 @@ import TranslatedComponent from './TranslatedComponent'; * Movement */ export default class Movement extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, eng: React.PropTypes.number.isRequired, fuel: React.PropTypes.number.isRequired, diff --git a/src/app/components/MovementSummary.jsx b/src/app/components/MovementSummary.jsx index adc3974e..f4c268f1 100644 --- a/src/app/components/MovementSummary.jsx +++ b/src/app/components/MovementSummary.jsx @@ -6,7 +6,7 @@ import TranslatedComponent from './TranslatedComponent'; * Movement summary */ export default class MovementSummary extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired }; diff --git a/src/app/components/OffenceSummary.jsx b/src/app/components/OffenceSummary.jsx index 1aac8b8c..d7778a70 100644 --- a/src/app/components/OffenceSummary.jsx +++ b/src/app/components/OffenceSummary.jsx @@ -7,7 +7,7 @@ import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive } from '. * Offence summary */ export default class OffenceSummary extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired }; diff --git a/src/app/components/Pips.jsx b/src/app/components/Pips.jsx index b6ecd59a..dfc7c7ce 100644 --- a/src/app/components/Pips.jsx +++ b/src/app/components/Pips.jsx @@ -14,7 +14,7 @@ import Module from '../shipyard/Module'; * Requires an onChange() function of the form onChange(sys, eng, wep) which is triggered whenever the pips change. */ export default class Pips extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, onChange: React.PropTypes.func.isRequired }; diff --git a/src/app/components/PowerManagement.jsx b/src/app/components/PowerManagement.jsx index e8a81fd0..39e78a19 100644 --- a/src/app/components/PowerManagement.jsx +++ b/src/app/components/PowerManagement.jsx @@ -17,7 +17,7 @@ const POWER = [ * Power Management Section */ export default class PowerManagement extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, code: React.PropTypes.string.isRequired, onChange: React.PropTypes.func.isRequired diff --git a/src/app/components/ShipPicker.jsx b/src/app/components/ShipPicker.jsx new file mode 100644 index 00000000..49b67a2e --- /dev/null +++ b/src/app/components/ShipPicker.jsx @@ -0,0 +1,143 @@ +import React from 'react'; +import TranslatedComponent from './TranslatedComponent'; +import Ship from '../shipyard/Ship'; +import { Ships } from 'coriolis-data/dist'; +import { Rocket } from './SvgIcons'; +import Persist from '../stores/Persist'; +import cn from 'classnames'; + +/** + * Ship picker + * Requires an onChange() function of the form onChange(ship), providing the ship, which is triggered on ship change + */ +export default class ShipPicker extends TranslatedComponent { + static propTypes = { + onChange: React.PropTypes.func.isRequired, + ship: React.PropTypes.object, + build: React.PropTypes.string + }; + + static defaultProps = { + ship: new Ship('anaconda', Ships['anaconda'].properties, Ships['anaconda'].slots) + } + + /** + * Constructor + * @param {Object} props React Component properties + * @param {Object} context React Component context + */ + constructor(props, context) { + super(props); + + this.shipOrder = Object.keys(Ships).sort(); + this._toggleMenu = this._toggleMenu.bind(this); + this._closeMenu = this._closeMenu.bind(this); + + this.state = { + ship: props.ship, + build: props.build + }; + } + + /** + * Update the state if our ship changes + * @param {Object} nextProps Incoming/Next properties + * @return {boolean} Returns true if the component should be rerendered + */ + componentWillReceiveProps(nextProps) { + const { ship, build } = this.state; + const { nextShip, nextBuild } = nextProps; + + if (nextShip != undefined && nextShip != ship && nextBuild != build) { + this.setState({ ship: nextShip, build: nextBuild }); + } + return true; + } + + /** + * Update ship + * @param {object} ship the ship + */ + _shipChange(shipId, build) { + const ship = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots); + if (build) { + // Ship is a particular build + ship.buildFrom(Persist.getBuild(shipId, build)); + } + this._closeMenu(); + this.setState({ ship, build }); + this.props.onChange(ship, build); + } + + /** + * Render the menu for the picker + */ + _renderPickerMenu() { + const { ship, build } = this.state; + const _shipChange = this._shipChange; + + const builds = Persist.getBuilds(); + const buildList = []; + for (let shipId of this.shipOrder) { + const shipBuilds = []; + // Add stock build + const stockSelected = (ship.id == shipId && !build); + shipBuilds.push(
  • Stock
  • ); + if (builds[shipId]) { + let buildNameOrder = Object.keys(builds[shipId]).sort(); + for (let buildName of buildNameOrder) { + const buildSelected = ship.id == shipId && build == buildName; + shipBuilds.push(
  • {buildName}
  • ); + } + } + buildList.push(
      {Ships[shipId].properties.name}{shipBuilds}
    ); + } + + return buildList; + } + + /** + * Toggle the menu state + */ + _toggleMenu() { + const { menuOpen } = this.state; + this.setState({ menuOpen: !menuOpen }); + } + + /** + * Close the menu + */ + _closeMenu() { + const { menuOpen } = this.state; + if (menuOpen) { + this._toggleMenu(); + } + } + + /** + * Render picker + * @return {React.Component} contents + */ + render() { + const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context; + const { formats, translate, units } = language; + const { menuOpen, ship, build } = this.state; + + const shipString = ship.name + ': ' + (build ? build : 'stock'); + return ( +
    e.stopPropagation() }> +
    +
    + {shipString} +
    + { menuOpen ? +
    e.stopPropagation() }> +
    + {this._renderPickerMenu()} +
    +
    : null } +
    +
    + ); + } +} diff --git a/src/app/components/ShipSelector.jsx b/src/app/components/ShipSelector.jsx index 34f11786..a48ba4a8 100644 --- a/src/app/components/ShipSelector.jsx +++ b/src/app/components/ShipSelector.jsx @@ -8,7 +8,7 @@ import { Rocket } from './SvgIcons'; * Selector for ships */ export default class ShipSelector extends TranslatedComponent { - static PropTypes = { + static propTypes = { initial: React.PropTypes.object.isRequired, onChange: React.PropTypes.func.isRequired }; diff --git a/src/less/app.less b/src/less/app.less index 22417f53..2af1c2f1 100755 --- a/src/less/app.less +++ b/src/less/app.less @@ -21,6 +21,7 @@ @import 'loader'; @import 'pips'; @import 'movement'; +@import 'shippicker'; html, body { height: 100%; diff --git a/src/less/shippicker.less b/src/less/shippicker.less new file mode 100755 index 00000000..11726fff --- /dev/null +++ b/src/less/shippicker.less @@ -0,0 +1,180 @@ +.shippicker { + background-color: @bgBlack; + margin: 0; + padding: 0 0 0 1em; + height: 3em; + line-height: 3em; + font-family: @fTitle; + vertical-align: middle; + position: relative; + + .user-select-none(); + + .menu { + position: relative; + z-index: 1; + cursor: default; + + &.r { + .menu-list { + right: 0; + } + } + + .smallTablet({ + position: static; + position: initial; + }); + } + + .menu-header { + height: 100%; + z-index: 2; + padding : 0 1em; + cursor: pointer; + color: @warning; + text-transform: uppercase; + // Less than 600px screen width: hide text + + &.disabled { + color: @warning-disabled; + cursor: default; + } + + &.selected { + background-color: @bgBlack; + } + + .menu-item-label { + margin-left: 1em; + display: inline-block; + + .smallTablet({ + display: none; + }); + } + } + + .menu-list { + font-family: @fStandard; + position: absolute; + padding: 0.5em 1em; + box-sizing: border-box; + min-width: 100%; + overflow-x: hidden; + background-color: @bgBlack; + font-size: 0.9em; + overflow-y: auto; + z-index: 0; + -webkit-overflow-scrolling: touch; + max-height: 500px; + + &::-webkit-scrollbar { + width: 0.5em; + } + + &::-webkit-scrollbar-track { + background-color: transparent; + } + + &::-webkit-scrollbar-thumb { + background-color: @warning-disabled; + } + + input { + border: none; + background-color: transparent; + text-align: right; + font-size: 1em; + font-family: @fStandard; + } + + .smallTablet({ + max-height: 400px; + left: 0; + right: 0; + border-bottom: 1px solid @bg; + }); + + .tablet({ + li, a { + padding: 0.3em 0; + } + }); + } + + .quad { + -webkit-column-count: 4; /* Chrome, Safari, Opera */ + -moz-column-count: 4; /* Firefox */ + column-count: 4; + ul { + min-width: 10em; + } + + .smallTablet({ + -webkit-column-count: 3; /* Chrome, Safari, Opera */ + -moz-column-count: 3; /* Firefox */ + column-count: 3; + + ul { + min-width: 20em; + } + }); + + .largePhone({ + -webkit-column-count: 2; /* Chrome, Safari, Opera */ + -moz-column-count: 2; /* Firefox */ + column-count: 2; + }); + + .smallPhone({ + -webkit-column-count: 1; /* Chrome, Safari, Opera */ + -moz-column-count: 1; /* Firefox */ + column-count: 1; + }); + } + + ul { + display: inline-block; + white-space: nowrap; + margin: 0 0 0.5em; + padding: 0; + line-height: 1.3em; + color: @fg; + } + + li { + white-space: normal; + list-style: none; + margin-left: 1em; + line-height: 1.1em; + color: @warning; + cursor: pointer; + + &.selected { + color: @primary; + } + } + + hr { + border: none; + border-top: 1px solid @disabled; + } + + .no-wrap { + overflow-x: auto; + white-space: nowrap; + } + + .block { + display: block; + line-height: 1.5em; + } + + .title { + font-size: 1.3em; + display: inline-block; + margin:0px; + text-transform: uppercase; + } +} From ec4e70326a1793010b29965a6fe98ffc570340b4 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sat, 11 Mar 2017 22:22:12 +0000 Subject: [PATCH 4/7] Set initial chart width --- src/app/pages/OutfittingPage.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index fb0ffbd3..35c43532 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -85,6 +85,7 @@ export default class OutfittingPage extends Page { title: this._getTitle(buildName), costTab: Persist.getCostTab() || 'costs', buildName, + thirdChartWidth: 400, newBuildName: buildName, shipId, ship, From 340121c6bdb4cbc308f18b6a11a0898dedf83602 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sun, 12 Mar 2017 17:00:04 +0000 Subject: [PATCH 5/7] Start of shields component --- .../components/.AvailableModulesMenu.jsx.swp | Bin 0 -> 16384 bytes src/app/components/.BattleCentre.jsx.swp | Bin 0 -> 20480 bytes src/app/components/BattleCentre.jsx | 38 +++- src/app/components/Cargo.jsx | 2 +- src/app/components/EngagementRange.jsx | 4 +- src/app/components/Movement.jsx | 10 +- src/app/components/Pips.jsx | 1 + src/app/components/Shields.jsx | 194 ++++++++++++++++++ src/app/components/ShipPicker.jsx | 4 +- src/app/shipyard/Calculations.js | 58 ++++++ src/app/shipyard/Ship.js | 6 +- 11 files changed, 296 insertions(+), 21 deletions(-) create mode 100644 src/app/components/.AvailableModulesMenu.jsx.swp create mode 100644 src/app/components/.BattleCentre.jsx.swp create mode 100644 src/app/components/Shields.jsx diff --git a/src/app/components/.AvailableModulesMenu.jsx.swp b/src/app/components/.AvailableModulesMenu.jsx.swp new file mode 100644 index 0000000000000000000000000000000000000000..12d3cfc9e0afa0f8a5dfceedba27b76485a3e6a9 GIT binary patch literal 16384 zcmeI3TWlOx8ONuPUVxTMP#-`@oB-(^do%&CZO?$% z5So>KJ3Hro{^vVq=6o~T9KUydf$gu4Tll=yvMyfv?)^`H>y?{c+Gkm3TLFb$_j61H z>DaVIj8g?8bAc&)ohgsT~TC;_3H+{Ykxw+5N1s>-0 zv+2h5N-yI}1xf|3t-x*8UHkURss7&g>|t-;ed^lD%h*zZQh`!|Qh`!|Qh`!|Qh`!| zQi1;~1+sXT^$GOob-GuV^znwi<7@h}tivDZi+@$e=Q{kkzWCSc^C^8?&=r_^bZ~hn z6(|)b6(|)b6(|)b6(|)b6(|)b6(|)b6(|+>4=7-}mbDkv`}a@C5i8_!MXX7dYSym<3bd zm76W=AK(hu1}8uT+yZ`g6WRlw1W$lR!RNqdz^B0_kb()Y3%q)xWxWV~0v-lufd@D^ z1MUL<++|tc2Q6TOd%$k+EF3T21`mRZAOa?g#*TPs7E_0tdSv4pk8Oz!tt)FQNm(K{#tMWCx zfl@?QgHfhJ*yN$2y=hy-3wD~aF|>o-aR&?P0iAx({Fq?lwNYK|x}Ag~Y?;%*{KyhJ zOnp1!MF1M2e)~LQ9WkJrYGWs;-V~u*l@(B0(r=VGd6J;*Qb45>#ceTk!L)&ymX8)% z6xw1?CRv1?oJW@hdW_-W%7)Q!+q}9YZ5C#Zw2hGwvPor# zax7u9$Y%z2NA1)|^jc_<%b2V(21=yOHqB$6s9}&HP$A8d+{q#xy5r#dclDd@w5v-b z)n{Xd*p`WlLF;vpa?Jv~w`WP~m%~&^9Xkwpl2#_z>57w9Mp@vfTX3VraMyH0O~^d4 zeU@+$dQswVsFlf7ZQ0HsiA^8dg8Qy&UfQBMo9Ym&L+a?>Ltt3|4oaQGljk_7^B5z{ z!xm4NW8)g4ii{x-%8VVM4uLwHHysv1j0*{^rAg%brt^-HXoC{Bn<|UW(Z(gh)}!pH zL0r>OUe0~lBrl^Zo*70@s8FHPIf*>SK{ebCtH0mpNhf17VmD{bBv)468+w!l-3Yu;+m=So%O1EEr+k z=C1l+4-JOx4@(^k4;~(h*?= zHYoGJ{CdvgH99xutCGm^>aX0-2OkB9flahcm<3sD|sN5lrMyXyCx2%0vQG;_W}x6m2xguNsRSf$=rUTe)$ z)s+LL6erY?@@g5{0WKRws_ zSJRXsaorVr*b&SuE}V#5UbH`H9DD`PzW;8p7mR}ua69-b_Vs@TFM`Y9 z2jD?K`~DArTfr{yGWz}k_!W2tTn41a&p`Pp6(|)b6(|)b6(|)b75E=lV7udMEd1C9 z8?IP81V@DWY;~NFb#lBK7kMWIIm@S&2bcbQIk`1>V{0-|kx1+d=iKC0mDk8q&g+!E z>n^xj>+-`(PrO6Ugh|zs!u{S|ro6#Efg9|}Ehns2Qt7&f(?GTz+t@g*7fEU+<3aS( zq84dCGdaoO700$8Er6$Lh$mWk)(U^tHaiYQWJ);KD5rD?kW2_1;>zXP@2%Xl9baw; zrx_`bjxzeG)vRfktp2a-eQ-W3afgdFF4IYDWlG_Fc z)tv$Vcu^U2>CHy^cKf_H$B1x$m3xc~Fl$FXb7cl$Vzt{Pd>4pkh!OY+07UvdBCJ@i&>W7_?ND z2MM5y`iJq1aOGaG-zGYxU_S}}lFO>7Qe^OdV))Zq&&HGlkjeVZLYv;WYBL%)6R|ML zn4Q7j6lc^(<%nu-GTJ~QLiU?Uci||r$ytT4Aa~BCERUuAsT}lxAwi`H4QMf=bcK66 zBH8r>o6oSt4zZ^Gsm;GX)7Y?F2M2PY7fY%{*$^lr_mFMTYIAJNt5Y@VmLILlv)Nit z`^?Z6vr^YN@F1{AU|PlK`A*fYOP$%|1@(6vtTO=wInQozvo*7OTpXE1krbzf7S6ag_`~Du?_x9>OtQu)>==z>!oqkfw{sjghxS9X} literal 0 HcmV?d00001 diff --git a/src/app/components/.BattleCentre.jsx.swp b/src/app/components/.BattleCentre.jsx.swp new file mode 100644 index 0000000000000000000000000000000000000000..a8be20232a793ea0242ccaddc4fa9cbafe6f54dc GIT binary patch literal 20480 zcmeI2Uu+yl9mlt6Na{dH)I7Wk^p-x6{$5l1-JV{bNq>));;&7w$I{=mQha);f3yNx0j+>mKr5gX z&Da@FMsQcnq|`aWD<0z)`Rr zyuHIPUIO0+kAiQ4uYoUtd%yyi1%H2!Vf+HDf>Yom_yBkYFJo>32f%)?6Z`=$V7?E& z2yO>Qz;)m^c(Lptv^roNjYmB}p3WR4RK2FYZz>%OzVhDM#X-3?-v+AQcGZmQ6nj<+Vv zZAgP6&tLQzvj*$;?Z|8o1_hqu42JY*S60$!5Lt3aXuwDuqPWN-e&G4E1u01w@o!TY zc39LY5vq_rG!o{{vkbHx33_3Tpcm9gm-#g!47o3D z8llweS=UJH+EFNSIp}>cN*&CjLQ#c#-gYgo3yJM7uo~gt3pBDll;?R)WTTN09s&O3 zt$NdF*j%za9*v^!MQ^+3*2TbBa6)nWl2a<|Rg zv{d;Z+cYH041uv6r%=VnhL%P*)v8jkYFsIJbhGlr1YH`V>{i<*EKRkjIDS^e#pv2) z=B-xAty)XJkEPWu;(cf29K zU@Bx>^@ETk%jisux_$K0m=Sx0P-Rub4$+Gqh;5i)n#-pF?GTdOwS0k@QJ=WzZ1a39 zL7^vIxJ=q^t8o^AVo+j1WV5tGN!veMF+NMr6vNxd-IUb*ZF8C+cGRRbi61$$Zrsz+?vIbVUip^Ix$f*M4TKG zC*=eq3~z?|)U>0XO2Dlnjpu*M(HK}K$q|(pRTZva0@;-qqUBiLBngoNxm>2GaU<=j zYCJEGFZE@NomHMi3_t0}wZS4ME2MPuX&oDsRj}WzfI`>?xwtIlwim)S$WSrwNH(je0(gDs zbLD)Z?jgBydzBey_*zG{3;}AOm)!v?7#NkD&E{d}OFf^3bx-uqx*aa${g)e*ZHg`@ z5<$qsVC%jYM8sk%GmOEU5*XmERtwRz9(g6KY+Eu^bbF6Yt8o zCCN#lCOUY?7@?$VTa2vHz-Cd8cq_zqNs9EBt0({D;+!He*|WPBxslaeo*juucrgO5k-!rQS%dD^o_EK&@m8 zY0{uP+3eshAuF#Go@ba#SAQ+*%&vUj4`I(kv5N|$JS~*+RYCOzSGGq3iwo%IiH6yRYT1yuJfrL{ zGHJk9WQgMX|EIBUe;x4sKeoc+^F!?OFM&1iK`;j1LYjYoH`4Tf<9YN)E1(t73TOqi z0$KsBfL1^&pcT*xXa%$aSDgag=lUs2XtI0ilrZUu+HHt;9x{V#({ z;38N9^MLySoCdSt2Jjlr`d(eWCg8pSJHR$@4R{OZ|IdMk!9!pH zjDuUi&EQ7xI{X2i1?wOHcYyt1FW3Xdz`x-G@H_AV_$6?`BzO%kI(`M72akd}_#V#l zzYD$rz5>pH8^J$uW`7y{7(5J4f!*L)X!aBk+Ff8I_b7u(iwVN<)r^( zPpNY2#kNnhnRdJwhv#^RBCLiZc(OFK$uiGY{hj!pQkfzDuS}5t-@x9K#3Cy95eF?82_*a-rK7?xemI1D$aHizW5)r&b;rrF&{uQeXw*qfXbCnxMw@ou=EF!!v z+UVo(yHH+X@D$}vo+h4F<}=2

    {translate('battle centre')}

    - +
    @@ -111,7 +128,10 @@ export default class BattleCentre extends TranslatedComponent {
    - + +
    +
    +
    ); diff --git a/src/app/components/Cargo.jsx b/src/app/components/Cargo.jsx index 25ec985a..bad1e2cf 100644 --- a/src/app/components/Cargo.jsx +++ b/src/app/components/Cargo.jsx @@ -25,7 +25,7 @@ export default class Cargo extends TranslatedComponent { this.state = { cargoCapacity: ship.cargoCapacity, - cargoLevel: 1, + cargoLevel: 0, }; } diff --git a/src/app/components/EngagementRange.jsx b/src/app/components/EngagementRange.jsx index c09b56ec..2f624bd0 100644 --- a/src/app/components/EngagementRange.jsx +++ b/src/app/components/EngagementRange.jsx @@ -26,7 +26,7 @@ export default class Range extends TranslatedComponent { const maxRange = this._calcMaxRange(ship); this.state = { - maxRange: maxRange, + maxRange, rangeLevel: 1, }; } @@ -75,7 +75,7 @@ export default class Range extends TranslatedComponent { /** * Update range - * @param {number} range percentage level from 0 to 1 + * @param {number} rangeLevel percentage level from 0 to 1 */ _rangeChange(rangeLevel) { const { maxRange } = this.state; diff --git a/src/app/components/Movement.jsx b/src/app/components/Movement.jsx index f61843df..a098ea2e 100644 --- a/src/app/components/Movement.jsx +++ b/src/app/components/Movement.jsx @@ -7,6 +7,7 @@ import TranslatedComponent from './TranslatedComponent'; */ export default class Movement extends TranslatedComponent { static propTypes = { + marker: React.PropTypes.string.isRequired, ship: React.PropTypes.object.isRequired, eng: React.PropTypes.number.isRequired, fuel: React.PropTypes.number.isRequired, @@ -44,7 +45,6 @@ export default class Movement extends TranslatedComponent { const { formats, translate, units } = language; const { boost } = this.state; - // return ( @@ -73,13 +73,13 @@ export default class Movement extends TranslatedComponent { // Speed {formats.int(ship.calcSpeed(eng, fuel, cargo, boost))}m/s // Pitch - {formats.f1(ship.calcPitch(eng, fuel, cargo, boost))}°/s + {formats.int(ship.calcPitch(eng, fuel, cargo, boost))}°/s // Roll - {formats.f1(ship.calcRoll(eng, fuel, cargo, boost))}°/s + {formats.int(ship.calcRoll(eng, fuel, cargo, boost))}°/s // Yaw - {formats.f1(ship.calcYaw(eng, fuel, cargo, boost))}°/s + {formats.int(ship.calcYaw(eng, fuel, cargo, boost))}°/s - + { ship.canBoost() ? : null } ); } } diff --git a/src/app/components/Pips.jsx b/src/app/components/Pips.jsx index dfc7c7ce..8d123e82 100644 --- a/src/app/components/Pips.jsx +++ b/src/app/components/Pips.jsx @@ -242,6 +242,7 @@ export default class Pips extends TranslatedComponent { /** * Handle a click + * @param {string} which Which item was clicked */ onClick(which) { if (which == 'SYS') { diff --git a/src/app/components/Shields.jsx b/src/app/components/Shields.jsx new file mode 100644 index 00000000..376f6f88 --- /dev/null +++ b/src/app/components/Shields.jsx @@ -0,0 +1,194 @@ +import React from 'react'; +import cn from 'classnames'; +import TranslatedComponent from './TranslatedComponent'; +import * as Calc from '../shipyard/Calculations'; +import { DamageAbsolute, DamageExplosive, DamageKinetic, DamageThermal } from './SvgIcons'; + +/** + * Shields + * Effective shield strength (including SCBs) + * Time for opponent to down shields + * - need sustained DPS for each type of damage (K/T/E/R) + * - turn in to % of shields removed per second + */ +export default class Shields extends TranslatedComponent { + static propTypes = { + marker: React.PropTypes.string.isRequired, + ship: React.PropTypes.object.isRequired, + opponent: React.PropTypes.object.isRequired, + sys: React.PropTypes.number.isRequired + }; + + /** + * Constructor + * @param {Object} props React Component properties + */ + constructor(props) { + super(props); + + const { shield, absolute, explosive, kinetic, thermal } = this._calcMetrics(props.ship, props.opponent, props.sys); + this.state = { shield, absolute, explosive, kinetic, thermal }; + } + + /** + * Update the state if our properties change + * @param {Object} nextProps Incoming/Next properties + * @return {boolean} Returns true if the component should be rerendered + */ + componentWillReceiveProps(nextProps) { + if (this.props.marker != nextProps.marker || this.props.sys != nextProps.sys) { + const { shield, absolute, explosive, kinetic, thermal } = this._calcMetrics(nextProps.ship, nextProps.opponent, nextProps.sys); + this.setState({ shield, absolute, explosive, kinetic, thermal }); + return true; + } + } + + /** + * Calculate shield metrics + * @param {Object} ship The ship + * @param {Object} opponent The opponent ship + * @param {int} sys The opponent ship + * @returns {Object} Shield metrics + */ + _calcMetrics(ship, opponent, sys) { + const sysResistance = this._calcSysResistance(sys); + + const shieldGenerator = ship.findShieldGenerator(); + + // Boosters + let boost = 1; + let boosterExplDmg = 1; + let boosterKinDmg = 1; + let boosterThermDmg = 1; + for (let slot of ship.hardpoints) { + if (slot.enabled && slot.m && slot.m.grp == 'sb') { + boost += slot.m.getShieldBoost(); + boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance()); + boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance()); + boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance()); + } + } + + // Calculate diminishing returns for boosters + boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5); + // Remove base shield generator strength + boost -= 1; + boosterExplDmg = boosterExplDmg > 0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterExplDmg) / 2; + boosterKinDmg = boosterKinDmg > 0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterKinDmg) / 2; + boosterThermDmg = boosterThermDmg > 0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterThermDmg) / 2; + + const generatorStrength = Calc.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1); + const boostersStrength = generatorStrength * boost; + const shield = { + generator: generatorStrength, + boosters: boostersStrength, + total: generatorStrength + boostersStrength + }; + + // Resistances have three components: the shield generator, the shield boosters and the SYS pips. + // We re-cast these as damage percentages + const absolute = { + generator: 1, + boosters: 1, + sys: 1 - sysResistance, + total: 1 - sysResistance + }; + + const explosive = { + generator: 1 - shieldGenerator.getExplosiveResistance(), + boosters: boosterExplDmg, + sys: (1 - sysResistance), + total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance) + }; + + const kinetic = { + generator: 1 - shieldGenerator.getKineticResistance(), + boosters: boosterKinDmg, + sys: (1 - sysResistance), + total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance) + }; + + const thermal = { + generator: 1 - shieldGenerator.getThermalResistance(), + boosters: boosterThermDmg, + sys: (1 - sysResistance), + total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance) + }; + + return { shield, absolute, explosive, kinetic, thermal }; + } + + /** + * Calculate the resistance provided by SYS pips + * @param {integer} sys the value of the SYS pips + * @returns {integer} the resistance for the given pips + */ + _calcSysResistance(sys) { + return Math.pow(sys,0.85) * 0.6 / Math.pow(4,0.85); + } + + /** + * Render shields + * @return {React.Component} contents + */ + render() { + const { ship, sys } = this.props; + const { language, tooltip, termtip } = this.context; + const { formats, translate, units } = language; + const { shield, absolute, explosive, kinetic, thermal } = this.state; + + const shieldTooltipDetails = []; + shieldTooltipDetails.push(
    {translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
    ); + shieldTooltipDetails.push(
    {translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
    ); + + const absoluteTooltipDetails = []; + absoluteTooltipDetails.push(
    {translate('generator') + ' ' + formats.pct1(absolute.generator)}
    ); + absoluteTooltipDetails.push(
    {translate('boosters') + ' ' + formats.pct1(absolute.boosters)}
    ); + absoluteTooltipDetails.push(
    {translate('power distributor') + ' ' + formats.pct1(absolute.sys)}
    ); + + const explosiveTooltipDetails = []; + explosiveTooltipDetails.push(
    {translate('generator') + ' ' + formats.pct1(explosive.generator)}
    ); + explosiveTooltipDetails.push(
    {translate('boosters') + ' ' + formats.pct1(explosive.boosters)}
    ); + explosiveTooltipDetails.push(
    {translate('power distributor') + ' ' + formats.pct1(explosive.sys)}
    ); + + const kineticTooltipDetails = []; + kineticTooltipDetails.push(
    {translate('generator') + ' ' + formats.pct1(kinetic.generator)}
    ); + kineticTooltipDetails.push(
    {translate('boosters') + ' ' + formats.pct1(kinetic.boosters)}
    ); + kineticTooltipDetails.push(
    {translate('power distributor') + ' ' + formats.pct1(kinetic.sys)}
    ); + + const thermalTooltipDetails = []; + thermalTooltipDetails.push(
    {translate('generator') + ' ' + formats.pct1(thermal.generator)}
    ); + thermalTooltipDetails.push(
    {translate('boosters') + ' ' + formats.pct1(thermal.boosters)}
    ); + thermalTooltipDetails.push(
    {translate('power distributor') + ' ' + formats.pct1(thermal.sys)}
    ); + + return ( + + + + + + + + + + + + + + +
    {shieldTooltipDetails})} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('shields')}: {formats.int(shield.total)}{units.MJ}
    {translate('damage from')} +   + {absoluteTooltipDetails})} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(absolute.total)} + +   + {explosiveTooltipDetails})} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(explosive.total)} + +   + {kineticTooltipDetails})} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(kinetic.total)} + +   + {thermalTooltipDetails})} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(thermal.total)} +
    +
    ); + } +} diff --git a/src/app/components/ShipPicker.jsx b/src/app/components/ShipPicker.jsx index 49b67a2e..9f319a14 100644 --- a/src/app/components/ShipPicker.jsx +++ b/src/app/components/ShipPicker.jsx @@ -56,7 +56,8 @@ export default class ShipPicker extends TranslatedComponent { /** * Update ship - * @param {object} ship the ship + * @param {object} shipId the ship + * @param {string} build the build, if present */ _shipChange(shipId, build) { const ship = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots); @@ -71,6 +72,7 @@ export default class ShipPicker extends TranslatedComponent { /** * Render the menu for the picker + * @returns {object} the picker menu */ _renderPickerMenu() { const { ship, build } = this.state; diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js index e5b56dc0..7e74913b 100644 --- a/src/app/shipyard/Calculations.js +++ b/src/app/shipyard/Calculations.js @@ -174,6 +174,20 @@ function normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, bas res]; } +/** + * Calculate a single value + * @param {number} minMass the minimum mass of the thrusters + * @param {number} optMass the optimum mass of the thrusters + * @param {number} maxMass the maximum mass of the thrusters + * @param {number} minMul the minimum multiplier of the thrusters + * @param {number} optMul the optimum multiplier of the thrusters + * @param {number} maxMul the maximum multiplier of the thrusters + * @param {number} mass the mass of the ship + * @param {base} base the base value from which to calculate + * @param {number} engpip the multiplier per pip to engines + * @param {number} eng the pips to engines + * @returns {number} the resultant value + */ function calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base, engpip, eng) { const xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass)); const exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass))); @@ -184,6 +198,17 @@ function calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base return res * (1 - (engpip * (4 - eng))); } +/** + * Calculate speed for a given setup + * @param {number} mass the mass of the ship + * @param {number} baseSpeed the base speed of the ship + * @param {ojbect} thrusters the thrusters of the ship + * @param {number} engpip the multiplier per pip to engines + * @param {number} eng the pips to engines + * @param {number} boostFactor the boost factor for ths ship + * @param {boolean} boost true if the boost is activated + * @returns {number} the resultant speed + */ export function calcSpeed(mass, baseSpeed, thrusters, engpip, eng, boostFactor, boost) { // thrusters might be a module or a template; handle either here const minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; @@ -201,6 +226,17 @@ export function calcSpeed(mass, baseSpeed, thrusters, engpip, eng, boostFactor, return result; } +/** + * Calculate pitch for a given setup + * @param {number} mass the mass of the ship + * @param {number} basePitch the base pitch of the ship + * @param {ojbect} thrusters the thrusters of the ship + * @param {number} engpip the multiplier per pip to engines + * @param {number} eng the pips to engines + * @param {number} boostFactor the boost factor for ths ship + * @param {boolean} boost true if the boost is activated + * @returns {number} the resultant pitch + */ export function calcPitch(mass, basePitch, thrusters, engpip, eng, boostFactor, boost) { // thrusters might be a module or a template; handle either here let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; @@ -218,6 +254,17 @@ export function calcPitch(mass, basePitch, thrusters, engpip, eng, boostFactor, return result; } +/** + * Calculate roll for a given setup + * @param {number} mass the mass of the ship + * @param {number} baseRoll the base roll of the ship + * @param {ojbect} thrusters the thrusters of the ship + * @param {number} engpip the multiplier per pip to engines + * @param {number} eng the pips to engines + * @param {number} boostFactor the boost factor for ths ship + * @param {boolean} boost true if the boost is activated + * @returns {number} the resultant roll + */ export function calcRoll(mass, baseRoll, thrusters, engpip, eng, boostFactor, boost) { // thrusters might be a module or a template; handle either here let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; @@ -235,6 +282,17 @@ export function calcRoll(mass, baseRoll, thrusters, engpip, eng, boostFactor, bo return result; } +/** + * Calculate yaw for a given setup + * @param {number} mass the mass of the ship + * @param {number} baseYaw the base yaw of the ship + * @param {ojbect} thrusters the thrusters of the ship + * @param {number} engpip the multiplier per pip to engines + * @param {number} eng the pips to engines + * @param {number} boostFactor the boost factor for ths ship + * @param {boolean} boost true if the boost is activated + * @returns {number} the resultant yaw + */ export function calcYaw(mass, baseYaw, thrusters, engpip, eng, boostFactor, boost) { // thrusters might be a module or a template; handle either here let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index b8d11ade..0caf79bd 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -1286,13 +1286,13 @@ export default class Ship { shield = Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.m, 1); shieldExplRes = 1 - sgSlot.m.getExplosiveResistance(); shieldExplDRStart = shieldExplRes * 0.7; - shieldExplDREnd = shieldExplRes * 0; // Currently don't know where this is + shieldExplDREnd = 0; shieldKinRes = 1 - sgSlot.m.getKineticResistance(); shieldKinDRStart = shieldKinRes * 0.7; - shieldKinDREnd = shieldKinRes * 0; // Currently don't know where this is + shieldKinDREnd = 0; shieldThermRes = 1 - sgSlot.m.getThermalResistance(); shieldThermDRStart = shieldThermRes * 0.7; - shieldThermDREnd = shieldThermRes * 0; // Currently don't know where this is + shieldThermDREnd = 0; // Shield from boosters for (let slot of this.hardpoints) { From 2731ec3b904766597c2f5295b053cb8cb44e3031 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sun, 12 Mar 2017 17:00:19 +0000 Subject: [PATCH 6/7] Remove swap files --- .../components/.AvailableModulesMenu.jsx.swp | Bin 16384 -> 0 bytes src/app/components/.BattleCentre.jsx.swp | Bin 20480 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/app/components/.AvailableModulesMenu.jsx.swp delete mode 100644 src/app/components/.BattleCentre.jsx.swp diff --git a/src/app/components/.AvailableModulesMenu.jsx.swp b/src/app/components/.AvailableModulesMenu.jsx.swp deleted file mode 100644 index 12d3cfc9e0afa0f8a5dfceedba27b76485a3e6a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI3TWlOx8ONuPUVxTMP#-`@oB-(^do%&CZO?$% z5So>KJ3Hro{^vVq=6o~T9KUydf$gu4Tll=yvMyfv?)^`H>y?{c+Gkm3TLFb$_j61H z>DaVIj8g?8bAc&)ohgsT~TC;_3H+{Ykxw+5N1s>-0 zv+2h5N-yI}1xf|3t-x*8UHkURss7&g>|t-;ed^lD%h*zZQh`!|Qh`!|Qh`!|Qh`!| zQi1;~1+sXT^$GOob-GuV^znwi<7@h}tivDZi+@$e=Q{kkzWCSc^C^8?&=r_^bZ~hn z6(|)b6(|)b6(|)b6(|)b6(|)b6(|)b6(|+>4=7-}mbDkv`}a@C5i8_!MXX7dYSym<3bd zm76W=AK(hu1}8uT+yZ`g6WRlw1W$lR!RNqdz^B0_kb()Y3%q)xWxWV~0v-lufd@D^ z1MUL<++|tc2Q6TOd%$k+EF3T21`mRZAOa?g#*TPs7E_0tdSv4pk8Oz!tt)FQNm(K{#tMWCx zfl@?QgHfhJ*yN$2y=hy-3wD~aF|>o-aR&?P0iAx({Fq?lwNYK|x}Ag~Y?;%*{KyhJ zOnp1!MF1M2e)~LQ9WkJrYGWs;-V~u*l@(B0(r=VGd6J;*Qb45>#ceTk!L)&ymX8)% z6xw1?CRv1?oJW@hdW_-W%7)Q!+q}9YZ5C#Zw2hGwvPor# zax7u9$Y%z2NA1)|^jc_<%b2V(21=yOHqB$6s9}&HP$A8d+{q#xy5r#dclDd@w5v-b z)n{Xd*p`WlLF;vpa?Jv~w`WP~m%~&^9Xkwpl2#_z>57w9Mp@vfTX3VraMyH0O~^d4 zeU@+$dQswVsFlf7ZQ0HsiA^8dg8Qy&UfQBMo9Ym&L+a?>Ltt3|4oaQGljk_7^B5z{ z!xm4NW8)g4ii{x-%8VVM4uLwHHysv1j0*{^rAg%brt^-HXoC{Bn<|UW(Z(gh)}!pH zL0r>OUe0~lBrl^Zo*70@s8FHPIf*>SK{ebCtH0mpNhf17VmD{bBv)468+w!l-3Yu;+m=So%O1EEr+k z=C1l+4-JOx4@(^k4;~(h*?= zHYoGJ{CdvgH99xutCGm^>aX0-2OkB9flahcm<3sD|sN5lrMyXyCx2%0vQG;_W}x6m2xguNsRSf$=rUTe)$ z)s+LL6erY?@@g5{0WKRws_ zSJRXsaorVr*b&SuE}V#5UbH`H9DD`PzW;8p7mR}ua69-b_Vs@TFM`Y9 z2jD?K`~DArTfr{yGWz}k_!W2tTn41a&p`Pp6(|)b6(|)b6(|)b75E=lV7udMEd1C9 z8?IP81V@DWY;~NFb#lBK7kMWIIm@S&2bcbQIk`1>V{0-|kx1+d=iKC0mDk8q&g+!E z>n^xj>+-`(PrO6Ugh|zs!u{S|ro6#Efg9|}Ehns2Qt7&f(?GTz+t@g*7fEU+<3aS( zq84dCGdaoO700$8Er6$Lh$mWk)(U^tHaiYQWJ);KD5rD?kW2_1;>zXP@2%Xl9baw; zrx_`bjxzeG)vRfktp2a-eQ-W3afgdFF4IYDWlG_Fc z)tv$Vcu^U2>CHy^cKf_H$B1x$m3xc~Fl$FXb7cl$Vzt{Pd>4pkh!OY+07UvdBCJ@i&>W7_?ND z2MM5y`iJq1aOGaG-zGYxU_S}}lFO>7Qe^OdV))Zq&&HGlkjeVZLYv;WYBL%)6R|ML zn4Q7j6lc^(<%nu-GTJ~QLiU?Uci||r$ytT4Aa~BCERUuAsT}lxAwi`H4QMf=bcK66 zBH8r>o6oSt4zZ^Gsm;GX)7Y?F2M2PY7fY%{*$^lr_mFMTYIAJNt5Y@VmLILlv)Nit z`^?Z6vr^YN@F1{AU|PlK`A*fYOP$%|1@(6vtTO=wInQozvo*7OTpXE1krbzf7S6ag_`~Du?_x9>OtQu)>==z>!oqkfw{sjghxS9X} diff --git a/src/app/components/.BattleCentre.jsx.swp b/src/app/components/.BattleCentre.jsx.swp deleted file mode 100644 index a8be20232a793ea0242ccaddc4fa9cbafe6f54dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 zcmeI2Uu+yl9mlt6Na{dH)I7Wk^p-x6{$5l1-JV{bNq>));;&7w$I{=mQha);f3yNx0j+>mKr5gX z&Da@FMsQcnq|`aWD<0z)`Rr zyuHIPUIO0+kAiQ4uYoUtd%yyi1%H2!Vf+HDf>Yom_yBkYFJo>32f%)?6Z`=$V7?E& z2yO>Qz;)m^c(Lptv^roNjYmB}p3WR4RK2FYZz>%OzVhDM#X-3?-v+AQcGZmQ6nj<+Vv zZAgP6&tLQzvj*$;?Z|8o1_hqu42JY*S60$!5Lt3aXuwDuqPWN-e&G4E1u01w@o!TY zc39LY5vq_rG!o{{vkbHx33_3Tpcm9gm-#g!47o3D z8llweS=UJH+EFNSIp}>cN*&CjLQ#c#-gYgo3yJM7uo~gt3pBDll;?R)WTTN09s&O3 zt$NdF*j%za9*v^!MQ^+3*2TbBa6)nWl2a<|Rg zv{d;Z+cYH041uv6r%=VnhL%P*)v8jkYFsIJbhGlr1YH`V>{i<*EKRkjIDS^e#pv2) z=B-xAty)XJkEPWu;(cf29K zU@Bx>^@ETk%jisux_$K0m=Sx0P-Rub4$+Gqh;5i)n#-pF?GTdOwS0k@QJ=WzZ1a39 zL7^vIxJ=q^t8o^AVo+j1WV5tGN!veMF+NMr6vNxd-IUb*ZF8C+cGRRbi61$$Zrsz+?vIbVUip^Ix$f*M4TKG zC*=eq3~z?|)U>0XO2Dlnjpu*M(HK}K$q|(pRTZva0@;-qqUBiLBngoNxm>2GaU<=j zYCJEGFZE@NomHMi3_t0}wZS4ME2MPuX&oDsRj}WzfI`>?xwtIlwim)S$WSrwNH(je0(gDs zbLD)Z?jgBydzBey_*zG{3;}AOm)!v?7#NkD&E{d}OFf^3bx-uqx*aa${g)e*ZHg`@ z5<$qsVC%jYM8sk%GmOEU5*XmERtwRz9(g6KY+Eu^bbF6Yt8o zCCN#lCOUY?7@?$VTa2vHz-Cd8cq_zqNs9EBt0({D;+!He*|WPBxslaeo*juucrgO5k-!rQS%dD^o_EK&@m8 zY0{uP+3eshAuF#Go@ba#SAQ+*%&vUj4`I(kv5N|$JS~*+RYCOzSGGq3iwo%IiH6yRYT1yuJfrL{ zGHJk9WQgMX|EIBUe;x4sKeoc+^F!?OFM&1iK`;j1LYjYoH`4Tf<9YN)E1(t73TOqi z0$KsBfL1^&pcT*xXa%$aSDgag=lUs2XtI0ilrZUu+HHt;9x{V#({ z;38N9^MLySoCdSt2Jjlr`d(eWCg8pSJHR$@4R{OZ|IdMk!9!pH zjDuUi&EQ7xI{X2i1?wOHcYyt1FW3Xdz`x-G@H_AV_$6?`BzO%kI(`M72akd}_#V#l zzYD$rz5>pH8^J$uW`7y{7(5J4f!*L)X!aBk+Ff8I_b7u(iwVN<)r^( zPpNY2#kNnhnRdJwhv#^RBCLiZc(OFK$uiGY{hj!pQkfzDuS}5t-@x9K#3Cy95eF?82_*a-rK7?xemI1D$aHizW5)r&b;rrF&{uQeXw*qfXbCnxMw@ou=EF!!v z+UVo(yHH+X@D$}vo+h4F<}=2 Date: Sun, 12 Mar 2017 17:00:33 +0000 Subject: [PATCH 7/7] Ignore swap files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1bbc61e9..0dee7465 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ nginx.pid .idea /bin env +*.swp