Merge branch 'develop'

This commit is contained in:
William Blythe
2018-12-04 12:02:02 +11:00
19 changed files with 252 additions and 89 deletions

View File

@@ -29,6 +29,20 @@ services:
- "traefik.basic.port=80" - "traefik.basic.port=80"
- "traefik.basic.protocol=http" - "traefik.basic.protocol=http"
coriolis_dw2:
image: edcd/coriolis:dw2
restart: always
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
networks:
- web
labels:
- "traefik.docker.network=web"
- "traefik.enable=true"
- "traefik.basic.frontend.rule=Host:dw2.coriolis.io"
- "traefik.basic.port=80"
- "traefik.basic.protocol=http"
networks: networks:
web: web:
external: true external: true

View File

@@ -135,6 +135,7 @@
"react": "^15.5.4", "react": "^15.5.4",
"react-dom": "^15.5.4", "react-dom": "^15.5.4",
"react-extras": "^0.7.1", "react-extras": "^0.7.1",
"react-fuzzy": "^0.5.2",
"react-ga": "^2.5.3", "react-ga": "^2.5.3",
"react-number-editor": "Athanasius/react-number-editor.git#miggy", "react-number-editor": "Athanasius/react-number-editor.git#miggy",
"recharts": "^1.2.0", "recharts": "^1.2.0",

View File

@@ -6,6 +6,7 @@ import { EventEmitter } from 'fbemitter';
import { getLanguage } from './i18n/Language'; import { getLanguage } from './i18n/Language';
import Persist from './stores/Persist'; import Persist from './stores/Persist';
import Announcement from './components/Announcement';
import Header from './components/Header'; import Header from './components/Header';
import Tooltip from './components/Tooltip'; import Tooltip from './components/Tooltip';
import ModalExport from './components/ModalExport'; import ModalExport from './components/ModalExport';
@@ -395,6 +396,7 @@ export default class Coriolis extends React.Component {
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu} return <div style={{ minHeight: '100%' }} onClick={this._closeMenu}
className={this.state.noTouch ? 'no-touch' : null}> className={this.state.noTouch ? 'no-touch' : null}>
<Header announcements={this.state.announcements} appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu} /> <Header announcements={this.state.announcements} appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu} />
<div className="announcement-container">{this.state.announcements.map(a => <Announcement text={a.message}/>)}</div>
{this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) : {this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) :
<NotFoundPage />} <NotFoundPage />}
{this.state.modal} {this.state.modal}

View File

@@ -25,7 +25,7 @@ export default class Announcement extends React.Component {
* @return {React.Component} A href element * @return {React.Component} A href element
*/ */
render() { render() {
return <p>{this.props.text}</p>; return <div className="announcement" >{this.props.text}</div>;
} }
} }

View File

