Compare commits

..

113 Commits

Author SHA1 Message Date
willyb321
3786fb907c 2.9.14 2018-05-29 07:03:11 +10:00
Pat Nellesen
752d9f0c68 Feature/#271 keyboard nav (#288)
* Initial stab at Tab/Enter key handlers for Slots - Added tabIndex="0" attribute for the Slot sections and for the <li> elements inside.

* Set up refs for slot <li> elements to allow focus manipulation.

* Added initial keyDown handlers for Enter and Tab keys.

* Additional update for focus handling inside slot selection <ul>

* Added Tab/Enter/Shift-Tab key handlers for Outfitting section. Needs some fine-tuning but is usable in current state

* Added keydown handler for Empty module selection. ToDo: handlers for engineering sections

* Cleanup

* Update to add focus on Modifications icon and Enter key handler to open modifications menu

* Updates to add keydown handlers for modifications menu, and tab/shift-tab/enter key handlers while mod menus are open.

* Additional updates for keyDown handlers

* Update to add tabindex=0 and Enter Key handling for specials menus. ToDo: keep focus inside specials menu until item is selected with Enter key.

* Further updates for keyDown handler in Modifications menus.

* Added keyDown handlers to full mod menu (when mod menu is opened and a mod has been previously selected)

* Update to add shift-tab and tab focus handling for number editor fields

* Additional tab/shift-tab handlers. Fixed bug with exp. effects being recaculated for every onBlur event.

* Added check to bypass focus reset if change in component was due to change in modification value.

* Additional updates to tab/shift-tab handlers for modifications menu.

* more updates

* "Final" updates and bug fixes - added code for cases where there were no specials.

* Final updates to set focus to appropriate element when slot/mod menus are closed.
2018-05-29 07:00:22 +10:00
Pat Nellesen
baf59aafcb Feature/#248 slider keyboard (#257)
* Added tabIndex=0 to <svg> in Slider to allow keyboard focus

* further tests for mobile keyboard fix

* Proof of concept of way to get mobile keyboard to open for Slider component

* Added CSS to slider input box to make it invisible but still usable

* Update to hide text input and move it up to same position as slider

* Change slider text field type to 'tel" to force numeric-only keyboard on mobile

* Added focus/blur handlers and initial styling to show/hide text field component

* Update to insure text field values are updated when slider moves, and vice versa.

* Added timeout handler for Outfitting sliders to raise keyboard after 1500ms. Doesn't work 100% on iPhone.

* Changed touch/mouse down event handlers to change the display value on the parent div, then use componentDidUpdate to set focus on the text box.

* Added tap/hold mousedown/hold keyboard display - works on desktop, iOS, and Android
2018-05-04 09:01:55 +10:00
willyb321
8787303d2a add a column with what type of shield, update tooltips too 2018-05-04 08:59:57 +10:00
William Blythe
118c80af27 Fix kinetic and thermal res swapped
Closes #265
2018-05-03 10:54:55 +10:00
willyb321
d103939e45 add experimentals to core internals 2018-05-03 07:38:54 +10:00
willyb321
2a6ade3cab Merge branch 'release/2.9.13' into develop 2018-05-03 07:14:52 +10:00
willyb321
c8c42689f9 2.9.13 2018-05-03 07:14:45 +10:00
willyb321
da2f472f4d hopefully fix #262 2018-05-01 07:13:58 +10:00
William Blythe
3ba878237b fix super cap, nan with no shield 2018-04-30 10:57:42 +10:00
willyb321
7577fb53a2 biweaves are purple 2018-04-28 13:47:46 +10:00
willyb321
f2509f89ee change colour depending on shield 2018-04-28 13:39:17 +10:00
willyb321
9dd1f78330 make shield summary bar blue, change "damage from x" to "x hp" 2018-04-28 13:39:17 +10:00
willyb321
ebb6c2c420 more work on summary 2018-04-28 13:39:17 +10:00
willyb321
9d23dc1763 use set pips to calc shield 2018-04-28 13:39:17 +10:00
willyb321
5fa3f8703e pass pips to summary table 2018-04-28 13:39:17 +10:00
willyb321
797885faea more wip table 2018-04-28 13:39:17 +10:00
willyb321
68e7e9f5b7 fix crash when removing shield 2018-04-28 13:39:17 +10:00
willyb321
d1d165ad51 WIP summary table 2018-04-28 13:39:17 +10:00
willyb321
0ebb247666 Merge branch 'release/2.9.12' into develop 2018-04-28 12:16:21 +10:00
willyb321
aa73bc2809 2.9.12 2018-04-28 12:16:11 +10:00
willyb321
c3fcdb918f prep for recon limpet controllers 2018-04-28 12:12:38 +10:00
willyb321
5e3722bcfd hopefully fix a crash 2018-04-28 11:37:24 +10:00
willyb321
6a4fca2eb1 fix .editorconfig 2018-04-26 13:39:52 +10:00
willyb321
6922cfd047 2.9.11 2018-04-26 09:19:57 +10:00
willyb321
d93fc1d2d0 just update everything when applying a special 2018-04-26 09:06:18 +10:00
willyb321
48290b2e75 2.9.10 2018-04-25 20:00:36 +10:00
willyb321
b62abef618 2.9.9 2018-04-25 19:55:05 +10:00
willyb321
e0c0778d82 Lowercase in import 2018-04-25 18:45:24 +10:00
willyb321
b4a82ae7c2 fix crash with not having modifications key 2018-04-25 18:00:01 +10:00
willyb321
fc73102b30 Merge remote-tracking branch 'origin/develop' into develop 2018-04-25 15:07:23 +10:00
willyb321
b14e7473f3 add guardian powerplant, gauss cannon and also plasma charger 2018-04-25 15:07:13 +10:00
willyb321
f4cc9fc722 fix typo 2018-04-25 14:20:45 +10:00
William
8d19ef7783 Merge pull request #256 from ExitCode/develop
Fixed bp preset highlight when specials alter on the same values
2018-04-24 14:49:39 +10:00
willyb321
32be186ec5 remove unused var 2018-04-23 18:15:44 +10:00
willyb321
abb0c7f90d remove debug logs 2018-04-23 18:15:24 +10:00
willyb321
71ddbdfe75 add specials tooltip 2018-04-23 14:51:45 +10:00
ExitCode
38463ad9a6 Fixed bp preset highlight when specials alter on the same values 2018-04-23 01:16:13 +02:00
willyb321
70375f94c8 bump 2018-04-23 07:38:24 +10:00
William
2d4336116a Merge pull request #254 from ExitCode/develop
Value highlights for bp presets
2018-04-23 07:36:12 +10:00
willyb321
f52880765e fix legacy mods showing up 2018-04-23 07:34:52 +10:00
ExitCode
dbfe68decb Implemented active selected blueprint value highlight; updated look and feel 2018-04-22 05:36:19 +02:00
ExitCode
659f337de9 Added a spacer between the mod values 2018-04-22 05:34:25 +02:00
ExitCode
1d36d41da1 Tuned visibility of components in mod menu 2018-04-22 05:33:47 +02:00
ExitCode
0f90efaa54 Implemented a func to determine the current percentage level of a mod 2018-04-22 05:31:16 +02:00
willyb321
4442930a82 bump 2018-04-22 12:26:06 +10:00
William
912a775088 Merge pull request #253 from ExitCode/develop
blue print workflow (easier editing of modules)
2018-04-22 12:08:01 +10:00
ExitCode
24a229d818 Implemented workflow on bp assignment; applied some styles 2018-04-22 03:43:57 +02:00
ExitCode
66afb61494 added new style for inline menu buttons 2018-04-22 03:41:21 +02:00
William
e7511cc05b Merge pull request #252 from ExitCode/develop
(optional) changed roll presets to 0-50-100
2018-04-22 09:09:37 +10:00
ExitCode
926f19a936 added default init 100% on bp selection as the new system makes it obvious to reach 100% 2018-04-22 00:59:44 +02:00
ExitCode
9e6f86b963 (optional) changed roll presets to 0-50-100 2018-04-22 00:28:04 +02:00
William
059de43a9a Merge pull request #251 from ExitCode/develop
reused random functionality as it worked better on percentage rolls between min max
2018-04-22 08:10:30 +10:00
ExitCode
e246b737b2 reused random functionality as it worked better on percentage rolls between min/max 2018-04-21 23:53:02 +02:00
willyb321
38842417b0 bump 2018-04-22 06:59:08 +10:00
willyb321
82d485a98e fix preset rolls locked to low value 2018-04-22 06:49:43 +10:00
willyb321
fac71feea7 Lowercase armour check
Fixes #231
2018-04-21 12:56:51 +10:00
willyb321
445c63878b add guard for tooltips crashing the import 2018-04-21 11:39:02 +10:00
willyb321
7df5953824 bump 2018-04-21 11:05:19 +10:00
willyb321
e391b563fb change preset rolls to 100%, 75%, 50% and random 2018-04-21 11:03:23 +10:00
willyb321
53f62f96d0 Merge branch 'master' into develop 2018-04-21 07:17:11 +10:00
willyb321
18745979a0 update about page 2018-04-20 19:20:47 +10:00
willyb321
c1966a38ff Merge branch 'master' into develop 2018-04-20 12:49:10 +10:00
willyb321
d86973f3b1 specify appVersion for bugsnag 2018-04-20 12:48:46 +10:00
willyb321
56b8d19649 Merge branch 'release/2.9.4' 2018-04-19 07:17:21 +10:00
willyb321
7cb037e0bc bump 2018-04-19 07:17:06 +10:00
willyb321
56e1b3f9e9 Revert "prep for guardian"
This reverts commit 7e67bd80dd.
2018-04-19 07:14:24 +10:00
willyb321
c792323a8a misc bits 2018-04-19 07:14:13 +10:00
willyb321
4d2865de13 fix resistance blueprint values 2018-04-19 07:14:02 +10:00
willyb321
0d360bc367 bugsnag update 2018-04-19 07:13:20 +10:00
willyb321
91c9b46b91 clean up webpack configs 2018-04-19 06:44:02 +10:00
willyb321
7e67bd80dd prep for guardian 2018-04-19 06:26:53 +10:00
willyb321
2f775ea09b Merge branch 'release/2.9.3' 2018-04-18 18:04:42 +10:00
willyb321
0603c55089 bump 2018-04-18 18:04:28 +10:00
willyb321
41f25a44e9 Merge branch 'master' into develop 2018-04-18 10:33:17 +10:00
willyb321
35440b7273 allow specials in internal and standard slots 2018-04-18 10:33:11 +10:00
willyb321
7d68b91018 Merge branch 'release/2.9.2' 2018-04-17 17:33:45 +10:00
willyb321
deaa61b848 bump version 2018-04-17 17:32:56 +10:00
willyb321
2d00cbc41b fix more values (hopefully done)
also, Fixes #247 by changing passengers to pax
2018-04-17 17:31:11 +10:00
willyb321
57c1e83c67 apply new experimentals structure 2018-04-17 15:01:09 +10:00
William
b7079dbd4e Merge pull request #246 from pnellesen/feature/bugfix_245
Fix for issue 245
2018-04-17 12:38:21 +10:00
willyb321
be642a5373 Merge branch 'master' into develop 2018-04-17 07:59:42 +10:00
willyb321
ffc691c1a2 Merge branch 'release/2.9.1' 2018-04-17 07:57:44 +10:00
willyb321
6122d99369 version bump 2018-04-17 07:57:13 +10:00
willyb321
b73a8bcdab hopefully fix some values and crashes
resistances are buggered on shields and armour by the looks of things
2018-04-17 07:55:17 +10:00
willyb321
dd624537b7 fix hardpoints / internals having the wrong mod applied 2018-04-17 07:50:57 +10:00
willyb321
50a67f73fd get rid of some redundant code 2018-04-17 07:50:13 +10:00
willyb321
2262a980d4 Merge remote-tracking branch 'origin/develop' into develop 2018-04-17 07:46:35 +10:00
willyb321
84964ceb5f add source maps to assist with debugging.
also upload them to bugsnag
2018-04-17 07:46:26 +10:00
William Blythe
c4bdb7a66e Merge remote-tracking branch 'origin/master' into develop 2018-04-16 13:11:44 +10:00
Pat
21309e129f Fix for issue 245 - check that the passenger property is defined for a slot before calculating total passenger capacity 2018-04-14 08:50:48 -05:00
willyb321
928e02c718 Merge branch 'release/2.9.0' 2018-04-14 16:53:08 +10:00
willyb321
7f494dd200 bump 2018-04-14 16:52:05 +10:00
William
f38e743e59 Merge pull request #244 from EDCD/feature/beyond-coriolis
Engineer mods.
2018-04-14 15:14:13 +10:00
Willyb321
58b55eb3da Engineers. Are. Back. Probably. 2018-04-14 14:50:37 +10:00
willyb321
8375ad95b3 Merge branch 'master' into feature/beyond-coriolis 2018-04-14 10:16:20 +10:00
willyb321
506d027a2d Merge branch 'develop' 2018-04-14 09:52:03 +10:00
willyb321
fbeb6237cf fix webpack 2018-04-14 09:51:46 +10:00
willyb321
bd95e2c5a5 Merge branch 'master' into develop 2018-04-14 09:48:03 +10:00
willyb321
f3276e557a Merge branch 'release/2.5.3' 2018-04-14 09:47:08 +10:00
willyb321
8b813e0e7f bump 2018-04-14 09:46:41 +10:00
willyb321
d660d2959f more roles fixing
use a smaller shield, add boosters, d rate some internals
2018-04-14 09:44:29 +10:00
willyb321
014ebda7d2 start fixing suggested roles. Thanks to rinzler + others 2018-04-14 08:06:44 +10:00
William
607398d364 Merge pull request #241 from Blackth0rn/fix/#240_incorrect_passenger_counts
Fix/#240 incorrect passenger counts
2018-04-13 16:20:52 +10:00
willyb321
97d141ce2b update lockfile version 2018-04-13 16:17:59 +10:00
willyb321
9f492db9c6 source map 2018-04-13 16:16:42 +10:00
willyb321
6ed82b366c update bugsnag 2018-04-13 16:16:01 +10:00
willyb321
a7ca037f48 loadout event prep 2018-04-13 14:38:22 +10:00
willyb321
6e21d0e74a Work on engineering import 2018-04-13 13:39:33 +10:00
willyb321
e6c75bf2af Merge branch 'develop' into feature/beyond-coriolis 2018-04-13 09:16:23 +10:00
Greg Matthews
94037cea38 Fix #240: Added passenger count to individual ship page 2018-04-11 21:09:57 -07:00
Greg Matthews
5c1a9d9eea Fix #240: There is no size 7 passenger cabin so use the new findMaxInternal function to get the biggest one that fits in the slot 2018-04-11 21:07:00 -07:00
Greg Matthews
3835c73ffd Add findMaxInternal function to find the biggest module of a given group, rating, slotsize 2018-04-11 21:06:07 -07:00
35 changed files with 14636 additions and 13080 deletions

View File

@@ -5,7 +5,7 @@ root = true
# change these settings to your own preference # change these settings to your own preference
indent_style = space indent_style = space
indent_size = 4 indent_size = 2
# we recommend you to keep these unchanged # we recommend you to keep these unchanged
end_of_line = lf end_of_line = lf

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ nginx.pid
/bin /bin
env env
*.swp *.swp
.project

25066
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,114 +1,116 @@
{ {
"name": "coriolis_shipyard", "name": "coriolis_shipyard",
"version": "2.5.2", "version": "2.9.14",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/EDCD/coriolis" "url": "https://github.com/EDCD/coriolis"
}, },
"homepage": "https://coriolis.edcd.io", "homepage": "https://coriolis.edcd.io",
"bugs": "https://github.com/EDCD/coriolis/issues", "bugs": "https://github.com/EDCD/coriolis/issues",
"private": true, "private": true,
"engine": "node >= 4.8.1", "engine": "node >= 4.8.1",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"prepublish": "rollup -c && uglifyjs d3.js -c -m -o d3.min.js", "prepublish": "rollup -c && uglifyjs d3.js -c -m -o d3.min.js",
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f", "extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
"clean": "rimraf build", "clean": "rimraf build",
"start": "node devServer.js", "start": "node devServer.js",
"lint": "eslint --ext .js,.jsx src", "lint": "eslint --ext .js,.jsx src",
"test": "jest", "test": "jest",
"prod-serve": "nginx -p $(pwd) -c nginx.conf", "prod-serve": "nginx -p $(pwd) -c nginx.conf",
"prod-stop": "kill -QUIT $(cat nginx.pid)", "prod-stop": "kill -QUIT $(cat nginx.pid)",
"build": "npm run clean && NODE_ENV=production webpack -p --config webpack.config.prod.js", "build": "npm run clean && cross-env NODE_ENV=production webpack -p --config webpack.config.prod.js",
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws", "rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
"deploy": "npm run lint && npm test && npm run build && npm run rsync" "deploy": "npm run lint && npm test && npm run build && npm run rsync"
}, },
"jest": { "jest": {
"transform": { "transform": {
".*": "<rootDir>/node_modules/babel-jest" ".*": "<rootDir>/node_modules/babel-jest"
}, },
"testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$", "testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
"moduleFileExtensions": [ "moduleFileExtensions": [
"js", "js",
"json", "json",
"jsx" "jsx"
], ],
"automock": true, "automock": true,
"bail": false, "bail": false,
"unmockedModulePathPatterns": [ "unmockedModulePathPatterns": [
"<rootDir>/node_modules/lodash", "<rootDir>/node_modules/lodash",
"<rootDir>/node_modules/react", "<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom", "<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-transition-group", "<rootDir>/node_modules/react-transition-group",
"<rootDir>/node_modules/react-testutils-additions", "<rootDir>/node_modules/react-testutils-additions",
"<rootDir>/node_modules/fbjs", "<rootDir>/node_modules/fbjs",
"<rootDir>/node_modules/fbemitter", "<rootDir>/node_modules/fbemitter",
"<rootDir>/node_modules/classnames", "<rootDir>/node_modules/classnames",
"<rootDir>/node_modules/d3", "<rootDir>/node_modules/d3",
"<rootDir>/node_modules/lz-string", "<rootDir>/node_modules/lz-string",
"<rootDir>/node_modules/jsen", "<rootDir>/node_modules/jsen",
"coriolis-data", "coriolis-data",
"<rootDir>/src/app/shipyard", "<rootDir>/src/app/shipyard",
"<rootDir>/src/app/i18n", "<rootDir>/src/app/i18n",
"<rootDir>/src/app/utils", "<rootDir>/src/app/utils",
"<rootDir>/src/schemas", "<rootDir>/src/schemas",
"<rootDir>/__tests__" "<rootDir>/__tests__"
] ]
}, },
"devDependencies": { "devDependencies": {
"appcache-webpack-plugin": "^1.3.0", "appcache-webpack-plugin": "^1.3.0",
"babel-core": "*", "babel-core": "*",
"babel-eslint": "*", "babel-eslint": "*",
"babel-jest": "*", "babel-jest": "*",
"babel-loader": "*", "babel-loader": "*",
"babel-preset-env": "*", "babel-preset-env": "*",
"babel-preset-react": "*", "babel-preset-react": "*",
"babel-preset-stage-0": "*", "babel-preset-stage-0": "*",
"create-react-class": "^15.6.2", "create-react-class": "^15.6.2",
"css-loader": "^0.28.0", "css-loader": "^0.28.0",
"d3-selection": "1", "cross-env": "^5.1.4",
"eslint": "3.19.0", "d3-selection": "1",
"eslint-plugin-react": "^6.10.3", "eslint": "3.19.0",
"expose-loader": "^0.7.3", "eslint-plugin-react": "^6.10.3",
"express": "^4.15.2", "expose-loader": "^0.7.3",
"extract-text-webpack-plugin": "2.1.0", "express": "^4.15.2",
"file-loader": "^0.11.1", "extract-text-webpack-plugin": "2.1.0",
"html-webpack-plugin": "^2.28.0", "file-loader": "^0.11.1",
"jest-cli": "^21.2.1", "html-webpack-plugin": "^2.28.0",
"jsen": "^0.6.4", "jest-cli": "^21.2.1",
"json-loader": "^0.5.4", "jsen": "^0.6.4",
"less": "^2.7.2", "json-loader": "^0.5.4",
"less-loader": "^4.0.3", "less": "^2.7.2",
"react-addons-perf": "^15.4.2", "less-loader": "^4.0.3",
"react-measure": "^1.4.7", "react-addons-perf": "^15.4.2",
"react-testutils-additions": "^15.2.0", "react-measure": "^1.4.7",
"react-transition-group": "^1.1.2", "react-testutils-additions": "^15.2.0",
"rimraf": "^2.6.1", "react-transition-group": "^1.1.2",
"rollup": "0.41", "rimraf": "^2.6.1",
"rollup-plugin-node-resolve": "3", "rollup": "0.41",
"style-loader": "^0.16.1", "rollup-plugin-node-resolve": "3",
"uglify-js": "^2.4.11", "style-loader": "^0.16.1",
"url-loader": "^0.5.8", "uglify-js": "^2.4.11",
"webpack": "^2.4.1", "url-loader": "^0.5.8",
"webpack-dev-server": "^2.4.4", "webpack": "^2.4.1",
"webpack-notifier": "^1.6.0" "webpack-dev-server": "^2.4.4",
}, "webpack-notifier": "^1.6.0",
"dependencies": { "webpack-bugsnag-plugins": "^1.1.1"
"babel-polyfill": "*", },
"browserify-zlib-next": "^1.0.1", "dependencies": {
"classnames": "^2.2.5", "babel-polyfill": "*",
"coriolis-data": "../coriolis-data", "browserify-zlib-next": "^1.0.1",
"d3": "4.8.0", "classnames": "^2.2.5",
"detect-browser": "^1.7.0", "coriolis-data": "../coriolis-data",
"fbemitter": "^2.1.1", "d3": "4.8.0",
"lodash": "^4.17.4", "detect-browser": "^1.7.0",
"lz-string": "^1.4.4", "fbemitter": "^2.1.1",
"pako": "^1.0.6", "lodash": "^4.17.4",
"prop-types": "^15.5.8", "lz-string": "^1.4.4",
"react": "^15.5.4", "pako": "^1.0.6",
"react-dom": "^15.5.4", "prop-types": "^15.5.8",
"react-number-editor": "Athanasius/react-number-editor.git#miggy", "react": "^15.5.4",
"recharts": "^0.22.3", "react-dom": "^15.5.4",
"superagent": "^3.5.2" "react-number-editor": "Athanasius/react-number-editor.git#miggy",
} "recharts": "^0.22.3",
} "superagent": "^3.5.2"
}
}

