Added multi crew pips

This commit is contained in:
felixlinker
2018-09-23 22:30:05 +01:00
parent 18d78b3089
commit be8934da80
4 changed files with 185 additions and 172 deletions

View File

@@ -1,13 +1,8 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import { Pip } from './SvgIcons'; import { Pip } from './SvgIcons';
import LineChart from '../components/LineChart'; import autoBind from 'auto-bind';
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. * Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area.
@@ -18,6 +13,9 @@ export default class Pips extends TranslatedComponent {
sys: PropTypes.number.isRequired, sys: PropTypes.number.isRequired,
eng: PropTypes.number.isRequired, eng: PropTypes.number.isRequired,
wep: PropTypes.number.isRequired, wep: PropTypes.number.isRequired,
mcSys: PropTypes.number.isRequired,
mcEng: PropTypes.number.isRequired,
mcWep: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired onChange: PropTypes.func.isRequired
}; };
@@ -28,9 +26,7 @@ export default class Pips extends TranslatedComponent {
*/ */
constructor(props, context) { constructor(props, context) {
super(props); super(props);
const { sys, eng, wep } = props; autoBind(this);
this._keyDown = this._keyDown.bind(this);
} }
/** /**
@@ -74,30 +70,21 @@ export default class Pips extends TranslatedComponent {
} }
} }
/**
* Handle a click
* @param {string} which Which item was clicked
*/
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();
}
}
/** /**
* Reset the capacitor * Reset the capacitor
*/ */
_reset() { _reset(isMc) {
let { sys, eng, wep } = this.props; let { sys, eng, wep, mcSys, mcEng, mcWep } = this.props;
if (sys != 2 || eng != 2 || wep != 2) { if (isMc) {
if (mcSys || mcEng || mcWep) {
sys -= mcSys;
eng -= mcEng;
wep -= mcWep;
this.props.onChange(sys, eng, wep, 0, 0, 0);
}
} else if (sys != 2 || eng != 2 || wep != 2) {
sys = eng = wep = 2; sys = eng = wep = 2;
this.props.onChange(sys, eng, wep); this.props.onChange(sys + mcSys, eng + mcEng, wep + mcWep, mcSys, mcEng, mcWep);
} }
} }
@@ -105,151 +92,133 @@ export default class Pips extends TranslatedComponent {
* Increment the SYS capacitor * Increment the SYS capacitor
*/ */
_incSys() { _incSys() {
let { sys, eng, wep } = this.props; this._inc('sys', false);
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.props.onChange(sys, eng, wep);
}
} }
/** /**
* Increment the ENG capacitor * Increment the ENG capacitor
*/ */
_incEng() { _incEng() {
let { sys, eng, wep } = this.props; this._inc('eng', false);
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.props.onChange(sys, eng, wep);
}
} }
/** /**
* Increment the WEP capacitor * Increment the WEP capacitor
*/ */
_incWep() { _incWep() {
let { sys, eng, wep } = this.props; this._inc('wep', false);
}
const required = Math.min(1, 4 - wep); _wrapMcClick(key) {
if (required > 0) { return (event) => {
if (required == 0.5) { event.stopPropagation();
// Take from whichever is larger event.preventDefault();
if (sys > eng) { if (key == 'rst') {
sys -= 0.5; this._reset(true);
wep += 0.5;
} else {
eng -= 0.5;
wep += 0.5;
}
} else { } else {
// Required is 1 - take from both if possible this._inc(key, true);
if (sys == 0) { }
eng -= 1; };
wep += 1; }
} else if (eng == 0) {
sys -= 1; /**
wep += 1; * Increases a given capacitor
* @param {String} key Pip name to increase (one of 'sys', 'eng', 'wep')
* @param {Boolean} isMc True when increase is by multi crew
*/
_inc(key, isMc) {
if (!['sys', 'eng', 'wep'].includes(key)) {
return;
}
let { sys, eng, wep, mcSys, mcEng, mcWep } = this.props;
let mc = key == 'sys' ? mcSys : (key == 'eng' ? mcEng : mcWep);
let pips = this.props[key] - mc;
let other1 = key == 'sys' ? eng - mcEng : sys - mcSys;
let other2 = key == 'wep' ? eng - mcEng : wep - mcWep;
const required = Math.min(1, 4 - mc - pips);
if (isMc) {
// We can only set full pips in multi-crew also we can only set two pips
if (required > 0.5 && mcSys + mcEng + mcWep < 2) {
if (key == 'sys') {
mcSys += 1;
} else if (key == 'eng') {
mcEng += 1;
} else { } else {
sys -= 0.5; mcWep += 1;
eng -= 0.5;
wep += 1;
} }
} }
this.props.onChange(sys, eng, wep); } else if (required > 0) {
if (required == 0.5) {
// Take from whichever is larger
if (other1 > other2) {
other1 -= 0.5;
} else {
other2 -= 0.5;
}
pips += 0.5;
} else {
// Required is 1 - take from both if possible
if (other1 == 0) {
other2 -= 1;
} else if (other2 == 0) {
other1 -= 1;
} else {
other1 -= 0.5;
other2 -= 0.5;
}
pips += 1;
}
} }
sys = mcSys + (key == 'sys' ? pips : other1);
eng = mcEng + (key == 'eng' ? pips : (key == 'sys' ? other1 : other2));
wep = mcWep + (key == 'wep' ? pips : other2);
this.props.onChange(sys, eng, wep, mcSys, mcEng, mcWep);
} }
/** /**
* Set up the rendering for pips * Set up the rendering for pips
* @param {int} sys the SYS pips * @param {Number} sys the SYS pips
* @param {int} eng the ENG pips * @param {Number} eng the ENG pips
* @param {int} wep the WEP pips * @param {Number} wep the WEP pips
* @param {Number} mcSys SYS pips from multi-crew
* @param {Number} mcEng ENG pips from multi-crew
* @param {Number} mcWep WEP pips from multi-crew
* @returns {Object} Object containing the rendering for the pips * @returns {Object} Object containing the rendering for the pips
*/ */
_renderPips(sys, eng, wep) { _renderPips(sys, eng, wep, mcSys, mcEng, mcWep) {
const pipsSvg = {}; const pipsSvg = {
SYS: [],
ENG: [],
WEP: [],
};
// SYS // Multi-crew pipsSettings actually are included in the overall pip count therefore
pipsSvg['SYS'] = []; // we can consider [0, sys - mcSys] as normal pipsSettings whilst [sys - mcSys, sys]
for (let i = 0; i < Math.floor(sys); i++) { // are the multi-crew pipsSettings in what follows.
pipsSvg['SYS'].push(<Pip className='full' key={i} />);
}
if (sys > Math.floor(sys)) {
pipsSvg['SYS'].push(<Pip className='half' key={'half'} />);
}
for (let i = Math.floor(sys + 0.5); i < 4; i++) {
pipsSvg['SYS'].push(<Pip className='empty' key={i} />);
}
// ENG let pipsSettings = {
pipsSvg['ENG'] = []; SYS: [sys, mcSys],
for (let i = 0; i < Math.floor(eng); i++) { ENG: [eng, mcEng],
pipsSvg['ENG'].push(<Pip className='full' key={i} />); WEP: [wep, mcWep],
} };
if (eng > Math.floor(eng)) {
pipsSvg['ENG'].push(<Pip className='half' key={'half'} />);
}
for (let i = Math.floor(eng + 0.5); i < 4; i++) {
pipsSvg['ENG'].push(<Pip className='empty' key={i} />);
}
// WEP for (let pipName in pipsSettings) {
pipsSvg['WEP'] = []; let [pips, mcPips] = pipsSettings[pipName];
for (let i = 0; i < Math.floor(wep); i++) { for (let i = 0; i < Math.floor(pips - mcPips); i++) {
pipsSvg['WEP'].push(<Pip className='full' key={i} />); pipsSvg[pipName].push(<Pip key={i} className='full' />);
} }
if (wep > Math.floor(wep)) { if (pips > Math.floor(pips)) {
pipsSvg['WEP'].push(<Pip className='half' key={'half'} />); pipsSvg[pipName].push(<Pip className='half' key={'half'} />);
} }
for (let i = Math.floor(wep + 0.5); i < 4; i++) { for (let i = pips - mcPips; i < Math.floor(pips); i++) {
pipsSvg['WEP'].push(<Pip className='empty' key={i} />); pipsSvg[pipName].push(<Pip key={i} className='mc' />);
}
for (let i = Math.floor(pips + 0.5); i < 4; i++) {
pipsSvg[pipName].push(<Pip className='empty' key={i} />);
}
} }
return pipsSvg; return pipsSvg;
@@ -260,15 +229,11 @@ export default class Pips extends TranslatedComponent {
* @return {React.Component} contents * @return {React.Component} contents
*/ */
render() { render() {
const { tooltip, termtip } = this.context;
const { formats, translate, units } = this.context.language; const { formats, translate, units } = this.context.language;
const { sys, eng, wep } = this.props; const { sys, eng, wep, mcSys, mcEng, mcWep } = this.props;
const onSysClicked = this.onClick.bind(this, 'SYS'); const pipsSvg = this._renderPips(sys, eng, wep, mcSys, mcEng, mcWep);
const onEngClicked = this.onClick.bind(this, 'ENG');
const onWepClicked = this.onClick.bind(this, 'WEP');
const onRstClicked = this.onClick.bind(this, 'RST');
const pipsSvg = this._renderPips(sys, eng, wep);
return ( return (
<span id='pips'> <span id='pips'>
<table> <table>
@@ -276,20 +241,38 @@ export default class Pips extends TranslatedComponent {
<tr> <tr>
<td>&nbsp;</td> <td>&nbsp;</td>
<td>&nbsp;</td> <td>&nbsp;</td>
<td className='clickable' onClick={onEngClicked}>{pipsSvg['ENG']}</td> <td className='clickable' onClick={() => this._inc('eng')}
onContextMenu={this._wrapMcClick('eng')}>{pipsSvg['ENG']}</td>
<td>&nbsp;</td> <td>&nbsp;</td>
</tr> </tr>
<tr> <tr>
<td>&nbsp;</td> <td>&nbsp;</td>
<td className='clickable' onClick={onSysClicked}>{pipsSvg['SYS']}</td> <td className='clickable' onClick={this._incSys}
<td className='clickable' onClick={onEngClicked}>{translate('ENG')}</td> onContextMenu={this._wrapMcClick('sys')}>{pipsSvg['SYS']}</td>
<td className='clickable' onClick={onWepClicked}>{pipsSvg['WEP']}</td> <td className='clickable' onClick={this._incEng}
onContextMenu={this._wrapMcClick('eng')}>{translate('ENG')}</td>
<td className='clickable' onClick={this._incWep}
onContextMenu={this._wrapMcClick('wep')}>{pipsSvg['WEP']}</td>
</tr> </tr>
<tr> <tr>
<td>&nbsp;</td> <td>&nbsp;</td>
<td className='clickable' onClick={onSysClicked}>{translate('SYS')}</td> <td className='clickable' onClick={this._incSys}
<td className='clickable' onClick={onRstClicked}>{translate('RST')}</td> onContextMenu={this._wrapMcClick('sys')}>{translate('SYS')}</td>
<td className='clickable' onClick={onWepClicked}>{translate('WEP')}</td> <td className='clickable' onClick={this._reset.bind(this, false)}>
{translate('RST')}
</td>
<td className='clickable' onClick={this._incWep}
onContextMenu={this._wrapMcClick('wep')}>{translate('WEP')}</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td className='clickable secondary' onClick={this._wrapMcClick('rst')}
onMouseEnter={termtip.bind(null, 'PHRASE_MULTI_CREW_CAPACITOR_POINTS')}
onMouseLeave={tooltip.bind(null, null)}>
{translate('RST')}
</td>
<td>&nbsp;</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -43,6 +43,7 @@
"PHRASE_SHIELD_DAMAGE": "Breakdown of sources for sustained DPS against shields", "PHRASE_SHIELD_DAMAGE": "Breakdown of sources for sustained DPS against shields",
"PHRASE_ARMOUR_DAMAGE": "Breakdown of sources for sustained DPS against armour", "PHRASE_ARMOUR_DAMAGE": "Breakdown of sources for sustained DPS against armour",
"PHRASE_TIME_TO_REMOVE_SHIELDS": "Will remove shields in", "PHRASE_TIME_TO_REMOVE_SHIELDS": "Will remove shields in",
"PHRASE_MULTI_CREW_CAPACITOR_POINTS": "Right click a capacitor to assign multi-crew capacitor points.",
"TT_TIME_TO_REMOVE_SHIELDS": "With sustained fire by all weapons", "TT_TIME_TO_REMOVE_SHIELDS": "With sustained fire by all weapons",
"PHRASE_TIME_TO_REMOVE_ARMOUR": "Will remove armour in", "PHRASE_TIME_TO_REMOVE_ARMOUR": "Will remove armour in",
"TT_TIME_TO_REMOVE_ARMOUR": "With sustained fire by all weapons", "TT_TIME_TO_REMOVE_ARMOUR": "With sustained fire by all weapons",

View File

@@ -100,7 +100,7 @@ export default class OutfittingPage extends Page {
this._getTitle = getTitle.bind(this, data.properties.name); this._getTitle = getTitle.bind(this, data.properties.name);
// Obtain ship control from code // Obtain ship control from code
const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = this._obtainControlFromCode(ship, code); const { sys, eng, wep, mcSys, mcEng, mcWep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = this._obtainControlFromCode(ship, code);
return { return {
error: null, error: null,
title: this._getTitle(buildName), title: this._getTitle(buildName),
@@ -114,6 +114,9 @@ export default class OutfittingPage extends Page {
sys, sys,
eng, eng,
wep, wep,
mcSys,
mcEng,
mcWep,
boost, boost,
fuel, fuel,
cargo, cargo,
@@ -176,6 +179,9 @@ export default class OutfittingPage extends Page {
let sys = 2; let sys = 2;
let eng = 2; let eng = 2;
let wep = 2; let wep = 2;
let mcSys = 0;
let mcEng = 0;
let mcWep = 0;
let boost = false; let boost = false;
let fuel = ship.fuelCapacity; let fuel = ship.fuelCapacity;
let cargo = ship.cargoCapacity; let cargo = ship.cargoCapacity;
@@ -222,20 +228,31 @@ export default class OutfittingPage extends Page {
} }
} }
engagementRange = parseInt(control[8]); engagementRange = parseInt(control[8]);
// Multi-crew pips were introduced later on so assign default values
// because those values might not be present.
mcSys = parseInt(control[9]) || mcSys;
mcEng = parseInt(control[10]) || mcEng;
mcWep = parseInt(control[11]) || mcWep;
} }
} }
return { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange }; return { sys, eng, wep, mcSys, mcEng, mcWep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange };
} }
/** /**
* Triggered when pips have been updated * Triggered when pips have been updated. Multi-crew pips are already included
* in sys, eng and wep but mcSys, mcEng and mcWep make clear where each pip
* comes from.
* @param {number} sys SYS pips * @param {number} sys SYS pips
* @param {number} eng ENG pips * @param {number} eng ENG pips
* @param {number} wep WEP pips * @param {number} wep WEP pips
* @param {number} mcSys SYS pips from multi-crew
* @param {number} mcEng ENG pips from multi-crew
* @param {number} mcWep WEP pips from multi-crew
*/ */
_pipsUpdated(sys, eng, wep) { _pipsUpdated(sys, eng, wep, mcSys, mcEng, mcWep) {
this.setState({ sys, eng, wep }, () => this._updateRouteOnControlChange()); this.setState({ sys, eng, wep, mcSys, mcEng, mcWep }, () => this._updateRouteOnControlChange());
} }
/** /**
@@ -309,8 +326,8 @@ export default class OutfittingPage extends Page {
* @returns {string} The control code * @returns {string} The control code
*/ */
_controlCode(fuel, cargo) { _controlCode(fuel, cargo) {
const { sys, eng, wep, boost, opponent, opponentBuild, engagementRange } = this.state; const { sys, eng, wep, mcSys, mcEng, mcWep, boost, opponent, opponentBuild, engagementRange } = this.state;
const code = `${sys}/${eng}/${wep}/${boost ? 1 : 0}/${fuel || this.state.fuel}/${cargo || this.state.cargo}/${opponent.id}/${opponentBuild ? opponentBuild : ''}/${engagementRange}`; const code = `${sys}/${eng}/${wep}/${boost ? 1 : 0}/${fuel || this.state.fuel}/${cargo || this.state.cargo}/${opponent.id}/${opponentBuild ? opponentBuild : ''}/${engagementRange}/${mcSys}/${mcEng}/${mcWep}`;
return code; return code;
} }
@@ -373,12 +390,15 @@ export default class OutfittingPage extends Page {
ship.buildWith(Ships[shipId].defaults); ship.buildWith(Ships[shipId].defaults);
// Reset controls // Reset controls
const code = ship.toString(); const code = ship.toString();
const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code); const { sys, eng, wep, mcSys, mcEng, mcWep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code);
// Update state, and refresh the ship // Update state, and refresh the ship
this.setState({ this.setState({
sys, sys,
eng, eng,
wep, wep,
mcSys,
mcEng,
mcWep,
boost, boost,
fuel, fuel,
cargo, cargo,
@@ -430,12 +450,15 @@ export default class OutfittingPage extends Page {
this.state.ship.buildFrom(code); this.state.ship.buildFrom(code);
// Obtain controls from the code // Obtain controls from the code
const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code); const { sys, eng, wep, mcSys, mcEng, mcWep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code);
// Update state, and refresh the route when complete // Update state, and refresh the route when complete
this.setState({ this.setState({
sys, sys,
eng, eng,
wep, wep,
mcSys,
mcEng,
mcWep,
boost, boost,
fuel, fuel,
cargo, cargo,
@@ -567,7 +590,7 @@ export default class OutfittingPage extends Page {
let state = this.state, let state = this.state,
{ language, termtip, tooltip, sizeRatio, onWindowResize } = this.context, { language, termtip, tooltip, sizeRatio, onWindowResize } = this.context,
{ translate, units, formats } = language, { translate, units, formats } = language,
{ ship, code, savedCode, buildName, newBuildName, sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = state, { ship, code, savedCode, buildName, newBuildName, sys, eng, wep, mcSys, mcEng, mcWep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = state,
hide = tooltip.bind(null, null), hide = tooltip.bind(null, null),
menu = this.props.currentMenu, menu = this.props.currentMenu,
shipUpdated = this._shipUpdated, shipUpdated = this._shipUpdated,
@@ -671,7 +694,7 @@ export default class OutfittingPage extends Page {
</div> </div>
</div> </div>
<div className='group quarter'> <div className='group quarter'>
<Pips sys={sys} eng={eng} wep={wep} onChange={this._pipsUpdated} /> <Pips sys={sys} eng={eng} wep={wep} mcSys={mcSys} mcEng={mcEng} mcWep={mcWep} onChange={this._pipsUpdated} />
</div> </div>
<div className='group quarter'> <div className='group quarter'>
<Fuel fuelCapacity={ship.fuelCapacity} fuel={fuel} onChange={this._fuelUpdated}/> <Fuel fuelCapacity={ship.fuelCapacity} fuel={fuel} onChange={this._fuelUpdated}/>

View File

@@ -12,6 +12,12 @@
cursor: pointer; cursor: pointer;
} }
// A multi-crew pip
.mc {
stroke: @secondary;
fill: @secondary;
}
// A full pip // A full pip
.full { .full {
stroke: @primary; stroke: @primary;