@@ -5,6 +5,7 @@ import TranslatedComponent from './TranslatedComponent';
import { stopCtxPropagation } from '../utils/UtilityFunctions'; import { stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames'; import cn from 'classnames';
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons'; import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import FuzzySearch from 'react-fuzzy';
const PRESS_THRESHOLD = 500; // mouse/touch down threshold const PRESS_THRESHOLD = 500; // mouse/touch down threshold
@@ -39,7 +40,7 @@ const GRPCAT = {
'mc': 'projectiles', 'mc': 'projectiles',
'axmc': 'experimental', 'axmc': 'experimental',
'fc': 'projectiles', 'fc': 'projectiles',
'rfl': 'experimental', 'rfl': 'experimental',
'pa': 'projectiles', 'pa': 'projectiles',
'rg': 'projectiles', 'rg': 'projectiles',
'mr': 'ordnance', 'mr': 'ordnance',
@@ -133,6 +134,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
constructor(props, context) { constructor(props, context) {
super(props); super(props);
this._hideDiff = this._hideDiff.bind(this); this._hideDiff = this._hideDiff.bind(this);
this._showSearch = this._showSearch.bind(this);
this.state = this._initState(props, context); this.state = this._initState(props, context);
this.slotItems = [];// Array to hold <li> refs. this.slotItems = [];// Array to hold <li> refs.
} }
@@ -159,7 +161,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
onSelect(m); onSelect(m);
} }
); );
let fuzzy = [];
if (modules instanceof Array) { if (modules instanceof Array) {
list = buildGroup(modules[0].grp, modules); list = buildGroup(modules[0].grp, modules);
} else { } else {
@@ -167,9 +169,11 @@ export default class AvailableModulesMenu extends TranslatedComponent {
// At present time slots with grouped options (Hardpoints and Internal) can be empty // At present time slots with grouped options (Hardpoints and Internal) can be empty
if (m) { if (m) {
let emptyId = 'empty'; let emptyId = 'empty';
if(this.firstSlotId == null) this.firstSlotId = emptyId; if (this.firstSlotId == null) this.firstSlotId = emptyId;
let keyDown = this._keyDown.bind(this, onSelect); let keyDown = this._keyDown.bind(this, onSelect);
list.push(<div className='empty-c upp' key={emptyId} data-id={emptyId} onClick={onSelect.bind(null, null)} onKeyDown={keyDown} tabIndex="0" ref={slotItem => this.slotItems[emptyId] = slotItem} >{translate('empty')}</div>); list.push(<div className='empty-c upp' key={emptyId} data-id={emptyId} onClick={onSelect.bind(null, null)}
onKeyDown={keyDown} tabIndex="0"
ref={slotItem => this.slotItems[emptyId] = slotItem}>{translate('empty')}</div>);
} }
// Need to regroup the modules by our own categorisation // Need to regroup the modules by our own categorisation
@@ -197,7 +201,8 @@ export default class AvailableModulesMenu extends TranslatedComponent {
if (categories.length === 1) { if (categories.length === 1) {
// Show category header instead of group header // Show category header instead of group header
if (m && grp == m.grp) { if (m && grp == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={category} className={'select-category upp'}>{translate(category)}</div>); list.push(<div ref={(elem) => this.groupElem = elem} key={category}
className={'select-category upp'}>{translate(category)}</div>);
} else { } else {
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>); list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
} }
@@ -208,19 +213,23 @@ export default class AvailableModulesMenu extends TranslatedComponent {
categoryHeader = true; categoryHeader = true;
} }
if (m && grp == m.grp) { if (m && grp == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={grp} className={'select-group cap'}>{translate(grp)}</div>); list.push(<div ref={(elem) => this.groupElem = elem} key={grp}
className={'select-group cap'}>{translate(grp)}</div>);
} else { } else {
list.push(<div key={grp} className={'select-group cap'}>{translate(grp)}</div>); list.push(<div key={grp} className={'select-group cap'}>{translate(grp)}</div>);
} }
} }
list.push(buildGroup(grp, modules[grp])); list.push(buildGroup(grp, modules[grp]));
for (const i of modules[grp]) {
fuzzy.push({ grp, m: i, name: `${i.class}${i.rating} ${translate(grp)} ${i.mount ? i.mount : ''}` });
}
} }
} }
} }
} }
} }
let trackingFocus = false; let trackingFocus = false;
return { list, currentGroup, trackingFocus }; return { list, currentGroup, fuzzy, trackingFocus };
} }
/** /**
@@ -242,9 +251,11 @@ export default class AvailableModulesMenu extends TranslatedComponent {
const sortedModules = modules.sort(this._moduleOrder); const sortedModules = modules.sort(this._moduleOrder);
// Calculate the number of items per class. Used so we don't have long lists with only a few items in each row // Calculate the number of items per class. Used so we don't have long lists with only a few items in each row
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => { count[cls] = ++count[cls] || 1; return count; }, {}); const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => {
count[cls] = ++count[cls] || 1;
return count;
}, {});
const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key])); const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key]));
let itemsOnThisRow = 0; let itemsOnThisRow = 0;
@@ -297,22 +308,29 @@ export default class AvailableModulesMenu extends TranslatedComponent {
}; };
} }
switch(m.mount) { switch (m.mount) {
case 'F': mount = <MountFixed className={'lg'} />; break; case 'F':
case 'G': mount = <MountGimballed className={'lg'}/>; break; mount = <MountFixed className={'lg'}/>;
case 'T': mount = <MountTurret className={'lg'}/>; break; break;
case 'G':
mount = <MountGimballed className={'lg'}/>;
break;
case 'T':
mount = <MountTurret className={'lg'}/>;
break;
} }
if (m.name && m.name === prevName) { if (m.name && m.name === prevName) {
// elems.push(<br key={'b' + m.grp + i} />); // elems.push(<br key={'b' + m.grp + i} />);
itemsOnThisRow = 0; itemsOnThisRow = 0;
} }
if (itemsOnThisRow == 6 || i > 0 && sortedModules.length > 3 && itemsPerClass > 2 && m.class != prevClass && (m.rating != prevRating || m.mount)) { if (itemsOnThisRow == 6 || i > 0 && sortedModules.length > 3 && itemsPerClass > 2 && m.class != prevClass && (m.rating != prevRating || m.mount)) {
elems.push(<br key={'b' + m.grp + i} />); elems.push(<br key={'b' + m.grp + i}/>);
itemsOnThisRow = 0; itemsOnThisRow = 0;
} }
let tbIdx = (classes.indexOf('disabled') < 0) ? 0 : undefined; let tbIdx = (classes.indexOf('disabled') < 0) ? 0 : undefined;
elems.push( elems.push(
<li key={m.id} data-id={m.id} className={classes} {...eventHandlers} tabIndex={tbIdx} ref={slotItem => this.slotItems[m.id] = slotItem}> <li key={m.id} data-id={m.id} className={classes} {...eventHandlers} tabIndex={tbIdx}
ref={slotItem => this.slotItems[m.id] = slotItem}>
{mount} {mount}
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')} {(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
</li> </li>
@@ -340,6 +358,36 @@ export default class AvailableModulesMenu extends TranslatedComponent {
} }
} }
/**
* Generate tooltip content for the difference between the
* mounted module and the hovered modules
*/
_showSearch() {
return (
<FuzzySearch
list={this.state.fuzzy}
keys={['grp', 'name']}
className={'input'}
width={'100%'}
style={{ padding: 0 }}
onSelect={e => this.props.onSelect.bind(null, e.m)()}
resultsTemplate={(props, state, styles, clickHandler) => {
return state.results.map((val, i) => {
return (
<div
key={i}
className={'lc'}
onClick={() => clickHandler(i)}
>
{val.name}
</div>
);
});
}}
/>
);
}
/** /**
* Mouse over diff handler * Mouse over diff handler
* @param {Function} showDiff diff tooltip callback * @param {Function} showDiff diff tooltip callback
@@ -405,7 +453,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @param {Function} select Select module callback * @param {Function} select Select module callback
* @param {SytheticEvent} event Event * @param {SytheticEvent} event Event
*/ */
_keyUp(select,event) { _keyUp(select, event) {
// nothing here yet // nothing here yet
} }
@@ -475,12 +523,13 @@ export default class AvailableModulesMenu extends TranslatedComponent {
this.slotItems[this.firstSlotId].focus(); this.slotItems[this.firstSlotId].focus();
} }
} }
/** /**
* Handle focus if the component updates * Handle focus if the component updates
* *
*/ */
componentWillUnmount() { componentWillUnmount() {
if(this.props.slotDiv) { if (this.props.slotDiv) {
this.props.slotDiv.focus(); this.props.slotDiv.focus();
} }
} }
@@ -501,11 +550,12 @@ export default class AvailableModulesMenu extends TranslatedComponent {
render() { render() {
return ( return (
<div ref={node => this.node = node} <div ref={node => this.node = node}
className={cn('select', this.props.className)} className={cn('select', this.props.className)}
onScroll={this._hideDiff} onScroll={this._hideDiff}
onClick={(e) => e.stopPropagation() } onClick={(e) => e.stopPropagation()}
onContextMenu={stopCtxPropagation} onContextMenu={stopCtxPropagation}
> >
{this._showSearch()}
{this.state.list} {this.state.list}
</div> </div>
); );

View File

@@ -2,12 +2,21 @@ import React from 'react';
import cn from 'classnames'; import cn from 'classnames';
import Slot from './Slot'; import Slot from './Slot';
import Persist from '../stores/Persist'; import Persist from '../stores/Persist';
import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons'; import {
DamageAbsolute,
DamageKinetic,
DamageThermal,
DamageExplosive,
MountFixed,
MountGimballed,
MountTurret,
ListModifications,
Modified
} from './SvgIcons';
import { Modifications } from 'coriolis-data/dist'; import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions'; import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { blueprintTooltip } from '../utils/BlueprintFunctions'; import { blueprintTooltip } from '../utils/BlueprintFunctions';
/** /**
* Hardpoint / Utility Slot * Hardpoint / Utility Slot
*/ */
@@ -27,7 +36,7 @@ export default class HardpointSlot extends Slot {
* @return {string} Label * @return {string} Label
*/ */
_getMaxClassLabel(translate) { _getMaxClassLabel(translate) {
return translate(['U','S','M','L','H'][this.props.maxClass]); return translate(['U', 'S', 'M', 'L', 'H'][this.props.maxClass]);
} }
/** /**
@@ -66,42 +75,73 @@ export default class HardpointSlot extends Slot {
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}> return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}> <div className={'cb'}>
<div className={'l'}> <div className={'l'}>
{m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed /></span> : ''} {m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')}
{m.mount && m.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : ''} onMouseOut={tooltip.bind(null, null)}><MountFixed/></span> : ''}
{m.mount && m.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : ''} {m.mount && m.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')}
{m.getDamageDist() && m.getDamageDist().K ? <span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span> : ''} onMouseOut={tooltip.bind(null, null)}><MountGimballed/></span> : ''}
{m.getDamageDist() && m.getDamageDist().T ? <span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span> : ''} {m.mount && m.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')}
{m.getDamageDist() && m.getDamageDist().E ? <span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span> : ''} onMouseOut={tooltip.bind(null, null)}><MountTurret/></span> : ''}
{m.getDamageDist() && m.getDamageDist().A ? <span onMouseOver={termtip.bind(null, 'absolute')} onMouseOut={tooltip.bind(null, null)}><DamageAbsolute /></span> : ''} {m.getDamageDist() && m.getDamageDist().K ? <span onMouseOver={termtip.bind(null, 'kinetic')}
{classRating} {translate(m.name || m.grp)}{ m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null } onMouseOut={tooltip.bind(null, null)}><DamageKinetic/></span> : ''}
{m.getDamageDist() && m.getDamageDist().T ? <span onMouseOver={termtip.bind(null, 'thermal')}
onMouseOut={tooltip.bind(null, null)}><DamageThermal/></span> : ''}
{m.getDamageDist() && m.getDamageDist().E ? <span onMouseOver={termtip.bind(null, 'explosive')}
onMouseOut={tooltip.bind(null, null)}><DamageExplosive/></span> : ''}
{m.getDamageDist() && m.getDamageDist().A ? <span onMouseOver={termtip.bind(null, 'absolute')}
onMouseOut={tooltip.bind(null, null)}><DamageAbsolute/></span> : ''}
{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r'
onMouseOver={termtip.bind(null, modTT)}
onMouseOut={tooltip.bind(null, null)}><Modified/></span> : null}
</div> </div>
<div className={'r'}>{formats.round(m.getMass())}{u.T}</div> <div className={'r'}>{formats.round(m.getMass())}{u.T}</div>
</div> </div>
<div className={'cb'}> <div className={'cb'}>
{ m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} { m.getClip() ? <span>({formats.round1(m.getSDps()) })</span> : null }</div> : null } {m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')}
{ m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')} onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) }{u.MW})</span> : null }</div> : null } onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} {m.getClip() ?
{ m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')} onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null } <span>({formats.round1(m.getSDps())})</span> : null}</div> : null}
{ m.getDps() && m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, 'dpe')} onMouseOut={tooltip.bind(null, null)}>{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}</div> : null } {m.getDamage() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getDamage() ? 'shotdmg' : 'shotdmg')}
{ m.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')} onMouseOut={tooltip.bind(null, null)}>{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}</div> : null } onMouseOut={tooltip.bind(null, null)}>{translate('shotdmg')}: {formats.round1(m.getDamage())}</div> : null}
{ m.getRange() ? <div className={'l'}>{translate('range', m.grp)} {formats.f1(m.getRange() / 1000)}{u.km}</div> : null } {m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')}
{ m.getScanTime() ? <div className={'l'}>{translate('scantime')} {formats.f1(m.getScanTime())}{u.s}</div> : null } onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} {m.getClip() ?
{ m.getFalloff() ? <div className={'l'}>{translate('falloff')} {formats.round(m.getFalloff() / 1000)}{u.km}</div> : null } <span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()))}{u.MW})</span> : null}</div> : null}
{ m.getShieldBoost() ? <div className={'l'}>+{formats.pct1(m.getShieldBoost())}</div> : null } {m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')}
{ m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}</div> : null } onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} {m.getClip() ?
{ m.getReload() ? <div className={'l'}>{translate('reload')}: {formats.round(m.getReload())}{u.s}</div> : null } <span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()))})</span> : null}</div> : null}
{ m.getShotSpeed() ? <div className={'l'}>{translate('shotspeed')}: {formats.int(m.getShotSpeed())}{u.mps}</div> : null } {m.getDps() && m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, 'dpe')}
{ m.getPiercing() ? <div className={'l'}>{translate('piercing')}: {formats.int(m.getPiercing())}</div> : null } onMouseOut={tooltip.bind(null, null)}>{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}</div> : null}
{ m.getJitter() ? <div className={'l'}>{translate('jitter')}: {formats.f2(m.getJitter())}°</div> : null } {m.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')}
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null } onMouseOut={tooltip.bind(null, null)}>{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}</div> : null}
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null } {m.getRange() ? <div
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null } className={'l'}>{translate('range', m.grp)} {formats.f1(m.getRange() / 1000)}{u.km}</div> : null}
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null } {m.getScanTime() ? <div
{ m && validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null } className={'l'}>{translate('scantime')} {formats.f1(m.getScanTime())}{u.s}</div> : null}
{m.getFalloff() ? <div
className={'l'}>{translate('falloff')} {formats.round(m.getFalloff() / 1000)}{u.km}</div> : null}
{m.getShieldBoost() ? <div className={'l'}>+{formats.pct1(m.getShieldBoost())}</div> : null}
{m.getAmmo() ? <div
className={'l'}>{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}</div> : null}
{m.getReload() ? <div className={'l'}>{translate('reload')}: {formats.round(m.getReload())}{u.s}</div> : null}
{m.getShotSpeed() ? <div
className={'l'}>{translate('shotspeed')}: {formats.int(m.getShotSpeed())}{u.mps}</div> : null}
{m.getPiercing() ? <div className={'l'}>{translate('piercing')}: {formats.int(m.getPiercing())}</div> : null}
{m.getJitter() ? <div className={'l'}>{translate('jitter')}: {formats.f2(m.getJitter())}°</div> : null}
{showModuleResistances && m.getExplosiveResistance() ? <div
className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null}
{showModuleResistances && m.getKineticResistance() ? <div
className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null}
{showModuleResistances && m.getThermalResistance() ? <div
className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null}
{m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null}
{m && validMods.length > 0 ? <div className='r' tabIndex="0" ref={modButton => this.modButton = modButton}>
<button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation}
onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}>
<ListModifications/></button>
</div> : null}
</div> </div>
</div>; </div>;
} else { } else {
return <div className={'empty'}>{translate('empty')}</div>; return <div className={'empty'}>{translate('empty')}</div>;
} }
} }

View File

@@ -153,6 +153,15 @@ export default class HardpointSlotSection extends SlotSection {
<ul> <ul>
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'nl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['nl-F'] = smRef}>{translate('nl')}</li> <li className='lc' tabIndex='0' onClick={_fill.bind(this, 'nl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['nl-F'] = smRef}>{translate('nl')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('ggc')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'ggc', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ggc-F'] = smRef}>{translate('ggc')}</li>
</ul>
<div className='select-group cap'>{translate('rfl')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'rfl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['rfl-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'rfl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['rfl-T'] = smRef}><MountTurret className='lg'/></li>
</ul>
</div>; </div>;
} }

View File

@@ -23,8 +23,10 @@ export default class ModalOrbis extends TranslatedComponent {
this.state = { this.state = {
orbisCreds: Persist.getOrbisCreds(), orbisCreds: Persist.getOrbisCreds(),
orbisUrl: '...', orbisUrl: '...',
ship: this.props.ship,
authenticatedStatus: 'Checking...' authenticatedStatus: 'Checking...'
}; };
this.orbisCategory = this.orbisCategory.bind(this);
} }
/** /**
@@ -88,6 +90,17 @@ export default class ModalOrbis extends TranslatedComponent {
}); });
} }
/**
* Handler for changing category
* @param {SyntheticEvent} e React Event
*/
orbisCategory(e) {
let ship = this.state.ship;
let cat = e.target.value;
ship.category = cat;
this.setState({ship});
}
/** /**
* Render the modal * Render the modal
* @return {React.Component} Modal Content * @return {React.Component} Modal Content
@@ -106,6 +119,17 @@ export default class ModalOrbis extends TranslatedComponent {
<br/><br/> <br/><br/>
<a className='button' href="https://orbis.zone/api/auth">Log in / signup to Orbis</a> <a className='button' href="https://orbis.zone/api/auth">Log in / signup to Orbis</a>
<br/><br/> <br/><br/>
<h3>Category</h3>
<select onChange={this.orbisCategory}>
<option value="">No Category</option>
<option>Combat</option>
<option>Mining</option>
<option>Trading</option>
<option>Exploration</option>
<option>Passenger Liner</option>
<option>PvP</option>
</select>
<br/><br/>
<h3 >{translate('Orbis link')}</h3> <h3 >{translate('Orbis link')}</h3>
<input value={this.state.orbisUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/> <input value={this.state.orbisUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
<br/><br/> <br/><br/>

View File

@@ -52,6 +52,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL'; const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const sgMetrics = Calc.shieldMetrics(ship, pips.sys); const sgMetrics = Calc.shieldMetrics(ship, pips.sys);
const shipBoost = canBoost ? Calc.calcBoost(ship) : 'No Boost'; const shipBoost = canBoost ? Calc.calcBoost(ship) : 'No Boost';
const restingHeat = Math.sqrt(((ship.standard[0].m.pgen * ship.standard[0].m.eff) / ship.heatCapacity) / 0.2);
const armourMetrics = Calc.armourMetrics(ship); const armourMetrics = Calc.armourMetrics(ship);
let shieldColour = 'blue'; let shieldColour = 'blue';
if (shieldGenerator && shieldGenerator.m.grp === 'psg') { if (shieldGenerator && shieldGenerator.m.grp === 'psg') {
@@ -85,6 +86,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
<th rowSpan={2}>{translate('crew')}</th> <th rowSpan={2}>{translate('crew')}</th>
<th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th> <th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_TIME', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost time')}</th> <th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_TIME', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost time')}</th>
<th rowSpan={2}>{translate('resting heat (Beta)')}</th>
</tr> </tr>
<tr> <tr>
<th className='lft'>{translate('max')}</th> <th className='lft'>{translate('max')}</th>
@@ -122,6 +124,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
<td>{ship.crew}</td> <td>{ship.crew}</td>
<td>{ship.masslock}</td> <td>{ship.masslock}</td>
<td>{shipBoost !== 'No Boost' ? formats.time(shipBoost) : 'No Boost'}</td> <td>{shipBoost !== 'No Boost' ? formats.time(shipBoost) : 'No Boost'}</td>
<td>{formats.pct(restingHeat)}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -240,6 +240,7 @@
"shieldboost": "Shield boost", "shieldboost": "Shield boost",
"shieldreinforcement": "Shield reinforcement", "shieldreinforcement": "Shield reinforcement",
"shotspeed": "Shot speed", "shotspeed": "Shot speed",
"shotdmg": "Damage Per Shot",
"spinup": "Spin up time", "spinup": "Spin up time",
"syscap": "Systems capacity", "syscap": "Systems capacity",
"sysrate": "Systems recharge rate", "sysrate": "Systems recharge rate",

View File

@@ -61,6 +61,7 @@ function shipSummary(shipId, shipData) {
id: shipId, id: shipId,
hpCount: 0, hpCount: 0,
intCount: 0, intCount: 0,
beta: shipData.beta,
maxCargo: 0, maxCargo: 0,
maxPassengers: 0, maxPassengers: 0,
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
@@ -318,7 +319,7 @@ export default class ShipyardPage extends Page {
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)} onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
> >
<td className="le"> <td className="le">
<Link href={'/outfit/' + s.id}>{s.name}</Link> <Link href={'/outfit/' + s.id}>{s.name} {s.beta === true ? '(Beta)' : null}</Link>
</td> </td>
</tr> </tr>
); );

View File

@@ -41,6 +41,7 @@ export default class Module {
* @return {object} The value of the modification. If it is a numeric value then it is returned as an integer value scaled so that 1.23% == 123 * @return {object} The value of the modification. If it is a numeric value then it is returned as an integer value scaled so that 1.23% == 123
*/ */
getModValue(name, raw) { getModValue(name, raw) {
let baseVal = this[name];
let result = this.mods && this.mods[name] ? this.mods[name] : null; let result = this.mods && this.mods[name] ? this.mods[name] : null;
if ((!raw) && this.blueprint && this.blueprint.special) { if ((!raw) && this.blueprint && this.blueprint.special) {
@@ -51,13 +52,8 @@ export default class Module {
const modification = Modifications.modifications[name]; const modification = Modifications.modifications[name];
const multiplier = modification.type === 'percentage' ? 10000 : 100; const multiplier = modification.type === 'percentage' ? 10000 : 100;
if (name === 'explres' || name === 'kinres' || name === 'thermres' || name === 'causres') { if (name === 'explres' || name === 'kinres' || name === 'thermres' || name === 'causres') {
// Resistance modifications in itself are additive, however their // Apply resistance modding mechanisms to special effects subsequently
// special effects are multiplicative. They affect the overall result result = result + modifierActions[name] * (1 - (this[name] + result / multiplier)) * 100;
// by (special effect resistance) * (damage mult after modification),
// i. e. we need to apply the special effect as a multiplier to the
// overall result and then calculate the difference.
let baseMult = this[name] ? 1 - this[name] : 1;
result = (baseMult - (baseMult - result / multiplier) * (1 - modifierActions[name] / 100)) * multiplier;
} else if (modification.method === 'additive') { } else if (modification.method === 'additive') {
result = result + modifierActions[name] * 100; result = result + modifierActions[name] * 100;
} else if (modification.method === 'overwrite') { } else if (modification.method === 'overwrite') {
@@ -75,15 +71,6 @@ export default class Module {
} }
} }
// Resistance modding for hull reinforcement packages has additional
// diminishing returns implemented. The mod value gets lowered by
// the amount of base resistance the hrp has.
if (!isNaN(result) && this.grp === 'hr' &&
(name === 'kinres' || name === 'thermres' || name === 'explres')) {
let baseRes = this[name];
result = result * (1 - baseRes);
}
// Sanitise the resultant value to 4dp equivalent // Sanitise the resultant value to 4dp equivalent
return isNaN(result) ? result : Math.round(result); return isNaN(result) ? result : Math.round(result);
} }
@@ -108,11 +95,11 @@ export default class Module {
// This special effect modifies the value being set, so we need to revert it prior to storing the value // This special effect modifies the value being set, so we need to revert it prior to storing the value
const modification = Modifications.modifications[name]; const modification = Modifications.modifications[name];
if (name === 'explres' || name === 'kinres' || name === 'thermres' || name === 'causres') { if (name === 'explres' || name === 'kinres' || name === 'thermres' || name === 'causres') {
// Resistance modifications in itself are additive but their let res = (this[name] ? this[name] : 0) + value / 10000;
// experimentals are applied multiplicatively therefor we must handle let experimental = modifierActions[name] / 100;
// them differently here (cf. documentation in getModValue). value = (experimental - res) / (experimental - 1) - this[name];
let baseMult = (this[name] ? 1 - this[name] : 1); value *= 10000;
value = ((baseMult - value / 10000) / (1 - modifierActions[name] / 100) - baseMult) * -10000; // value = ((baseMult - value / 10000) / (1 - modifierActions[name] / 100) - baseMult) * -10000;
} else if (modification.method === 'additive') { } else if (modification.method === 'additive') {
value = value - modifierActions[name]; value = value - modifierActions[name];
} else if (modification.method === 'overwrite') { } else if (modification.method === 'overwrite') {
@@ -177,10 +164,6 @@ export default class Module {
baseValue = 0; baseValue = 0;
} }
modValue = value - baseValue; modValue = value - baseValue;
if (this.grp === 'hr' &&
(name === 'kinres' || name === 'thermres' || name === 'explres')) {
modValue = modValue / (1 - baseValue);
}
} else if (name === 'shieldboost' || name === 'hullboost') { } else if (name === 'shieldboost' || name === 'hullboost') {
modValue = (1 + value) / (1 + baseValue) - 1; modValue = (1 + value) / (1 + baseValue) - 1;
} else { // multiplicative } else { // multiplicative

View File

@@ -63,7 +63,10 @@ export function standard(type, id) {
if (!isNaN(type)) { if (!isNaN(type)) {
type = StandardArray[type]; type = StandardArray[type];
} }
let s = Modules.standard[type].find(e => e.id == id || (e.class == id.charAt(0) && e.rating == id.charAt(1))); let s = Modules.standard[type].find(e => e.id === id);
if (!s) {
s = Modules.standard[type].find(e => (e.class == id.charAt(0) && e.rating == id.charAt(1)));
}
if (s) { if (s) {
s = new Module({ template: s }); s = new Module({ template: s });
} }

View File

@@ -505,6 +505,11 @@ export default class Ship {
if (isAbsolute) { if (isAbsolute) {
m.setPretty(name, value, sentfromui); m.setPretty(name, value, sentfromui);
} else { } else {
// Resistance modifiers scale with the base value
if (name == 'kinres' || name == 'thermres' || name == 'causres' || name == 'explres') {
let baseValue = m.get(name, false);
value = (1 - baseValue) * value;
}
m.setModValue(name, value, sentfromui); m.setModValue(name, value, sentfromui);
} }
@@ -1501,7 +1506,7 @@ export default class Ship {
} else { } else {
buffer.writeInt32LE(slotMod.value, curpos); buffer.writeInt32LE(slotMod.value, curpos);
} }
// const modification = _.find(Modifications.modifications, function(o) { return o.id === slotMod.id; }); const modification = _.find(Modifications.modifications, function(o) { return o.id === slotMod.id; });
// console.log('ENCODE Slot ' + i + ': ' + modification.name + ' = ' + slotMod.value); // console.log('ENCODE Slot ' + i + ': ' + modification.name + ' = ' + slotMod.value);
curpos += 4; curpos += 4;
} }
@@ -1514,6 +1519,7 @@ export default class Ship {
} }
this.serialized.modifications = zlib.gzipSync(buffer).toString('base64'); this.serialized.modifications = zlib.gzipSync(buffer).toString('base64');
// console.log(this.serialized.modifications)
} else { } else {
this.serialized.modifications = null; this.serialized.modifications = null;
} }

View File

@@ -31,6 +31,7 @@ export const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
'Independant_Trader': 'keelback', 'Independant_Trader': 'keelback',
'Krait_MkII': 'krait_mkii', 'Krait_MkII': 'krait_mkii',
'Mamba': 'mamba', 'Mamba': 'mamba',
'Krait_Light': 'krait_phantom',
'Orca': 'orca', 'Orca': 'orca',
'Python': 'python', 'Python': 'python',
'SideWinder': 'sidewinder', 'SideWinder': 'sidewinder',

View File

@@ -59,9 +59,7 @@
<!-- End Piwik Code --> <!-- End Piwik Code -->
<!-- Bugsnag --> <!-- Bugsnag -->
<script src="//d2wy8f7a9ursnm.cloudfront.net/v4/bugsnag.min.js"></script> <script src="https://d2wy8f7a9ursnm.cloudfront.net/v5.0.0/bugsnag.min.js"></script>
<script
src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-plugins/v1/bugsnag-react.min.js"></script>
<script> <script>
window.bugsnagClient = bugsnag('ba9fae819372850fb660755341fa6ef5', {appVersion: window.BUGSNAG_VERSION || undefined}) window.bugsnagClient = bugsnag('ba9fae819372850fb660755341fa6ef5', {appVersion: window.BUGSNAG_VERSION || undefined})
window.Bugsnag = window.bugsnagClient window.Bugsnag = window.bugsnagClient

View File

@@ -171,3 +171,16 @@ footer {
text-align: right; text-align: right;
} }
} }
.announcement-container {
display: flex;
align-items: center;
padding-top: 10px;
justify-content: center;
flex-flow: row wrap;
}
.announcement {
border: 1px @secondary solid;
padding: 10px;
}