View File

@@ -134,9 +134,9 @@ export default class Coriolis extends React.Component {
console && console.error && console.error(arguments); // eslint-disable-line no-console console && console.error && console.error(arguments); // eslint-disable-line no-console
if (errObj) { if (errObj) {
if (errObj instanceof Error) { if (errObj instanceof Error) {
Bugsnag.notifyException(errObj) // eslint-disable-line bugsnagClient.notify(errObj) // eslint-disable-line
} else if (errObj instanceof String) { } else if (errObj instanceof String) {
Bugsnag.notify(msg, errObj) // eslint-disable-line bugsnagClient.notify(msg, errObj) // eslint-disable-line
} }
} }
this.setState({ this.setState({

View File

@@ -44,6 +44,7 @@ const GRPCAT = {
'rg': 'projectiles', 'rg': 'projectiles',
'mr': 'ordnance', 'mr': 'ordnance',
'axmr': 'experimental', 'axmr': 'experimental',
'rcpl': 'experimental',
'tp': 'ordnance', 'tp': 'ordnance',
'nl': 'ordnance', 'nl': 'ordnance',
'sc': 'scanners', 'sc': 'scanners',
@@ -56,7 +57,11 @@ const GRPCAT = {
'ch': 'defence', 'ch': 'defence',
'po': 'defence', 'po': 'defence',
'ec': 'defence', 'ec': 'defence',
'sfn': 'defence' 'sfn': 'defence',
// Standard
'gpp': 'guardian',
'gpc': 'guardian',
'ggc': 'guardian'
}; };
// Order here is the order in which items will be shown in the modules menu // Order here is the order in which items will be shown in the modules menu
const CATEGORIES = { const CATEGORIES = {
@@ -82,7 +87,10 @@ const CATEGORIES = {
'defence': ['ch', 'po', 'ec'], 'defence': ['ch', 'po', 'ec'],
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners 'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
// Experimental // Experimental
'experimental': ['axmc', 'axmr', 'rfl', 'xs', 'sfn'] 'experimental': ['axmc', 'axmr', 'rfl', 'xs', 'sfn', 'rcpl'],
// Guardian
'guardian': ['gpp', 'gpc', 'ggc']
}; };
/** /**
@@ -96,7 +104,10 @@ export default class AvailableModulesMenu extends TranslatedComponent {
diffDetails: PropTypes.func, diffDetails: PropTypes.func,
m: PropTypes.object, m: PropTypes.object,
shipMass: PropTypes.number, shipMass: PropTypes.number,
warning: PropTypes.func warning: PropTypes.func,
firstSlotId: PropTypes.string,
lastSlotId: PropTypes.string,
slotDiv: PropTypes.object
}; };
static defaultProps = { static defaultProps = {
@@ -112,6 +123,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
super(props); super(props);
this._hideDiff = this._hideDiff.bind(this); this._hideDiff = this._hideDiff.bind(this);
this.state = this._initState(props, context); this.state = this._initState(props, context);
this.slotItems = [];// Array to hold <li> refs.
} }
/** /**
@@ -122,8 +134,9 @@ export default class AvailableModulesMenu extends TranslatedComponent {
*/ */
_initState(props, context) { _initState(props, context) {
let translate = context.language.translate; let translate = context.language.translate;
let { m, warning, shipMass, onSelect, modules } = props; let { m, warning, shipMass, onSelect, modules, firstSlotId, lastSlotId } = props;
let list, currentGroup; let list, currentGroup;
let buildGroup = this._buildGroup.bind( let buildGroup = this._buildGroup.bind(
this, this,
translate, translate,
@@ -134,15 +147,18 @@ export default class AvailableModulesMenu extends TranslatedComponent {
this._hideDiff(event); this._hideDiff(event);
onSelect(m); onSelect(m);
} }
); );
if (modules instanceof Array) { if (modules instanceof Array) {
list = buildGroup(modules[0].grp, modules); list = buildGroup(modules[0].grp, modules);
} else { } else {
list = []; list = [];
// 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) {
list.push(<div className='empty-c upp' key='empty' onClick={onSelect.bind(null, null)} >{translate('empty')}</div>); let emptyId = 'empty';
if(this.firstSlotId == null) this.firstSlotId = emptyId;
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>);
} }
// Need to regroup the modules by our own categorisation // Need to regroup the modules by our own categorisation
@@ -192,37 +208,38 @@ export default class AvailableModulesMenu extends TranslatedComponent {
} }
} }
} }
let trackingFocus = false;
return { list, currentGroup }; return { list, currentGroup, trackingFocus};
} }
/** /**
* Generate React Components for Module Group * Generate React Components for Module Group
* @param {Function} translate Translate function * @param {Function} translate Translate function
* @param {Objecy} mountedModule Mounted Module * @param {Object} mountedModule Mounted Module
* @param {Funciton} warningFunc Warning function * @param {Function} warningFunc Warning function
* @param {number} mass Mass * @param {number} mass Mass
* @param {function} onSelect Select/Mount callback * @param {function} onSelect Select/Mount callback
* @param {string} grp Group name * @param {string} grp Group name
* @param {Array} modules Available modules * @param {Array} modules Available modules
* @return {React.Component} Available Module Group contents * @return {React.Component} Available Module Group contents
*/ */
_buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules) { _buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules, firstSlotId, lastSlotId) {
let prevClass = null, prevRating = null; let prevClass = null, prevRating = null, prevName;
let elems = []; let elems = [];
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;
for (let i = 0; i < sortedModules.length; i++) { for (let i = 0; i < sortedModules.length; i++) {
let m = sortedModules[i]; let m = sortedModules[i];
let mount = null; let mount = null;
let disabled = false; let disabled = false;
prevName = m.name
if (ModuleUtils.isShieldGenerator(m.grp)) { if (ModuleUtils.isShieldGenerator(m.grp)) {
// Shield generators care about maximum hull mass // Shield generators care about maximum hull mass
disabled = mass > m.maxmass; disabled = mass > m.maxmass;
@@ -239,8 +256,23 @@ export default class AvailableModulesMenu extends TranslatedComponent {
let eventHandlers; let eventHandlers;
if (disabled || active) { if (disabled || active) {
eventHandlers = {}; /**
* ToDo: possibly create an "activeSlotId" variable to allow
* focus to be set on active slot when slot menu is opened
*/
eventHandlers = {
onKeyDown: this._keyDown.bind(this, null),
onKeyUp: this._keyUp.bind(this, null)
};
} else { } else {
/**
* Get the ids of the first and last <li> elements in the <ul> that are focusable (i.e. are not active or disabled)
* Will be used to keep focus inside the <ul> on Tab and Shift-Tab while it is visible
*/
if (this.firstSlotId == null) this.firstSlotId = sortedModules[i].id;
this.lastSlotId = sortedModules[i].id;
let showDiff = this._showDiff.bind(this, mountedModule, m); let showDiff = this._showDiff.bind(this, mountedModule, m);
let select = onSelect.bind(null, m); let select = onSelect.bind(null, m);
@@ -249,7 +281,9 @@ export default class AvailableModulesMenu extends TranslatedComponent {
onTouchStart: this._touchStart.bind(this, showDiff), onTouchStart: this._touchStart.bind(this, showDiff),
onTouchEnd: this._touchEnd.bind(this, select), onTouchEnd: this._touchEnd.bind(this, select),
onMouseLeave: this._hideDiff, onMouseLeave: this._hideDiff,
onClick: select onClick: select,
onKeyDown: this._keyDown.bind(this, select),
onKeyUp: this._keyUp.bind(this, select)
}; };
} }
@@ -258,24 +292,28 @@ export default class AvailableModulesMenu extends TranslatedComponent {
case 'G': mount = <MountGimballed className={'lg'}/>; break; case 'G': mount = <MountGimballed className={'lg'}/>; break;
case 'T': mount = <MountTurret className={'lg'}/>; break; case 'T': mount = <MountTurret className={'lg'}/>; break;
} }
if (m.name && m.name === prevName) {
// elems.push(<br key={'b' + m.grp + i} />);
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 && classes.indexOf('active') < 0) ? 0 : undefined;
elems.push( elems.push(
<li key={m.id} className={classes} {...eventHandlers}> <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>
); );
itemsOnThisRow++; itemsOnThisRow++;
prevClass = m.class; prevClass = m.class;
prevRating = m.rating; prevRating = m.rating;
prevName = m.name;
} }
return <ul key={'modules' + grp}>{elems}</ul>;
return <ul key={'modules' + grp} >{elems}</ul>;
} }
/** /**
@@ -326,6 +364,41 @@ export default class AvailableModulesMenu extends TranslatedComponent {
this._hideDiff(); this._hideDiff();
} }
/**
* Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc
* @param {Function} select Select module callback
* @param {SyntheticEvent} event Event
*/
_keyDown(select, event) {
var className = event.currentTarget.attributes['class'].value;
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
select();
return
}
var elemId = event.currentTarget.attributes['data-id'].value;
if (className.indexOf('disabled') < 0 && event.key == 'Tab') {
if (event.shiftKey && elemId == this.firstSlotId) {
event.preventDefault();
this.slotItems[this.lastSlotId].focus();
return;
}
if (!event.shiftKey && elemId == this.lastSlotId) {
event.preventDefault();
this.slotItems[this.firstSlotId].focus();
return;
}
}
}
/**
* Key Up
*
*/
_keyUp(select,event) {
//nothing here yet
}
/** /**
* Hide diff tooltip * Hide diff tooltip
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
@@ -379,10 +452,29 @@ export default class AvailableModulesMenu extends TranslatedComponent {
/** /**
* Scroll to mounted (if it exists) module group on mount * Scroll to mounted (if it exists) module group on mount
*/ */
componentDidMount() { componentDidMount() {
if (this.groupElem) { // Scroll to currently selected group if (this.groupElem) { // Scroll to currently selected group
this.node.scrollTop = this.groupElem.offsetTop; this.node.scrollTop = this.groupElem.offsetTop;
} }
if (this.slotItems) {
/**
* Set focus on first focusable slot <li> after component mounts. May want to consider
* changing this to the Active item instead.
*/
this.slotItems[this.firstSlotId].focus();
}
}
componentWillUnmount() {
if(this.props.slotDiv) {
console.log("AvailableModulesMenu component will unmount. Set focus to slot");
this.props.slotDiv.focus();
} else {
console.log("AvailableModulesMenu component will unmount. No slotDiv prop present.");
}
} }
/** /**
@@ -399,6 +491,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @return {React.Component} List * @return {React.Component} List
*/ */
render() { render() {
console.log("Tracking focus? " + this.state.trackingFocus);
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)}

View File

@@ -97,11 +97,12 @@ export default class HardpointSlot extends Slot {
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</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 } { 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.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></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

@@ -21,6 +21,8 @@ export default class InternalSlot extends Slot {
* @param {Object} u Localized Units Map * @param {Object} u Localized Units Map
* @return {React.Component} Slot contents * @return {React.Component} Slot contents
*/ */
_getSlotDetails(m, enabled, translate, formats, u) { _getSlotDetails(m, enabled, translate, formats, u) {
if (m) { if (m) {
let classRating = m.class + m.rating; let classRating = m.class + m.rating;
@@ -33,6 +35,9 @@ export default class InternalSlot extends Slot {
let modTT = translate('modified'); let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) { if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id >= 0) {
modTT += ', ' + translate(m.blueprint.special.name);
}
modTT = ( modTT = (
<div> <div>
<div>{modTT}</div> <div>{modTT}</div>
@@ -67,6 +72,7 @@ export default class InternalSlot extends Slot {
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null } { m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null }
{ m.getRangeT() ? <div className={'l'}>{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}</div> : null } { m.getRangeT() ? <div className={'l'}>{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}</div> : null }
{ m.getTime() ? <div className={'l'}>{translate('time')}: {formats.time(m.getTime())}</div> : null } { m.getTime() ? <div className={'l'}>{translate('time')}: {formats.time(m.getTime())}</div> : null }
{ m.getHackTime() ? <div className={'l'}>{translate('hacktime')}: {formats.time(m.getHackTime())}</div> : null }
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null } { m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null } { m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
{ m.rangeLS === null ? <div className={'l'}>{u.Ls}</div> : null } { m.rangeLS === null ? <div className={'l'}>{u.Ls}</div> : null }
@@ -81,7 +87,7 @@ export default class InternalSlot extends Slot {
{ m.getHullReinforcement() ? <div className='l'>{translate('armour')}: {formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)}</div> : null } { m.getHullReinforcement() ? <div className='l'>{translate('armour')}: {formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)}</div> : null }
{ m.getProtection() ? <div className='l'>{translate('protection')}: {formats.rPct(m.getProtection())}</div> : null } { m.getProtection() ? <div className='l'>{translate('protection')}: {formats.rPct(m.getProtection())}</div> : null }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null } { m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></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 {

View File

@@ -14,7 +14,10 @@ export default class Modification extends TranslatedComponent {
m: PropTypes.object.isRequired, m: PropTypes.object.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
value: PropTypes.number.isRequired, value: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired onChange: PropTypes.func.isRequired,
onKeyDown: PropTypes.func.isRequired,
modItems: PropTypes.array.isRequired,
handleModChange: PropTypes.func.isRequired
}; };
/** /**
@@ -36,7 +39,6 @@ export default class Modification extends TranslatedComponent {
*/ */
_updateValue(value) { _updateValue(value) {
const name = this.props.name; const name = this.props.name;
let scaledValue = Math.round(Number(value) * 100); let scaledValue = Math.round(Number(value) * 100);
// Limit to +1000% / -99.99% // Limit to +1000% / -99.99%
if (scaledValue > 100000) { if (scaledValue > 100000) {
@@ -57,9 +59,15 @@ export default class Modification extends TranslatedComponent {
/** /**
* Triggered when an update to slider value is finished i.e. when losing focus * Triggered when an update to slider value is finished i.e. when losing focus
*
* pnellesen (24/05/2018): added value check below - this should prevent experimental effects from being recalculated
* with each onBlur event, even when no change has actually been made to the field.
*/ */
_updateFinished() { _updateFinished() {
this.props.onChange(); if (this.props.value != this.state.value) {
this.props.handleModChange(true);
this.props.onChange();
}
} }
/** /**
@@ -86,9 +94,9 @@ export default class Modification extends TranslatedComponent {
} }
return ( return (
<div onBlur={this._updateFinished.bind(this)} className={'cb'} key={name}> <div onBlur={this._updateFinished.bind(this)} className={'cb'} key={name} ref={ modItem => this.props.modItems[name] = modItem }>
<div className={'cb'}>{translate(name, m.grp)}{symbol}</div> <div className={'cb'}>{translate(name, m.grp)}{symbol}</div>
<NumberEditor className={'cb'} style={{ width: '90%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} /> <NumberEditor className={'cb'} style={{ width: '90%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} onKeyDown={ this.props.onKeyDown } />
</div> </div>
); );
} }

View File

@@ -6,7 +6,14 @@ import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames'; import cn from 'classnames';
import { Modifications } from 'coriolis-data/dist'; import { Modifications } from 'coriolis-data/dist';
import Modification from './Modification'; import Modification from './Modification';
import { getBlueprint, blueprintTooltip, setWorst, setBest, setExtreme, setRandom } from '../utils/BlueprintFunctions'; import {
getBlueprint,
blueprintTooltip,
setPercent,
getPercent,
setRandom,
specialToolTip
} from '../utils/BlueprintFunctions'
/** /**
* Modifications menu * Modifications menu
@@ -17,7 +24,8 @@ export default class ModificationsMenu extends TranslatedComponent {
ship: PropTypes.object.isRequired, ship: PropTypes.object.isRequired,
m: PropTypes.object.isRequired, m: PropTypes.object.isRequired,
marker: PropTypes.string.isRequired, marker: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired onChange: PropTypes.func.isRequired,
modButton:PropTypes.object
}; };
/** /**
@@ -30,14 +38,24 @@ export default class ModificationsMenu extends TranslatedComponent {
this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this); this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this);
this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this); this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this);
this._rollWorst = this._rollWorst.bind(this); this._rollFifty = this._rollFifty.bind(this);
this._rollRandom = this._rollRandom.bind(this); this._rollRandom = this._rollRandom.bind(this);
this._rollBest = this._rollBest.bind(this); this._rollBest = this._rollBest.bind(this);
this._rollExtreme = this._rollExtreme.bind(this); this._rollWorst = this._rollWorst.bind(this);
this._reset = this._reset.bind(this); this._reset = this._reset.bind(this);
this._keyDown = this._keyDown.bind(this);
this.modItems = [];// Array to hold various element refs (<li>, <div>, <ul>, etc.)
this.firstModId = null;
this.firstBPLabel = null;// First item in mod menu
this.lastModId = null;
this.lastNeId = null;//Last number editor id. Used to set focus to last number editor when shift-tab pressed on first element in mod menu.
this.modValDidChange = false; //used to determine if component update was caused by change in modification value.
this._handleModChange = this._handleModChange.bind(this);
this.state = { this.state = {
blueprintMenuOpened: false, blueprintMenuOpened: !(props.m.blueprint && props.m.blueprint.name),
specialMenuOpened: false specialMenuOpened: false
}; };
} }
@@ -52,7 +70,6 @@ export default class ModificationsMenu extends TranslatedComponent {
const { m } = props; const { m } = props;
const { language, tooltip, termtip } = context; const { language, tooltip, termtip } = context;
const translate = language.translate; const translate = language.translate;
const blueprints = []; const blueprints = [];
for (const blueprintName in Modifications.modules[m.grp].blueprints) { for (const blueprintName in Modifications.modules[m.grp].blueprints) {
const blueprint = getBlueprint(blueprintName, m); const blueprint = getBlueprint(blueprintName, m);
@@ -66,9 +83,14 @@ export default class ModificationsMenu extends TranslatedComponent {
const close = this._blueprintSelected.bind(this, blueprintName, grade); const close = this._blueprintSelected.bind(this, blueprintName, grade);
const key = blueprintName + ':' + grade; const key = blueprintName + ':' + grade;
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp); const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
blueprintGrades.unshift(<li key={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close}>{grade}</li>);
blueprintGrades.unshift(<li key={key} tabIndex="0" data-id={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close} onKeyDown={this._keyDown} ref={modItem => this.modItems[key] = modItem}>{grade}</li>);
} }
if (blueprintGrades) { if (blueprintGrades) {
const thisLen = blueprintGrades.length;
if (this.firstModId == null) this.firstModId = blueprintGrades[0].key;
this.lastModId = blueprintGrades[thisLen-1].key;
blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>); blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>);
blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>); blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>);
} }
@@ -76,6 +98,68 @@ export default class ModificationsMenu extends TranslatedComponent {
return blueprints; return blueprints;
} }
/**
* Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc
* @param {Function} select Select module callback
* @param {SyntheticEvent} event Event
*
*/
_keyDown(event) {
var className = null;
var elemId = null;
if (event.currentTarget.attributes['class']) className = event.currentTarget.attributes['class'].value;
if (event.currentTarget.attributes['data-id']) elemId = event.currentTarget.attributes['data-id'].value;
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
event.stopPropagation();
if (elemId != null) {
this.modItems[elemId].click();
} else {
event.currentTarget.click();
}
return
}
if (event.key == 'Tab') {
//Shift-Tab
if(event.shiftKey) {
if (elemId == this.firstModId && elemId != null) {
// Initial modification menu
event.preventDefault();
this.modItems[this.lastModId].focus();
return;
} else if (event.currentTarget.className.indexOf("button-inline-menu") >= 0 && event.currentTarget.previousElementSibling == null && this.lastNeId != null && this.modItems[this.lastNeId] != null) {
// shift-tab on first element in modifications menu. set focus to last number editor field if open
event.preventDefault();
this.modItems[this.lastNeId].lastChild.focus();
return;
} else if (event.currentTarget.className.indexOf("button-inline-menu") >= 0 && event.currentTarget.previousElementSibling == null) {
// shift-tab on button-inline-menu with no number editor
event.preventDefault();
event.currentTarget.parentElement.lastElementChild.focus();
}
} else {
if (elemId == this.lastModId && elemId != null) {
// Initial modification menu
event.preventDefault();
this.modItems[this.firstModId].focus();
return;
} else if (event.currentTarget.className.indexOf("button-inline-menu") >= 0 && event.currentTarget.nextSibling == null && event.currentTarget.nodeName != "TD") {
// Experimental menu
event.preventDefault();
event.currentTarget.parentElement.firstElementChild.focus();
return;
} else if (event.currentTarget.className == 'cb' && event.currentTarget.parentElement.nextSibling == null) {
event.preventDefault();
this.modItems[this.firstBPLabel].focus();
}
}
}
}
/** /**
* Render the specials * Render the specials
* @param {Object} props React component properties * @param {Object} props React component properties
@@ -83,20 +167,40 @@ export default class ModificationsMenu extends TranslatedComponent {
* @return {Object} list: Array of React Components * @return {Object} list: Array of React Components
*/ */
_renderSpecials(props, context) { _renderSpecials(props, context) {
const { m } = props; const { m } = props;
const { language, tooltip, termtip } = context; const { language, tooltip, termtip } = context;
const translate = language.translate; const translate = language.translate;
const specials = []; const specials = [];
const specialsId = m.missile && Modifications.modules[m.grp]['specials_' + m.missile] ? 'specials_' + m.missile : 'specials'; const specialsId = m.missile && Modifications.modules[m.grp]['specials_' + m.missile] ? 'specials_' + m.missile : 'specials';
if (Modifications.modules[m.grp][specialsId] && Modifications.modules[m.grp][specialsId].length > 0) { if (Modifications.modules[m.grp][specialsId] && Modifications.modules[m.grp][specialsId].length > 0) {
const close = this._specialSelected.bind(this, null); const close = this._specialSelected.bind(this, null);
specials.push(<div style={{ cursor: 'pointer' }} key={ 'none' } onClick={ close }>{translate('PHRASE_NO_SPECIAL')}</div>); specials.push(<div tabIndex="0" style={{ cursor: 'pointer', fontWeight: 'bold' }} className={ 'button-inline-menu warning' } key={ 'none' } data-id={ 'none' } onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems['none'] = modItem}>{translate('PHRASE_NO_SPECIAL')}</div>);
for (const specialName of Modifications.modules[m.grp][specialsId]) { for (const specialName of Modifications.modules[m.grp][specialsId]) {
if (Modifications.specials[specialName].name.search('Legacy') >= 0) {
continue;
}
const classes = cn('button-inline-menu', {
active: m.blueprint && m.blueprint.special && m.blueprint.special.edname == specialName
});
const close = this._specialSelected.bind(this, specialName); const close = this._specialSelected.bind(this, specialName);
specials.push(<div style={{ cursor: 'pointer' }} key={ specialName } onClick={ close }>{translate(Modifications.specials[specialName].name)}</div>); if (m.blueprint && m.blueprint.name) {
let tmp = {};
if (m.blueprint.special) {
tmp = m.blueprint.special;
} else {
tmp = undefined;
}
m.blueprint.special = Modifications.specials[specialName];
let specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, specialName);
m.blueprint.special = tmp;
specials.push(<div tabIndex="0" style={{ cursor: 'pointer' }} className={classes} key={ specialName } data-id={ specialName } onMouseOver={termtip.bind(null, specialTt)} onMouseOut={tooltip.bind(null, null)} onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems[specialName] = modItem}>{translate(Modifications.specials[specialName].name)}</div>);
} else {
specials.push(<div tabIndex="0" style={{ cursor: 'pointer' }} className={classes} key={ specialName } data-id={ specialName }onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems[specialName] = modItem}>{translate(Modifications.specials[specialName].name)}</div>);
}
} }
} }
console.log("_renderSpecials. specials: %O", specials);
return specials; return specials;
} }
@@ -111,9 +215,11 @@ export default class ModificationsMenu extends TranslatedComponent {
for (const modName of Modifications.modules[m.grp].modifications) { for (const modName of Modifications.modules[m.grp].modifications) {
if (!Modifications.modifications[modName].hidden) { if (!Modifications.modifications[modName].hidden) {
const key = modName + (m.getModValue(modName) / 100 || 0); const key = modName + (m.getModValue(modName) / 100 || 0);
modifications.push(<Modification key={ key } ship={ ship } m={ m } name={ modName } value={ m.getModValue(modName) / 100 || 0 } onChange={ onChange }/>); this.lastNeId = modName;
modifications.push(<Modification key={ key } ship={ ship } m={ m } name={ modName } value={ m.getModValue(modName) / 100 || 0 } onChange={ onChange } onKeyDown={ this._keyDown } modItems={ this.modItems } handleModChange = {this._handleModChange} />);
} }
} }
console.log("_renderModifications. modItems: %O", this.modItems);
return modifications; return modifications;
} }
@@ -136,8 +242,9 @@ export default class ModificationsMenu extends TranslatedComponent {
const blueprint = getBlueprint(fdname, m); const blueprint = getBlueprint(fdname, m);
blueprint.grade = grade; blueprint.grade = grade;
ship.setModuleBlueprint(m, blueprint); ship.setModuleBlueprint(m, blueprint);
setPercent(ship, m, 100);
this.setState({ blueprintMenuOpened: false }); this.setState({ blueprintMenuOpened: false, specialMenuOpened: true });
this.props.onChange(); this.props.onChange();
} }
@@ -168,11 +275,15 @@ export default class ModificationsMenu extends TranslatedComponent {
} }
/** /**
* Provide a 'worst' roll within the information we have * Provide a '50%' roll within the information we have
*/ */
_rollWorst() { _rollFifty() {
const { m, ship } = this.props; const { m, ship } = this.props;
setWorst(ship, m); setPercent(ship, m, 50);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true);
this.props.onChange(); this.props.onChange();
} }
@@ -182,6 +293,10 @@ export default class ModificationsMenu extends TranslatedComponent {
_rollRandom() { _rollRandom() {
const { m, ship } = this.props; const { m, ship } = this.props;
setRandom(ship, m); setRandom(ship, m);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true);
this.props.onChange(); this.props.onChange();
} }
@@ -190,17 +305,27 @@ export default class ModificationsMenu extends TranslatedComponent {
*/ */
_rollBest() { _rollBest() {
const { m, ship } = this.props; const { m, ship } = this.props;
setBest(ship, m); setPercent(ship, m, 100);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true);
this.props.onChange(); this.props.onChange();
} }
/** /**
* Provide an 'extreme' roll within the information we have * Provide a 'worst' roll within the information we have
*/ */
_rollExtreme() { _rollWorst() {
const { m, ship } = this.props; const { m, ship } = this.props;
setExtreme(ship, m); setPercent(ship, m, 0);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true);
this.props.onChange(); this.props.onChange();
} }
/** /**
@@ -210,10 +335,56 @@ export default class ModificationsMenu extends TranslatedComponent {
const { m, ship } = this.props; const { m, ship } = this.props;
ship.clearModifications(m); ship.clearModifications(m);
ship.clearModuleBlueprint(m); ship.clearModuleBlueprint(m);
this.props.onChange(); this.props.onChange();
} }
/**
* set mod did change boolean
*/
_handleModChange(b) {
this.modValDidChange = b;
}
componentDidMount() {
/**
* Set focus on first element in modifications menu
* after it first mounts
*/
let firstEleCn = this.modItems['modMainDiv'].children.length > 0 ? this.modItems['modMainDiv'].children[0].className : null;
if (firstEleCn.indexOf('select-group cap') >= 0) {
this.modItems['modMainDiv'].children[1].firstElementChild.focus();
} else {
this.modItems['modMainDiv'].firstElementChild.focus();
}
}
componentDidUpdate() {
/**
* Set focus on first element in modifications menu
* if component updates, unless update is due to value change
* in a modification
*/
if (!this.modValDidChange) {
if (this.modItems['modMainDiv'].children.length > 0) {
let firstEleCn = this.modItems['modMainDiv'].children[0].className;
if (firstEleCn.indexOf('button-inline-menu') >= 0) {
this.modItems['modMainDiv'].firstElementChild.focus();
} else if (firstEleCn.indexOf('select-group cap') >= 0) {
this.modItems['modMainDiv'].children[1].firstElementChild.focus();
}
}
} else {
this._handleModChange(false);//Need to reset if component update due to value change
}
}
componentWillUnmount() {
if (this.props.modButton) {
this.props.modButton.focus();// set focus to the modification menu icon after mod menu is unmounted.
}
}
/** /**
* Render the list * Render the list
* @return {React.Component} List * @return {React.Component} List
@@ -226,66 +397,77 @@ export default class ModificationsMenu extends TranslatedComponent {
const _toggleBlueprintsMenu = this._toggleBlueprintsMenu; const _toggleBlueprintsMenu = this._toggleBlueprintsMenu;
const _toggleSpecialsMenu = this._toggleSpecialsMenu; const _toggleSpecialsMenu = this._toggleSpecialsMenu;
const _rollBest = this._rollBest; const _rollFull = this._rollBest;
const _rollExtreme = this._rollExtreme;
const _rollWorst = this._rollWorst; const _rollWorst = this._rollWorst;
const _rollFifty = this._rollFifty;
const _rollRandom = this._rollRandom; const _rollRandom = this._rollRandom;
const _reset = this._reset; const _reset = this._reset;
let blueprintLabel; let blueprintLabel;
let haveBlueprint = false; let haveBlueprint = false;
let blueprintTt; let blueprintTt;
if (m.blueprint && m.blueprint.name) { let blueprintCv;
if (m.blueprint && m.blueprint.name && Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade]) {
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
haveBlueprint = true; haveBlueprint = true;
blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp); blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp);
blueprintCv = getPercent(m);
} }
let specialLabel; let specialLabel;
let specialTt;
if (m.blueprint && m.blueprint.special) { if (m.blueprint && m.blueprint.special) {
specialLabel = m.blueprint.special.name; specialLabel = m.blueprint.special.name;
specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, m.blueprint.special.edname);
} else { } else {
specialLabel = translate('PHRASE_SELECT_SPECIAL'); specialLabel = translate('PHRASE_SELECT_SPECIAL');
} }
const specials = this._renderSpecials(this.props, this.context); const specials = this._renderSpecials(this.props, this.context);
/**
* pnellesen - 05/28/2018 - added additional checks for specials.length below to ensure menus
* display correctly in cases where there are no specials (ex: AFMUs.)
*/
const showBlueprintsMenu = blueprintMenuOpened; const showBlueprintsMenu = blueprintMenuOpened;
const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened; const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened;
const showSpecialsMenu = specialMenuOpened; const showSpecialsMenu = specialMenuOpened && specials.length;
const showRolls = haveBlueprint && !blueprintMenuOpened && !specialMenuOpened; const showRolls = haveBlueprint && !blueprintMenuOpened && (!specialMenuOpened || !specials.length);
const showReset = !blueprintMenuOpened && !specialMenuOpened; const showReset = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
const showMods = !blueprintMenuOpened && !specialMenuOpened; const showMods = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
if (haveBlueprint) {
this.firstBPLabel = blueprintLabel
} else {
this.firstBPLabel = 'selectBP';
}
return ( return (
<div <div
className={cn('select', this.props.className)} className={cn('select', this.props.className)}
onClick={(e) => e.stopPropagation() } onClick={(e) => e.stopPropagation() }
onContextMenu={stopCtxPropagation} onContextMenu={stopCtxPropagation}
ref={modItem => this.modItems['modMainDiv'] = modItem}
> >
{ showBlueprintsMenu ? '' : haveBlueprint ? { showBlueprintsMenu | showSpecialsMenu ? '' : haveBlueprint ?
<div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu}>{blueprintLabel}</div> : <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{blueprintLabel}</div> :
<div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> } <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
{ showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null } { showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null }
{ showSpecial ? <div className={ cn('section-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleSpecialsMenu}>{specialLabel}</div> : null } { showSpecial & !showSpecialsMenu ? <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={specialTt ? termtip.bind(null, specialTt) : null} onMouseOut={specialTt ? tooltip.bind(null, null) : null} onClick={_toggleSpecialsMenu} onKeyDown={ this._keyDown }>{specialLabel}</div> : null }
{ showSpecialsMenu ? specials : null } { showSpecialsMenu ? specials : null }
{ showRolls || showReset ? { showReset ? <div tabIndex="0" className={'section-menu button-inline-menu warning'} style={{ cursor: 'pointer' }} onClick={_reset} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </div> : null }
{ showRolls ?
<table style={{ width: '100%', backgroundColor: 'transparent' }}> <table style={{ width: '100%', backgroundColor: 'transparent' }}>
<tbody> <tbody>
{ showRolls ? { showRolls ?
<tr> <tr>
<td> { translate('roll') }: </td> <td tabIndex="0" className={ cn('section-menu button-inline-menu', {active: false})}> { translate('roll') }: </td>
<td style={{ cursor: 'pointer' }} onClick={_rollWorst} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('worst') } </td> <td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 0 })} style={{ cursor: 'pointer' }} onClick={_rollWorst} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('0%') } </td>
<td style={{ cursor: 'pointer' }} onClick={_rollBest}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('best') } </td> <td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 50 })} style={{ cursor: 'pointer' }} onClick={_rollFifty} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')} onMouseOut={tooltip.bind(null, null)}> { translate('50%') } </td>
<td style={{ cursor: 'pointer' }} onClick={_rollExtreme}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_EXTREME')} onMouseOut={tooltip.bind(null, null)}> { translate('extreme') } </td> <td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 100 })} style={{ cursor: 'pointer' }} onClick={_rollFull} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('100%') } </td>
<td style={{ cursor: 'pointer' }} onClick={_rollRandom} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td> <td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === null || blueprintCv % 50 != 0 })} style={{ cursor: 'pointer' }} onClick={_rollRandom} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
</tr> : null }
{ showReset ?
<tr>
<td colSpan={'5'} style={{ cursor: 'pointer' }} onClick={_reset}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </td>
</tr> : null } </tr> : null }
</tbody> </tbody>
</table> : null } </table> : null }
{ showMods ? <hr /> : null }
{ showMods ? { showMods ?
<span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} > <span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} >
{ this._renderModifications(this.props) } { this._renderModifications(this.props) }

View File