View File

@@ -22,6 +22,15 @@ select {
} }
} }
.react-fuzzy-search > * {
padding: 0 !important;
color: @primary;
& > input {
border: 1px solid @primary !important;
color: @primary-bg;
}
}
.cmdr-select { .cmdr-select {
border: 1px solid @primary; border: 1px solid @primary;
padding: 0.5em 0.5em; padding: 0.5em 0.5em;

View File

@@ -4,7 +4,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin');
const { InjectManifest } = require('workbox-webpack-plugin'); const { InjectManifest } = require('workbox-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin');
const { BugsnagSourceMapUploaderPlugin } = require('webpack-bugsnag-plugins'); const { BugsnagSourceMapUploaderPlugin, BugsnagBuildReporterPlugin } = require('webpack-bugsnag-plugins');
const pkgJson = require('./package'); const pkgJson = require('./package');
const buildDate = new Date(); const buildDate = new Date();
@@ -48,10 +48,15 @@ module.exports = {
disable: false, disable: false,
allChunks: true allChunks: true
}), }),
// new BugsnagSourceMapUploaderPlugin({ new BugsnagBuildReporterPlugin({
// apiKey: 'ba9fae819372850fb660755341fa6ef5', apiKey: 'ba9fae819372850fb660755341fa6ef5',
// appVersion: `${pkgJson.version}-${buildDate.toISOString()}` appVersion: `${pkgJson.version}-${buildDate.toISOString()}`
// }), }, { /* opts */ }),
new BugsnagSourceMapUploaderPlugin({
apiKey: 'ba9fae819372850fb660755341fa6ef5',
overwrite: true,
appVersion: `${pkgJson.version}-${buildDate.toISOString()}`
}),
new InjectManifest({ new InjectManifest({
swSrc: './src/sw.js', swSrc: './src/sw.js',
importWorkboxFrom: 'cdn', importWorkboxFrom: 'cdn',