@@ -15,22 +15,30 @@ export default class ShipSummaryTable extends TranslatedComponent {
cargo: PropTypes.number.isRequired, cargo: PropTypes.number.isRequired,
fuel: PropTypes.number.isRequired, fuel: PropTypes.number.isRequired,
marker: PropTypes.string.isRequired, marker: PropTypes.string.isRequired,
pips: PropTypes.object.isRequired
}; };
constructor(props) {
super(props)
this.didContextChange = this.didContextChange.bind(this);
this.state = {
shieldColour: 'blue'
}
}
/** /**
* Render the table * Render the table
* @return {React.Component} Summary table * @return {React.Component} Summary table
*/ */
render() { render() {
const { ship, cargo, fuel } = this.props; const { ship, cargo, fuel, pips } = this.props;
let { language, tooltip, termtip } = this.context; let { language, tooltip, termtip } = this.context;
let translate = language.translate; let translate = language.translate;
let u = language.units; let u = language.units;
let formats = language.formats; let formats = language.formats;
let { time, int, round, f1, f2 } = formats; let { time, int, round, f1, f2 } = formats;
let hide = tooltip.bind(null, null); let hide = tooltip.bind(null, null);
const shieldGenerator = ship.findInternalByGroup('sg') || ship.findInternalByGroup('psg');
const shieldGenerator = ship.findInternalByGroup('sg');
const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator }); const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL'; const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
const timeToDrain = Calc.timeToDrainWep(ship, 4); const timeToDrain = Calc.timeToDrainWep(ship, 4);
@@ -38,9 +46,19 @@ export default class ShipSummaryTable extends TranslatedComponent {
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL'; const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const canBoost = ship.canBoost(cargo, ship.fuelCapacity); const canBoost = ship.canBoost(cargo, ship.fuelCapacity);
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 || 2);
const armourMetrics = Calc.armourMetrics(ship);
let shieldColour = 'blue';
if (shieldGenerator && shieldGenerator.m.grp === 'psg') {
shieldColour = 'green';
} else if (shieldGenerator && shieldGenerator.m.grp === 'bsg') {
shieldColour = 'purple';
}
this.state = {
shieldColour
}
return <div id='summary'> return <div id='summary'>
<table id='summaryTable'> <table className={'summaryTable'}>
<thead> <thead>
<tr className='main'> <tr className='main'>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th> <th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
@@ -53,6 +71,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
<th rowSpan={2}>{translate('TTD')}</th> <th rowSpan={2}>{translate('TTD')}</th>
{/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */} {/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
<th rowSpan={2}>{translate('cargo')}</th> <th rowSpan={2}>{translate('cargo')}</th>
<th rowSpan={2}>{translate('pax')}</th>
<th rowSpan={2}>{translate('fuel')}</th> <th rowSpan={2}>{translate('fuel')}</th>
<th colSpan={3}>{translate('mass')}</th> <th colSpan={3}>{translate('mass')}</th>
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th> <th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
@@ -86,6 +105,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
{/* <td>{f1(ship.totalHps)}</td> */} {/* <td>{f1(ship.totalHps)}</td> */}
<td>{round(ship.cargoCapacity)}{u.T}</td> <td>{round(ship.cargoCapacity)}{u.T}</td>
<td>{ship.passengerCapacity}</td>
<td>{round(ship.fuelCapacity)}{u.T}</td> <td>{round(ship.fuelCapacity)}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td>
@@ -96,6 +116,70 @@ export default class ShipSummaryTable extends TranslatedComponent {
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table className={'summaryTable'}>
<thead className={this.state.shieldColour}>
<tr>
<th onMouseEnter={termtip.bind(null, 'shield', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('shield')}</th>
<th onMouseEnter={termtip.bind(null, 'explres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('explres')}</th>
<th onMouseEnter={termtip.bind(null, 'kinres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('kinres')}</th>
<th onMouseEnter={termtip.bind(null, 'thermres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('thermres')}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('absolute')} ${translate('HP')}`}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('explosive')} ${translate('HP')}`}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('kinetic')} ${translate('HP')}`}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('thermal')} ${translate('HP')}`}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recovery')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recharge')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{translate(shieldGenerator && shieldGenerator.m.grp || 'No Shield')}</td>
<td>{int(ship.shieldExplRes * 100) + '%'}</td>
<td>{int(ship.shieldKinRes * 100) + '%'}</td>
<td>{int(ship.shieldThermRes * 100) + '%'}</td>
<td>{int(sgMetrics && sgMetrics.generator ? sgMetrics.total / sgMetrics.absolute.total : 0)}</td>
<td>{int(sgMetrics && sgMetrics.generator ? sgMetrics.total / sgMetrics.explosive.total : 0)}</td>
<td>{int(sgMetrics && sgMetrics.generator ? sgMetrics.total / sgMetrics.kinetic.total : 0 )}</td>
<td>{int(sgMetrics && sgMetrics.generator ? sgMetrics.total / sgMetrics.thermal.total : 0 )}</td>
<td>{sgMetrics && sgMetrics.recover ? formats.time(sgMetrics.recover) : 0}</td>
<td>{sgMetrics && sgMetrics.recharge ? formats.time(sgMetrics.recharge) : 0}</td>
</tr>
</tbody>
<thead>
<tr>
<th onMouseEnter={termtip.bind(null, 'armour', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('armour')}</th>
<th onMouseEnter={termtip.bind(null, 'explres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('explres')}</th>
<th onMouseEnter={termtip.bind(null, 'kinres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('kinres')}</th>
<th onMouseEnter={termtip.bind(null, 'thermres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('thermres')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('absolute')} ${translate('HP')}`}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('explosive')} ${translate('HP')}`}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('kinetic')} ${translate('HP')}`}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('thermal')} ${translate('HP')}`}</th>
<th onMouseEnter={termtip.bind(null, 'TT_MODULE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('raw module armour')}</th>
<th onMouseEnter={termtip.bind(null, 'armour', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('internal protection')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{translate(ship && ship.bulkheads && ship.bulkheads.m && ship.bulkheads.m.name || 'No Armour')}</td>
<td>{int(ship.hullExplRes * 100) + '%'}</td>
<td>{int(ship.hullKinRes * 100) + '%'}</td>
<td>{int(ship.hullThermRes * 100) + '%'}</td>
<td>{int(armourMetrics.total / armourMetrics.absolute.total)}</td>
<td>{int(armourMetrics.total / armourMetrics.explosive.total)}</td>
<td>{int(armourMetrics.total / armourMetrics.kinetic.total)}</td>
<td>{int(armourMetrics.total / armourMetrics.thermal.total)}</td>
<td>{int(armourMetrics.modulearmour)}</td>
<td>{int(armourMetrics.moduleprotection * 100) + '%'}</td>
</tr>
</tbody>
</table>
</div>; </div>;
} }
} }

View File

@@ -1,166 +1,361 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
const MARGIN_LR = 8; // Left/ Right margin const MARGIN_LR = 8; // Left/ Right margin
/** /**
* Horizontal Slider * Horizontal Slider
*/ */
export default class Slider extends React.Component { export default class Slider extends React.Component {
static defaultProps = { static defaultProps = {
axis: false, axis: false,
min: 0, min: 0,
max: 1, max: 1,
scale: 1 // SVG render scale scale: 1 // SVG render scale
}; };
static propTypes = { static propTypes = {
axis: PropTypes.bool, axis: PropTypes.bool,
axisUnit: PropTypes.string, axisUnit: PropTypes.string,//units (T, M, etc.)
max: PropTypes.number, max: PropTypes.number,
min: PropTypes.number, min: PropTypes.number,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,// function which determins percent value
onResize: PropTypes.func, onResize: PropTypes.func,
percent: PropTypes.number.isRequired, percent: PropTypes.number.isRequired,//value of slider
scale: PropTypes.number scale: PropTypes.number
}; };
/** /**
* Constructor * Constructor
* @param {Object} props React Component properties * @param {Object} props React Component properties
*/ */
constructor(props) { constructor(props) {
super(props); super(props);
this._down = this._down.bind(this); this._down = this._down.bind(this);
this._move = this._move.bind(this); this._move = this._move.bind(this);
this._up = this._up.bind(this); this._up = this._up.bind(this);
this._updatePercent = this._updatePercent.bind(this); this._keyup = this._keyup.bind(this);
this._updateDimensions = this._updateDimensions.bind(this); this._keydown = this._keydown.bind(this);
this._touchstart = this._touchstart.bind(this);
this.state = { width: 0 }; this._touchend = this._touchend.bind(this);
}
this._updatePercent = this._updatePercent.bind(this);
/** this._updateDimensions = this._updateDimensions.bind(this);
* On Mouse/Touch down handler
* @param {SyntheticEvent} event Event this.state = { width: 0 };
*/ }
_down(event) {
let rect = event.currentTarget.getBoundingClientRect(); /**
this.left = rect.left; * On Mouse/Touch down handler
this.width = rect.width; * @param {SyntheticEvent} event Event
this._move(event); */
} _down(event) {
/** let rect = event.currentTarget.getBoundingClientRect();
* Update the slider percentage on move this.left = rect.left;
* @param {SyntheticEvent} event Event this.width = rect.width;
*/ this._move(event);
_move(event) { this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
if(this.width !== null && this.left != null) { }
let clientX = event.touches ? event.touches[0].clientX : event.clientX;
event.preventDefault(); /**
this._updatePercent(clientX - this.left, this.width); * Update the slider percentage on move
} * @param {SyntheticEvent} event Event
} */
_move(event) {
/** if(this.width !== null && this.left != null) {
* On Mouse/Touch up handler let clientX = event.touches ? event.touches[0].clientX : event.clientX;
* @param {Event} event DOM Event event.preventDefault();
*/ this._updatePercent(clientX - this.left, this.width);
_up(event) { }
event.preventDefault(); }
this.left = null;
this.width = null; /**
} * On Mouse/Touch up handler
* @param {Event} event DOM Event
/** */
* Determine if the user is still dragging _up(event) {
* @param {SyntheticEvent} event Event this.sliderInputBox.sliderVal.focus();
*/ clearTimeout(this.touchStartTimer);
_enter(event) { event.preventDefault();
if(event.buttons !== 1) { this.left = null;
this.left = null; this.width = null;
this.width = null; }
}
}
/**
/** * Key up handler for keyboard.
* Update the slider percentage * display the number field then set focus to it
* @param {number} pos Slider drag position * when "Enter" key is pressed
* @param {number} width Slider width * @param {Event} event Keyboard event
* @param {Event} event DOM Event */
*/ _keyup(event) {
_updatePercent(pos, width) { switch (event.key) {
this.props.onChange(Math.min(Math.max(pos / width, 0), 1)); case 'Enter':
} event.preventDefault();
this.sliderInputBox._setDisplay('block');
/** //this.enterTimer = setTimeout(() => this.sliderInputBox.sliderVal.focus(), 10);
* Update dimenions from rendered DOM return;
*/ default:
_updateDimensions() { return;
this.setState({ }
outerWidth: this.node.getBoundingClientRect().width
}); }
} /**
* Key down handler
/** * increment slider position by +/- 1 when right/left arrow key is pressed or held
* Add listeners when about to mount * @param {Event} event
*/ */
componentWillMount() { _keydown(event) {
if (this.props.onResize) {
this.resizeListener = this.props.onResize(this._updateDimensions); switch (event.key) {
} case 'ArrowRight':
} var newVal = this.props.percent*this.props.max + 1;
if (newVal <= this.props.max) this.props.onChange(newVal/this.props.max);
/** return;
* Trigger DOM updates on mount case 'ArrowLeft':
*/ var newVal = this.props.percent*this.props.max - 1;
componentDidMount() { if (newVal >= 0) this.props.onChange(newVal/this.props.max);
this._updateDimensions(); return;
} default:
return;
/** }
* Remove listeners on unmount }
*/
componentWillUnmount() { /**
if (this.resizeListener) { * Touch start handler
this.resizeListener.remove(); * @param {Event} event DOM Event
} *
} */
_touchstart(event) {
/** this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
* Render the slider }
* @return {React.Component} The slider
*/ _touchend(event) {
render() { this.sliderInputBox.sliderVal.focus();
let outerWidth = this.state.outerWidth; clearTimeout(this.touchStartTimer);
let { axis, axisUnit, min, max, scale } = this.props; }
let style = { /**
width: '100%', * Determine if the user is still dragging
height: axis ? '2.5em' : '1.5em', * @param {SyntheticEvent} event Event
boxSizing: 'border-box' */
}; _enter(event) {
if(event.buttons !== 1) {
if (!outerWidth) { this.left = null;
return <svg style={style} ref={node => this.node = node} />; this.width = null;
} }
}
let margin = MARGIN_LR * scale;
let width = outerWidth - (margin * 2); /**
let pctPos = width * this.props.percent; * Update the slider percentage
* @param {number} pos Slider drag position
return <svg onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onTouchEnd={this._up} style={style} ref={node => this.node = node}> * @param {number} width Slider width
<rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' /> * @param {Event} event DOM Event
<rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' /> */
<circle className='primary' r={margin} cy='0.6em' cx={pctPos + margin} /> _updatePercent(pos, width) {
<rect x={margin} width={width} height='100%' fillOpacity='0' style={{ cursor: 'col-resize' }} onMouseDown={this._down} onTouchMove={this._move} onTouchStart={this._down} /> this.props.onChange(Math.min(Math.max(pos / width, 0), 1));
{axis && <g style={{ fontSize: '.7em' }}> }
<text className='primary-disabled' y='3em' x={margin} style={{ textAnchor: 'middle' }}>{min + axisUnit}</text>
<text className='primary-disabled' y='3em' x='50%' style={{ textAnchor: 'middle' }}>{(min + max / 2) + axisUnit}</text> /**
<text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text> * Update dimenions from rendered DOM
</g>} */
</svg>; _updateDimensions() {
} this.setState({
} outerWidth: this.node.getBoundingClientRect().width
});
}
/**
* Add listeners when about to mount
*/
componentWillMount() {
if (this.props.onResize) {
this.resizeListener = this.props.onResize(this._updateDimensions);
}
}
/**
* Trigger DOM updates on mount
*/
componentDidMount() {
this._updateDimensions();
}
/**
* Remove listeners on unmount
*/
componentWillUnmount() {
if (this.resizeListener) {
this.resizeListener.remove();
}
}
/**
* Render the slider
* @return {React.Component} The slider
*/
render() {
let outerWidth = this.state.outerWidth;
let { axis, axisUnit, min, max, scale } = this.props;
let style = {
width: '100%',
height: axis ? '2.5em' : '1.5em',
boxSizing: 'border-box'
};
if (!outerWidth) {
return <svg style={style} ref={node => this.node = node} />;
}
let margin = MARGIN_LR * scale;
let width = outerWidth - (margin * 2);
let pctPos = width * this.props.percent;
return <div><svg
onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onKeyUp={this._keyup} onKeyDown={this._keydown} style={style} ref={node => this.node = node} tabIndex="0">
<rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' />
<rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' />
<circle className='primary' r={margin} cy='0.6em' cx={pctPos + margin} />
<rect x={margin} width={width} height='100%' fillOpacity='0' style={{ cursor: 'col-resize' }} onMouseDown={this._down} onTouchMove={this._move} onTouchStart={this._down} onTouchEnd={this._touchend} />
{axis && <g style={{ fontSize: '.7em' }}>
<text className='primary-disabled' y='3em' x={margin} style={{ textAnchor: 'middle' }}>{min + axisUnit}</text>
<text className='primary-disabled' y='3em' x='50%' style={{ textAnchor: 'middle' }}>{(min + max / 2) + axisUnit}</text>
<text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text>
</g>}
</svg>
<TextInputBox ref={(tb) => this.sliderInputBox = tb}
onChange={this.props.onChange}
percent={this.props.percent}
axisUnit={this.props.axisUnit}
scale={this.props.scale}
max={this.props.max}
/>
</div>;
}
}
/**
* New component to add keyboard support for sliders - works on all devices (desktop, iOS, Android)
**/
class TextInputBox extends React.Component {
static propTypes = {
axisUnit: PropTypes.string,//units (T, M, etc.)
max: PropTypes.number,
onChange: PropTypes.func.isRequired,// function which determins percent value
percent: PropTypes.number.isRequired,//value of slider
scale: PropTypes.number
};
constructor(props) {
super(props);
this._handleFocus = this._handleFocus.bind(this);
this._handleBlur = this._handleBlur.bind(this);
this._handleChange = this._handleChange.bind(this);
//this._keydown = this._keydown.bind(this);
this._keyup = this._keyup.bind(this);
this.state = this._getInitialState();
this.percent = this.props.percent;
this.max = this.props.max;
this.state.inputValue = this.percent * this.max;
}
componentWillReceiveProps(nextProps, nextState) {
var nextValue = nextProps.percent * nextProps.max;
// See https://stackoverflow.com/questions/32414308/updating-state-on-props-change-in-react-form
if (nextValue !== this.state.inputValue && nextValue <= nextProps.max) {
this.setState({ inputValue: nextValue });
}
}
componentDidUpdate(prevProps, prevState) {
if (prevState.divStyle.display == 'none' && this.state.divStyle.display == 'block') {
this.enterTimer = setTimeout(() => this.sliderVal.focus(), 10);
}
if (prevProps.max !== this.props.max && this.state.inputValue > this.props.max) {
// they chose a different module
this.setState({ inputValue: this.props.max });
}
if (this.state.inputValue != prevState.inputValue && prevProps.max == this.props.max) {
this.props.onChange(this.state.inputValue/this.props.max);
}
}
_getInitialState() {
return {
divStyle: {display:'none'},
inputStyle: {width:'4em'},
labelStyle: {marginLeft: '.1em'},
maxLength:5,
size:5,
min:0,
tabIndex:-1,
type:'number',
readOnly: true
}
}
_setDisplay(val) {
this.setState({
divStyle: {display:val}
});
}
_handleFocus() {
this.setState({
inputValue:this._getValue()
});
}
_handleBlur() {
this._setDisplay('none');
if (this.state.inputValue !== '') {
this.props.onChange(this.state.inputValue/this.props.max);
} else {
this.state.inputValue = this.props.percent * this.props.max;
}
}
_getValue() {
return this.state.inputValue;
}
_handleChange(event) {
if (event.target.value < 0) {
this.setState({inputValue: 0});
} else if (event.target.value <= this.props.max) {
this.setState({inputValue: event.target.value});
} else {
this.setState({inputValue: this.props.max});
}
}
/**
* Key up handler for input field.
* If user hits Enter key, blur/close the input field
* @param {Event} event Keyboard event
*/
_keyup(event) {
switch (event.key) {
case 'Enter':
this.sliderVal.blur();
return;
default:
return;
}
}
render() {
let { axisUnit, onChange, percent, scale } = this.props;
return <div style={this.state.divStyle}><input style={this.state.inputStyle} value={this._getValue()} min={this.state.min} max={this.props.max} onChange={this._handleChange} onKeyUp={this._keyup} tabIndex={this.state.tabIndex} maxLength={this.state.maxLength} size={this.state.size} onBlur={() => {this._handleBlur()}} onFocus={() => {this._handleFocus()}} type={this.state.type} ref={(ip) => this.sliderVal = ip}/><text className="primary upp" style={this.state.labelStyle}>{this.props.axisUnit}</text></div>;
}
}

View File

@@ -40,6 +40,8 @@ export default class Slot extends TranslatedComponent {
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this)); this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
this._getMaxClassLabel = this._getMaxClassLabel.bind(this); this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
this._keyDown = this._keyDown.bind(this);
this.slotDiv = null;
} }
// Must be implemented by subclasses: // Must be implemented by subclasses:
@@ -73,6 +75,25 @@ export default class Slot extends TranslatedComponent {
this.props.onSelect(null,null); this.props.onSelect(null,null);
} }
/** Key Down handler
* @param {SyntheticEvent} event Event
* ToDo: see if this can be moved up
* we do more or less the same thing
* in every section when Enter key is pressed
* on a focusable item
*
*/
_keyDown(event) {
if (event.key == 'Enter') {
if(event.target.className == 'r') {
console.log("Slot: Enter key pressed on mod icon");
this._toggleModifications();
} else {
console.log("Slot: Enter key pressed on: %O", event.target);
}
this.props.onOpen(event);
}
}
/** /**
* Render the slot * Render the slot
* @return {React.Component} The slot * @return {React.Component} The slot
@@ -104,6 +125,7 @@ export default class Slot extends TranslatedComponent {
ship={ship} ship={ship}
m={m} m={m}
marker={modificationsMarker} marker={modificationsMarker}
modButton = {this.modButton}
/>; />;
} else { } else {
menu = <AvailableModulesMenu menu = <AvailableModulesMenu
@@ -114,6 +136,7 @@ export default class Slot extends TranslatedComponent {
onSelect={onSelect} onSelect={onSelect}
warning={warning} warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)} diffDetails={diffDetails.bind(ship, this.context.language)}
slotDiv = {this.slotDiv}
/>; />;
} }
} }
@@ -121,7 +144,7 @@ export default class Slot extends TranslatedComponent {
// TODO: implement touch dragging // TODO: implement touch dragging
return ( return (
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onContextMenu={this._contextMenu} onDragOver={dragOver}> <div className={cn('slot', dropClass, { selected })} onClick={onOpen} onKeyDown={this._keyDown} onContextMenu={this._contextMenu} onDragOver={dragOver} tabIndex="0" ref={slotDiv => this.slotDiv = slotDiv}>
<div className='details-container'> <div className='details-container'>
<div className='sz'>{this._getMaxClassLabel(translate)}</div> <div className='sz'>{this._getMaxClassLabel(translate)}</div>
{slotDetails} {slotDetails}
@@ -131,6 +154,7 @@ export default class Slot extends TranslatedComponent {
); );
} }
/** /**
* Toggle the modifications flag when selecting the modifications icon * Toggle the modifications flag when selecting the modifications icon
*/ */

View File

@@ -35,6 +35,18 @@ export default class StandardSlot extends TranslatedComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this._modificationsSelected = false; this._modificationsSelected = false;
this._keyDown = this._keyDown.bind(this);
this.modButton = null;
this.slotDiv = null;
}
_keyDown(event) {
if (event.key == 'Enter') {
if(event.target.className == 'r') {
this._toggleModifications();
}
this.props.onOpen(event);
}
} }
/** /**
@@ -48,7 +60,7 @@ export default class StandardSlot extends TranslatedComponent {
let m = slot.m; let m = slot.m;
let classRating = m.class + m.rating; let classRating = m.class + m.rating;
let menu; let menu;
let validMods = m == null ? [] : (Modifications.modules[m.grp].modifications || []); let validMods = m == null || !Modifications.modules[m.grp] ? [] : (Modifications.modules[m.grp].modifications || []);
let showModuleResistances = Persist.showModuleResistances(); let showModuleResistances = Persist.showModuleResistances();
let mass = m.getMass() || m.cargo || m.fuel || 0; let mass = m.getMass() || m.cargo || m.fuel || 0;
@@ -56,6 +68,9 @@ export default class StandardSlot extends TranslatedComponent {
let modTT = translate('modified'); let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) { if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id >= 0) {
modTT += ', ' + translate(m.blueprint.special.name);
}
modTT = ( modTT = (
<div> <div>
<div>{modTT}</div> <div>{modTT}</div>
@@ -79,6 +94,7 @@ export default class StandardSlot extends TranslatedComponent {
ship={ship} ship={ship}
m={m} m={m}
marker={modificationsMarker} marker={modificationsMarker}
modButton = {this.modButton}
/>; />;
} else { } else {
menu = <AvailableModulesMenu menu = <AvailableModulesMenu
@@ -89,12 +105,13 @@ export default class StandardSlot extends TranslatedComponent {
onSelect={onSelect} onSelect={onSelect}
warning={warning} warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)} diffDetails={diffDetails.bind(ship, this.context.language)}
slotDiv = {this.slotDiv}
/>; />;
} }
} }
return ( return (
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onContextMenu={stopCtxPropagation}> <div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onKeyDown={this._keyDown} onContextMenu={stopCtxPropagation} tabIndex="0" ref={ slotDiv => this.slotDiv = slotDiv }>
<div className={cn('details-container', { warning: warning && warning(slot.m), disabled: m.grp !== 'bh' && !slot.enabled })}> <div className={cn('details-container', { warning: warning && warning(slot.m), disabled: m.grp !== 'bh' && !slot.enabled })}>
<div className={'sz'}>{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}</div> <div className={'sz'}>{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}</div>
<div> <div>
@@ -118,7 +135,7 @@ export default class StandardSlot extends TranslatedComponent {
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</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 } { 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.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null } { 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>
</div> </div>
@@ -131,6 +148,7 @@ export default class StandardSlot extends TranslatedComponent {
* Toggle the modifications flag when selecting the modifications icon * Toggle the modifications flag when selecting the modifications icon
*/ */
_toggleModifications() { _toggleModifications() {
this._modificationsSelected = !this._modificationsSelected; this._modificationsSelected = !this._modificationsSelected;
} }
} }

View File

@@ -235,12 +235,10 @@ export default class StandardSlotSection extends SlotSection {
<ul> <ul>
<li className='lc' onClick={this._multiPurpose.bind(this, false, 0)}>{translate('Multi-purpose')}</li> <li className='lc' onClick={this._multiPurpose.bind(this, false, 0)}>{translate('Multi-purpose')}</li>
<li className='lc' onClick={this._multiPurpose.bind(this, true, 2)}>{translate('Combat')}</li> <li className='lc' onClick={this._multiPurpose.bind(this, true, 2)}>{translate('Combat')}</li>
<li className='lc' onClick={this._optimizeCargo.bind(this, false)}>{translate('Trader')}</li> <li className='lc' onClick={this._optimizeCargo.bind(this, true)}>{translate('Trader')}</li>
<li className='lc' onClick={this._optimizeCargo.bind(this, true)}>{translate('Shielded Trader')}</li>
<li className='lc' onClick={this._optimizeExplorer.bind(this, false)}>{translate('Explorer')}</li> <li className='lc' onClick={this._optimizeExplorer.bind(this, false)}>{translate('Explorer')}</li>
<li className={cn('lc', { disabled: planetaryDisabled })} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)}>{translate('Planetary Explorer')}</li> <li className={cn('lc', { disabled: planetaryDisabled })} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)}>{translate('Planetary Explorer')}</li>
<li className='lc' onClick={this._optimizeMiner.bind(this, false)}>{translate('Miner')}</li> <li className='lc' onClick={this._optimizeMiner.bind(this, true)}>{translate('Miner')}</li>
<li className='lc' onClick={this._optimizeMiner.bind(this, true)}>{translate('Shielded Miner')}</li>
<li className='lc' onClick={this._optimizeRacer.bind(this)}>{translate('Racer')}</li> <li className='lc' onClick={this._optimizeRacer.bind(this)}>{translate('Racer')}</li>
</ul> </ul>
</div>; </div>;

View File

@@ -16,6 +16,8 @@
"PHRASE_ENGAGEMENT_RANGE": "The distance between your ship and its target", "PHRASE_ENGAGEMENT_RANGE": "The distance between your ship and its target",
"PHRASE_SELECT_BLUEPRINT": "Click to select a blueprint", "PHRASE_SELECT_BLUEPRINT": "Click to select a blueprint",
"PHRASE_BLUEPRINT_WORST": "Worst primary values for this blueprint", "PHRASE_BLUEPRINT_WORST": "Worst primary values for this blueprint",
"PHRASE_BLUEPRINT_FIFTY": "50% of full values for this blueprint",
"PHRASE_BLUEPRINT_SEVEN_FIVE": "75% of full values for this blueprint",
"PHRASE_BLUEPRINT_RANDOM": "Random selection between worst and best primary values for this blueprint", "PHRASE_BLUEPRINT_RANDOM": "Random selection between worst and best primary values for this blueprint",
"PHRASE_BLUEPRINT_BEST": "Best primary values for this blueprint", "PHRASE_BLUEPRINT_BEST": "Best primary values for this blueprint",
"PHRASE_BLUEPRINT_EXTREME": "Best beneficial and worst detrimental primary values for this blueprint", "PHRASE_BLUEPRINT_EXTREME": "Best beneficial and worst detrimental primary values for this blueprint",
@@ -60,6 +62,7 @@
"TT_SUMMARY_BOOST": "With full fuel tank and 4 pips to ENG", "TT_SUMMARY_BOOST": "With full fuel tank and 4 pips to ENG",
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Power distributor not able to supply enough power to boost", "TT_SUMMARY_BOOST_NONFUNCTIONAL": "Power distributor not able to supply enough power to boost",
"TT_SUMMARY_SHIELDS": "Raw shield strength, including boosters", "TT_SUMMARY_SHIELDS": "Raw shield strength, including boosters",
"TT_SUMMARY_SHIELDS_SCB": "Raw shield strength, including boosters and SCBs",
"TT_SUMMARY_SHIELDS_NONFUNCTIONAL": "No shield generator or shield generator powered off", "TT_SUMMARY_SHIELDS_NONFUNCTIONAL": "No shield generator or shield generator powered off",
"TT_SUMMARY_INTEGRITY": "Ship integrity, including bulkheads and hull reinforcement packages", "TT_SUMMARY_INTEGRITY": "Ship integrity, including bulkheads and hull reinforcement packages",
"TT_SUMMARY_HULL_MASS": "Mass of the hull prior to any modules being installed", "TT_SUMMARY_HULL_MASS": "Mass of the hull prior to any modules being installed",
@@ -115,6 +118,10 @@
"pl": "Pulse Laser", "pl": "Pulse Laser",
"po": "Point Defence", "po": "Point Defence",
"pp": "Power Plant", "pp": "Power Plant",
"gpp": "Guardian Hybrid Power Plant",
"gpd": "Guardian Hybrid Power Distributor",
"gpc": "Guardian Plasma Charger",
"ggc": "Guardian Gauss Cannon",
"psg": "Prismatic Shield Generator", "psg": "Prismatic Shield Generator",
"pv": "Planetary Vehicle Hangar", "pv": "Planetary Vehicle Hangar",
"rf": "Refinery", "rf": "Refinery",
@@ -132,6 +139,7 @@
"ul": "Burst Laser", "ul": "Burst Laser",
"ws": "Frame Shift Wake Scanner", "ws": "Frame Shift Wake Scanner",
"rpl": "Repair Limpet Controller", "rpl": "Repair Limpet Controller",
"rcpl": "Recon Limpet Controller",
"xs": "Xeno Scanner", "xs": "Xeno Scanner",
"emptyrestricted": "empty (restricted)", "emptyrestricted": "empty (restricted)",
"damage dealt to": "Damage dealt to", "damage dealt to": "Damage dealt to",
@@ -173,6 +181,7 @@
"total": "Total", "total": "Total",
"ammo": "Ammunition maximum", "ammo": "Ammunition maximum",
"boot": "Boot time", "boot": "Boot time",
"hacktime": "Hack time",
"brokenregen": "Broken regeneration rate", "brokenregen": "Broken regeneration rate",
"burst": "Burst", "burst": "Burst",
"burstrof": "Burst rate of fire", "burstrof": "Burst rate of fire",
@@ -207,6 +216,7 @@
"scanrate": "Scan rate", "scanrate": "Scan rate",
"scantime": "Scan time", "scantime": "Scan time",
"shield": "Shield", "shield": "Shield",
"armour": "Armour",
"shieldboost": "Shield boost", "shieldboost": "Shield boost",
"shieldreinforcement": "Shield reinforcement", "shieldreinforcement": "Shield reinforcement",
"shotspeed": "Shot speed", "shotspeed": "Shot speed",

View File

@@ -41,6 +41,9 @@ export default class AboutPage extends Page {
<h3>Chat</h3> <h3>Chat</h3>
<p>You can chat to us on our <a href='https://discord.gg/0uwCh6R62aPRjk9w' target='_blank'>EDCD Discord server</a>.</p> <p>You can chat to us on our <a href='https://discord.gg/0uwCh6R62aPRjk9w' target='_blank'>EDCD Discord server</a>.</p>
<h3>Supporting Coriolis</h3>
<p>Coriolis is an open source project, and I work on it in my free time. I have set up a patreon at <a href='https://www.patreon.com/coriolis_elite'>patreon.com/coriolis_elite</a>, which will be used to keep Coriolis up to date and the servers running.</p>
</div>; </div>;
} }
} }

View File

@@ -602,7 +602,7 @@ export default class OutfittingPage extends Page {
</div> </div>
{/* Main tables */} {/* Main tables */}
<ShipSummaryTable ship={ship} fuel={fuel} cargo={cargo} marker={shipSummaryMarker} /> <ShipSummaryTable ship={ship} fuel={fuel} cargo={cargo} marker={shipSummaryMarker} pips={{sys: this.state.sys, wep: this.state.wep, eng: this.state.eng}} />
<StandardSlotSection ship={ship} fuel={fuel} cargo={cargo} code={standardSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} /> <StandardSlotSection ship={ship} fuel={fuel} cargo={cargo} code={standardSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} />
<InternalSlotSection ship={ship} code={internalSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} /> <InternalSlotSection ship={ship} code={internalSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} />
<HardpointSlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} /> <HardpointSlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} />

View File

@@ -42,7 +42,7 @@ function countInt(slot) {
passSlotType = 'pcq'; passSlotType = 'pcq';
passSlotRating = 'B'; passSlotRating = 'B';
} }
let passengerBay = passSlotType ? ModuleUtils.findInternal(passSlotType, slot.maxClass, passSlotRating) : null; let passengerBay = passSlotType ? ModuleUtils.findMaxInternal(passSlotType, slot.maxClass, passSlotRating) : null;
this.maxPassengers += passengerBay ? passengerBay.passengers : 0; this.maxPassengers += passengerBay ? passengerBay.passengers : 0;
} }
@@ -346,7 +346,7 @@ export default class ShipyardPage extends Page {
<th className='sortable' onClick={sortShips('topBoost')}>{translate('boost')}</th> <th className='sortable' onClick={sortShips('topBoost')}>{translate('boost')}</th>
<th className='sortable' onClick={sortShips('maxJumpRange')}>{translate('jump')}</th> <th className='sortable' onClick={sortShips('maxJumpRange')}>{translate('jump')}</th>
<th className='sortable' onClick={sortShips('maxCargo')}>{translate('cargo')}</th> <th className='sortable' onClick={sortShips('maxCargo')}>{translate('cargo')}</th>
<th className='sortable' onClick={sortShips('maxPassengers')}>{translate('passengers')}</th> <th className='sortable' onClick={sortShips('maxPassengers')}>{translate('pax')}</th>
<th className='lft' colSpan={7}>{translate('core module classes')}</th> <th className='lft' colSpan={7}>{translate('core module classes')}</th>
<th colSpan={5} className='sortable lft' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th> <th colSpan={5} className='sortable lft' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>

View File

@@ -47,7 +47,7 @@ export function totalJumpRange(mass, fsd, fuel) {
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any) * @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
* @return {number} Approximate shield strengh in MJ * @return {number} Approximate shield strengh in MJ
*/ */
export function shieldStrength(mass, baseShield, sg, multiplier) { export function shieldStrength(mass, baseShield, sg, multiplier, ship) {
// sg might be a module or a template; handle either here // sg might be a module or a template; handle either here
let minMass = sg instanceof Module ? sg.getMinMass() : sg.minmass; let minMass = sg instanceof Module ? sg.getMinMass() : sg.minmass;
let optMass = sg instanceof Module ? sg.getOptMass() : sg.optmass; let optMass = sg instanceof Module ? sg.getOptMass() : sg.optmass;
@@ -55,7 +55,17 @@ export function shieldStrength(mass, baseShield, sg, multiplier) {
let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul; let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul;
let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul; let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul;
let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.maxmul; let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.maxmul;
if (ship) {
for (const i of ship.hardpoints) {
if (!i.maxClass) {
if (i.grp === 'sb' || (i.m && i.m.grp === 'sb')) {
if (!isNaN(i.m.getModValue('optmul'))) {
optMul += i.m.getModValue('optmul') / 10000;
}
}
}
}
}
let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass)); let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass))); let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
let ynorm = Math.pow(xnorm, exponent); let ynorm = Math.pow(xnorm, exponent);
@@ -351,7 +361,7 @@ export function shieldMetrics(ship, sys) {
boosterKinDmg = boosterKinDmg > 0.7 ? boosterKinDmg : 0.7 - (0.7 - boosterKinDmg) / 2; boosterKinDmg = boosterKinDmg > 0.7 ? boosterKinDmg : 0.7 - (0.7 - boosterKinDmg) / 2;
boosterThermDmg = boosterThermDmg > 0.7 ? boosterThermDmg : 0.7 - (0.7 - boosterThermDmg) / 2; boosterThermDmg = boosterThermDmg > 0.7 ? boosterThermDmg : 0.7 - (0.7 - boosterThermDmg) / 2;
const generatorStrength = this.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1); const generatorStrength = this.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1, ship);
const boostersStrength = generatorStrength * boost; const boostersStrength = generatorStrength * boost;
// Recover time is the time taken to go from 0 to 50%. It includes a 16-second wait before shields start to recover // Recover time is the time taken to go from 0 to 50%. It includes a 16-second wait before shields start to recover

View File

@@ -9,12 +9,16 @@ export const StandardArray = [
'pd', // Power Distributor 'pd', // Power Distributor
's', // Sensors 's', // Sensors
'ft', // Fuel Tank 'ft', // Fuel Tank
'gpp', // Guardian Hybrid Power Plant
'gpd' // Guardian Hybrid Power Distributor
]; ];
// Map to lookup group labels/names for component grp, used for JSON Serialization // Map to lookup group labels/names for component grp, used for JSON Serialization
export const ModuleGroupToName = { export const ModuleGroupToName = {
// Standard // Standard
pp: 'Power Plant', pp: 'Power Plant',
gpp: 'Guardian Hybrid Power Plant',
gpd: 'Guardian Hybrid Power Distributor',
t: 'Thrusters', t: 'Thrusters',
fsd: 'Frame Shift Drive', fsd: 'Frame Shift Drive',
ls: 'Life Support', ls: 'Life Support',
@@ -75,7 +79,10 @@ export const ModuleGroupToName = {
sb: 'Shield Booster', sb: 'Shield Booster',
tp: 'Torpedo Pylon', tp: 'Torpedo Pylon',
sfn: 'Shutdown Field Neutraliser', sfn: 'Shutdown Field Neutraliser',
xs: 'Xeno Scanner' xs: 'Xeno Scanner',
rcpl: 'Recon Limpet Controller',
gpc: 'Guardian Plasma Charger',
ggc: 'Guardian Gauss Cannon',
}; };
let GrpNameToCodeMap = {}; let GrpNameToCodeMap = {};

View File

@@ -64,7 +64,6 @@ export default class Module {
} }
} }
} }
// 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);
} }
@@ -79,6 +78,9 @@ export default class Module {
if (!this.mods) { if (!this.mods) {
this.mods = {}; this.mods = {};
} }
if (!this.origVals) {
this.origVals = {};
}
if (valueiswithspecial && this.blueprint && this.blueprint.special) { if (valueiswithspecial && this.blueprint && this.blueprint.special) {
// This module has a special effect, see if we need to alter the stored value // This module has a special effect, see if we need to alter the stored value
const modifierActions = Modifications.modifierActions[this.blueprint.special.edname]; const modifierActions = Modifications.modifierActions[this.blueprint.special.edname];
@@ -117,7 +119,6 @@ export default class Module {
_getModifiedValue(name) { _getModifiedValue(name) {
const modification = Modifications.modifications[name]; const modification = Modifications.modifications[name];
let result = this[name]; let result = this[name];
if (!result) { if (!result) {
if (modification && modification.method === 'additive') { if (modification && modification.method === 'additive') {
// Additive modifications start at 0 rather than NULL // Additive modifications start at 0 rather than NULL
@@ -135,7 +136,7 @@ export default class Module {
if (modification.type === 'percentage') { if (modification.type === 'percentage') {
modValue = this.getModValue(name) / 10000; modValue = this.getModValue(name) / 10000;
} else if (modification.type === 'numeric') { } else if (modification.type === 'numeric') {
modValue = this.getModValue(name) / 100; modValue = this.getModValue(name)/ 100;
} else { } else {
modValue = this.getModValue(name); modValue = this.getModValue(name);
} }
@@ -721,4 +722,13 @@ export default class Module {
getTime() { getTime() {
return this._getModifiedValue('time'); return this._getModifiedValue('time');
} }
/**
* Get the hack time for this module, taking in to account modifications
* @return {string} the time for this module
*/
getHackTime() {
return this._getModifiedValue('hacktime');
}
} }

View File

@@ -195,6 +195,29 @@ export function findInternal(groupName, clss, rating, name) {
return null; return null;
} }
/**
* Finds an internal module based on Class, Rating, Group and/or name.
* At least one of Group name or unique module name must be provided.
* will start searching at specified class and proceed lower until a
* module is found or 0 is hit
* Uses findInternal internally
*
* @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss module Class
* @param {String} rating module Rating
* @param {String} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner'
* @return {Object} The module if found, null if not found
*/
export function findMaxInternal(groupName, clss, rating, name) {
let foundModule = null;
let currentClss = clss;
while (currentClss > 0 && foundModule == null) {
foundModule = findInternal(groupName, currentClss, rating, name);
currentClss = currentClss - 1;
}
return foundModule;
}
/** /**
* Finds an internal Module ID based on Class, Rating, Group and/or name. * Finds an internal Module ID based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique module name must be provided * At least one ofGroup name or unique module name must be provided

View File

@@ -241,7 +241,7 @@ export default class Ship {
} }
// TODO Not accurate if the ship has modified shield boosters // TODO Not accurate if the ship has modified shield boosters
return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, 1 + (multiplierDelta || 0)); return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, 1 + (multiplierDelta || 0), this);
} }
/** /**
@@ -464,7 +464,17 @@ export default class Ship {
if (m.blueprint) { if (m.blueprint) {
m.blueprint.special = special; m.blueprint.special = special;
} }
this.recalculateDps().recalculateHps().recalculateEps(); this.updatePowerGenerated()
.updatePowerUsed()
.recalculateMass()
.updateJumpStats()
.recalculateShield()
.recalculateShieldCells()
.recalculateArmour()
.recalculateDps()
.recalculateEps()
.recalculateHps()
.updateMovement();
} }
/** /**
@@ -490,7 +500,6 @@ export default class Ship {
// Value passed is invalid; reset it to 0 // Value passed is invalid; reset it to 0
value = 0; value = 0;
} }
// Handle special cases // Handle special cases
if (name === 'pgen') { if (name === 'pgen') {
// Power generation // Power generation
@@ -569,6 +578,7 @@ export default class Ship {
// Reset Cumulative stats // Reset Cumulative stats
this.fuelCapacity = 0; this.fuelCapacity = 0;
this.cargoCapacity = 0; this.cargoCapacity = 0;
this.passengerCapacity = 0;
this.ladenMass = 0; this.ladenMass = 0;
this.armour = this.baseArmour; this.armour = this.baseArmour;
this.shield = this.baseShieldStrength; this.shield = this.baseShieldStrength;
@@ -770,7 +780,7 @@ export default class Ship {
// Alter as required due to changes in the (build) code from one version to the next // Alter as required due to changes in the (build) code from one version to the next
this.upgradeInternals(internal, 1 + this.standard.length + this.hardpoints.length, priorities, enabled, modifications, blueprints, version); this.upgradeInternals(internal, 1 + this.standard.length + this.hardpoints.length, priorities, enabled, modifications, blueprints, version);
} }
return this.buildWith( return this.buildWith(
{ {
bulkheads: code.charAt(0) * 1, bulkheads: code.charAt(0) * 1,
@@ -1188,6 +1198,7 @@ export default class Ship {
let unladenMass = this.hullMass; let unladenMass = this.hullMass;
let cargoCapacity = 0; let cargoCapacity = 0;
let fuelCapacity = 0; let fuelCapacity = 0;
let passengerCapacity = 0;
unladenMass += this.bulkheads.m.getMass(); unladenMass += this.bulkheads.m.getMass();
@@ -1209,6 +1220,10 @@ export default class Ship {
fuelCapacity += slot.m.fuel; fuelCapacity += slot.m.fuel;
} else if (slot.m.grp === 'cr') { } else if (slot.m.grp === 'cr') {
cargoCapacity += slot.m.cargo; cargoCapacity += slot.m.cargo;
} else if (slot.m.grp.slice(0,2) === 'pc') {
if (slot.m.passengers) {
passengerCapacity += slot.m.passengers;
}
} }
} }
} }
@@ -1224,6 +1239,7 @@ export default class Ship {
this.unladenMass = unladenMass; this.unladenMass = unladenMass;
this.cargoCapacity = cargoCapacity; this.cargoCapacity = cargoCapacity;
this.fuelCapacity = fuelCapacity; this.fuelCapacity = fuelCapacity;
this.passengerCapacity = passengerCapacity;
this.ladenMass = unladenMass + fuelCapacity + cargoCapacity; this.ladenMass = unladenMass + fuelCapacity + cargoCapacity;
return this; return this;

View File

@@ -1,5 +1,5 @@
import * as ModuleUtils from './ModuleUtils'; import * as ModuleUtils from './ModuleUtils'
import { canMount } from '../utils/SlotFunctions'; import { canMount } from '../utils/SlotFunctions'
/** /**
* Standard / typical role for multi-purpose or combat (if shielded with better bulkheads) * Standard / typical role for multi-purpose or combat (if shielded with better bulkheads)
@@ -7,20 +7,20 @@ import { canMount } from '../utils/SlotFunctions';
* @param {Boolean} shielded True if shield generator should be included * @param {Boolean} shielded True if shield generator should be included
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames * @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
*/ */
export function multiPurpose(ship, shielded, bulkheadIndex) { export function multiPurpose (ship, shielded, bulkheadIndex) {
ship.useStandard('A') ship.useStandard('A')
.use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support .use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support
.use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')) // D Sensors .use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')) // D Sensors
.useBulkhead(bulkheadIndex); .useBulkhead(bulkheadIndex)
if (shielded) { if (shielded) {
ship.internal.some(function(slot) { ship.internal.some(function (slot) {
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A')); ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A'))
ship.setSlotEnabled(slot, true); ship.setSlotEnabled(slot, true)
return true; return true
} }
}); })
} }
} }
@@ -30,40 +30,54 @@ export function multiPurpose(ship, shielded, bulkheadIndex) {
* @param {Boolean} shielded True if shield generator should be included * @param {Boolean} shielded True if shield generator should be included
* @param {Object} standardOpts [Optional] Standard module optional overrides * @param {Object} standardOpts [Optional] Standard module optional overrides
*/ */
export function trader(ship, shielded, standardOpts) { export function trader (ship, shielded, standardOpts) {
let usedSlots = [], let usedSlots = []
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass); let bstCount = 2
let sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass)
// Shield generator if required ship.useStandard('A')
if (shielded) { .use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8]; .use(ship.standard[1], ModuleUtils.standard(1, ship.standard[1].maxClass + 'D')) // D Life Support
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) .use(ship.standard[4], ModuleUtils.standard(4, ship.standard[4].maxClass + 'D')) // D Life Support
.filter(a => (!a.eligible) || a.eligible.sg) .use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')) // D Sensors
.filter(a => a.maxClass >= sg.class)
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass)); const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8]
for (let i = 0; i < shieldInternals.length; i++) { const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
if (canMount(ship, shieldInternals[i], 'sg')) { .filter(a => (!a.eligible) || a.eligible.sg)
ship.use(shieldInternals[i], sg); .filter(a => a.maxClass >= sg.class)
usedSlots.push(shieldInternals[i]); .sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass))
break; shieldInternals.some(function (slot) {
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
const shield = ModuleUtils.findInternal('sg', slot.maxClass, 'A')
if (shield && shield.maxmass > ship.hullMass) {
ship.use(slot, shield)
ship.setSlotEnabled(slot, true)
usedSlots.push(slot)
return true
} }
} }
} })
// Fill the empty internals with cargo racks // Fill the empty internals with cargo racks
for (let i = ship.internal.length; i--;) { for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i]; let slot = ship.internal[i]
if (usedSlots.indexOf(slot) == -1 && canMount(ship, slot, 'cr')) { if (usedSlots.indexOf(slot) == -1 && canMount(ship, slot, 'cr')) {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E')); ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'))
} }
} }
// Empty the hardpoints // Empty the hardpoints
for (let s of ship.hardpoints) { for (let s of ship.hardpoints) {
ship.use(s, null); ship.use(s, null)
} }
for (let s of ship.hardpoints) {
ship.useLightestStandard(standardOpts); if (s.maxClass == 0 && bstCount) { // Mount up to 2 boosters
ship.use(s, ModuleUtils.hardpoints('04'))
bstCount--
} else {
ship.use(s, null)
}
}
// ship.useLightestStandard(standardOpts);
} }
/** /**
@@ -71,127 +85,127 @@ export function trader(ship, shielded, standardOpts) {
* @param {Ship} ship Ship instance * @param {Ship} ship Ship instance
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included * @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
*/ */
export function explorer(ship, planetary) { export function explorer (ship, planetary) {
let standardOpts = { ppRating: 'A' }, let standardOpts = {ppRating: 'A'},
heatSinkCount = 2, // Fit 2 heat sinks if possible heatSinkCount = 2, // Fit 2 heat sinks if possible
usedSlots = [], usedSlots = [],
sgSlot, sgSlot,
fuelScoopSlot, fuelScoopSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass); sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass)
if (!planetary) { // Non-planetary explorers don't really need to boost if (!planetary) { // Non-planetary explorers don't really need to boost
standardOpts.pd = '1D'; standardOpts.pd = '1D'
} }
// Cargo hatch can be disabled // Cargo hatch can be disabled
ship.setSlotEnabled(ship.cargoHatch, false); ship.setSlotEnabled(ship.cargoHatch, false)
// Advanced Discovery Scanner - class 1 or higher // Advanced Discovery Scanner - class 1 or higher
const adsOrder = [1, 2, 3, 4, 5, 6, 7, 8]; const adsOrder = [1, 2, 3, 4, 5, 6, 7, 8]
const adsInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const adsInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sc) .filter(a => (!a.eligible) || a.eligible.sc)
.sort((a,b) => adsOrder.indexOf(a.maxClass) - adsOrder.indexOf(b.maxClass)); .sort((a, b) => adsOrder.indexOf(a.maxClass) - adsOrder.indexOf(b.maxClass))
for (let i = 0; i < adsInternals.length; i++) { for (let i = 0; i < adsInternals.length; i++) {
if (canMount(ship, adsInternals[i], 'sc')) { if (canMount(ship, adsInternals[i], 'sc')) {
ship.use(adsInternals[i], ModuleUtils.internal('2f')); ship.use(adsInternals[i], ModuleUtils.internal('2f'))
usedSlots.push(adsInternals[i]); usedSlots.push(adsInternals[i])
break; break
} }
} }
if (planetary) { if (planetary) {
// Planetary Vehicle Hangar - class 2 or higher // Planetary Vehicle Hangar - class 2 or higher
const pvhOrder = [2, 3, 4, 5, 6, 7, 8, 1]; const pvhOrder = [2, 3, 4, 5, 6, 7, 8, 1]
const pvhInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const pvhInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pv) .filter(a => (!a.eligible) || a.eligible.pv)
.sort((a,b) => pvhOrder.indexOf(a.maxClass) - pvhOrder.indexOf(b.maxClass)); .sort((a, b) => pvhOrder.indexOf(a.maxClass) - pvhOrder.indexOf(b.maxClass))
for (let i = 0; i < pvhInternals.length; i++) { for (let i = 0; i < pvhInternals.length; i++) {
if (canMount(ship, pvhInternals[i], 'pv')) { if (canMount(ship, pvhInternals[i], 'pv')) {
// Planetary Vehical Hangar only has even classes // Planetary Vehical Hangar only has even classes
const pvhClass = pvhInternals[i].maxClass % 2 === 1 ? pvhInternals[i].maxClass - 1 : pvhInternals[i].maxClass; const pvhClass = pvhInternals[i].maxClass % 2 === 1 ? pvhInternals[i].maxClass - 1 : pvhInternals[i].maxClass
ship.use(pvhInternals[i], ModuleUtils.findInternal('pv', pvhClass, 'G')); // G is lower mass ship.use(pvhInternals[i], ModuleUtils.findInternal('pv', pvhClass, 'G')) // G is lower mass
ship.setSlotEnabled(pvhInternals[i], false); // Disable power for Planetary Vehical Hangar ship.setSlotEnabled(pvhInternals[i], false) // Disable power for Planetary Vehical Hangar
usedSlots.push(pvhInternals[i]); usedSlots.push(pvhInternals[i])
break; break
} }
} }
} }
// Shield generator // Shield generator
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8]; const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8]
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg) .filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class) .filter(a => a.maxClass >= sg.class)
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass)); .sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass))
for (let i = 0; i < shieldInternals.length; i++) { for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) { if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg); ship.use(shieldInternals[i], sg)
usedSlots.push(shieldInternals[i]); usedSlots.push(shieldInternals[i])
sgSlot = shieldInternals[i]; sgSlot = shieldInternals[i]
break; break
} }
} }
// Detailed Surface Scanner // Detailed Surface Scanner
const dssOrder = [1, 2, 3, 4, 5, 6, 7, 8]; const dssOrder = [1, 2, 3, 4, 5, 6, 7, 8]
const dssInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const dssInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sc) .filter(a => (!a.eligible) || a.eligible.sc)
.sort((a,b) => dssOrder.indexOf(a.maxClass) - dssOrder.indexOf(b.maxClass)); .sort((a, b) => dssOrder.indexOf(a.maxClass) - dssOrder.indexOf(b.maxClass))
for (let i = 0; i < dssInternals.length; i++) { for (let i = 0; i < dssInternals.length; i++) {
if (canMount(ship, dssInternals[i], 'sc')) { if (canMount(ship, dssInternals[i], 'sc')) {
ship.use(dssInternals[i], ModuleUtils.internal('2i')); ship.use(dssInternals[i], ModuleUtils.internal('2i'))
usedSlots.push(dssInternals[i]); usedSlots.push(dssInternals[i])
break; break
} }
} }
// Fuel scoop - best possible // Fuel scoop - best possible
const fuelScoopOrder = [8, 7, 6, 5, 4, 3, 2, 1]; const fuelScoopOrder = [8, 7, 6, 5, 4, 3, 2, 1]
const fuelScoopInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const fuelScoopInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.fs) .filter(a => (!a.eligible) || a.eligible.fs)
.sort((a,b) => fuelScoopOrder.indexOf(a.maxClass) - fuelScoopOrder.indexOf(b.maxClass)); .sort((a, b) => fuelScoopOrder.indexOf(a.maxClass) - fuelScoopOrder.indexOf(b.maxClass))
for (let i = 0; i < fuelScoopInternals.length; i++) { for (let i = 0; i < fuelScoopInternals.length; i++) {
if (canMount(ship, fuelScoopInternals[i], 'fs')) { if (canMount(ship, fuelScoopInternals[i], 'fs')) {
ship.use(fuelScoopInternals[i], ModuleUtils.findInternal('fs', fuelScoopInternals[i].maxClass, 'A')); ship.use(fuelScoopInternals[i], ModuleUtils.findInternal('fs', fuelScoopInternals[i].maxClass, 'A'))
usedSlots.push(fuelScoopInternals[i]); usedSlots.push(fuelScoopInternals[i])
fuelScoopSlot = fuelScoopInternals[i]; fuelScoopSlot = fuelScoopInternals[i]
break; break
} }
} }
// AFMUs - fill as they are 0-weight // AFMUs - fill as they are 0-weight
const afmuOrder = [8, 7, 6, 5, 4, 3, 2, 1]; const afmuOrder = [8, 7, 6, 5, 4, 3, 2, 1]
const afmuInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const afmuInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pc) .filter(a => (!a.eligible) || a.eligible.pc)
.sort((a,b) => afmuOrder.indexOf(a.maxClass) - afmuOrder.indexOf(b.maxClass)); .sort((a, b) => afmuOrder.indexOf(a.maxClass) - afmuOrder.indexOf(b.maxClass))
for (let i = 0; i < afmuInternals.length; i++) { for (let i = 0; i < afmuInternals.length; i++) {
if (canMount(ship, afmuInternals[i], 'am')) { if (canMount(ship, afmuInternals[i], 'am')) {
ship.use(afmuInternals[i], ModuleUtils.findInternal('am', afmuInternals[i].maxClass, 'A')); ship.use(afmuInternals[i], ModuleUtils.findInternal('am', afmuInternals[i].maxClass, 'A'))
usedSlots.push(afmuInternals[i]); usedSlots.push(afmuInternals[i])
ship.setSlotEnabled(afmuInternals[i], false); // Disable power for AFM Unit ship.setSlotEnabled(afmuInternals[i], false) // Disable power for AFM Unit
} }
} }
for (let s of ship.hardpoints) { for (let s of ship.hardpoints) {
if (s.maxClass == 0 && heatSinkCount) { // Mount up to 2 heatsinks if (s.maxClass == 0 && heatSinkCount) { // Mount up to 2 heatsinks
ship.use(s, ModuleUtils.hardpoints('02')); ship.use(s, ModuleUtils.hardpoints('02'))
ship.setSlotEnabled(s, heatSinkCount == 2); // Only enable a single Heatsink ship.setSlotEnabled(s, heatSinkCount == 2) // Only enable a single Heatsink
heatSinkCount--; heatSinkCount--
} else { } else {
ship.use(s, null); ship.use(s, null)
} }
} }
if (sgSlot && fuelScoopSlot) { if (sgSlot && fuelScoopSlot) {
// The SG and Fuel scoop to not need to be powered at the same time // The SG and Fuel scoop to not need to be powered at the same time
if (sgSlot.m.getPowerUsage() > fuelScoopSlot.m.getPowerUsage()) { // The Shield generator uses the most power if (sgSlot.m.getPowerUsage() > fuelScoopSlot.m.getPowerUsage()) { // The Shield generator uses the most power
ship.setSlotEnabled(fuelScoopSlot, false); ship.setSlotEnabled(fuelScoopSlot, false)
} else { // The Fuel scoop uses the most power } else { // The Fuel scoop uses the most power
ship.setSlotEnabled(sgSlot, false); ship.setSlotEnabled(sgSlot, false)
} }
} }
ship.useLightestStandard(standardOpts); ship.useLightestStandard(standardOpts)
} }
/** /**
@@ -199,187 +213,188 @@ export function explorer(ship, planetary) {
* @param {Ship} ship Ship instance * @param {Ship} ship Ship instance
* @param {Boolean} shielded True if shield generator should be included * @param {Boolean} shielded True if shield generator should be included
*/ */
export function miner(ship, shielded) { export function miner (ship, shielded) {
let standardOpts = { ppRating: 'A' }, shielded = true
miningLaserCount = 2, let standardOpts = {ppRating: 'A'},
usedSlots = [], miningLaserCount = 2,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass); usedSlots = [],
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass)
// Cargo hatch should be enabled // Cargo hatch should be enabled
ship.setSlotEnabled(ship.cargoHatch, true); ship.setSlotEnabled(ship.cargoHatch, true)
// Largest possible refinery // Largest possible refinery
const refineryOrder = [4, 5, 6, 7, 8, 3, 2, 1]; const refineryOrder = [4, 5, 6, 7, 8, 3, 2, 1]
const refineryInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const refineryInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.rf) .filter(a => (!a.eligible) || a.eligible.rf)
.sort((a,b) => refineryOrder.indexOf(a.maxClass) - refineryOrder.indexOf(b.maxClass)); .sort((a, b) => refineryOrder.indexOf(a.maxClass) - refineryOrder.indexOf(b.maxClass))
for (let i = 0; i < refineryInternals.length; i++) { for (let i = 0; i < refineryInternals.length; i++) {
if (canMount(ship, refineryInternals[i], 'rf')) { if (canMount(ship, refineryInternals[i], 'rf')) {
ship.use(refineryInternals[i], ModuleUtils.findInternal('rf', Math.min(refineryInternals[i].maxClass, 4), 'A')); ship.use(refineryInternals[i], ModuleUtils.findInternal('rf', Math.min(refineryInternals[i].maxClass, 4), 'A'))
usedSlots.push(refineryInternals[i]); usedSlots.push(refineryInternals[i])
break; break
} }
} }
// Prospector limpet controller - 3A if possible // Prospector limpet controller - 3A if possible
const prospectorOrder = [3, 4, 5, 6, 7, 8, 2, 1]; const prospectorOrder = [3, 4, 5, 6, 7, 8, 2, 1]
const prospectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const prospectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pc) .filter(a => (!a.eligible) || a.eligible.pc)
.sort((a,b) => prospectorOrder.indexOf(a.maxClass) - prospectorOrder.indexOf(b.maxClass)); .sort((a, b) => prospectorOrder.indexOf(a.maxClass) - prospectorOrder.indexOf(b.maxClass))
for (let i = 0; i < prospectorInternals.length; i++) { for (let i = 0; i < prospectorInternals.length; i++) {
if (canMount(ship, prospectorInternals[i], 'pc')) { if (canMount(ship, prospectorInternals[i], 'pc')) {
// Prospector only has odd classes // Prospector only has odd classes
const prospectorClass = prospectorInternals[i].maxClass % 2 === 0 ? prospectorInternals[i].maxClass - 1 : prospectorInternals[i].maxClass; const prospectorClass = prospectorInternals[i].maxClass % 2 === 0 ? prospectorInternals[i].maxClass - 1 : prospectorInternals[i].maxClass
ship.use(prospectorInternals[i], ModuleUtils.findInternal('pc', prospectorClass, 'A')); ship.use(prospectorInternals[i], ModuleUtils.findInternal('pc', prospectorClass, 'A'))
usedSlots.push(prospectorInternals[i]); usedSlots.push(prospectorInternals[i])
break; break
} }
} }
// Shield generator if required // Shield generator if required
if (shielded) { if (shielded) {
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8]; const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8]
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg) .filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class) .filter(a => a.maxClass >= sg.class)
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass)); .sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass))
for (let i = 0; i < shieldInternals.length; i++) { for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) { if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg); ship.use(shieldInternals[i], sg)
usedSlots.push(shieldInternals[i]); usedSlots.push(shieldInternals[i])
break; break
} }
} }
} }
// Dual mining lasers of highest possible class; remove anything else // Dual mining lasers of highest possible class; remove anything else
const miningLaserOrder = [2, 3, 4, 1, 0]; const miningLaserOrder = [2, 3, 4, 1, 0]
const miningLaserHardpoints = ship.hardpoints.concat().sort(function(a,b) { const miningLaserHardpoints = ship.hardpoints.concat().sort(function (a, b) {
return miningLaserOrder.indexOf(a.maxClass) - miningLaserOrder.indexOf(b.maxClass); return miningLaserOrder.indexOf(a.maxClass) - miningLaserOrder.indexOf(b.maxClass)
}); })
for (let s of miningLaserHardpoints) { for (let s of miningLaserHardpoints) {
if (s.maxClass >= 1 && miningLaserCount) { if (s.maxClass >= 1 && miningLaserCount) {
ship.use(s, ModuleUtils.hardpoints(s.maxClass >= 2 ? '2m' : '2l')); ship.use(s, ModuleUtils.hardpoints(s.maxClass >= 2 ? '2m' : '2l'))
miningLaserCount--; miningLaserCount--
} else { } else {
ship.use(s, null); ship.use(s, null)
} }
} }
// Number of collector limpets required to be active is a function of the size of the ship and the power of the lasers // Number of collector limpets required to be active is a function of the size of the ship and the power of the lasers
const miningLaserDps = ship.hardpoints.filter(h => h.m != null) const miningLaserDps = ship.hardpoints.filter(h => h.m != null)
.reduce(function(a, b) { .reduce(function (a, b) {
return a + b.m.getDps(); return a + b.m.getDps()
}, 0); }, 0)
// Find out how many internal slots we have, and their potential cargo size // Find out how many internal slots we have, and their potential cargo size
const potentialCargo = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const potentialCargo = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.cr) .filter(a => (!a.eligible) || a.eligible.cr)
.map(b => Math.pow(2, b.maxClass)); .map(b => Math.pow(2, b.maxClass))
// One collector for each 1.25 DPS, multiply by 1.25 for medium ships and 1.5 for large ships as they have further to travel // One collector for each 1.25 DPS, multiply by 1.25 for medium ships and 1.5 for large ships as they have further to travel
// 0 if we only have 1 cargo slot, otherwise minium of 1 and maximum of 6 (excluding size modifier) // 0 if we only have 1 cargo slot, otherwise minium of 1 and maximum of 6 (excluding size modifier)
const sizeModifier = ship.class == 2 ? 1.2 : ship.class == 3 ? 1.5 : 1; const sizeModifier = ship.class == 2 ? 1.2 : ship.class == 3 ? 1.5 : 1
let collectorLimpetsRequired = potentialCargo.length == 1 ? 0 : Math.ceil(sizeModifier * Math.min(6, Math.floor(miningLaserDps / 1.25))); let collectorLimpetsRequired = potentialCargo.length == 1 ? 0 : Math.ceil(sizeModifier * Math.min(6, Math.floor(miningLaserDps / 1.25)))
if (collectorLimpetsRequired > 0) { if (collectorLimpetsRequired > 0) {
const collectorOrder = [1, 2, 3, 4, 5, 6, 7, 8]; const collectorOrder = [1, 2, 3, 4, 5, 6, 7, 8]
const collectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const collectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.cc) .filter(a => (!a.eligible) || a.eligible.cc)
.sort((a,b) => collectorOrder.indexOf(a.maxClass) - collectorOrder.indexOf(b.maxClass)); .sort((a, b) => collectorOrder.indexOf(a.maxClass) - collectorOrder.indexOf(b.maxClass))
// Always keep at least 2 slots free for cargo racks (1 for shielded) // Always keep at least 2 slots free for cargo racks (1 for shielded)
for (let i = 0; i < collectorInternals.length - (shielded ? 1 : 2) && collectorLimpetsRequired > 0; i++) { for (let i = 0; i < collectorInternals.length - (shielded ? 1 : 2) && collectorLimpetsRequired > 0; i++) {
if (canMount(ship, collectorInternals[i], 'cc')) { if (canMount(ship, collectorInternals[i], 'cc')) {
// Collector only has odd classes // Collector only has odd classes
const collectorClass = collectorInternals[i].maxClass % 2 === 0 ? collectorInternals[i].maxClass - 1 : collectorInternals[i].maxClass; const collectorClass = collectorInternals[i].maxClass % 2 === 0 ? collectorInternals[i].maxClass - 1 : collectorInternals[i].maxClass
ship.use(collectorInternals[i], ModuleUtils.findInternal('cc', collectorClass, 'D')); ship.use(collectorInternals[i], ModuleUtils.findInternal('cc', collectorClass, 'D'))
usedSlots.push(collectorInternals[i]); usedSlots.push(collectorInternals[i])
collectorLimpetsRequired -= collectorInternals[i].m.maximum; collectorLimpetsRequired -= collectorInternals[i].m.maximum
} }
} }
} }
// Power distributor to power the mining lasers indefinitely // Power distributor to power the mining lasers indefinitely
const wepRateRequired = ship.hardpoints.filter(h => h.m != null) const wepRateRequired = ship.hardpoints.filter(h => h.m != null)
.reduce(function(a, b) { .reduce(function (a, b) {
return a + b.m.getEps(); return a + b.m.getEps()
}, 0); }, 0)
standardOpts.pd = ship.getAvailableModules().matchingPowerDist({ weprate: wepRateRequired }).id; standardOpts.pd = ship.getAvailableModules().matchingPowerDist({weprate: wepRateRequired}).id
// Fill the empty internals with cargo racks // Fill the empty internals with cargo racks
for (let i = ship.internal.length; i--;) { for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i]; let slot = ship.internal[i]
if (usedSlots.indexOf(slot) == -1 && canMount(ship, slot, 'cr')) { if (usedSlots.indexOf(slot) == -1 && canMount(ship, slot, 'cr')) {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E')); ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'))
} }
} }
ship.useLightestStandard(standardOpts); ship.useLightestStandard(standardOpts)
} }
/** /**
* Racer Role * Racer Role
* @param {Ship} ship Ship instance * @param {Ship} ship Ship instance
*/ */
export function racer(ship) { export function racer (ship) {
let standardOpts = {}, let standardOpts = {},
usedSlots = [], usedSlots = [],
sgSlot, sgSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass); sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass)
// Cargo hatch can be disabled // Cargo hatch can be disabled
ship.setSlotEnabled(ship.cargoHatch, false); ship.setSlotEnabled(ship.cargoHatch, false)
// Shield generator // Shield generator
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8]; const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8]
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg) .filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class) .filter(a => a.maxClass >= sg.class)
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass)); .sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass))
for (let i = 0; i < shieldInternals.length; i++) { for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) { if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg); ship.use(shieldInternals[i], sg)
usedSlots.push(shieldInternals[i]); usedSlots.push(shieldInternals[i])
sgSlot = shieldInternals[i]; sgSlot = shieldInternals[i]
break; break
} }
} }
// Empty the hardpoints // Empty the hardpoints
for (let s of ship.hardpoints) { for (let s of ship.hardpoints) {
ship.use(s, null); ship.use(s, null)
} }
// Empty the internals // Empty the internals
for (let i = ship.internal.length; i--;) { for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i]; let slot = ship.internal[i]
if (usedSlots.indexOf(slot) == -1) { if (usedSlots.indexOf(slot) == -1) {
ship.use(slot, null); ship.use(slot, null)
} }
} }
// Best thrusters // Best thrusters
if (ship.standard[1].maxClass === 3) { if (ship.standard[1].maxClass === 3) {
standardOpts.th = 'tz'; standardOpts.th = 'tz'
} else if (ship.standard[1].maxClass === 2) { } else if (ship.standard[1].maxClass === 2) {
standardOpts.th = 'u0'; standardOpts.th = 'u0'
} else { } else {
standardOpts.th = ship.standard[1].maxClass + 'A'; standardOpts.th = ship.standard[1].maxClass + 'A'
} }
// Best power distributor for more boosting // Best power distributor for more boosting
standardOpts.pd = ship.standard[4].maxClass + 'A'; standardOpts.pd = ship.standard[4].maxClass + 'A'
// Smallest possible FSD drive // Smallest possible FSD drive
standardOpts.fsd = '2D'; standardOpts.fsd = '2D'
// Minimal fuel tank // Minimal fuel tank
standardOpts.ft = '1C'; standardOpts.ft = '1C'
// Disable nearly everything // Disable nearly everything
standardOpts.fsdDisabled = true; standardOpts.fsdDisabled = true
standardOpts.sDisabled = true; standardOpts.sDisabled = true
standardOpts.pdDisabled = true; standardOpts.pdDisabled = true
standardOpts.lsDisabled = true; standardOpts.lsDisabled = true
ship.useLightestStandard(standardOpts); ship.useLightestStandard(standardOpts)
// Apply engineering to each module // Apply engineering to each module
// ship.standard[1].m.blueprint = getBlueprint('Engine_Dirty', ship.standard[0]); // ship.standard[1].m.blueprint = getBlueprint('Engine_Dirty', ship.standard[0]);
@@ -398,4 +413,3 @@ export function racer(ship) {
// ship.standard[5].m.blueprint.grade = 5; // ship.standard[5].m.blueprint.grade = 5;
// setBest(ship, ship.standard[5].m); // setBest(ship, ship.standard[5].m);
} }

View File

@@ -1,6 +1,67 @@
import React from 'react'; import React from 'react';
import { Modifications } from 'coriolis-data/dist'; import { Modifications } from 'coriolis-data/dist';
/**
* Generate a tooltip with details of a blueprint's specials
* @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade
* @param {string} grp The group of the module
* @param {Object} m The module to compare with
* @param specialName
* @returns {Object} The react components
*/
export function specialToolTip(translate, blueprint, grp, m, specialName) {
const effects = [];
if (!blueprint || !blueprint.features) {
return undefined;
}
if (m) {
// We also add in any benefits from specials that aren't covered above
if (m.blueprint) {
for (const feature in Modifications.modifierActions[specialName]) {
// if (!blueprint.features[feature] && !m.mods.feature) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature) - m.getModValue(feature, true);
if (featureDef.type === 'percentage') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature + '_specialTT'}>
<td style={{textAlign: 'left'}}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'}
style={{textAlign: 'right'}}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
}
return (
<div>
<table width='100%'>
<tbody>
{effects}
</tbody>
</table>
</div>
);
}
/** /**
* Generate a tooltip with details of a blueprint's effects * Generate a tooltip with details of a blueprint's effects
* @param {Object} translate The translate object * @param {Object} translate The translate object
@@ -12,6 +73,9 @@ import { Modifications } from 'coriolis-data/dist';
*/ */
export function blueprintTooltip(translate, blueprint, engineers, grp, m) { export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
const effects = []; const effects = [];
if (!blueprint || !blueprint.features) {
return undefined;
}
for (const feature in blueprint.features) { for (const feature in blueprint.features) {
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]); const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
const featureDef = Modifications.modifications[feature]; const featureDef = Modifications.modifications[feature];
@@ -225,27 +289,13 @@ export function isValueBeneficial(feature, value) {
*/ */
export function getBlueprint(name, module) { export function getBlueprint(name, module) {
// Start with a copy of the blueprint // Start with a copy of the blueprint
const blueprint = JSON.parse(JSON.stringify(Modifications.blueprints[name])); const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0)
const found = Modifications.blueprints[findMod(name)];
if (!found || !found.fdname) {
return {};
}
const blueprint = JSON.parse(JSON.stringify(found));
if (module) { if (module) {
if (module.grp === 'bh' || module.grp === 'hr' || module.grp === 'sg' || module.grp === 'psg' || module.grp === 'bsg') {
// Bulkheads, hull reinforcements and shield generators need to have their resistances altered by the base values
for (const grade in blueprint.grades) {
for (const feature in blueprint.grades[grade].features) {
if (feature === 'explres') {
blueprint.grades[grade].features[feature][0] *= (1 - module.explres);
blueprint.grades[grade].features[feature][1] *= (1 - module.explres);
}
if (feature === 'kinres') {
blueprint.grades[grade].features[feature][0] *= (1 - module.kinres);
blueprint.grades[grade].features[feature][1] *= (1 - module.kinres);
}
if (feature === 'thermres') {
blueprint.grades[grade].features[feature][0] *= (1 - module.thermres);
blueprint.grades[grade].features[feature][1] *= (1 - module.thermres);
}
}
}
}
if (module.grp === 'sb') { if (module.grp === 'sb') {
// Shield boosters are treated internally as straight modifiers, so rather than (for example) // Shield boosters are treated internally as straight modifiers, so rather than (for example)
// being a 4% boost they are a 104% multiplier. We need to fix the values here so that they look // being a 4% boost they are a 104% multiplier. We need to fix the values here so that they look
@@ -264,56 +314,31 @@ export function getBlueprint(name, module) {
} }
/** /**
* Provide 'worst' primary modifications * Provide 'percent' primary modifications
* @param {Object} ship The ship for which to perform the modifications * @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications * @param {Object} m The module for which to perform the modifications
* @param {Number} percent The percent to set values to of full.
*/ */
export function setWorst(ship, m) { export function setPercent(ship, m, percent) {
ship.clearModifications(m);
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
const value = features[featureName][0];
_setValue(ship, m, featureName, value);
}
}
/**
* Provide 'best' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
*/
export function setBest(ship, m) {
ship.clearModifications(m);
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
const value = features[featureName][1];
_setValue(ship, m, featureName, value);
}
}
/**
* Provide 'extreme' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
*/
export function setExtreme(ship, m) {
ship.clearModifications(m); ship.clearModifications(m);
// Pick given value as multiplier
const mult = percent / 100;
const features = m.blueprint.grades[m.blueprint.grade].features; const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) { for (const featureName in features) {
let value; let value;
if (Modifications.modifications[featureName].higherbetter) { if (Modifications.modifications[featureName].higherbetter) {
// Higher is better, but is this making it better or worse? // Higher is better, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][0]; value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
} else { } else {
value = features[featureName][1]; value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
} }
} else { } else {
// Higher is worse, but is this making it better or worse? // Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][1]; value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
} else { } else {
value = features[featureName][0]; value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
} }
} }
@@ -327,30 +352,8 @@ export function setExtreme(ship, m) {
* @param {Object} m The module for which to perform the modifications * @param {Object} m The module for which to perform the modifications
*/ */
export function setRandom(ship, m) { export function setRandom(ship, m) {
ship.clearModifications(m);
// Pick a single value for our randomness // Pick a single value for our randomness
const mult = Math.random(); setPercent(ship, m, Math.random() * 100);
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
let value;
if (Modifications.modifications[featureName].higherbetter) {
// Higher is better, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
} else {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
}
} else {
// Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
} else {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
}
}
_setValue(ship, m, featureName, value);
}
} }
/** /**
@@ -369,3 +372,60 @@ function _setValue(ship, m, featureName, value) {
ship.setModification(m, featureName, value); ship.setModification(m, featureName, value);
} }
} }
/**
* Provide 'percent' primary query
* @param {Object} m The module for which to perform the query
* @returns {Number} percent The percentage indicator of current applied values.
*/
export function getPercent(m) {
let result = null;
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
if (features[featureName][0] === features[featureName][1]) {
continue;
}
let value = _getValue(m, featureName);
let mult;
if (Modifications.modifications[featureName].higherbetter) {
// Higher is better, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
} else {
mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
}
} else {
// Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
} else {
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
}
}
if (result && result != mult) {
return null;
} else if (result != mult) {
result = mult;
}
}
return result;
}
/**
* Query a feature value
* @param {Object} m The module for which to perform the query
* @param {string} featureName The feature being queried
*/
function _getValue(m, featureName) {
if (Modifications.modifications[featureName].type == 'percentage') {
return m.getModValue(featureName, true) / 10000;
} else if (Modifications.modifications[featureName].type == 'numeric') {
return m.getModValue(featureName, true) / 100;
} else {
return m.getModValue(featureName, true);
}
}

View File

@@ -106,7 +106,7 @@ function _moduleFromEdId(edId) {
* @return {string} the Coriolis model of the ship * @return {string} the Coriolis model of the ship
*/ */
function _shipModelFromEDName(edName) { function _shipModelFromEDName(edName) {
return SHIP_FD_NAME_TO_CORIOLIS_NAME[edName]; return SHIP_FD_NAME_TO_CORIOLIS_NAME[Object.keys(SHIP_FD_NAME_TO_CORIOLIS_NAME).find(elem => elem.toLowerCase() === edName.toLowerCase())];
} }
/** /**
@@ -148,15 +148,15 @@ export function shipFromJson(json) {
// Add the bulkheads // Add the bulkheads
const armourJson = json.modules.Armour.module; const armourJson = json.modules.Armour.module;
if (armourJson.name.endsWith('_Armour_Grade1')) { if (armourJson.name.toLowerCase().endsWith('_armour_grade1')) {
ship.useBulkhead(0, true); ship.useBulkhead(0, true);
} else if (armourJson.name.endsWith('_Armour_Grade2')) { } else if (armourJson.name.toLowerCase().endsWith('_armour_grade2')) {
ship.useBulkhead(1, true); ship.useBulkhead(1, true);
} else if (armourJson.name.endsWith('_Armour_Grade3')) { } else if (armourJson.name.toLowerCase().endsWith('_armour_grade3')) {
ship.useBulkhead(2, true); ship.useBulkhead(2, true);
} else if (armourJson.name.endsWith('_Armour_Mirrored')) { } else if (armourJson.name.toLowerCase().endsWith('_armour_mirrored')) {
ship.useBulkhead(3, true); ship.useBulkhead(3, true);
} else if (armourJson.name.endsWith('_Armour_Reactive')) { } else if (armourJson.name.toLowerCase().endsWith('_armour_reactive')) {
ship.useBulkhead(4, true); ship.useBulkhead(4, true);
} else { } else {
throw 'Unknown bulkheads "' + armourJson.name + '"'; throw 'Unknown bulkheads "' + armourJson.name + '"';

View File

@@ -2,23 +2,25 @@ import Ship from '../shipyard/Ship'
import { HARDPOINT_NUM_TO_CLASS, shipModelFromJson } from './CompanionApiUtils' import { HARDPOINT_NUM_TO_CLASS, shipModelFromJson } from './CompanionApiUtils'
import { Ships } from 'coriolis-data/dist' import { Ships } from 'coriolis-data/dist'
import Module from '../shipyard/Module' import Module from '../shipyard/Module'
import { Modules } from '../../../../coriolis-data/dist' import { Modules } from 'coriolis-data/dist'
import { Modifications } from 'coriolis-data/dist'
import { getBlueprint } from './BlueprintFunctions'
/** /**
* Obtain a module given its FD Name * Obtain a module given its FD Name
* @param {string} fdname the FD Name of the module * @param {string} fdname the FD Name of the module
* @return {Module} the module * @return {Module} the module
*/ */
function _moduleFromFdName(fdname) { function _moduleFromFdName (fdname) {
if (!fdname) return null; if (!fdname) return null
fdname = fdname.toLowerCase(); fdname = fdname.toLowerCase()
// Check standard modules // Check standard modules
for (const grp in Modules.standard) { for (const grp in Modules.standard) {
if (Modules.standard.hasOwnProperty(grp)) { if (Modules.standard.hasOwnProperty(grp)) {
for (const i in Modules.standard[grp]) { for (const i in Modules.standard[grp]) {
if (Modules.standard[grp][i].symbol && Modules.standard[grp][i].symbol.toLowerCase() === fdname) { if (Modules.standard[grp][i].symbol && Modules.standard[grp][i].symbol.toLowerCase() === fdname) {
// Found it // Found it
return new Module({ template: Modules.standard[grp][i] }); return new Module({template: Modules.standard[grp][i]})
} }
} }
} }
@@ -30,7 +32,7 @@ function _moduleFromFdName(fdname) {
for (const i in Modules.hardpoints[grp]) { for (const i in Modules.hardpoints[grp]) {
if (Modules.hardpoints[grp][i].symbol && Modules.hardpoints[grp][i].symbol.toLowerCase() === fdname) { if (Modules.hardpoints[grp][i].symbol && Modules.hardpoints[grp][i].symbol.toLowerCase() === fdname) {
// Found it // Found it
return new Module({ template: Modules.hardpoints[grp][i] }); return new Module({template: Modules.hardpoints[grp][i]})
} }
} }
} }
@@ -42,180 +44,249 @@ function _moduleFromFdName(fdname) {
for (const i in Modules.internal[grp]) { for (const i in Modules.internal[grp]) {
if (Modules.internal[grp][i].symbol && Modules.internal[grp][i].symbol.toLowerCase() === fdname) { if (Modules.internal[grp][i].symbol && Modules.internal[grp][i].symbol.toLowerCase() === fdname) {
// Found it // Found it
return new Module({ template: Modules.internal[grp][i] }); return new Module({template: Modules.internal[grp][i]})
} }
} }
} }
} }
console.log(fdname)
// Not found // Not found
return null; return null
} }
/** /**
* Build a ship from the journal Loadout event JSON * Build a ship from the journal Loadout event JSON
* @param {object} json the Loadout event JSON * @param {object} json the Loadout event JSON
* @return {Ship} the built ship * @return {Ship} the built ship
*/ */
export function shipFromLoadoutJSON(json) { export function shipFromLoadoutJSON (json) {
// Start off building a basic ship // Start off building a basic ship
const shipModel = shipModelFromJson(json); const shipModel = shipModelFromJson(json)
if (!shipModel) { if (!shipModel) {
throw 'No such ship found: "' + json.Ship + '"'; throw 'No such ship found: "' + json.Ship + '"'
} }
const shipTemplate = Ships[shipModel]; const shipTemplate = Ships[shipModel]
let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots); let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots)
ship.buildWith(null); ship.buildWith(null)
// Initial Ship building, don't do engineering yet. // Initial Ship building, don't do engineering yet.
let opts = [];
for (const module of json.Modules) { for (const module of json.Modules) {
switch (module.Slot) { switch (module.Slot.toLowerCase()) {
// Cargo Hatch. // Cargo Hatch.
case 'CargoHatch': case 'cargohatch':
ship.cargoHatch.enabled = module.On; ship.cargoHatch.enabled = module.On
ship.cargoHatch.priority = module.Priority; ship.cargoHatch.priority = module.Priority
break; break
// Add the bulkheads // Add the bulkheads
case 'Armour': case 'armour':
if (module.Item.endsWith('_Armour_Grade1')) { if (module.Item.toLowerCase().endsWith('_armour_grade1')) {
ship.useBulkhead(0, true); ship.useBulkhead(0, true)
} else if (module.Item.endsWith('_Armour_Grade2')) { } else if (module.Item.toLowerCase().endsWith('_armour_grade2')) {
ship.useBulkhead(1, true); ship.useBulkhead(1, true)
} else if (module.Item.endsWith('_Armour_Grade3')) { } else if (module.Item.toLowerCase().endsWith('_armour_grade3')) {
ship.useBulkhead(2, true); ship.useBulkhead(2, true)
} else if (module.Item.endsWith('_Armour_Mirrored')) { } else if (module.Item.toLowerCase().endsWith('_armour_mirrored')) {
ship.useBulkhead(3, true); ship.useBulkhead(3, true)
} else if (module.Item.endsWith('_Armour_Reactive')) { } else if (module.Item.toLowerCase().endsWith('_armour_reactive')) {
ship.useBulkhead(4, true); ship.useBulkhead(4, true)
} else { } else {
throw 'Unknown bulkheads "' + module.Item + '"'; throw 'Unknown bulkheads "' + module.Item + '"'
} }
ship.bulkheads.enabled = true; ship.bulkheads.enabled = true
break; if (module.Engineering) _addModifications(ship.bulkheads.m, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect)
case 'PowerPlant': break
const powerplant = _moduleFromFdName(module.Item); case 'powerplant':
ship.use(ship.standard[0], powerplant, true); const powerplant = _moduleFromFdName(module.Item)
ship.standard[0].enabled = module.On; ship.use(ship.standard[0], powerplant, true)
ship.standard[0].priority = module.Priority; ship.standard[0].enabled = module.On
break; ship.standard[0].priority = module.Priority
case 'MainEngines': if (module.Engineering) _addModifications(powerplant, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect)
const thrusters = _moduleFromFdName(module.Item); break
ship.use(ship.standard[1], thrusters, true); case 'mainengines':
ship.standard[1].enabled = module.On; const thrusters = _moduleFromFdName(module.Item)
ship.standard[1].priority = module.Priority; ship.use(ship.standard[1], thrusters, true)
break; ship.standard[1].enabled = module.On
case 'FrameShiftDrive': ship.standard[1].priority = module.Priority
const frameshiftdrive = _moduleFromFdName(module.Item); if (module.Engineering) _addModifications(thrusters, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect)
ship.use(ship.standard[2], frameshiftdrive, true); break
ship.standard[2].enabled = module.On; case 'frameshiftdrive':
ship.standard[2].priority = module.Priority; const frameshiftdrive = _moduleFromFdName(module.Item)
break; ship.use(ship.standard[2], frameshiftdrive, true)
case 'LifeSupport': ship.standard[2].enabled = module.On
const lifesupport = _moduleFromFdName(module.Item); ship.standard[2].priority = module.Priority
ship.use(ship.standard[3], lifesupport, true); if (module.Engineering) _addModifications(frameshiftdrive, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect)
ship.standard[3].enabled = module.On === true; break
ship.standard[3].priority = module.Priority; case 'lifesupport':
break; const lifesupport = _moduleFromFdName(module.Item)
case 'PowerDistributor': ship.use(ship.standard[3], lifesupport, true)
const powerdistributor = _moduleFromFdName(module.Item); ship.standard[3].enabled = module.On === true
ship.use(ship.standard[4], powerdistributor, true); ship.standard[3].priority = module.Priority
ship.standard[4].enabled = module.On; if (module.Engineering) _addModifications(lifesupport, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect)
ship.standard[4].priority = module.Priority; break
break; case 'powerdistributor':
case 'Radar': const powerdistributor = _moduleFromFdName(module.Item)
const sensors = _moduleFromFdName(module.Item); ship.use(ship.standard[4], powerdistributor, true)
ship.use(ship.standard[5], sensors, true); ship.standard[4].enabled = module.On
ship.standard[5].enabled = module.On; ship.standard[4].priority = module.Priority
ship.standard[5].priority = module.Priority; if (module.Engineering) _addModifications(powerdistributor, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect)
break; break
case 'FuelTank': case 'radar':
const fueltank = _moduleFromFdName(module.Item); const sensors = _moduleFromFdName(module.Item)
ship.use(ship.standard[6], fueltank, true); ship.use(ship.standard[5], sensors, true)
ship.standard[6].enabled = true; ship.standard[5].enabled = module.On
ship.standard[6].priority = 0; ship.standard[5].priority = module.Priority
break; if (module.Engineering) _addModifications(sensors, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect)
break
case 'fueltank':
const fueltank = _moduleFromFdName(module.Item)
ship.use(ship.standard[6], fueltank, true)
ship.standard[6].enabled = true
ship.standard[6].priority = 0
break
default: default:
} }
if (module.Slot.search(/Hardpoint/) !== -1) { for (const module of json.Modules) {
// Add hardpoints if (module.Slot.toLowerCase().search(/hardpoint/) !== -1) {
let hardpointClassNum = -1; // Add hardpoints
let hardpointSlotNum = -1; let hardpoint;
let hardpointArrayNum = 0; let hardpointClassNum = -1
for (let i in shipTemplate.slots.hardpoints) { let hardpointSlotNum = -1
if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) { let hardpointArrayNum = 0
// Another slot of the same class for (let i in shipTemplate.slots.hardpoints) {
hardpointSlotNum++; if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) {
} else { // Another slot of the same class
// The first slot of a new class hardpointSlotNum++
hardpointClassNum = shipTemplate.slots.hardpoints[i]; } else {
hardpointSlotNum = 1; // The first slot of a new class
} hardpointClassNum = shipTemplate.slots.hardpoints[i]
hardpointSlotNum = 1
}
// Now that we know what we're looking for, find it // Now that we know what we're looking for, find it
const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum; const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum
const hardpointSlot = json.Modules.find(elem => elem.Slot === hardpointName); const hardpointSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === hardpointName.toLowerCase())
if (!hardpointSlot) { if (!hardpointSlot) {
// This can happen with old imports that don't contain new hardpoints // This can happen with old imports that don't contain new hardpoints
} else if (!hardpointSlot) { } else if (!hardpointSlot) {
// No module // No module
} else { } else {
const hardpoint = _moduleFromFdName(hardpointSlot.Item); hardpoint = _moduleFromFdName(hardpointSlot.Item)
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true); ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true)
ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On; ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On
ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority; ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority
opts.push({coriolisMod: hardpoint, json: hardpointSlot});
}
hardpointArrayNum++
} }
hardpointArrayNum++;
} }
} if (module.Slot.toLowerCase().search(/slot\d/) !== -1) {
if (module.Slot.search(/Slot\d/) !== -1) { let internalSlotNum = 1
let internalSlotNum = 1; let militarySlotNum = 1
let militarySlotNum = 1; for (let i in shipTemplate.slots.internal) {
for (let i in shipTemplate.slots.internal) { const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name = 'military' : false
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name = 'Military' : false;
// The internal slot might be a standard or a military slot. Military slots have a different naming system // The internal slot might be a standard or a military slot. Military slots have a different naming system
let internalSlot = null; let internalSlot = null
if (isMilitary) { if (isMilitary) {
const internalName = 'Military0' + militarySlotNum; const internalName = 'Military0' + militarySlotNum
internalSlot = json.Modules.find(elem => elem.Slot === internalName); internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase())
militarySlotNum++; militarySlotNum++
} else { } else {
// Slot numbers are not contiguous so handle skips. // Slot numbers are not contiguous so handle skips.
while (internalSlot === null && internalSlotNum < 99) { while (internalSlot === null && internalSlotNum < 99) {
// Slot sizes have no relationship to the actual size, either, so check all possibilities // Slot sizes have no relationship to the actual size, either, so check all possibilities
for (let slotsize = 0; slotsize < 9; slotsize++) { for (let slotsize = 0; slotsize < 9; slotsize++) {
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + slotsize; const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + slotsize
if (json.Modules.find(elem => elem.Slot === internalName)) { if (json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase())) {
internalSlot = json.Modules.find(elem => elem.Slot === internalName); internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
break; break
}
} }
internalSlotNum++
} }
internalSlotNum++; }
if (!internalSlot) {
// This can happen with old imports that don't contain new slots
} else if (!internalSlot) {
// No module
} else {
const internalJson = internalSlot
const internal = _moduleFromFdName(internalJson.Item)
ship.use(ship.internal[i], internal, true)
ship.internal[i].enabled = internalJson.On === true
ship.internal[i].priority = internalJson.Priority
opts.push({coriolisMod: internal, json: internalSlot});
} }
} }
if (!internalSlot) {
// This can happen with old imports that don't contain new slots
} else if (!internalSlot) {
// No module
} else {
const internalJson = internalSlot;
const internal = _moduleFromFdName(internalJson.Item);
ship.use(ship.internal[i], internal, true);
ship.internal[i].enabled = internalJson.On === true;
ship.internal[i].priority = internalJson.Priority;
}
} }
} }
}
for (const i of opts) {
if (i.json.Engineering) _addModifications(i.coriolisMod, i.json.Engineering.Modifiers, i.json.Engineering.BlueprintName, i.json.Engineering.Level, i.json.Engineering.ExperimentalEffect)
} }
// We don't have any information on it so guess it's priority 5 and disabled // We don't have any information on it so guess it's priority 5 and disabled
if (!ship.cargoHatch) { if (!ship.cargoHatch) {
ship.cargoHatch.enabled = false; ship.cargoHatch.enabled = false
ship.cargoHatch.priority = 4; ship.cargoHatch.priority = 4
} }
console.log(ship); console.log(ship)
// Now update the ship's codes before returning it // Now update the ship's codes before returning it
return ship.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString(); return ship.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString()
}
/**
* Add the modifications for a module
* @param {Module} module the module
* @param {Object} modifiers the modifiers
* @param {Object} blueprint the blueprint of the modification
* @param {Object} grade the grade of the modification
* @param {Object} specialModifications special modification
*/
function _addModifications (module, modifiers, blueprint, grade, specialModifications) {
if (!modifiers) return
let special
if (specialModifications) {
special = Modifications.specials[specialModifications]
}
for (const i in modifiers) {
// Some special modifications
// Look up the modifiers to find what we need to do
const findMod = val => Object.keys(Modifications.modifierActions).find(elem => elem.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, '') === val.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, ''))
const modifierActions = Modifications.modifierActions[findMod(modifiers[i].Label)]
//TODO: Figure out how to scale this value.
if (!!modifiers[i].LessIsGood) {
}
let value = (modifiers[i].Value / modifiers[i].OriginalValue * 100 - 100) * 100;
if (value === Infinity) {
value = modifiers[i].Value * 100;
}
if (modifiers[i].Label.search('Resistance') >= 0) {
value = (modifiers[i].Value * 100) - (modifiers[i].OriginalValue * 100)
}
// Carry out the required changes
for (const action in modifierActions) {
if (isNaN(modifierActions[action])) {
module.setModValue(action, modifierActions[action])
} else {
module.setModValue(action, value)
}
}
}
// Add the blueprint definition, grade and special
if (blueprint) {
module.blueprint = getBlueprint(blueprint, module)
if (grade) {
module.blueprint.grade = Number(grade)
}
if (special) {
module.blueprint.special = special
}
}
} }

View File

@@ -55,10 +55,12 @@
<!-- End Piwik Code --> <!-- End Piwik Code -->
<!-- Bugsnag --> <!-- Bugsnag -->
<script <script src="//d2wy8f7a9ursnm.cloudfront.net/v4/bugsnag.min.js"></script>
src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-3.min.js" <script src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-plugins/v1/bugsnag-react.min.js"></script>
data-apikey="2382691c622937f28f8fa82a1bfd797a"></script> <script>
window.bugsnagClient = bugsnag('ba9fae819372850fb660755341fa6ef5', {appVersion: window.CORIOLIS_VERSION || undefined})
window.Bugsnag = window.bugsnagClient
</script>
</head> </head>
<body style="background-color:#000;"> <body style="background-color:#000;">
<section id="coriolis"></section> <section id="coriolis"></section>

View File

@@ -31,6 +31,50 @@ button {
} }
} }
.button-inline-menu {
white-space: nowrap;
line-height: 1.5em;
text-align: center;
margin: 0.5em 0;
padding-left: 5px;
border-top: 1px solid @primary-disabled;
border-bottom: 1px solid @primary-disabled;
overflow: hidden;
text-overflow: ellipsis;
background: @primary-bg;
&.warning {
border-color: @warning-disabled;
color: @warning-disabled;
stroke: @warning-disabled;
.no-touch &:hover {
border-color: @warning;
color: @warning;
stroke: @warning;
}
}
&.disabled, &.disabled:hover {
cursor: not-allowed;
border-color: @disabled;
color: @disabled;
stroke: @disabled;
}
&.active {
border-color: @secondary;
color: @secondary;
stroke: @secondary;
}
&:hover {
border-color: @primary;
color: @primary;
stroke: @primary;
}
}
.button-lbl { .button-lbl {
margin-left: 0.5em; margin-left: 0.5em;

View File

@@ -10,6 +10,8 @@
@secondary: #1FB0FF; // Light blue @secondary: #1FB0FF; // Light blue
@warning: #FF3B00; // Dark Orange @warning: #FF3B00; // Dark Orange
@disabled: #555; // Light grey @disabled: #555; // Light grey
@success: #71a052; // Green
@purple: #800080; // Purple
@primary-disabled: darken(@primary, @disabledDarken); @primary-disabled: darken(@primary, @disabledDarken);
@secondary-disabled: darken(@secondary, @disabledDarken); @secondary-disabled: darken(@secondary, @disabledDarken);
@warning-disabled: darken(@warning, @disabledDarken); @warning-disabled: darken(@warning, @disabledDarken);

View File

@@ -70,10 +70,29 @@
padding: 0.5em 0.2em; padding: 0.5em 0.2em;
font-size: 0.9em; font-size: 0.9em;
#summaryTable { .summaryTable {
.user-select-none(); .user-select-none();
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
& > thead.blue {
background-color: @secondary;
border-left: 1px solid @primary-bg;
color: @primary-bg;
}
& > thead.green {
background-color: @success;
border-left: 1px solid @primary-bg;
color: @primary-bg;
}
& > thead.purple {
background-color: @purple;
border-left: 1px solid @primary-bg;
color: @primary-bg;
}
} }
} }

View File

@@ -41,6 +41,10 @@
overflow: hidden; overflow: hidden;
} }
input.cb:focus {
border-color:#fff;
}
.l { .l {
text-transform: capitalize; text-transform: capitalize;
margin-right: 0.8em; margin-right: 0.8em;

View File

@@ -1,29 +1,30 @@
var path = require('path'); const path = require('path')
var exec = require('child_process').exec; const exec = require('child_process').exec
var webpack = require('webpack'); const webpack = require('webpack')
var pkgJson = require('./package'); const HtmlWebpackPlugin = require('html-webpack-plugin')
var HtmlWebpackPlugin = require("html-webpack-plugin"); const ExtractTextPlugin = require('extract-text-webpack-plugin')
var ExtractTextPlugin = require("extract-text-webpack-plugin"); const WebpackNotifierPlugin = require('webpack-notifier')
var WebpackNotifierPlugin = require('webpack-notifier'); const pkgJson = require('./package')
function CopyDirPlugin(source, destination) { function CopyDirPlugin(source, destination) {
this.source = source; this.source = source
this.destination = destination; this.destination = destination
}
CopyDirPlugin.prototype.apply = function (compiler) {
compiler.plugin('done', () => {
console.log(compiler.outputPath, this.destination)
exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination))
})
} }
CopyDirPlugin.prototype.apply = function(compiler) {
compiler.plugin('done', function() {
console.log(compiler.outputPath, this.destination);
exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination));
}.bind(this));
};
module.exports = { module.exports = {
devtool: 'eval', devtool: 'source-map',
devServer: { devServer: {
headers: { "Access-Control-Allow-Origin": "*" } headers: {'Access-Control-Allow-Origin': '*'}
}, },
entry: { entry: {
app: [ 'webpack-dev-server/client?http://0.0.0.0:3300', 'webpack/hot/only-dev-server', path.join(__dirname, "src/app/index.js") ], app: ['webpack-dev-server/client?http://0.0.0.0:3300', 'webpack/hot/only-dev-server', path.join(__dirname, 'src/app/index.js')],
lib: ['d3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string'] lib: ['d3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string']
}, },
resolve: { resolve: {
@@ -38,19 +39,19 @@ module.exports = {
plugins: [ plugins: [
new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''), new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''),
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
name: 'lib', name: 'lib',
filename: 'lib.js' filename: 'lib.js'
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
inject: false, inject: false,
template: path.join(__dirname, "src/index.ejs"), template: path.join(__dirname, 'src/index.ejs'),
version: pkgJson.version, version: pkgJson.version,
gapiKey: process.env.CORIOLIS_GAPI_KEY || '', gapiKey: process.env.CORIOLIS_GAPI_KEY || ''
}), }),
new ExtractTextPlugin({ new ExtractTextPlugin({
filename: 'app.css', filename: 'app.css',
disable: false, disable: false,
allChunks: true allChunks: true
}), }),
new WebpackNotifierPlugin({alwaysNotify: true}), new WebpackNotifierPlugin({alwaysNotify: true}),
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
@@ -58,14 +59,14 @@ module.exports = {
], ],
module: { module: {
rules: [ rules: [
{ test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader'}) }, {test: /\.css$/, loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader'})},
{ test: /\.less$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader!less-loader'}) }, {test: /\.less$/, loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader!less-loader'})},
{ test: /\.(js|jsx)$/, loaders: [ 'babel-loader' ], include: path.join(__dirname, 'src') }, {test: /\.(js|jsx)$/, loaders: ['babel-loader'], include: path.join(__dirname, 'src')},
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' }, {test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'},
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' }, {test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'},
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream' }, {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream'},
{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader' }, {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader'},
{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml' } {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml'}
] ]
} }
}; }

View File

@@ -1,24 +1,27 @@
var path = require('path'); const path = require('path')
var exec = require('child_process').exec; const exec = require('child_process').exec
var webpack = require('webpack'); const webpack = require('webpack')
var pkgJson = require('./package'); const HtmlWebpackPlugin = require('html-webpack-plugin')
var HtmlWebpackPlugin = require("html-webpack-plugin"); const ExtractTextPlugin = require('extract-text-webpack-plugin')
var ExtractTextPlugin = require("extract-text-webpack-plugin"); const AppCachePlugin = require('appcache-webpack-plugin')
var AppCachePlugin = require('appcache-webpack-plugin'); const {BugsnagSourceMapUploaderPlugin} = require('webpack-bugsnag-plugins')
const pkgJson = require('./package')
function CopyDirPlugin(source, destination) { function CopyDirPlugin (source, destination) {
this.source = source; this.source = source
this.destination = destination; this.destination = destination
}
CopyDirPlugin.prototype.apply = function (compiler) {
compiler.plugin('done', () => {
console.log(compiler.outputPath, this.destination)
exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination))
})
} }
CopyDirPlugin.prototype.apply = function(compiler) {
compiler.plugin('done', function() {
console.log(compiler.outputPath, this.destination);
exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination));
}.bind(this));
};
module.exports = { module.exports = {
cache: true, cache: true,
devtool: 'source-map',
entry: { entry: {
app: ['babel-polyfill', path.resolve(__dirname, 'src/app/index')], app: ['babel-polyfill', path.resolve(__dirname, 'src/app/index')],
lib: ['d3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string'] lib: ['d3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string']
@@ -34,34 +37,39 @@ module.exports = {
}, },
plugins: [ plugins: [
new webpack.optimize.UglifyJsPlugin({ new webpack.optimize.UglifyJsPlugin({
'screw-ie8': true 'screw-ie8': true,
sourceMap: true
}), }),
//new webpack.optimize.CommonsChunkPlugin({ //new webpack.optimize.CommonsChunkPlugin({
// name: 'lib', // name: 'lib',
// filename: 'lib.[chunkhash:6].js' // filename: 'lib.[chunkhash:6].js'
//}), //}),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
inject: false, inject: false,
appCache: 'coriolis.appcache', appCache: 'coriolis.appcache',
minify: { minify: {
collapseBooleanAttributes: true, collapseBooleanAttributes: true,
collapseWhitespace: true, collapseWhitespace: true,
removeAttributeQuotes: true, removeAttributeQuotes: true,
removeComments: true, removeComments: true,
removeEmptyAttributes: true, removeEmptyAttributes: true,
removeRedundantAttributes: true, removeRedundantAttributes: true,
removeScriptTypeAttributes: true, removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true removeStyleLinkTypeAttributes: true
}, },
template: path.join(__dirname, "src/index.ejs"), template: path.join(__dirname, 'src/index.ejs'),
uaTracking: process.env.CORIOLIS_UA_TRACKING || '', uaTracking: process.env.CORIOLIS_UA_TRACKING || '',
gapiKey: process.env.CORIOLIS_GAPI_KEY || '', gapiKey: process.env.CORIOLIS_GAPI_KEY || '',
version: pkgJson.version version: pkgJson.version
}), }),
new ExtractTextPlugin({ new ExtractTextPlugin({
filename: '[contenthash:6].css', filename: '[contenthash:6].css',
disable: false, disable: false,
allChunks: true allChunks: true
}),
new BugsnagSourceMapUploaderPlugin({
apiKey: 'ba9fae819372850fb660755341fa6ef5',
appVersion: pkgJson.version
}), }),
new CopyDirPlugin(path.join(__dirname, 'src/schemas'), 'schemas'), new CopyDirPlugin(path.join(__dirname, 'src/schemas'), 'schemas'),
new CopyDirPlugin(path.join(__dirname, 'src/images/logo/*'), ''), new CopyDirPlugin(path.join(__dirname, 'src/images/logo/*'), ''),
@@ -75,14 +83,14 @@ module.exports = {
], ],
module: { module: {
rules: [ rules: [
{ test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader'}) }, {test: /\.css$/, loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader'})},
{ test: /\.less$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader',use: 'css-loader!less-loader'}) }, {test: /\.less$/, loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader!less-loader'})},
{ test: /\.(js|jsx)$/, loader: 'babel-loader?cacheDirectory=true', include: path.join(__dirname, 'src') }, {test: /\.(js|jsx)$/, loader: 'babel-loader?cacheDirectory=true', include: path.join(__dirname, 'src')},
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' }, {test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'},
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' }, {test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'},
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream' }, {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream'},
{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader' }, {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader'},
{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml' } {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml'}
] ]
} }
}; }