Compare commits

...

780 Commits

Author SHA1 Message Date
willyb321
f8a215d790 2.9.17 2018-06-14 07:08:17 +10:00
willyb321
da69f3b2c8 New URL Shortener
eddp.co shortened links are considered End-Of-Life from now on
2018-06-14 07:00:05 +10:00
willyb321
fc442c1a42 Merge branch 'master' into develop 2018-06-14 06:50:46 +10:00
willyb321
cf59a6b9fd whoops 2018-06-14 06:50:08 +10:00
willyb321
34afcd511a Merge branch 'develop' 2018-06-14 06:44:13 +10:00
willyb321
e2444a2e4e track ship in ga 2018-06-14 06:43:54 +10:00
willyb321
692516de9b Merge branch 'develop' 2018-06-13 08:37:31 +10:00
willyb321
d51009c823 add time between boosts
Fixes #269
2018-06-13 08:36:56 +10:00
willyb321
2f121bef5e Merge branch 'develop' 2018-06-13 06:46:27 +10:00
willyb321
61f7d376d2 store number of rolls per grade 2018-06-13 06:46:12 +10:00
willyb321
9e253012e6 Merge branch 'develop' 2018-06-13 06:23:57 +10:00
willyb321
e4e5b1327b fix rank link showing on things that dont need a rank link 2018-06-13 06:22:45 +10:00
willyb321
6ef3b227b8 Merge branch 'develop' 2018-06-13 06:16:37 +10:00
willyb321
bf657a0945 fix view changes 2018-06-13 06:16:21 +10:00
willyb321
c3dd1886c9 Merge branch 'develop' 2018-06-12 12:12:29 +10:00
willyb321
32498bb8a7 properly fix super cap 2018-06-12 11:32:37 +10:00
willyb321
69bb90a0e4 Merge branch 'release/2.9.16' 2018-06-12 11:03:18 +10:00
willyb321
8e2988edf0 Merge branch 'release/2.9.16' into develop 2018-06-12 11:03:18 +10:00
willyb321
876a352cfd 2.9.16 2018-06-12 11:03:13 +10:00
willyb321
84e44cabfa mats list modal allow setting # of rolls 2018-06-12 09:49:07 +10:00
willyb321
36a838d565 add material shopping list 2018-06-12 09:09:05 +10:00
willyb321
9ee8693f40 add material icon 2018-06-12 09:08:44 +10:00
willyb321
6f02965149 Merge branch 'develop' 2018-06-12 07:56:19 +10:00
willyb321
27ce82de3b switch to google analytics from piwik 2018-06-12 07:56:01 +10:00
Pat Nellesen
3d5a9ef220 Cleanup for linting issues, and added last bit of focus handling for … (#304)
* Cleanup for linting issues, and added last bit of focus handling for selected modifications and specials

* added correct value for selectedRefId for utility slot section menu when Empty All selected
2018-06-11 08:23:03 +10:00
Pat Nellesen
9b81f6efd2 Feature/#293 header keynav (#303)
Added keydown and focus handlers for Slot Section Menus ("Core Internal", "Optional Internal", etc.)

When focus is on the header, Enter key will open the menu and set focus to either the first option, or else the currently selected option, such as "Planetary Explorer" in Core Internal menu (if one has been previously selected).

While menu is open, Tab and Shift-Tab will move the focus up and down as expected. Shift-tab on first option will move focus to last option in the menu, and Tab on the last option will move focus to the top. Focus will stay inside the menu until menu is closed.

When focus is on a menu options, hitting the Enter key will trigger the onClick function for that option, and will set the option as the currently selected option for that menu.

Esc key will close the menu and set focus to the menu header H1 element.
2018-06-10 09:00:43 +10:00
willyb321
3e77e23d71 Merge remote-tracking branch 'origin/develop' into develop 2018-06-10 06:26:17 +10:00
willyb321
120c032c82 Fix approximately a lot of lint issues 2018-06-10 06:24:55 +10:00
William
46e15b8ecd Merge pull request #301 from BenJuan26/feature/linting
Fix obvious lint issues
2018-06-08 07:15:39 +10:00
William
d71d87041b Merge pull request #300 from BenJuan26/feature/travis-install-coriolis-data
Clone coriolis-data into Travis before npm install
2018-06-08 07:14:54 +10:00
Benjamin Schubert
124bd58b9f Fix obvious linting issues 2018-06-07 10:50:52 -04:00
Benjamin Schubert
257b9b0562 Clone coriolis-data into Travis before npm install 2018-06-07 10:00:46 -04:00
willyb321
b8e15f691d Merge branch 'develop' 2018-06-07 06:57:33 +10:00
willyb321
2255e3bfc4 Temp fix for #273
Don't allow engineering on guardian PP
2018-06-07 06:57:00 +10:00
willyb321
8797d84605 Merge branch 'develop' 2018-06-06 06:27:18 +10:00
willyb321
719179a923 Add link to ranks page
Fixes #298
2018-06-06 06:26:02 +10:00
willyb321
1d544099f6 Merge branch 'release/2.9.15' 2018-05-30 07:43:16 +10:00
willyb321
9b131a762a Merge branch 'release/2.9.15' into develop 2018-05-30 07:43:15 +10:00
willyb321
08c5d2256a 2.9.15 2018-05-30 07:43:09 +10:00
willyb321
ed6ee4341f Fix crash when applying blueprint
thanks bugsnag
2018-05-30 07:42:33 +10:00
willyb321
157c1148fb Implement temporary fix for #280 2018-05-30 07:37:20 +10:00
Pat Nellesen
507ea9e09e Fix to account for cases where there is only one module available for… (#291)
* Fix to account for cases where there is only one module available for a given slot (e.g. Sidewinder fuel tank)

* Fix for issue #289 - set focus to active module section if the slot has an active module

* Added activeSlot ref to set focus to current module when module list is opened

* Comment/console cleanup for PR #291
2018-05-30 07:25:35 +10:00
willyb321
af68cba7be add "view release changes" button below the update available banner 2018-05-29 07:31:58 +10:00
willyb321
224fbe0e8f add a "commits since last release button"
makes it easier for people to see activity
2018-05-29 07:23:20 +10:00
willyb321
49a6c5f2c4 Merge branch 'release/2.9.14' 2018-05-29 07:03:18 +10:00
willyb321
07c936897c Merge branch 'release/2.9.14' into develop 2018-05-29 07:03:18 +10:00
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
willyb321
298eaa8b4b Merge remote-tracking branch 'origin/develop' 2018-05-03 10:58:46 +10:00
William Blythe
118c80af27 Fix kinetic and thermal res swapped
Closes #265
2018-05-03 10:54:55 +10:00
willyb321
7b87038a8c Merge branch 'develop' 2018-05-03 07:39:08 +10:00
willyb321
d103939e45 add experimentals to core internals 2018-05-03 07:38:54 +10:00
willyb321
fdc9171c69 Merge branch 'release/2.9.13' 2018-05-03 07:14:53 +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
f43bd100e6 Merge branch 'release/2.9.12' 2018-04-28 12:16:21 +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
fbba0e3ea5 Merge branch 'release/2.9.11' 2018-04-26 09:21:01 +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
b15695128f Merge branch 'release/release/v2.9.10' 2018-04-25 20:00:47 +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
0870b90443 Merge branch 'release/2.9.8' 2018-04-23 07:38:34 +10: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
48ccab152b Merge branch 'release/2.9.7' 2018-04-22 12:27:20 +10: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
f3bc900f16 Merge branch 'release/2.9.6' 2018-04-22 07:00:47 +10: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
ec148847a9 Merge branch 'develop' 2018-04-21 11:39:19 +10:00
willyb321
445c63878b add guard for tooltips crashing the import 2018-04-21 11:39:02 +10:00
willyb321
0474af912a Merge branch 'release/2.9.5' 2018-04-21 11:05:29 +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
willyb321
270c2f386e Merge branch 'release/2.5.2' 2018-04-13 07:56:38 +10:00
willyb321
83f7880c58 bump version 2018-04-13 07:51:07 +10:00
William
51d29aee38 Merge pull request #235 from pnellesen/feature/fueltank_column
Added fuel tank column to Core Module Classes on Shipyard
2018-04-13 07:38:07 +10:00
William
c7754c0365 Merge pull request #234 from joelgarboden/feature/remote_testing
Disable 'localhost' host header requirement
2018-04-13 07:34:59 +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
Pat
b8c1effecb Update for issue #183 - use first letter of bulkhead instead of "Max Rating" value 2018-04-09 23:10:28 -05:00
Pat
dd07241dd9 Added fuel tank column to Core Module Classes on Shipyard 2018-04-07 18:57:54 -05:00
Joel Garboden
5ad828a613 Disable 'localhost' limit
Disale host header so webpack allows more than localhost access
2018-04-07 22:34:50 +00:00
Unknown
9856df5527 Portiguese 2018-03-22 22:00:19 +00:00
Unknown
7b249900ec Portuguese 2018-03-22 21:59:50 +00:00
Unknown
e3b9267c3f Merge remote-tracking branch 'origin/feature/beyond-coriolis' into develop 2018-03-22 21:59:37 +00:00
willyb321
a996b8135a inital engineerless import working 2018-03-23 08:51:48 +11:00
willyb321
5e1237390b Initial work on 3.0 imports.
Decided to split journal imports to a separate file.
2018-03-22 16:22:59 +11:00
Willyb321
83571b4bef add .editorconfig
Makes contributing and not messing up the codestyle easier
2018-03-22 12:07:19 +11:00
Willyb321
c2a0dad9a8 Export textarea select
Fixes #146
2018-03-22 12:06:36 +11:00
Unknown
f6f057689c Merge branch 'feature/chieftain' into develop 2018-01-27 11:47:17 +00:00
willyb321
4378f0020e add chieftain to map
also remove console logs that i forgot to remove
2018-01-27 10:55:37 +11:00
Unknown
2e9e7c4fc6 Merge branch 'release/2.5.1' 2018-01-24 09:18:58 +00:00
Unknown
b7b8ee5580 Merge branch 'release/2.5.1' into develop 2018-01-24 09:18:36 +00:00
Unknown
c630dbbed0 Version Bump 2018-01-24 09:18:21 +00:00
Unknown
fa6703a3b8 Merge remote-tracking branch 'Blackth0rn/feature/#186-passenger-totals' into develop 2018-01-24 08:45:54 +00:00
Unknown
bdcb64c9d1 Merge remote-tracking branch 'willyb321/feature/fix-engineer-values' into develop 2018-01-24 08:45:48 +00:00
Unknown
a1afc869a7 Merge remote-tracking branch 'Ghnuberath/feature/ax-modules' into develop 2018-01-24 08:45:44 +00:00
Unknown
d8ce26c7cf Merge remote-tracking branch 'origin/master' into develop 2018-01-24 08:44:45 +00:00
Sean McIntyre
95c474dc05 Recategorizing experimental modules into an "experimental" category 2018-01-22 16:48:10 -05:00
Sean McIntyre
56ca73b4ad Forgot Remote Release Flak Launcher 2018-01-22 16:43:29 -05:00
Sean McIntyre
d82cd6a89e Removing unnecessary change 2018-01-22 15:21:59 -05:00
Sean McIntyre
bf20d32364 Adding AX modules 2018-01-22 15:20:41 -05:00
willyb321
e968e62fca Fix clip size (hopefully) 2018-01-16 08:14:21 +11:00
Unknown
b9c9ca9fa1 Merge branch 'release/2.5.0' 2018-01-11 21:59:31 +00:00
Unknown
6e965e2e98 Version Bump 2018-01-11 21:59:20 +00:00
Unknown
7d569f9036 Merge branch 'feature/fix-engineers' into develop 2018-01-11 21:51:35 +00:00
Unknown
df5a77199d Merge branch 'develop' 2018-01-11 21:50:28 +00:00
Greg Matthews
3c8dfebfdc Issue #186. Added passenger totals to shipyard screen 2018-01-10 21:20:27 -08:00
Unknown
3cc422596f Something something consistant naming 2017-12-24 09:47:49 +00:00
Unknown
4ed167de22 Add T10 2017-12-24 09:47:33 +00:00
Unknown
b420647501 Something something consistant naming 2017-12-24 09:46:49 +00:00
Unknown
0c318b5e68 Add T10 2017-12-23 12:03:21 +00:00
willyb321
1c627297b8 shorten the terrible _addModifications call 2017-12-19 16:39:42 +11:00
willyb321
f41e2d0552 fix indent and hopefully fix ammo clip
dont know if it was broken, if someone has it DM it to me thanks
2017-12-19 16:28:05 +11:00
willyb321
1a1d539c60 fix engagement range slider and armour integrity
hopefully
2017-12-19 16:18:20 +11:00
willyb321
ba2e46f88f fix rate of fire
probably
2017-12-19 14:49:04 +11:00
willyb321
c2f1fa81af remove console logs 2017-12-17 18:48:24 +11:00
willyb321
752e03fa0f possible armour fix
need more data to confirm however
2017-12-17 18:24:34 +11:00
willyb321
1da69664d7 shields should be accurate now
dont now what the issue with hull is
if you see it let me know
2017-12-17 14:49:51 +11:00
willyb321
9aa986a133 thanks webstorm 2017-12-17 12:48:13 +11:00
willyb321
bdbfb28c4a missed a spot 2017-12-17 12:47:02 +11:00
willyb321
162156bb2b fix random weird indents 2017-12-17 12:46:18 +11:00
willyb321
64c5d542e9 more work on mods 2017-12-17 12:27:04 +11:00
willyb321
31dc789f6e initial (re)implementation of engineer mods
still some kinds to work out.
2017-12-17 11:16:50 +11:00
Unknown
b0e2cfd7db Merge branch 'feature/#112_ship_table_highlights' into develop 2017-12-04 09:41:54 +00:00
Greg Matthews
54ddb0d014 Rename sortValue and tmpSortValue to be more meaningful 2017-11-11 13:29:59 -08:00
Greg Matthews
e19688e96f Add alternate row highlighting for sorted value 2017-11-11 13:25:17 -08:00
Greg Matthews
4f53d75999 Fix whitespace in html 2017-11-11 13:24:57 -08:00
Unknown
7277460060 Merge branch 'release/2.4.2' 2017-11-09 17:23:46 +00:00
Unknown
93c4f6f3c0 Merge branch 'release/2.4.2' into develop 2017-11-09 17:23:32 +00:00
Unknown
c6919a7518 Bumps and changelog 2017-11-09 17:23:15 +00:00
Unknown
ca428e67dc Package Lock and D3 2017-11-09 17:18:55 +00:00
Unknown
c6726cf020 Merge remote-tracking branch 'BenJuan26/feature/fix-tests' into develop 2017-11-09 17:15:06 +00:00
Unknown
56ae1378da Merge branch 'feature/slot-commas' into develop 2017-11-09 17:14:49 +00:00
Unknown
e982ab1a3b Merge branch 'feature/ship-purchase-requirements' into develop 2017-11-09 17:11:32 +00:00
Unknown
0d6aa87e89 Update coriolis to use directory basis 2017-11-09 17:04:23 +00:00
Joshua Campbell
94d06e4025 Show ship purchase requirements on outfitting page 2017-11-07 01:46:58 -05:00
Benjamin Schubert
cee5b297ac Add create-react-class 2017-11-02 12:02:15 -04:00
Benjamin Schubert
c549213ce0 Switch to babel-preset-env 2017-11-02 11:44:53 -04:00
Benjamin Schubert
f3f9112767 Change jest version 2017-11-02 11:25:32 -04:00
Benjamin Schubert
fb325ea3e2 Add commas to slot cost 2017-11-01 15:56:51 -04:00
Unknown
3773f6f7ec Merge branch 'release/2.4.1' into develop 2017-10-31 16:52:51 +00:00
Unknown
b90ab6fe48 Merge branch 'release/2.4.1' 2017-10-31 16:52:34 +00:00
Unknown
cc2e56dc8a bump 2017-10-31 16:52:13 +00:00
William
ae65af7bbf prevent crashing when no modifications are in the data (#160) 2017-10-12 08:33:36 +01:00
William
c8fb513cd1 Fix repair limpet grouping (#161)
* Group repair limpets with the rest

* editor decided to add a random whitespace

* also it was set to tabs
2017-10-12 08:33:22 +01:00
Unknown
7fc3855af4 Merge branch 'release/2.4.0' into develop 2017-10-10 16:47:21 +01:00
Unknown
bcebb26d4a Merge branch 'release/2.4.0' 2017-10-10 16:46:57 +01:00
Unknown
1f23e4cfcc Version Bump 2017-10-10 16:46:34 +01:00
William
65998778fe fix zbuf error (#154)
Add an optional extended description…
2017-10-10 11:59:44 +01:00
Unknown
e53c04a07f Repair Limpet 2017-10-03 20:25:01 +01:00
Unknown
e5a8e106c1 Merge branch 'feature/languages' into develop 2017-09-01 20:56:34 +01:00
Unknown
11d1d80a53 Other languages JSON import 2017-09-01 20:55:11 +01:00
Unknown
c8a3d86a45 JSON Import 2017-09-01 20:52:45 +01:00
Unknown
4484ca226a Polish , Spanish, Italian JSON 2017-09-01 19:50:52 +01:00
Unknown
496c9ba35c Full bugsnag 2017-08-30 20:29:46 +01:00
Unknown
dae2fc9192 Bugsnag 2017-08-30 20:29:29 +01:00
Unknown
07b00e6230 Full bugsnag 2017-08-30 20:28:59 +01:00
Unknown
36526f0824 Merge branch 'feature/bugsnag' into develop 2017-08-30 19:47:52 +01:00
Unknown
f4691939ba Bugsnag 2017-08-30 19:46:36 +01:00
Unknown
534f735b63 Translation Advice 2017-08-30 19:33:42 +01:00
Unknown
bc2c8406a2 Russian JSON Generated 2017-08-30 19:30:27 +01:00
Unknown
032c44f39a German JSON Generated 2017-08-30 19:15:17 +01:00
Unknown
2b43c8e91a French JSON Generated 2017-08-30 19:04:57 +01:00
Unknown
e91b3df31a English JSON Generated 2017-08-30 19:04:49 +01:00
Unknown
3e0597023a Merge remote-tracking branch 'origin/master' 2017-08-29 20:06:30 +01:00
Unknown
1dd2edf742 Merge branch 'master' into develop 2017-08-29 20:05:51 +01:00
Unknown
97f3bece33 Merge branch 'release/2.3.7' 2017-08-29 20:05:28 +01:00
Unknown
be02444487 Changelog
Bump Version
2017-08-29 20:04:52 +01:00
Kamen Litchev
fc012fe68a Fix stroke-width handling bug in Safari 11 (#138)
* Fix stroke-width handling bug in Safari 11

* Further fix Safari 11 SVG stroke-width issues
2017-08-29 13:29:47 +01:00
strideynet
7ac16d6d22 Update Tracking 2017-08-22 21:03:23 +01:00
strideynet
4ee92b1f3e Update Tracking 2017-08-22 20:59:39 +01:00
strideynet
d82128690b Outdated URLs in tests don't match actual URLs 2017-08-17 19:47:13 +01:00
strideynet
9e57eb4262 Remove unwanted console.log and spacing fix 2017-08-17 15:00:13 +01:00
strideynet
6e0d45f419 ESLint issue
ESLint is picking a fault with the JSDoc despite it being correct. Will investigate further but for now this resolves the issue.
2017-08-17 14:42:44 +01:00
strideynet
645e86714e attempted fix for jsdoc detection 2017-08-17 14:31:00 +01:00
strideynet
00afd1cd6a Attempt to fix JSDoc 2017-08-17 14:12:25 +01:00
strideynet
77ae126a51 Compliance
As per #132
2017-08-17 14:05:22 +01:00
strideynet
9c82c7caed remove redundant LoC 2017-08-17 14:01:12 +01:00
strideynet
d9a92e7a78 Remove redundant LoC 2017-08-17 13:59:23 +01:00
strideynet
d8a87029a6 Compliance
As per #132
2017-08-17 13:42:12 +01:00
strideynet
f407d0f92a Compliance
Issue #132
2017-08-17 13:41:41 +01:00
strideynet
4ac42e62e6 Bumping NodeJS Version
See commit to travis.yml
2017-08-17 13:26:09 +01:00
strideynet
748c63fa0b Update NodeJS Version
Over the new few days I will test the newer major versions, for now just bumping to the latest V4 LTS
2017-08-17 13:24:21 +01:00
strideynet
78ca756cef Update package.json 2017-08-17 13:13:24 +01:00
strideynet
a8554b51c2 Uglify JS
Required for the build process for D3
2017-08-17 13:11:37 +01:00
sascha-dev
e54e4da289 Updated german translation (#127)
PR #127 merged to EDCD:Develop
2017-08-16 19:29:22 +01:00
Cmdr McDonald
f97cb5f5a7 Merge branch 'release/2.3.6' into develop 2017-06-24 09:34:41 +01:00
Cmdr McDonald
318e8077d0 Merge branch 'release/2.3.6' 2017-06-24 09:34:37 +01:00
Cmdr McDonald
c8355a532d Bump version 2017-06-24 09:33:21 +01:00
Cmdr McDonald
3686ccd4ed Update Russian translation 2017-06-24 09:24:38 +01:00
Cmdr McDonald
f02db0120a Fix for Spanish translation of Chaff Launcher 2017-06-24 09:18:27 +01:00
Cmdr McDonald
d9ad93d3cd Fix typo causing long range blueprint to not modify shot speed in some circumstances 2017-06-06 11:45:59 +01:00
Cmdr McDonald
77018cc1ad Fix issue where torpedo special effects were not showing 2017-05-30 19:44:35 +01:00
Cmdr McDonald
30a8a29ce3 Update miner role with better defaults 2017-05-30 19:44:11 +01:00
Cmdr McDonald
7b1aa646ac Fix 2017-05-17 20:56:13 +01:00
Cmdr McDonald
49e4409862 Merge branch 'release/2.3.5' into develop 2017-05-17 07:49:06 +01:00
Cmdr McDonald
ab87ddc649 Merge branch 'release/2.3.5' 2017-05-17 07:49:00 +01:00
Cmdr McDonald
57784329ac Bump version 2017-05-17 07:31:46 +01:00
Cmdr McDonald
1db613c56d Grey out modules that are powered off to provide a clearer visual indication 2017-05-16 20:31:56 +01:00
Cmdr McDonald
464cd4165f Added translation 2017-05-16 16:39:15 +01:00
Cmdr McDonald
420ebe4cc7 Merge branch 'feature/fixes' into develop 2017-05-11 13:14:27 +01:00
Cmdr McDonald
15ead440d6 Updated German translation; fix for #100 2017-05-11 13:14:19 +01:00
Cmdr McDonald
ef0614a62f Handle display when summary values show thrusters disabled but current mass keeps them enabled 2017-05-11 13:09:18 +01:00
Cmdr McDonald
2fd2af3e31 Ensure that hidden blueprint effects are applied when a blueprint is selected 2017-05-11 12:45:57 +01:00
Cmdr McDonald
f30a904a02 Remove logging 2017-05-03 19:18:50 +01:00
Cmdr McDonald
e00680113f Do not add hidden features 2017-05-03 19:15:07 +01:00
Cmdr McDonald
93e6a29057 Merge branch 'release/2.3.4' into develop 2017-05-03 17:12:16 +01:00
Cmdr McDonald
1fbba6a5f3 Merge branch 'release/2.3.4' 2017-05-03 17:12:13 +01:00
Cmdr McDonald
d1c0b635b3 Package bump 2017-05-03 17:12:07 +01:00
Cmdr McDonald
060cf75ec4 Handle different specials for dumbfire and seeker missiles 2017-05-03 12:37:49 +01:00
Cmdr McDonald
62e3b70581 Merge branch 'feature/edge' into develop 2017-04-28 07:20:23 -04:00
Cmdr McDonald
4accb901af Bump zlib 2017-04-26 07:58:05 -04:00
Cmdr McDonald
82725e1c5a Fixes 2017-04-25 12:10:44 +01:00
Cmdr McDonald
4dc8ab928c Remove findDOMNode as per warning 2017-04-23 18:50:13 +01:00
Cmdr McDonald
7db76ecba0 Lint fixes 2017-04-23 18:36:20 +01:00
Cmdr McDonald
16a5b2a72a Version bumps 2017-04-23 16:26:16 +01:00
Cmdr McDonald
59a5ba0227 Updaes 2017-04-23 16:06:14 +01:00
Cmdr McDonald
c130b52b7c Fix incorrect height of shipyard ship header 2017-04-21 22:39:07 +01:00
Cmdr McDonald
03f32097e9 Merge branch 'feature/ordering' into develop 2017-04-21 21:38:48 +01:00
Cmdr McDonald
8660dcbd2b Tidy up ordering 2017-04-21 21:38:42 +01:00
Cmdr McDonald
95f358f9d8 Upgrade to React 15.5 2017-04-21 16:07:41 +01:00
Cmdr McDonald
8f944bc4c8 Add racer role 2017-04-21 10:43:39 +01:00
Cmdr McDonald
e9cbe0d952 Bump package version 2017-04-20 15:11:59 +01:00
Cmdr McDonald
684ecfcafd Ensure that stock builds are saved with a code; ignore builds without codes in comparisons 2017-04-20 15:10:03 +01:00
Cmdr McDonald
1516bd4fc0 Fix crash when clearing a speical 2017-04-20 13:17:30 +01:00
Cmdr McDonald
4ea0bbfb93 Merge branch 'release/2.3.3' into develop 2017-04-15 20:46:50 +01:00
Cmdr McDonald
9b6df8ea4f Merge branch 'release/2.3.3' 2017-04-15 20:46:45 +01:00
Cmdr McDonald
9c6f5fb44e Bump version 2017-04-15 20:46:40 +01:00
Cmdr McDonald
c0a732a76b Merge branch 'feature/refit' into develop 2017-04-15 18:11:56 +01:00
Cmdr McDonald
9551b8b5de Add shopping list for refit 2017-04-15 18:11:50 +01:00
Cmdr McDonald
22f83b79af Encode blueprint information event when no active modifications 2017-04-15 18:11:09 +01:00
Cmdr McDonald
0a26f76fb4 Merge branch 'release/2.3.2' into develop 2017-04-14 13:09:55 +01:00
Cmdr McDonald
077f432daf Merge branch 'release/2.3.2' 2017-04-14 13:09:52 +01:00
Cmdr McDonald
e846b4508b Bump version 2017-04-14 11:06:09 +01:00
Cmdr McDonald
3be78885b8 Bump 2017-04-14 10:58:15 +01:00
Cmdr McDonald
e29a4b263d Fix scan time/scan rate confusion 2017-04-13 23:19:27 +01:00
Cmdr McDonald
061ab77de1 Merge branch 'release/23' into develop 2017-04-13 22:26:33 +01:00
Cmdr McDonald
c6a8e48fda Merge branch 'release/23' 2017-04-13 22:26:29 +01:00
Cmdr McDonald
f48af272c3 Bump for 2.3 2017-04-09 09:23:31 +01:00
Cmdr McDonald
2b1402d099 Bump for 2.3 2017-04-09 09:23:20 +01:00
Cmdr McDonald
1ad0afa0d7 Merge branch 'feature/fixes' into develop 2017-04-09 09:07:50 +01:00
Cmdr McDonald
93d6c1b871 Update defence tooltips - #93 2017-04-08 00:00:08 +01:00
Cmdr McDonald
243f1123ba Fix typo 2017-04-06 19:54:45 +01:00
Cmdr McDonald
2fa2625a8f Fix display and selection of modules near weight limit in menu 2017-04-04 23:55:08 +01:00
Cmdr McDonald
42c43d3f2d Use single calculation for shield metrics 2017-04-04 08:51:02 +01:00
Cmdr McDonald
1237833c7a Ensure that opponent piips are updated immediately when a new opponent is selecteD 2017-04-03 13:46:14 +01:00
Cmdr McDonald
171a84dd33 Add reload to hardpoint info 2017-04-01 21:20:44 +01:00
Cmdr McDonald
79dac7b38f Merge branch 'feature/fixes' into develop 2017-03-31 16:49:10 +01:00
Cmdr McDonald
0e86ae79c1 Revert 2.3 diminishing returns on boosters 2017-03-31 16:34:50 +01:00
Cmdr McDonald
7d7ea18447 Tidy-ups for modification 2017-03-31 12:28:29 +01:00
Cmdr McDonald
2a6da36aeb Fix names of features in tooltips 2017-03-31 12:04:38 +01:00
Cmdr McDonald
db1a976e20 Restyle modifications menu 2017-03-31 09:21:13 +01:00
Cmdr McDonald
3b058bda7f Consistency 2017-03-31 07:40:12 +01:00
Cmdr McDonald
da274f1b75 Fix issue for additive specials not showing up 2017-03-30 08:17:13 +01:00
Cmdr McDonald
d624663278 Remove unused components 2017-03-28 14:06:25 +01:00
Cmdr McDonald
d8ac185b4d Add optimal multiplier to thrusters 2017-03-27 11:04:17 +01:00
Cmdr McDonald
b66baef998 Add engineer info to blueprint tooltip 2017-03-27 10:17:30 +01:00
Cmdr McDonald
8a115f8323 Add component list to blueprint display tooltip 2017-03-26 20:48:21 +01:00
Cmdr McDonald
442da6f05b Use cargo hatch info on import if available 2017-03-25 18:19:31 +00:00
Cmdr McDonald
f536c037b1 Updates 2017-03-25 16:24:13 +00:00
Cmdr McDonald
33a678a200 Add SDPS totals 2017-03-24 22:30:55 +00:00
Cmdr McDonald
b0dc4d7864 Shirnk large labels on vertical bar chart 2017-03-24 12:04:41 +00:00
Cmdr McDonald
8b0e822ea9 Fixes 2017-03-23 10:14:43 +00:00
Cmdr McDonald
006811f5a9 Merge branch 'feature/battlecentre' into develop 2017-03-23 08:20:14 +00:00
Cmdr McDonald
873935c1d4 Update to documentation 2017-03-23 08:17:07 +00:00
Cmdr McDonald
94d876e934 Per-cell colour coding for blueprint tooltips 2017-03-22 21:38:33 +00:00
Cmdr McDonald
b055963fe0 Fixes 2017-03-22 18:34:57 +00:00
Cmdr McDonald
8ad9472d56 Provide correct blueprints limits for HRPs 2017-03-22 18:24:22 +00:00
Cmdr McDonald
d15e49f315 Ignore rpshot for eps and hps 2017-03-22 17:53:26 +00:00
Cmdr McDonald
c7ea1eb95a Use opponent's pip info 2017-03-22 14:19:21 +00:00
Cmdr McDonald
82ce86a374 Updates 2017-03-22 13:55:45 +00:00
Cmdr McDonald
a1a17bc836 Add blueprint info to modification tooltip 2017-03-22 11:44:08 +00:00
Cmdr McDonald
75a4e54453 Add tooltips for blueprints 2017-03-22 10:51:23 +00:00
Cmdr McDonald
32fb66139a Take base hull reinforcement resistances in to account when calculating modifiers 2017-03-22 09:20:51 +00:00
Cmdr McDonald
3f18987007 Rewrite vertical bar chart to use recharts 2017-03-21 14:46:35 +00:00
Cmdr McDonald
eb042b2778 Tidy-ups for build changes 2017-03-21 12:19:25 +00:00
Cmdr McDonald
85f108556a Fix for ship summary 2017-03-21 00:41:33 +00:00
Cmdr McDonald
73a75c69a3 Make various components stateless 2017-03-20 13:52:24 +00:00
Cmdr McDonald
2f5d123f02 Change chart label colour to black; persist select outfitting subpage 2017-03-20 07:48:44 +00:00
Cmdr McDonald
e278babee8 Use correct values for boost/speed when working out boost factor 2017-03-19 22:34:19 +00:00
Cmdr McDonald
86df6f20f6 Ensure ship picker z index is correct 2017-03-19 20:18:31 +00:00
Cmdr McDonald
117028875f Name change 2017-03-19 18:54:38 +00:00
Cmdr McDonald
cadd699bdf Manual merge of #90 2017-03-19 18:53:22 +00:00
Cmdr McDonald
6afd80002c Tidy-ups 2017-03-19 17:40:41 +00:00
Cmdr McDonald
2736e1df79 More metrics 2017-03-19 16:16:00 +00:00
Cmdr McDonald
0ff95ed1f1 Shrink movement profile 2017-03-19 08:49:38 +00:00
Cmdr McDonald
fcb8980a38 Tidy-ups 2017-03-19 08:41:36 +00:00
Cmdr McDonald
49a076fd9e Updates 2017-03-18 23:46:46 +00:00
Cmdr McDonald
eb83969015 Break out metric calculations 2017-03-18 13:42:32 +00:00
Cmdr McDonald
369d882354 Tidy-ups 2017-03-17 18:10:07 +00:00
Cmdr McDonald
23c4da55de Make ship summary numbers react to pips/fuel/cargo/etc 2017-03-17 14:04:07 +00:00
Cmdr McDonald
7259a666eb Tidy-ups 2017-03-17 12:34:36 +00:00
Cmdr McDonald
91cab5a4f1 Embed battle centre in main pages 2017-03-17 12:07:18 +00:00
Cmdr McDonald
d60a8f2625 Fix shield recovery/recharge calculations 2017-03-16 12:05:15 +00:00
Cmdr McDonald
69489aa267 Update shortcut information 2017-03-16 10:13:36 +00:00
Cmdr McDonald
750d23b10a Provide live sustained DPS for opponent 2017-03-16 07:35:52 +00:00
Cmdr McDonald
cd753af48e Fix issue putting 4A refinery in class 5 slot 2017-03-15 11:45:52 +00:00
Cmdr McDonald
365810a610 Working on shield recovery calculation 2017-03-15 11:40:12 +00:00
Cmdr McDonald
73d609610a Fix axis for effectice shield 2017-03-14 08:29:09 +00:00
Cmdr McDonald
a3c03266bf On-going tidy-ups 2017-03-13 22:59:01 +00:00
Cmdr McDonald
7b3ad555a1 Tidy-ups 2017-03-13 22:13:41 +00:00
Cmdr McDonald
1605e80884 Fix to calculate boost when engcap changes 2017-03-13 19:55:45 +00:00
Cmdr McDonald
0729fc29fa Add vertical bar chart and use it in battle centre 2017-03-13 17:07:39 +00:00
Cmdr McDonald
964cdd2b9a Move boost in to pips component 2017-03-12 19:11:40 +00:00
Cmdr McDonald
49c3e395db Merge branch 'feature/battlecentre' of https://github.com/EDCD/coriolis into feature/battlecentre 2017-03-12 18:37:21 +00:00
Cmdr McDonald
bdd2299335 Ignore swap files 2017-03-12 18:37:07 +00:00
Cmdr McDonald
f782adb21d Remove swap files 2017-03-12 18:37:07 +00:00
Cmdr McDonald
950c0c61f9 Start of shields component 2017-03-12 18:37:07 +00:00
Cmdr McDonald
f6ebaf7445 Set initial chart width 2017-03-12 18:37:07 +00:00
Cmdr McDonald
3b35d5030e Add ship picker 2017-03-12 18:37:07 +00:00
Cmdr McDonald
067b69f449 Link in components 2017-03-12 18:37:07 +00:00
Cmdr McDonald
6f67267fec Building components for battle centre 2017-03-12 18:37:07 +00:00
Cmdr McDonald
ec0cd37896 Merge branch 'feature/23' into develop 2017-03-12 18:36:45 +00:00
Cmdr McDonald
84719c997f Ignore swap files 2017-03-12 17:00:33 +00:00
Cmdr McDonald
2731ec3b90 Remove swap files 2017-03-12 17:00:19 +00:00
Cmdr McDonald
340121c6bd Start of shields component 2017-03-12 17:00:04 +00:00
Cmdr McDonald
ec4e70326a Set initial chart width 2017-03-11 22:22:12 +00:00
Cmdr McDonald
3a271e4b7b Add ship picker 2017-03-11 17:57:03 +00:00
Cmdr McDonald
02db800c7b Link in components 2017-03-11 13:28:24 +00:00
Cmdr McDonald
3a55c8cc0a Building components for battle centre 2017-03-10 17:17:37 +00:00
Cmdr McDonald
e00b07c2f6 Reset old modification values when a new roll is applied 2017-03-08 07:23:07 +00:00
Cmdr McDonald
fbfd0deb6c Show integrity 2017-03-06 09:19:05 +00:00
Cmdr McDonald
dc11cc182f Fixes 2017-03-04 15:42:24 +00:00
Cmdr McDonald
597344353a Update test with new hardness 2017-03-03 22:40:50 +00:00
Cmdr McDonald
9aef1ff8a6 Revert fixed header due to issues with ios 2017-03-03 22:03:47 +00:00
Cmdr McDonald
70f218f833 Tidy-ups for diffDetails 2017-03-03 21:57:00 +00:00
Cmdr McDonald
5937843be8 Fix PD/boost calculation 2017-03-02 14:37:03 +00:00
Cmdr McDonald
d4b384eded Updates for 2.3 2017-03-01 22:10:47 +00:00
Cmdr McDonald
2b5a42d4e2 Merge branch 'feature/x' into develop 2017-02-27 09:26:22 +00:00
Cmdr McDonald
afd8ad7678 Lint 2017-02-27 09:26:17 +00:00
Cmdr McDonald
cdc5c29458 Alter way that X axis for profile charts is calculated 2017-02-27 09:24:32 +00:00
Cmdr McDonald
5a351d4c0d Merge branch 'release/2.2.19' into develop 2017-02-26 21:26:00 +00:00
Cmdr McDonald
fac279d9dd Merge branch 'release/2.2.19' 2017-02-26 21:25:57 +00:00
Cmdr McDonald
f4e5254832 Relocate slider values 2017-02-26 21:22:53 +00:00
Cmdr McDonald
2fb3ee8cd8 Bump version 2017-02-26 21:14:12 +00:00
Cmdr McDonald
cc2f3fd1fe Merge branch 'feature/wp2' into develop 2017-02-26 21:06:17 +00:00
Cmdr McDonald
87146b2cf3 Add engine and FSD profiles 2017-02-26 21:06:07 +00:00
Cmdr McDonald
bec3ae3f89 Tidy ups 2017-02-26 09:14:10 +00:00
Cmdr McDonald
af2e0cbed3 Fixes 2017-02-25 22:18:09 +00:00
Cmdr McDonald
4bf30c0cd5 Updates 2017-02-25 14:45:47 +00:00
Cmdr McDonald
a9fdf73d86 Update to webpack 2 2017-02-25 13:15:24 +00:00
Cmdr McDonald
fe691d12c7 Remove shot speed modification 2017-02-23 18:55:44 +00:00
Cmdr McDonald
a5df542aa2 Power management default sort order is power usage in descending order 2017-02-22 10:44:56 +00:00
Cmdr McDonald
069959dabb Merge branch 'release/2.2.18' into develop 2017-02-22 08:29:47 +00:00
Cmdr McDonald
a0e8f19683 Merge branch 'release/2.2.18' 2017-02-22 08:29:43 +00:00
Cmdr McDonald
342ca7af05 Tidy-ups for release 2017-02-22 08:28:55 +00:00
Cmdr McDonald
d00c0c3904 Merge branch 'feature/ddgraph' into develop 2017-02-22 08:01:09 +00:00
Cmdr McDonald
8e0f1ca977 Tweaks 2017-02-22 07:58:51 +00:00
Cmdr McDonald
33360fd6cf Doc updates 2017-02-22 00:19:41 +00:00
Cmdr McDonald
78ad34b082 Update docs 2017-02-22 00:16:33 +00:00
Cmdr McDonald
ba98fe49a9 Reorder panels 2017-02-22 00:14:21 +00:00
Cmdr McDonald
11f5c04efa Updates for production version of d3 2017-02-21 23:54:27 +00:00
Cmdr McDonald
ee9f65052a Tidy-ups 2017-02-21 23:21:33 +00:00
Cmdr McDonald
a25dde8d2d Tweaks 2017-02-21 21:50:28 +00:00
Cmdr McDonald
8d813688f7 Merge branch 'feature/ddgraph' of https://github.com/EDCD/coriolis into feature/ddgraph 2017-02-21 21:36:41 +00:00
Cmdr McDonald
99fc55ba6c Fix up formats for d3 v4 2017-02-21 21:36:24 +00:00
Cmdr McDonald
402a4c1939 Move to newer version of d3 2017-02-21 21:36:24 +00:00
Cmdr McDonald
d1e16470b8 Fixes 2017-02-21 21:36:24 +00:00
Cmdr McDonald
76027b8537 Playing with damage dealt graph 2017-02-21 21:36:24 +00:00
Cmdr McDonald
fd5ff3b6a8 Fix up formats for d3 v4 2017-02-21 21:35:54 +00:00
Cmdr McDonald
d2d8f084d2 Move to newer version of d3 2017-02-21 19:22:14 +00:00
Cmdr McDonald
ea3d57399c Tidy up available modules layout 2017-02-21 16:27:09 +00:00
Cmdr McDonald
77d3053ff8 Handle 'higher better' modifications when both min and max are negative - fixes #80 2017-02-20 14:05:19 +00:00
Cmdr McDonald
aea3e43e1c Fixes and tidy-ups 2017-02-19 20:49:31 +00:00
Cmdr McDonald
a949bd6738 Merge branch 'feature/explorer' into develop 2017-02-19 19:26:32 +00:00
Cmdr McDonald
4981ffb908 Make integrity for module reinforcement packages visible 2017-02-19 19:26:16 +00:00
Cmdr McDonald
4859138053 Tidy up layout for module selection 2017-02-19 19:25:50 +00:00
Cmdr McDonald
1f3c66d9ba Tidy up explorer role 2017-02-19 12:50:21 +00:00
Cmdr McDonald
f2af463d00 Remove cruft 2017-02-19 12:50:09 +00:00
Cmdr McDonald
3858712613 Hardcode shield translation for internal slot optmul 2017-02-18 21:29:21 +00:00
Cmdr McDonald
d7941e0a8a Merge branch 'release/2.2.17' into develop 2017-02-18 10:00:14 +00:00
Cmdr McDonald
178d38f28d Merge branch 'release/2.2.17' 2017-02-18 10:00:10 +00:00
Cmdr McDonald
e42a0d1210 Bump 2017-02-18 10:00:07 +00:00
Cmdr McDonald
ddb89c47e7 Merge branch 'feature/multicrew' into develop 2017-02-18 09:51:24 +00:00
Cmdr McDonald
8b9aae342b Fix incorrect terminology for shield metrics. Fixes #5 2017-02-18 09:07:18 +00:00
Cmdr McDonald
ab1d73a6ea Add crew 2017-02-16 22:43:06 +00:00
Cmdr McDonald
d73a3cc2b4 Merge branch 'release/2.2.16' into develop 2017-02-15 20:44:21 +00:00
Cmdr McDonald
230351b959 Merge branch 'release/2.2.16' 2017-02-15 20:44:17 +00:00
Cmdr McDonald
4ee5c03cd1 Bump version 2017-02-15 20:44:09 +00:00
Cmdr McDonald
d8e9733170 Fix issue where extreme values didn't know if a given value was higher == better or higher == worse 2017-02-15 19:10:58 +00:00
Cmdr McDonald
aba2abe507 Merge branch 'release/2.2.15' into develop 2017-02-13 19:50:55 +00:00
Cmdr McDonald
45e6b71ec9 Merge branch 'release/2.2.15' 2017-02-13 19:50:51 +00:00
Cmdr McDonald
15a14dc280 Package version 2017-02-13 19:50:47 +00:00
Cmdr McDonald
cbac650b9e Added miner and shielded miner roles 2017-02-13 08:07:35 +00:00
Cmdr McDonald
f011f1f4d5 Add ability to export module list to EDDB to find where they can be purchased 2017-02-13 07:59:29 +00:00
Cmdr McDonald
d467ad5f7c Merge branch 'feature/fixes' into develop 2017-02-12 11:11:10 +00:00
Cmdr McDonald
b56ac177d9 Merge branch 'willyb321-master' into feature/fixes 2017-02-12 11:08:05 +00:00
Cmdr McDonald
5b13d64a1d Merge branch 'master' of https://github.com/willyb321/coriolis into willyb321-master 2017-02-12 11:07:46 +00:00
Cmdr McDonald
2620935745 Add additional explanation for import failures 2017-02-12 08:31:09 +00:00
Cmdr McDonald
45852db507 Add 'Extreme' roll 2017-02-11 19:57:46 +00:00
Cmdr McDonald
7fbcbb75ed Do not rely on slot sizes being correct 2017-02-11 19:45:28 +00:00
Cmdr McDonald
abf65ee436 Reload page if Safari throws a security error 2017-02-10 18:22:39 +00:00
Cmdr McDonald
24849cee08 Ensure that standard slots are repainted when any component changes 2017-02-08 10:09:30 +00:00
Cmdr McDonald
9e5efe50dc Merge branch 'release/2.2.14' into develop 2017-02-08 09:29:08 +00:00
Cmdr McDonald
39e9a38068 Merge branch 'release/2.2.14' 2017-02-08 09:29:04 +00:00
Cmdr McDonald
73d5915b3a Package bump 2017-02-08 09:27:27 +00:00
Cmdr McDonald
b06d2d1f72 Merge branch 'feature/mods' into develop 2017-02-08 09:26:09 +00:00
Cmdr McDonald
6e71f5e6db Use ship name rather than model if possible 2017-02-08 09:24:13 +00:00
William
588cfc3990 fix wiki link 2017-02-08 16:14:34 +11:00
Cmdr McDonald
191e31ff18 Use new-style blueprint data; fix numeric special effect calculations 2017-02-05 15:38:04 +00:00
Cmdr McDonald
c655b65779 Ensure TTD is recalculated when EPS is recalculated 2017-02-02 23:40:24 +00:00
Cmdr McDonald
3e168a3e5f Merge branch 'release/2.2.13' into develop 2017-02-02 23:05:50 +00:00
Cmdr McDonald
ee52a4b716 Merge branch 'release/2.2.13' 2017-02-02 23:05:46 +00:00
Cmdr McDonald
a14974fef4 Bump package 2017-02-02 23:03:35 +00:00
Cmdr McDonald
2c0959d68b Merge branch 'feature/fixed' into develop 2017-02-02 23:01:08 +00:00
Cmdr McDonald
0221d05b04 Tweak recalculation of mass 2017-02-01 08:57:56 +00:00
Cmdr McDonald
975432b45a Lint 2017-02-01 08:33:16 +00:00
Cmdr McDonald
e78551f5d9 Ensure that ship mass is recalculated when appropriate - #67 2017-02-01 08:24:50 +00:00
Cmdr McDonald
b1daf6f799 Fix issue with auto loader not showing - #66 2017-02-01 07:45:44 +00:00
Cmdr McDonald
bc31be5884 Fixes 2017-02-01 07:32:33 +00:00
Cmdr McDonald
df03102c35 Added time to drain calculation and display 2017-01-30 21:31:55 +00:00
Cmdr McDonald
55e4c51d77 Playing with damage dealt graph 2017-01-30 13:34:41 +00:00
Cmdr McDonald
e7d1a0df63 Merge branch 'release/2.2.12' into develop 2017-01-29 08:27:28 +00:00
Cmdr McDonald
ca0c9675b3 Merge branch 'release/2.2.12' 2017-01-29 08:27:24 +00:00
Cmdr McDonald
491899fe49 Package bump 2017-01-29 08:27:17 +00:00
Cmdr McDonald
cc22a89ad6 Merge branch 'feature/specials' into develop 2017-01-29 08:26:34 +00:00
Cmdr McDonald
d389114189 Remove extraneous functions 2017-01-28 20:40:58 +00:00
Cmdr McDonald
9d0f7a166b Don't show both power generation and power draw in diffDetails 2017-01-27 22:23:36 +00:00
Cmdr McDonald
c344d56d48 Merge branch 'feature/specials' of https://github.com/EDCD/coriolis into feature/specials 2017-01-27 21:34:33 +00:00
Cmdr McDonald
eb5d0e868c Remove logging 2017-01-27 21:34:27 +00:00
Cmdr McDonald
c28f22a78b Add units for time 2017-01-27 21:34:27 +00:00
Cmdr McDonald
e2b22dd4ca Alter shortlink hotkey - for #58 2017-01-27 21:32:07 +00:00
Cmdr McDonald
f5ecccec48 Add weapon mods to damage dealt 2017-01-27 21:32:07 +00:00
Cmdr McDonald
7c8eee4707 Add package info 2017-01-27 21:32:07 +00:00
Cmdr McDonald
99e251fd4b Fix missing break in case 2017-01-27 21:32:07 +00:00
Cmdr McDonald
c1ddecfd3e Add specials 2017-01-27 21:32:07 +00:00
Cmdr McDonald
0ae59c5e48 Remove old reference to coriolis.io 2017-01-27 21:32:07 +00:00
Cmdr McDonald
eb76040b08 Remove logging 2017-01-27 21:31:05 +00:00
Cmdr McDonald
7698cb75e9 Add units for time 2017-01-27 21:30:15 +00:00
Cmdr McDonald
bd7139c703 Merge branch 'feature/specials' of https://github.com/EDCD/coriolis into feature/specials 2017-01-27 09:54:26 +00:00
Cmdr McDonald
c9b9404ba1 Alter shortlink hotkey - for #58 2017-01-27 09:54:17 +00:00
Cmdr McDonald
84ff013a9e Add weapon mods to damage dealt 2017-01-27 09:54:17 +00:00
Cmdr McDonald
d2bcf6cd71 Add package info 2017-01-27 09:54:17 +00:00
Cmdr McDonald
e52c6730f4 Fix missing break in case 2017-01-27 09:54:17 +00:00
Cmdr McDonald
e448b3cb0a Add specials 2017-01-27 09:54:17 +00:00
Cmdr McDonald
705f3158aa Remove old reference to coriolis.io 2017-01-27 09:54:17 +00:00
Cmdr McDonald
f4789717fe Alter shortlink hotkey - for #58 2017-01-27 09:54:08 +00:00
Cmdr McDonald
93e69f215b Add weapon mods to damage dealt 2017-01-27 09:46:32 +00:00
Cmdr McDonald
d04e662de4 Merge branch 'hotfix/2.2.11b' into develop 2017-01-27 08:14:37 +00:00
Cmdr McDonald
a9f9285278 Merge branch 'hotfix/2.2.11b' 2017-01-27 08:14:26 +00:00
Cmdr McDonald
c5e27d43aa Do not apply dropoff twice when calculating weapon effectiveness 2017-01-27 08:14:22 +00:00
Cmdr McDonald
db376829f5 Add package info 2017-01-27 07:56:50 +00:00
Cmdr McDonald
79a31fd992 Fix missing break in case 2017-01-26 18:01:22 +00:00
Cmdr McDonald
094f3aa3e2 Add specials 2017-01-26 16:06:29 +00:00
Cmdr McDonald
41d51743d3 Remove old reference to coriolis.io 2017-01-26 14:38:23 +00:00
Cmdr McDonald
cf741d18ed Merge branch 'release/2.2.11' into develop 2017-01-25 19:46:52 +00:00
Cmdr McDonald
c02f674530 Merge branch 'release/2.2.11' 2017-01-25 19:46:47 +00:00
Cmdr McDonald
89f2273b5d Tidy-ups 2017-01-25 19:44:36 +00:00
Cmdr McDonald
351b084ce9 Merge branch 'feature/help' into develop 2017-01-25 19:34:06 +00:00
Cmdr McDonald
dd48d2d007 Increase falloff with range for focused mods 2017-01-25 19:08:55 +00:00
Cmdr McDonald
63e850b5aa Fixes for fragment cannons 2017-01-25 14:23:57 +00:00
Cmdr McDonald
46775879f7 Add EDMC import help 2017-01-25 11:07:17 +00:00
Cmdr McDonald
c9ba4e3fcb Remove editor file 2017-01-25 11:00:10 +00:00
Cmdr McDonald
8b855b62a1 Fix tests with new values 2017-01-25 10:59:28 +00:00
Cmdr McDonald
d6a687300c Add engagement range to damage received panel 2017-01-25 10:52:09 +00:00
Cmdr McDonald
ddc968129d Damage dealt only shows enabled weapons 2017-01-25 10:20:28 +00:00
Cmdr McDonald
e364560ca7 Provide damage dealt statistics for both shields and hull 2017-01-25 10:11:24 +00:00
Cmdr McDonald
baf6f8130a Tidy up spacing for movement summary 2017-01-25 09:25:01 +00:00
Cmdr McDonald
f8f5cd2581 Add 'average' roll for blueprints 2017-01-25 09:20:01 +00:00
Cmdr McDonald
8019453d0d Make absolute damage visible 2017-01-25 08:01:20 +00:00
Cmdr McDonald
6ad17595dc Merge branch 'hotfix/2.2.10c' into develop 2017-01-24 11:49:24 +00:00
Cmdr McDonald
50e1f7d4df Lint fixes 2017-01-24 11:41:50 +00:00
Cmdr McDonald
fdb931fe00 Merge branch 'feature/help' of https://github.com/EDCD/coriolis into feature/help 2017-01-23 14:21:17 +00:00
Cmdr McDonald
86efb59237 Add beta flag 2017-01-23 14:21:05 +00:00
Cmdr McDonald
f05aa66e53 Add help system and initial help file 2017-01-23 14:21:05 +00:00
Cmdr McDonald
f4e95cd150 Add framework for help 2017-01-23 14:21:05 +00:00
Cmdr McDonald
5798c0e8a6 Add help system and initial help file 2017-01-23 14:20:06 +00:00
Cmdr McDonald
22f98fa8a7 Add framework for help 2017-01-23 14:19:43 +00:00
Cmdr McDonald
897668c0d4 Merge branch 'hotfix/2.2.10b' into develop 2017-01-23 14:18:50 +00:00
Cmdr McDonald
99c0bfcee1 Merge branch 'hotfix/2.2.10b' 2017-01-23 14:17:50 +00:00
Cmdr McDonald
37bfc700e9 Fix for direct entering of modifications 2017-01-23 14:17:42 +00:00
Cmdr McDonald
8fcebf59f6 Add beta flag 2017-01-23 13:17:53 +00:00
Cmdr McDonald
486c055631 Merge branch 'feature/help' of https://github.com/EDCD/coriolis into feature/help 2017-01-23 13:16:22 +00:00
Cmdr McDonald
92246302b9 Add help system and initial help file 2017-01-23 13:15:45 +00:00
Cmdr McDonald
0d646c6193 Add framework for help 2017-01-23 13:14:59 +00:00
Cmdr McDonald
217ea3c5ac Merge branch 'release/2.10' into develop 2017-01-23 13:05:32 +00:00
Cmdr McDonald
fcf0494df6 Merge branch 'release/2.10' 2017-01-23 13:05:21 +00:00
Cmdr McDonald
20ba6eb822 Tidy-ups prior to release 2017-01-23 13:05:14 +00:00
Cmdr McDonald
0e2c0349e0 Merge branch 'feature/blueprints' into develop 2017-01-22 17:16:40 +00:00
Cmdr McDonald
c49e2cff03 Lint and test fixes 2017-01-22 17:12:48 +00:00
Cmdr McDonald
d79313bfbe Fix shield boost values; add tooltips 2017-01-22 14:15:33 +00:00
Cmdr McDonald
fd404b5155 Handle heavy duty shield booster special modification value 2017-01-22 10:59:36 +00:00
Cmdr McDonald
49e72146b4 Only update relevant modification parameters 2017-01-22 09:02:32 +00:00
Cmdr McDonald
9b534b62c8 Add modification functionality 2017-01-21 10:34:08 +00:00
Cmdr McDonald
0be59af9b0 Allow selection of blueprints type and grade 2017-01-20 17:53:49 +00:00
Cmdr McDonald
5d87a6cd56 Add help system and initial help file 2017-01-20 11:56:32 +00:00
Cmdr McDonald
c61c17d465 Add framework for help 2017-01-19 22:25:51 +00:00
Cmdr McDonald
029ba63aa5 Use new-style modification information 2017-01-19 22:25:13 +00:00
Cmdr McDonald
99e9e0c76f Merge branch 'feature/fixes' into develop 2017-01-15 23:06:13 +00:00
Cmdr McDonald
5bbc6e1cbe Use damage distribution numbers 2017-01-15 17:10:46 +00:00
Cmdr McDonald
1e5f66e528 Fix detailed export of module reinforcement packages 2017-01-15 17:07:12 +00:00
Cmdr McDonald
cdb837a25a Merge branch 'release/2.2.9' into develop 2017-01-14 16:24:18 +00:00
Cmdr McDonald
dd1175abf4 Merge branch 'release/2.2.9' 2017-01-14 16:24:09 +00:00
Cmdr McDonald
619976230d Bump version 2017-01-14 16:24:04 +00:00
Cmdr McDonald
2cb0d5209b Merge branch 'feature/falloff' into develop 2017-01-14 16:23:12 +00:00
Cmdr McDonald
ad06e23afa Add total DPS and effectiveness information to 'Damage Dealt' section 2017-01-14 16:20:48 +00:00
Cmdr McDonald
3def84e435 Use better DPE calculation methodology 2017-01-14 13:15:15 +00:00
Cmdr McDonald
7f377d6345 Add and use range when calculating weapon effectiveness for damage dealt 2017-01-14 13:10:09 +00:00
Cmdr McDonald
53137e0ae1 Add falloff for weapons 2017-01-14 09:52:31 +00:00
Cmdr McDonald
792eda2572 Use SSL-enabled server for shortlinks 2017-01-13 20:05:45 +00:00
Cmdr McDonald
550c94fa94 Merge branch 'release/2.2.8' into develop 2017-01-13 11:42:36 +00:00
Cmdr McDonald
2a841281d4 Merge branch 'release/2.2.8' 2017-01-13 11:42:31 +00:00
Cmdr McDonald
260f29834a Update release notes 2017-01-13 11:35:01 +00:00
Cmdr McDonald
ddb35d321c Fix issue where filling all internals with cargo racks would include restricted slots 2017-01-13 11:34:18 +00:00
Cmdr McDonald
6017c1ecff Merge branch 'release/2.2.7' into develop 2017-01-11 21:59:09 +00:00
Cmdr McDonald
05e06f30f5 Merge branch 'release/2.2.7' 2017-01-11 21:59:01 +00:00
Cmdr McDonald
fb5ba6a0b2 Update damage dealt to use actual resistances 2017-01-11 21:57:49 +00:00
Cmdr McDonald
80656a7a78 Fix resistance diminishing return calculations 2017-01-11 21:33:31 +00:00
Cmdr McDonald
ce980cf091 Merge branch 'release/2.2.6' into develop 2017-01-10 19:34:18 +00:00
Cmdr McDonald
a4656e223a Merge branch 'release/2.2.6' 2017-01-10 19:34:10 +00:00
Cmdr McDonald
66d4b5ac4c Fix tests due to new module values 2017-01-10 19:30:43 +00:00
Cmdr McDonald
6961469ae5 Merge branch 'feature/changes' into develop 2017-01-10 19:15:56 +00:00
Cmdr McDonald
06f4abdf8b Import builds with military slots 2017-01-10 19:12:19 +00:00
Cmdr McDonald
7855d0e171 Reinstate jump range graph 2017-01-02 12:06:44 +00:00
Cmdr McDonald
40f213c883 Do not lose ship selector selection on narrow screens 2017-01-02 12:05:55 +00:00
Cmdr McDonald
be1bfeb6f3 Ensure that information is not lost on narrow screens. Fix for #48 2017-01-02 12:05:22 +00:00
Cmdr McDonald
b40a2e96e0 Fix for change to military slots 2016-12-24 23:49:33 +00:00
Cmdr McDonald
5f036c586c Use own URL shortener 2016-12-24 21:57:11 +00:00
Cmdr McDonald
091789c819 Revert overloading of 'reload' translation key 2016-12-24 20:57:51 +00:00
Cmdr McDonald
0ce8bfac79 Fixes for #46 2016-12-24 20:56:37 +00:00
Cmdr McDonald
e53ffd0273 Update shield recovery/regeneration calculations 2016-12-20 13:58:00 +00:00
Cmdr McDonald
bb7db144d6 Allow collapse/expand of damage sections 2016-12-19 21:50:53 +00:00
Cmdr McDonald
2e42a328e0 Add base resistances to defence summary tooltip 2016-12-19 17:56:54 +00:00
Cmdr McDonald
f82122f29f Add module copy functionality - drag module whilst holding 'alt' to copy 2016-12-18 21:37:21 +00:00
Cmdr McDonald
5bf907809d Make weapons real modules to benefits from standard DPS etc. calculations 2016-12-18 21:26:30 +00:00
Cmdr McDonald
51d7b6c9aa Add 'Piercing' information to hardpoints; add 'Hardness' information to ship summary 2016-12-18 09:36:33 +00:00
Cmdr McDonald
b8cff0c2fc Add 'Damage received' section 2016-12-17 10:46:52 +00:00
Cmdr McDonald
6ac69a6388 Add translation 2016-12-16 21:16:17 +00:00
Cmdr McDonald
32282141cf Use ship rather than ship ID 2016-12-16 21:09:11 +00:00
Cmdr McDonald
059c2badf4 Lint 2016-12-16 20:38:58 +00:00
Cmdr McDonald
fb090618da Add 'Damage dealt' section 2016-12-16 20:37:06 +00:00
Cmdr McDonald
9ed0e30538 Add hardness to shipyard 2016-12-16 16:25:57 +00:00
Cmdr McDonald
af82b8ca1e Fix up tests 2016-12-15 16:46:38 +00:00
Cmdr McDonald
6e18793d82 Lint fix 2016-12-14 22:41:10 +00:00
Cmdr McDonald
22e74164c5 Alternate (embedded) code versioning scheme; start to fix up tests 2016-12-14 22:38:21 +00:00
Cmdr McDonald
93ba1bf67a Do not include disabled shield boosters in calculations 2016-12-14 17:39:01 +00:00
Cmdr McDonald
46ed9003dd Tidy-ups 2016-12-14 13:31:20 +00:00
Cmdr McDonald
5603315bf0 Version URLs to handle changes to ship specifications over time 2016-12-14 13:23:54 +00:00
Cmdr McDonald
5bbc6be3d8 Obey restricted slot rules when adding all for internal slots 2016-12-14 13:23:19 +00:00
Cmdr McDonald
203e9c7b46 Fix for importing definitions with missing slots 2016-12-14 08:49:23 +00:00
Cmdr McDonald
2a6850ded0 Set initial shield boost correctly 2016-12-13 22:08:49 +00:00
Cmdr McDonald
041f873f97 Updates 2016-12-13 18:56:59 +00:00
Cmdr McDonald
b944035541 Use separate speed/rotation/acceleration multipliers for thrusters if available 2016-12-12 13:37:36 +00:00
Cmdr McDonald
7c6a4fc5f8 Do not rely on coriolis-data' internal ordering of modules for display purposes 2016-12-12 10:38:50 +00:00
Cmdr McDonald
5426b55637 Ensure module ordering is consistent 2016-12-12 10:28:54 +00:00
Cmdr McDonald
a6a10df39c Add movement summary; add standard internal class sizes to shipyard page; fix issue when importing Viper Mk IV 2016-12-11 21:52:49 +00:00
Cmdr McDonald
0dc58bad7e Beta version 2016-12-03 15:52:17 +00:00
Cmdr McDonald
794faacbd4 Merge branch 'release/2.2.5' into develop 2016-12-03 15:48:50 +00:00
Cmdr McDonald
0a37b36ec2 Merge branch 'release/2.2.5' 2016-12-03 15:48:45 +00:00
Cmdr McDonald
85e6796e88 Bump version 2016-12-03 15:48:39 +00:00
Cmdr McDonald
fa1ef47b71 Add note to disable ghostery in error situations 2016-12-01 13:26:33 +00:00
Cmdr McDonald
f31e3c09f4 Merge branch 'feature/rebalance' into develop 2016-12-01 10:28:21 +00:00
Cmdr McDonald
e6ab536601 Merge branch 'feature/rebalance' of https://github.com/EDCD/coriolis into feature/rebalance 2016-12-01 10:28:08 +00:00
Cmdr McDonald
5bced9fe56 Bump version number 2016-11-30 15:09:25 +00:00
Cmdr McDonald
67742060d3 Merge branch 'feature/burst' into develop 2016-11-30 15:09:04 +00:00
Cmdr McDonald
ca2136544c Handle unmodifiable values 2016-11-30 15:08:32 +00:00
Cmdr McDonald
ee19e9af50 Lint 2016-11-30 14:56:33 +00:00
Cmdr McDonald
f457fd0bff Fixes for burst calculations 2016-11-30 14:51:19 +00:00
Cmdr McDonald
c1ce07e039 Calculate rate of fire for multi-burst weapons 2016-11-30 12:19:40 +00:00
Cmdr McDonald
c8d1536f77 Bump version number 2016-11-29 13:53:23 +00:00
Cmdr McDonald
231ad4af59 Merge branch 'release/2.2.4' into develop 2016-11-28 16:54:35 +00:00
Cmdr McDonald
d5f61d7ae8 Merge branch 'release/2.2.4' 2016-11-28 16:54:32 +00:00
Cmdr McDonald
8a5d4a36bf Bumped revision 2016-11-28 16:51:24 +00:00
Cmdr McDonald
2c9237626d Show specials; handle import of specials 2016-11-27 19:43:43 +00:00
Cmdr McDonald
37f889e317 Merge branch 'feature/incendiary' into develop 2016-11-26 23:24:37 +00:00
Cmdr McDonald
f86ba48295 Fix tests 2016-11-26 18:58:29 +00:00
Cmdr McDonald
aac35633a3 Merge branch 'feature/incendiary' of https://github.com/EDCD/coriolis into feature/incendiary 2016-11-26 18:56:57 +00:00
Cmdr McDonald
e73e0a305d Use new-style modification data 2016-11-26 18:54:22 +00:00
Cmdr McDonald
4b2b0efe37 Allow non-numeric modifiers 2016-11-26 18:54:22 +00:00
Cmdr McDonald
8fe20f6f65 Checkpoint - handle non-numeric modifiers 2016-11-26 18:54:22 +00:00
Cmdr McDonald
11af7f567a Move to method for damage type to allow for modifications 2016-11-26 18:54:22 +00:00
Cmdr McDonald
3b8444482f Add URL shortlink for outfitting page 2016-11-26 18:53:47 +00:00
Cmdr McDonald
c09e1b1b3e Merge branch 'feature/incendiary' of https://github.com/EDCD/coriolis into feature/incendiary 2016-11-26 13:16:38 +00:00
Cmdr McDonald
5770cf8d39 Use new-style modification data 2016-11-26 13:15:43 +00:00
Cmdr McDonald
6da09f2e5d Allow non-numeric modifiers 2016-11-26 13:15:43 +00:00
Cmdr McDonald
294fadf7cd Checkpoint - handle non-numeric modifiers 2016-11-26 13:15:43 +00:00
Cmdr McDonald
2a97678574 Move to method for damage type to allow for modifications 2016-11-26 13:15:43 +00:00
Cmdr McDonald
76b3bd34f5 Use new-style modification data 2016-11-26 12:54:22 +00:00
Cmdr McDonald
02bfecb92d Allow non-numeric modifiers 2016-11-24 22:23:14 +00:00
Cmdr McDonald
719759ad56 Merge branch 'release/2.2.3' into develop 2016-11-24 14:37:43 +00:00
Cmdr McDonald
fd446b29ba Merge branch 'release/2.2.3' 2016-11-24 14:37:39 +00:00
Cmdr McDonald
e5552d3e10 Updates ready for release 2016-11-24 13:49:37 +00:00
Cmdr McDonald
50946eeeb8 Fix misnamed diamondbacks - issue #36 2016-11-24 13:19:14 +00:00
Cmdr McDonald
faab41117c Checkpoint - handle non-numeric modifiers 2016-11-24 12:50:33 +00:00
Cmdr McDonald
0ab59c1f9a Handle import of restricted slots - fix for #35 2016-11-24 11:10:41 +00:00
Cmdr McDonald
1067dceaa3 Move to method for damage type to allow for modifications 2016-11-23 13:02:23 +00:00
Cmdr McDonald
9042de422a patch 2016-11-23 01:05:13 +00:00
Cmdr McDonald
f0547feb93 Merge branch 'feature/blueprints' into develop 2016-11-23 00:56:19 +00:00
Cmdr McDonald
f863daa347 Fix hull boost calculation. Partial fix for #29 2016-11-23 00:54:42 +00:00
Cmdr McDonald
fdb202e7d6 Add blueprints 2016-11-22 15:52:31 +00:00
Cmdr McDonald
c6bde19052 Merge branch 'release/2.2.2' into develop 2016-11-21 16:44:19 +00:00
Cmdr McDonald
f6aff3d3bb Merge branch 'release/2.2.2' 2016-11-21 16:44:15 +00:00
Cmdr McDonald
2f4a2ebe03 Bumped release 2016-11-21 16:42:17 +00:00
Cmdr McDonald
ca20e94b93 Merge branch 'feature/res' into develop 2016-11-21 16:39:25 +00:00
Cmdr McDonald
40a87dceeb Update tooltip to match reality 2016-11-21 11:34:45 +00:00
Cmdr McDonald
95b7d60be4 Fix URL strings for query parameter method 2016-11-21 11:33:34 +00:00
Cmdr McDonald
24abd6583f Remove requirement for double encoding 2016-11-21 10:14:16 +00:00
Cmdr McDonald
8857aba53f Use query parameters rather than long path 2016-11-21 10:06:14 +00:00
Cmdr McDonald
e4830811b0 Fix up jitter 2016-11-17 14:23:40 +00:00
Cmdr McDonald
143380ac58 Tidy-ups 2016-11-16 20:58:35 +00:00
Cmdr McDonald
a2f6fb6ac0 Added help tooltip for modifications 2016-11-16 20:49:22 +00:00
Cmdr McDonald
0d3c128059 Lints and tests 2016-11-15 13:38:02 +00:00
Cmdr McDonald
930a555425 Update costs for reload 2016-11-15 13:33:59 +00:00
Cmdr McDonald
d6f213fbe7 Remove logging 2016-11-14 23:02:56 +00:00
Cmdr McDonald
0571e8e099 Added jitter for hardpoints 2016-11-14 22:58:09 +00:00
Cmdr McDonald
54c61ecb7d Fix base armour 2016-11-14 22:18:16 +00:00
Cmdr McDonald
030867c4f8 Update test results 2016-11-14 16:47:28 +00:00
Cmdr McDonald
33a7c71fec Tweaks for restoring data from previous builds 2016-11-14 16:39:58 +00:00
Cmdr McDonald
4e0f682ad6 Use forrked browserify-zlib as it has bug fixes 2016-11-14 12:17:30 +00:00
Cmdr McDonald
4486aa2e2b Handle saved builds and old URLs 2016-11-14 11:57:39 +00:00
Cmdr McDonald
0c94c81746 Remove explicit bulkheads name from slot 2016-11-13 21:02:39 +00:00
Cmdr McDonald
5b037e3a00 Additional info if import fails 2016-11-13 17:04:57 +00:00
Cmdr McDonald
42a2b907ce Linting 2016-11-13 16:51:07 +00:00
Cmdr McDonald
a65dae1631 Various fixes; allow direct import from URL 2016-11-13 16:42:59 +00:00
Cmdr McDonald
7d4c534956 Re-enable shields for comparison 2016-11-13 13:44:00 +00:00
Cmdr McDonald
8397d3505b Rework per-module resistance calculations 2016-11-13 13:13:57 +00:00
Cmdr McDonald
9556f28ba4 Add ability to import directly from companion API output 2016-11-12 12:02:52 +00:00
Cmdr McDonald
f489257f86 Update shield cell numbers when appropriate 2016-11-11 12:30:32 +00:00
Cmdr McDonald
c96693c439 Tidy up descriptions; allow total cost to be non-integer 2016-11-11 12:19:08 +00:00
Cmdr McDonald
3d4f6d7861 Slightly friendlier modifications 2016-11-11 12:04:30 +00:00
Cmdr McDonald
606eabfec7 Fix issues with losing precision due to using decimal modification values. Validate modification information 2016-11-11 11:15:56 +00:00
Cmdr McDonald
782603727a Re-add recovery/recharge times and tweak styling 2016-11-11 00:39:14 +00:00
Cmdr McDonald
ad570534a0 Update test outputs 2016-11-11 00:21:53 +00:00
Cmdr McDonald
87e903e473 Add 'Offence summary' and 'Defence summary' components 2016-11-11 00:15:49 +00:00
Cmdr McDonald
cf6d32ea04 Enable boost display even if power distributor is disabled 2016-11-10 22:15:42 +00:00
Cmdr McDonald
5b81a0b25f Fix modification value for additive modifications 2016-11-10 15:18:05 +00:00
Cmdr McDonald
0688faac93 Added offence and defence summary 2016-11-10 00:13:56 +00:00
Cmdr McDonald
3719bb9696 Merge branch 'feature/fixes' into develop 2016-11-09 19:05:43 +00:00
Cmdr McDonald
21582d1598 Handle potentially null modifications object 2016-11-09 18:57:45 +00:00
Cmdr McDonald
9a9607fcfb Merge branch 'gienkov-pl-language' into feature/fixes 2016-11-09 16:41:19 +00:00
Cmdr McDonald
8602c5667b Merge branch 'pl-language' of https://github.com/gienkov/coriolis into gienkov-pl-language 2016-11-09 16:40:43 +00:00
Cmdr McDonald
f13a987388 Lint tidy-ups 2016-11-09 16:40:22 +00:00
Cmdr McDonald
38eaebefc0 Fix import and export of ships with modifications, bump schema version to 4 2016-11-09 16:32:11 +00:00
Cmdr McDonald
c1bc514e6b Remove swapfile 2016-11-08 09:29:32 +00:00
Cmdr McDonald
42e98fd015 Fix tooltip DPS 2016-11-08 09:25:01 +00:00
Cmdr McDonald
5f0b851de7 Take modifications in to account when deciding whether to issue a
warning on a standard module.
Fix for #16.
2016-11-08 09:05:54 +00:00
Cmdr McDonald
616ed0bf10 Show modification icon for modified modules
Fix for #14
2016-11-07 17:11:39 +00:00
Cmdr McDonald
108ab3b1ee Update DPS/HPS/EPS in real-time as modifiers change 2016-11-07 10:15:20 +00:00
Cmdr McDonald
04caef9613 Merge branch 'release/2.2.1' into develop 2016-11-05 14:39:28 +00:00
Cmdr McDonald
cae7edbe01 Merge branch 'release/2.2.1' 2016-11-05 14:39:21 +00:00
Cmdr McDonald
2fa96fc1a5 Bump version 2016-11-05 14:37:37 +00:00
Cmdr McDonald
6b8a4d1f85 Merge branch 'feature/mods' into develop 2016-11-05 14:03:24 +00:00
Cmdr McDonald
07df44a907 Allow 3dp for ship and module discounts 2016-11-05 13:59:23 +00:00
Cmdr McDonald
be45637435 Use GZIP to decrease length of modifications URL string 2016-11-05 13:43:57 +00:00
Cmdr McDonald
87ab684ba3 Use degree symbol for jitter 2016-11-04 23:58:03 +00:00
Cmdr McDonald
916a6d5e48 Show shield boost decimal place 2016-11-04 23:00:19 +00:00
Cmdr McDonald
b99fbf07f5 Fix up name of shield boost 2016-11-04 22:34:08 +00:00
Cmdr McDonald
6d6ef2a93e Provide weights to 2dp 2016-11-04 20:53:05 +00:00
Cmdr McDonald
d08e5e2858 Fixes for serialization 2016-11-04 17:13:28 +00:00
Cmdr McDonald
c1f4a8d416 Add tooltips for icons; fix import of old-style modification strings 2016-11-04 13:12:44 +00:00
Cmdr McDonald
c17c7125e3 Tidy-ups 2016-11-04 11:34:39 +00:00
Cmdr McDonald
97fc4ce45d Fixes and tidy-ups 2016-11-04 11:31:48 +00:00
Cmdr McDonald
abfc338240 Tidy-ups 2016-11-03 16:19:13 +00:00
Cmdr McDonald
3d129946ce Updates module diff details 2016-11-03 12:23:25 +00:00
Cmdr McDonald
2aab31d317 Fix typo 2016-11-03 11:33:00 +00:00
Cmdr McDonald
f8ff9a6a87 Add more fillouts 2016-11-03 11:29:41 +00:00
Cmdr McDonald
6727c2fc13 Move to pure damage/distdraw/thermload numbers 2016-11-03 10:52:54 +00:00
Cmdr McDonald
ba1f11a88b Updates for shield strength 2016-11-02 14:14:15 +00:00
Cmdr McDonald
c88e4369c8 Update to handle armour 2016-11-02 13:32:49 +00:00
Cmdr McDonald
c490f97c22 Use modifiers when calculating regen rates 2016-11-02 11:11:45 +00:00
Cmdr McDonald
7c71555384 Added facing limit and range for fsdi 2016-11-02 11:03:29 +00:00
Cmdr McDonald
35538f971a Tidy-ups 2016-11-02 10:47:10 +00:00
Cmdr McDonald
b3c82ac2de Added (non-functional) resistances 2016-11-02 10:35:55 +00:00
Cmdr McDonald
ff0d8dccea Handle bulkheads 2016-11-02 09:54:52 +00:00
Cmdr McDonald
06841b1485 Change arc to facinglimit 2016-11-01 12:22:03 +00:00
Cmdr McDonald
f55448c0a8 Ensure that fuel tank mass is displayed 2016-11-01 07:24:38 +00:00
Cmdr McDonald
258701c377 Separate modification slider in to its own component 2016-10-31 23:19:22 +00:00
Cmdr McDonald
1a0f05511b Various bug fixes 2016-10-31 21:08:15 +00:00
Cmdr McDonald
3ec9679893 Tidy up diff 2016-10-31 12:13:03 +00:00
Cmdr McDonald
e5cc3e269e Updates for modifications 2016-10-31 11:22:36 +00:00
Cmdr McDonald
4b14f617ec Changes for mods 2016-10-30 20:54:36 +00:00
Cmdr McDonald
0df051e40f Move more values to functions over direct variable access 2016-10-29 23:32:52 +01:00
Cmdr McDonald
2b464f34e5 Use react-number-editor for modifications 2016-10-29 14:14:57 +01:00
Cmdr McDonald
7a33bd64f8 Updates to modifications menu 2016-10-28 01:46:21 +01:00
Cmdr McDonald
3656c7a18f Update naming to match Elite names 2016-10-27 15:11:23 +01:00
Cmdr McDonald
3114852c63 Updates for mods UI 2016-10-27 13:26:09 +01:00
Cmdr McDonald
45337913ba Make modifications icon clickable 2016-10-25 13:36:46 +01:00
Cmdr McDonald
adc5d1c039 Only show modifications icon if the module has valid modifications. Use no more than two decimal places for modification values. 2016-10-24 18:40:12 +01:00
Cmdr McDonald
183f22c223 Updates to internal representation of modification information. Temporary power usage slider with standard modules for testing 2016-10-24 00:38:12 +01:00
Cmdr McDonald
43eb4935e6 Merge branch 'master' into develop 2016-10-22 21:03:35 +01:00
Cmdr McDonald
2c45011664 Rework modules to be individual objects rather than references to templates 2016-10-22 09:47:43 +01:00
Cmdr McDonald
f29f3f4f8f Merge branch 'release/2.2.0' into develop 2016-10-22 09:31:08 +01:00
Grzegorz
5634fbd568 pl language 2016-10-10 17:47:37 +02:00
Grzegorz
7d99d59790 pl option in menu 2016-10-10 17:39:17 +02:00
151 changed files with 38411 additions and 3106 deletions

View File

@@ -1,3 +1,3 @@
{
"presets": ["es2015", "react", "stage-0"]
}
"presets": ["env", "react", "stage-0"]
}

21
.editorconfig Normal file
View File

@@ -0,0 +1,21 @@
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

View File

@@ -5,7 +5,7 @@
"jsx": true,
"classes": true,
"modules": true
},
}
},
"env": {
"browser": true,
@@ -33,7 +33,6 @@
"ClassDeclaration": true
}
}],
"no-console": 2,
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"comma-style": [2, "last"],
"indent": [2, 2, { "SwitchCase": 1, "VariableDeclarator": 2 }],

5
.gitignore vendored
View File

@@ -4,4 +4,7 @@ build
*.log
nginx.pid
.idea
/bin
/bin
env
*.swp
.project

View File

@@ -3,13 +3,14 @@ notifications:
email: false
sudo: false
node_js:
- "4.2.6"
- "4.8.1"
cache:
directories:
- node_modules
before_script:
before_install:
- git clone https://github.com/EDCD/coriolis-data.git ../coriolis-data
script:
- npm run lint
- npm test
- npm test

349
ChangeLog.md Normal file
View File

@@ -0,0 +1,349 @@
#2.5.1
* Passenger count on main page
* AX Modules
* Engineering fixes
* Use coriolis-data 2.5.1
#2.5.0
* willyb321 and myself have conquered engineering. Mainly him though...
* Use coriolis-data 2.5.0
#2.4.2
Lots of kind people have helped out for this release! Check out the PR history!
* Uses coriolis-data update:
* Fixes issues with repair limpets
* Adds requirement data
* Adds requirements panel
* Adds comma formatting to tooltip numbers
#2.4.1
* Small patches and changes
#2.4.0
* Changed compression library to Pako
* Use coriolis-data 2.4.0
* Repair Limpets added
#2.3.7
* Fixed Travis test issues
* Bumped NodeJS version to provide better compatability and support
* Added updated German Translation
* Fixed issues with Safari
* Use coriolis-data 2.3.7
* Fixed Orca mass-lock
#2.3.6
* Update miner role to provide better defaults
* Fix issue where torpedo special effects were not showing
* Fix typo causing long range blueprint to not modify shot speed in some circumstances
* Fix for Spanish translation of Chaff Launcher (thanks to DamonFstr)
* Update for Russian translation (thanks to LeeNTien)
* Use coriolis-data 2.3.6:
* Add shotspeed modifier to cannon/multi-cannon/fragment cannon
#2.3.5
* Ensure that hidden blueprint effects are applied when a blueprint is selected
* Handle display when summary values show thrusters disabled but current mass keeps them enabled
* Added updated German translations (thanks to @sweisgerber-dev)
* Power state (enabled and priority) now follows modules when they are swapped or copied
* Grey out modules that are powered off to provide a clearer visual indication
* Use coriolis-data 2.3.5:
* Fix list of available blueprints for Point Defence
* Fix integrity values for class 6 power plants
* Add shot speed for long range weapon
* Fix components for dirty drive grade 3
* Update values for Cytoscrambler
#2.3.4
* Fix crash when removing the special effect from a module
* Ensure comparisons with saved stock ships work correctly
* Add 'Racer' role
* Tidy up shipyard page; remove units from data columns and re-order for legibility
* Allow basic drag/drop functionality in Edge/Internet Explorer 11 browser
* Provide separate special effects for dumbfire and seeker missiles
* Include special effect modifiers in blueprint tooltip
* Use coriolis-data 2.3.4:
* Add missing Long Range blueprint to multi-cannon
* Fix values for thermal load of focused weapon grade 4
* Fix internal module information for power plant blueprints
* Add 'FSD Interrupt' special to dumbfire missile racks; this module now has `specials_S` and `specials_D` keys for specials to differentiate
#2.3.3
* Remove unused blueprint when hitting reset
* Add 'purchase module' external link to EDDB for refit items
* Use coriolis-data 2.3.3:
* Add Felicity Farseer to list of engineers that supply sensor and detailed surface scanner modifications
#2.3.2
* Use scan range for DSS rather than scan time
* Fix companion API import of Dolphin
* Use coriolis-data 2.3.2:
* Separate scan time and scan range
* Add Frontier IDs for new items in 2.3
* Update ownership of module blueprints for sensors and scanners
* Update railgun penetration
#2.3.0
* Make scan time visible on scanners where available
* Update power distributor able-to-boost calculation to take fractional MJ values in to account
* Revert to floating header due to issues on iOS
* Fix issue where new module added to a slot did not reset its enabled status
* Show integrity value for relevant modules
* Reset old modification values when a new roll is applied
* Fix issue with miner role where refinery would not be present in ships with class 5 slots but no class 4
* Ensure that boost value is set correctly when modifications to power distributor enable/disable boost
* Ensure that hull reinforcement modifications take the inherent resistance in to account when calculating modification percentages
* Add tooltip for blueprints providing details of the features they alter, the components required for the blueprint and the engineer(s) who cam craft them
* Use opponent's saved pips if available
* Ignore rounds per shot for EPS and HPS calculations; it's already factored in to the numbers
* Ensure that clip size modification imports result in whole numbers
* Rework of separate offence/defence/movement sections to a unified interface
* Use cargo hatch information on import if available
* Additional information of power distributor pips, boost, cargo and fuel loads added to build
* Additional information of opponent and engagement range added to build
* Reworking of offence, defence and movement information in to separate tabs as part of the outfitting screen:
* Power and costs section provides the existing 'Power' and 'Costs' sections
* Profiles section provides a number of graphs that show how various components of the build (top speed, sustained DPS against opponent's shields and armour etc) are affected by mass, range, etc.
* Offence section provides details of your build's damage distribution and per-weapon effectiveness. It also gives summary information for how long it will take for your build to wear down your opponent's shields and armour
* Defence section provides details of your build's defences against your selected opponent. It provides details of the effectiveness of your resistances of both shields and armour, and effective strength of each as a result. It also provides key metrics around shield longevity and recovery times, as well as module protection
* Fix power band marker to show safe power limit at 40% rather than 50%
* Restyle blueprint list to improve consistency with similar menus
* Use coriolis-data 2.3.0:
* Add Dolphin
* Add turreted mining lasers
* Add long range / wide angle / fast scan scanner blueprints
* Fix EDDB IDs for class 5 and 7 fighter hangars for correct shopping list
* Fix cost for rocket-propelled FSD disruptor
* Add module names for blueprints
* Fix erroneous value for grade 5 kinetic shield booster
* Add missing integrity values for some modules
* Update module reinforcement package integrity
* Update specs of Beluga as per 2.3
* Update specs of Asp Scout as per 2.3
* Update specs of Diamondback Explorer as per 2.3
* Add ED ID for Rocket Propelled FSD Disruptor
* Fix ED name for target lock breaker special
* Update scan range and angle information for sensors
* Tidy up shield cell bank information to allow for accurate calculations with modifications
* Update mine launcher stats
* Add appropriate engineers to per-module blueprint information
#2.2.19
* Power management panel now displays modules in descending order of power usage by default
* Shot speed can no longer be modified directly. Its value is derived from the range modifier for Long Range and Focused modifications
* Ensure that jump range chart updates when fuel slider is changed
* Add 'Engine profile' and 'FSD profile' charts. These show how your maximum speed/jump range will alter as you alter the mass of your build
* Use coriolis-data 2.2.19:
* Remove shot speed modification - it is directly tied to range
* Fix incorrect minimal mass for 3C bi-weave shield generator
#2.2.18
* Change methodology for calculating explorer role; can result in lighter builds
* Tidy up layout for module selection and lay everything out in a consistent best-to-worst for both class and grade
* Make integrity for module reinforcement packages visible
* Clean up breakpoints for modules in available modules list; stops 7- or 8- module long lines
* Add damager/range graphs to damage dealt
* Reorder panels
* Use coriolis-data 2.2.18:
* Correct lower efficiency value to be better, not worse
#2.2.17
* Use in-game terminology for shield generator optmul and optmass items
* Add crew to shipyard and outfitting page information
* Use coriolis-data 2.2.17:
* Add mass as potential SCB modification
* Fix mining laser statistics
* Remove non-existent grade 4 and 5 wake scanner modifications
* Add number of crew for each ship
#2.2.16
* Fix 'Extreme' blueprint roll where some incorrect ranges were chosen
* Use coriolis-data 2.2.16:
* Fix incorrect thermal load modifiers for dirty drives
* Provide explicit information about if values are higher numeric value == better or not
#2.2.15
* Ensure that standard slots are repainted when any component changes
* Reload page if Safari throws a security error
* Handle import of ships with incorrectly-sized slots
* Add 'Extreme' blueprint roll: best beneficial and worst detrimental outcome (in place of 'Average' roll)
* Display information about Microsoft browser issues when an import fails
* Add 'purchase this build' icon link to EDDB
* Add 'miner' and 'shielded miner' ship roles
* Use coriolis-data 2.2.15:
* Fix location of initial cargo rack for Vulture
* Fix broken regeneration rate for 6B shield generators
* Tidy up breach damage values
#2.2.14
* Ensure that jitter is shown correctly when the result of a special effect
* Use restyled blueprint information
* Use the ship name (if available) rather than the ship model for the window title
* Use coriolis-data 2.2.14:
* Alter blueprint structure to combine components and features
* Make hidden value of modifications its own attribute
* Fix incorrect ED ID for class 6 passenger cabins
#2.2.13
* Add 'time to drain' summary value. This is the time to drain the WEP capacitor if firing all enabled weapons
* Do not include utility slot DPS/EPS/HPS in summary information
* Ensure that auto loader special shows in the tooltip
* Ensure that ship mass is recalculated when appropriate
* Use coriolis-data 2.2.13:
* Add plasma slug special effect for plasma accelerator
* Tweak hull costs of ships
#2.2.12
* Tidy up old references to coriolis.io
* Add ability to add and remove special effects to weapon modifications
* Add weapon engineering information to Damage Dealt section
* Change shortcut for link from ctrl-l to ctrl-o to avoid clash with location bar
* Only show one of power generation or draw in tooltips, according to module
* Use coriolis-data 2.2.12:
* Add special effects for each blueprint
* Add IDs for most Powerplay modules
#2.2.11
* Add help system and initial help file
* Make absolute damage visible
* Add 'average' roll for blueprints
* Update spacing for movement summary to make it more readable
* Provide damage dealt statistics for both shields and hull
* Damage dealt panel only shows enabled weapons
* Add engagement range to damage received panel
* Handle burst rate of fire as an absolute number rather than a perentage modification
* Ensure that clip values are always rounded up
* Ensure that focused weapon mod uses range modifier to increase falloff as well
* Use coriolis-data 2.2.11:
* Remove non-existent chaff launcher capacity blueprint grades
* Fix incorrect values for charge enhanced power distributor
* Remove incorrect AFMU blueprints
* Correct fragment cannon Double Shot blueprint information
* Correct Focused weapon blueprint information
#2.2.10
* Fix detailed export of module reinforcement packages
* Use damagedist for exact breakdown of weapons that have more than one type of damage
* Use new-style modification validity data
* Provide ability to select engineering blueprint and roll sample values for them
* Use coriolis-data 2.2.10:
* Fix incorrect base shield values for Cutter and Corvette
* Update weapons to have %-based damage distributions
* Remove power draw for detailed surface scanner - although shown in outfitting it is not part of active power
* Fix incorrect names for lightweight and kinetic armour
* Add engineering blueprints
#2.2.9
* Use SSL-enabled server for shortlinks
* Add falloff for weapons
* Use falloff when calculating weapon effectiveness in damage dealt
* Add engagement range slider to 'Damage Dealt' section to allow user to see change in weapon effectiveness with range
* Use better DPE calculation methodology
* Add total DPS and effectiveness information to 'Damage Dealt' section
* Use coriolis-data 2.2.9:
* Add falloff metric for weapons
* Add falloff from range modification
#2.2.8
* Fix issue where filling all internals with cargo racks would include restricted slots
* Use coriolis-data 2.2.8:
* Set military slot of Viper Mk IV to class 3; was incorrectly set as class 2
* Update base regeneration rate of prismatic shield generators to values in 2.2.03
* Update specials with information in 2.2.03
#2.2.7
* Fix resistance diminishing return calculations
* Do not allow -100% to be entered as a modification value
#2.2.6
* Add pitch/roll/yaw information
* Use combination of pitch, roll and yaw to provide a more useful agility metric
* Add movement summary to outfitting page
* Add standard internal class sizes to shipyard page
* Fix issue when importing Viper Mk IV
* Ensure ordering of all types of modules (standard, internal, utilities) is consistent
* Add rebuilds per bay information for fighter hangars
* Add ability to show military compartments
* Show module reinforcement package results in defence summary
* Use separate speed/rotation/acceleration multipliers for thrusters if available
* Obey restricted slot rules when adding all for internal slots
* Version URLs to handle changes to ship specifications over time
* Do not include disabled shield boosters in calculations
* Add 'Damage dealt' section
* Add 'Damage received' section
* Add 'Piercing' information to hardpoints
* Add 'Hardness' information to ship summary
* Add module copy functionality - drag module whilst holding 'alt' to copy
* Add base resistances to defence summary tooltip
* Update shield recovery/regeneration calculations
* Pin menu to top of page
* Switch to custom shortlink method to avoid google length limitations
* Ensure that information is not lost on narrow screens
* Do not lose ship selector selection on narrow screens
* Reinstate jump range graph
* Use coriolis-data 2.2.6:
* Update weapons with changed values for 2.2.03
* Add individual pitch/roll/yaw statistics for each ship
* Remove old and meaningless agility stat
* Use sane order for multi-module JSON - coriolis can re-order as it sees fit when displaying modules
* Fix cost of fighter hangars
* Update Powerplay weapons with current statistics
* Add separate min/opt/max multipliers for enhanced thrusters for speed, acceleration and rotation
* Add module reinforcement packages
* Add military compartments
* Fix missing damage value for 2B dumbfires
* Update shield recharge rates
* Reduce hull mass of Viper to 50T
* Fix incorrect optimal mass value for 8A thrusters
* Add power draw for detailed surface scanner
#2.2.5
* Calculate rate of fire for multi-burst weapons
* Add note to disable ghostery in error situation
* Use coriolis-data 2.2.5:
* Fix incorrect ID for emissive munitions special
* Fix rate of fire for burst lasers
* Add fragment cannon modifications
* Fix internal name of dazzle shell
#2.2.4
* Add shortlink for outfitting page
* Use coriolis-data 2.2.4:
* Fix incorrect ID for class 5 luxury passenger cabin
* Add damage type modifier
* Change modifications from simple strings to objects, to allow more data-driven behaviour
* Add special effects
* Modification tooltip now shows special effect
#2.2.3
* Fix hull boost calculation - now shows correct % modifier and total armour
* Fix import of DiamondBack - can now be imported
* Fix import of Beluga - can now be imported
* Use coriolis-data 2.2.3:
* Fix mismatch between class 5 and class 7 fighter hangars - now shows correct module
* Add details for concordant sequence special effect - now shows correct damage
* Fix details for thermal shock special effect - now shows correct damage
* Add engineer blueprints
* Modification tooltip now shows name and grade of modifications for imported builds
* Retain import URL unless user changes the build - allows future updates of Coriolis to take advantage of additional build information
#2.2.2
* Update DPS/HPS/EPS in real-time as modifiers change
* Use coriolis-data 2.2.2:
* Add distributor draw modifier to shield generators
* Remove modifiers for sensors
* Add initial loadout passenger cabins for Beluga
* Add initial loadout passenger cabins for Orca
* Update costs and initial loadouts for Keelback and Type-7
* Add resistances for hull reinforcement packages
* Added modifier actions to create modifications from raw data
* Show modification icon for modified modules
* Take modifications in to account when deciding whether to issue a warning on a standard module
* Fix hardpoint comparison DPS number when selecting an alternate module
* Ensure that retrofit tab only shows changed modules
* Fix import and export of ships with modifications, bump schema version to 4
* Enable boost display even if power distributor is disabled
* Calculate breakdown of ship offensive and defensive stats
* Add 'Offence summary' and 'Defence summary' components
* Add ability to import from companion API output through import feature
* Add ability to import from companion API output through URL

View File

@@ -10,13 +10,18 @@ Coriolis was created using assets and imagery from Elite: Dangerous, with the pe
Please [submit issues](https://github.com/EDCD/coriolis/issues), or better yet [pull requests](https://github.com/EDCD/coriolis/pulls) for any corrections or additions to the database or the code.
### Translations
Please use the OneSky translation site to suggest new translations: http://edcd-coriolis.oneskyapp.com
These will be merged regularly by the project manager.
### Feature Requests, Suggestions & Bugs
Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
## Development
See the [Developer's Guide](https://github.com/cmmcleod/coriolis/wiki/Developer's-Guide) in the wiki.
See the [Developer's Guide](https://github.com/EDCD/coriolis/wiki/Developing-for-Coriolis) in the wiki.
### Ship and Module Database

View File

@@ -0,0 +1,30 @@
{
"adder": {
"t3": {"speed": 205, "boost": 298, "pitch": 35.37, "roll": 93.09, "yaw": 13.03},
"t2": {"speed": 209, "boost": 304, "pitch": 36.06, "roll": 94.90, "yaw": 13.29},
"t1": {"speed": 213, "boost": 310, "pitch": 36.80, "roll": 96.84, "yaw": 13.56},
"t0": {"speed": 218, "boost": 317, "pitch": 37.70, "roll": 99.20, "yaw": 13.89},
"t9": {"speed": 220, "boost": 321, "pitch": 38.08, "roll": 100.21, "yaw": 14.03},
"t8": {"speed": 225, "boost": 327, "pitch": 38.86, "roll": 102.26, "yaw": 14.32},
"t7": {"speed": 230, "boost": 334, "pitch": 39.69, "roll": 104.44, "yaw": 14.62},
"t6": {"speed": 234, "boost": 340, "pitch": 40.41, "roll": 106.34, "yaw": 14.89},
"t5": {"speed": 242, "boost": 351, "pitch": 41.71, "roll": 109.78, "yaw": 15.37}
},
"eagle": {
"t2": {"speed": 223, "boost": 325, "pitch": 46.45, "roll": 111.48, "yaw": 16.72},
"t1": {"speed": 229, "boost": 334, "pitch": 47.69, "roll": 114.46, "yaw": 17.17},
"t0": {"speed": 235, "boost": 343, "pitch": 49.00, "roll": 117.60, "yaw": 17.64},
"t9": {"speed": 239, "boost": 349, "pitch": 49.80, "roll": 119.53, "yaw": 17.93},
"t8": {"speed": 243, "boost": 355, "pitch": 50.70, "roll": 121.69, "yaw": 18.25},
"t7": {"speed": 248, "boost": 361, "pitch": 51.62, "roll": 123.89, "yaw": 18.58},
"t6": {"speed": 252, "boost": 367, "pitch": 52.46, "roll": 125.91, "yaw": 18.89},
"t5": {"speed": 259, "boost": 378, "pitch": 53.99, "roll": 129.56, "yaw": 19.43}
},
"hauler": {
"t4": {"speed": 203, "boost": 305, "pitch": 36.61, "roll": 101.71, "yaw": 14.24},
"t3": {"speed": 209, "boost": 314, "pitch": 37.63, "roll": 104.54, "yaw": 14.64},
"t2": {"speed": 216, "boost": 324, "pitch": 38.89, "roll": 108.03, "yaw": 15.12},
"t1": {"speed": 222, "boost": 333, "pitch": 39.97, "roll": 111.02, "yaw": 15.54},
"t0": {"speed": 232, "boost": 348, "pitch": 41.76, "roll": 116.00, "yaw": 16.24}
}
}

View File

@@ -0,0 +1,325 @@
{
"$schema": "http://cdn.coriolis.io/schemas/ship-loadout/4.json#",
"name": "Test My Ship",
"ship": "Anaconda",
"references": [
{
"name": "Coriolis.io",
"url": "http://localhost:3300/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==?bn=Test%20My%20Ship",
"old-code": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==",
"code": "4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==",
"shipId": "anaconda"
}
],
"components": {
"standard": {
"bulkheads": "Reactive Surface Composite",
"cargoHatch": {
"enabled": false,
"priority": 5
},
"powerPlant": {
"class": 8,
"rating": "A",
"enabled": true,
"priority": 1,
"modifications": {
"pgen": 1000
}
},
"thrusters": {
"class": 6,
"rating": "A",
"enabled": true,
"priority": 1
},
"frameShiftDrive": {
"class": 6,
"rating": "A",
"enabled": true,
"priority": 3
},
"lifeSupport": {
"class": 5,
"rating": "A",
"enabled": true,
"priority": 1
},
"powerDistributor": {
"class": 8,
"rating": "A",
"enabled": true,
"priority": 1
},
"sensors": {
"class": 8,
"rating": "A",
"enabled": true,
"priority": 1
},
"fuelTank": {
"class": 5,
"rating": "C",
"enabled": true,
"priority": 1
}
},
"hardpoints": [
{
"class": 4,
"rating": "A",
"enabled": true,
"priority": 2,
"group": "Plasma Accelerator",
"mount": "Fixed"
},
{
"class": 3,
"rating": "D",
"enabled": true,
"priority": 2,
"group": "Beam Laser",
"mount": "Turret"
},
{
"class": 3,
"rating": "D",
"enabled": true,
"priority": 2,
"group": "Beam Laser",
"mount": "Turret"
},
{
"class": 3,
"rating": "D",
"enabled": true,
"priority": 2,
"group": "Beam Laser",
"mount": "Turret"
},
{
"class": 2,
"rating": "E",
"enabled": true,
"priority": 2,
"group": "Cannon",
"mount": "Turret"
},
{
"class": 2,
"rating": "E",
"enabled": true,
"priority": 2,
"group": "Cannon",
"mount": "Turret"
},
{
"class": 1,
"rating": "F",
"enabled": true,
"priority": 2,
"group": "Beam Laser",
"mount": "Turret"
},
{
"class": 1,
"rating": "F",
"enabled": true,
"priority": 2,
"group": "Beam Laser",
"mount": "Turret"
}
],
"utility": [
{
"class": 0,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Shield Booster"
},
{
"class": 0,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Shield Booster"
},
null,
{
"class": 0,
"rating": "C",
"enabled": true,
"priority": 2,
"group": "Kill Warrant Scanner"
},
{
"class": 0,
"rating": "C",
"enabled": true,
"priority": 2,
"group": "Cargo Scanner"
},
{
"class": 0,
"rating": "F",
"enabled": false,
"priority": 1,
"group": "Electronic Countermeasure",
"name": "Electronic Countermeasure"
},
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Chaff Launcher",
"name": "Chaff Launcher"
},
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 2,
"group": "Point Defence",
"name": "Point Defence"
}
],
"internal": [
{
"class": 7,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Shield Generator"
},
{
"class": 6,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Shield Cell Bank"
},
{
"class": 6,
"rating": "E",
"enabled": true,
"priority": 1,
"group": "Cargo Rack"
},
{
"class": 5,
"rating": "D",
"enabled": true,
"priority": 1,
"group": "Hull Reinforcement Package"
},
{
"class": 5,
"rating": "E",
"enabled": true,
"priority": 1,
"group": "Cargo Rack"
},
null,
null,
null,
{
"class": 4,
"rating": "E",
"enabled": true,
"priority": 1,
"group": "Cargo Rack"
},
{
"class": 4,
"rating": "E",
"enabled": true,
"priority": 1,
"group": "Cargo Rack"
},
{
"class": 4,
"rating": "A",
"enabled": true,
"priority": 3,
"group": "Fuel Scoop"
},
{
"class": 2,
"rating": "A",
"enabled": true,
"priority": 3,
"group": "Frame Shift Drive Interdictor"
}
]
},
"stats": {
"class": 3,
"fighterHangars": 1,
"hullCost": 141889930,
"speed": 180,
"topSpeed": 186.5,
"boost": 240,
"boostEnergy": 27,
"topBoost": 249.34,
"topPitch": 25.97,
"topRoll": 62.34,
"topYaw": 10.39,
"topSpeed": 187.01,
"totalCost": 882362058,
"totalDpe": 142.68,
"totalDps": 101.13,
"totalEps": 18.71,
"totalExplDpe": 0,
"totalExplDps": 0,
"totalExplSDps": 0,
"totalAbsDpe": 3.57,
"totalAbsDps": 18.78,
"totalAbsSDps": 14.45,
"totalHps": 28.28,
"totalKinDpe": 117.48,
"totalKinDps": 22.27,
"totalKinSDps": 16.91,
"totalSDps": 89.99,
"totalThermDpe": 21.63,
"totalThermDps": 60.08,
"totalThermSDps": 58.64,
"baseShieldStrength": 350,
"baseArmour": 945,
"hullExplRes": 0.22,
"hullKinRes": 0.27,
"hullMass": 400,
"hullThermRes": -0.36,
"masslock": 23,
"pipSpeed": 0.14,
"pitch": 25,
"moduleCostMultiplier": 1,
"modulearmour": 0,
"moduleprotection": 0,
"fuelCapacity": 32,
"cargoCapacity": 128,
"ladenMass": 1323.2,
"armour": 2227.5,
"baseArmour": 525,
"unladenMass": 1163.2,
"powerAvailable": 39.6,
"powerRetracted": 23.33,
"powerDeployed": 34.13,
"roll": 60,
"unladenRange": 18.74,
"yaw": 10,
"fullTankRange": 18.36,
"hardness": 65,
"ladenRange": 16.59,
"unladenFastestRange": 74.2,
"ladenFastestRange": 66.96,
"maxJumpCount": 4,
"shield": 833,
"shieldCells": 1840,
"shieldExplRes": 0.5,
"shieldKinRes": 0.4,
"shieldThermRes": -0.2,
"crew": 3
}
}

View File

@@ -0,0 +1,255 @@
{
"$schema": "http://cdn.coriolis.io/schemas/ship-loadout/4.json#",
"name": "Multi-purpose Asp Explorer",
"ship": "Asp Explorer",
"references": [
{
"name": "Coriolis.io",
"url": "https://coriolis.edcd.io/outfit/asp?code=0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FAwDFxwtofAAAAA%3D%3D&bn=Multi-purpose%20Asp%20Explorer",
"code": "0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx/78YG5AltB7I/8/0TwImJboDSPJ/+/f/v/KlX///i3AwMTBIfARK/Gf+JwVSxArStVAYqOjvz///JVo5GRhE2IBc4SKQSSz/DGEmCa398P8//2+gTf//AwDFxwtofAAAAA==",
"shipId": "asp"
}
],
"components": {
"standard": {
"bulkheads": "Lightweight Alloy",
"cargoHatch": {
"enabled": false,
"priority": 5
},
"powerPlant": {
"class": 5,
"rating": "A",
"enabled": true,
"priority": 2,
"modifications": {
"eff": -1850,
"pgen": 6,
"mass": 431
},
"blueprint": {
"id": 64,
"name": "Low emissions",
"grade": 1
}
},
"thrusters": {
"class": 5,
"rating": "D",
"enabled": true,
"priority": 1,
"modifications": {
"optmul": 440,
"integrity": -266,
"thermload": -1326,
"optmass": 520,
"power": 241
},
"blueprint": {
"id": 24,
"name": "Clean",
"grade": 1
}
},
"frameShiftDrive": {
"class": 5,
"rating": "A",
"enabled": true,
"priority": 1,
"modifications": {
"mass": 5025,
"integrity": -1539,
"power": 2437,
"optmass": 4870,
"maxfuel": 370
},
"blueprint": {
"id": 26,
"name": "Increased range",
"grade": 5
}
},
"lifeSupport": {
"class": 4,
"rating": "A",
"enabled": true,
"priority": 1,
"modifications": {
"mass": -3923,
"integrity": -1797
},
"blueprint": {
"id": 49,
"name": "Lightweight",
"grade": 1
}
},
"powerDistributor": {
"class": 3,
"rating": "D",
"enabled": true,
"priority": 1
},
"sensors": {
"class": 5,
"rating": "D",
"enabled": true,
"priority": 1
},
"fuelTank": {
"class": 5,
"rating": "C",
"enabled": true,
"priority": 1
}
},
"hardpoints": [
null,
null,
null,
null,
null,
null
],
"utility": [
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Heat Sink Launcher",
"name": "Heat Sink Launcher"
},
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Heat Sink Launcher",
"name": "Heat Sink Launcher"
},
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Heat Sink Launcher",
"name": "Heat Sink Launcher"
},
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Point Defence",
"name": "Point Defence"
}
],
"internal": [
{
"class": 6,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Fuel Scoop"
},
{
"class": 5,
"rating": "E",
"enabled": true,
"priority": 2,
"group": "Cargo Rack"
},
{
"class": 3,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Shield Generator"
},
{
"class": 3,
"rating": "E",
"enabled": true,
"priority": 2,
"group": "Cargo Rack"
},
{
"class": 2,
"rating": "G",
"enabled": true,
"priority": 1,
"group": "Planetary Vehicle Hangar"
},
{
"class": 1,
"rating": "C",
"enabled": true,
"priority": 2,
"group": "Scanner",
"name": "Advanced Discovery Scanner"
},
{
"class": 1,
"rating": "C",
"enabled": true,
"priority": 2,
"group": "Scanner",
"name": "Detailed Surface Scanner"
}
]
},
"stats": {
"class": 2,
"hullCost": 6135660,
"speed": 250,
"boost": 340,
"boostEnergy": 13,
"agility": 6,
"baseShieldStrength": 140,
"baseArmour": 210,
"hullMass": 280,
"masslock": 11,
"pipSpeed": 0.13,
"moduleCostMultiplier": 1,
"fuelCapacity": 32,
"cargoCapacity": 40,
"ladenMass": 435.26,
"armour": 378,
"shield": 113.43,
"shieldCells": 0,
"totalCost": 48402550,
"unladenMass": 363.26,
"totalDpe": 0,
"totalExplDpe": 0,
"totalKinDpe": 0,
"totalThermDpe": 0,
"totalDps": 0,
"totalExplDps": 0,
"totalKinDps": 0,
"totalThermDps": 0,
"totalSDps": 0,
"totalExplSDps": 0,
"totalKinSDps": 0,
"totalThermSDps": 0,
"totalEps": 1.2,
"totalHps": 1,
"shieldExplRes": 0.5,
"shieldKinRes": 0.6,
"shieldThermRes": 1.2,
"hullExplRes": 1.4,
"hullKinRes": 1.2,
"hullThermRes": 1,
"powerAvailable": 20.41,
"powerRetracted": 11.91,
"powerDeployed": 11.91,
"unladenRange": 50.45,
"fullTankRange": 47.03,
"ladenRange": 42.71,
"unladenFastestRange": 317.24,
"ladenFastestRange": 287.02,
"maxJumpCount": 7,
"topSpeed": 274.01,
"topBoost": 372.65
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,552 @@
{
"cargo": {
"capacity": 32
},
"free": false,
"fuel": {
"main": {
"capacity": 128
},
"reserve": {
"capacity": 0.81
}
},
"id": 31,
"modules": {
"Armour": {
"module": {
"free": false,
"id": 128049346,
"name": "BelugaLiner_Armour_Grade1",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"Bobble01": [],
"Bobble02": [],
"Bobble03": [],
"Bobble04": [],
"Bobble05": [],
"Bobble06": [],
"Bobble07": [],
"Bobble08": [],
"Bobble09": [],
"Bobble10": [],
"Decal1": {
"module": {
"free": false,
"id": 128667757,
"name": "Decal_Explorer_Ranger",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"Decal2": {
"module": {
"free": false,
"id": 128667742,
"name": "Decal_Combat_Deadly",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"Decal3": {
"module": {
"free": false,
"id": 128667750,
"name": "Decal_Trade_Tycoon",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"EngineColour": [],
"FrameShiftDrive": {
"module": {
"free": false,
"id": 128064132,
"modifiers": {
"engineerID": 300100,
"id": 175,
"modifiers": [
{
"name": "mod_mass",
"type": 1,
"value": 0.4457540512085
},
{
"name": "mod_health",
"type": 1,
"value": -0.24584779143333
},
{
"name": "mod_passive_power",
"type": 1,
"value": 0.24457727372646
},
{
"name": "mod_fsd_optimised_mass",
"type": 1,
"value": 0.49257898330688
},
{
"name": "mod_fsd_max_fuel_per_jump",
"type": 2,
"value": 0.028505677357316
},
{
"name": "mod_fsd_heat_rate",
"type": 2,
"value": -0.079360365867615
}
],
"moduleTags": [
16
],
"recipeID": 128673694,
"slotIndex": 53
},
"name": "Int_Hyperdrive_Size7_Class5",
"on": true,
"priority": 0,
"recipeLevel": 5,
"recipeName": "FSD_LongRange",
"recipeValue": 0,
"unloaned": 0,
"value": 46160201
}
},
"FuelTank": {
"module": {
"free": false,
"id": 128064352,
"name": "Int_FuelTank_Size7_Class3",
"on": true,
"priority": 1,
"unloaned": 1602822,
"value": 1602822
}
},
"LifeSupport": {
"module": {
"free": false,
"id": 128064174,
"name": "Int_LifeSupport_Size8_Class2",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 1569565
}
},
"MainEngines": {
"module": {
"free": false,
"id": 128064094,
"modifiers": {
"engineerID": 300100,
"id": 253,
"modifiers": [
{
"name": "mod_engine_mass_curve_multiplier",
"type": 1,
"value": 0.098235413432121
},
{
"name": "mod_engine_heat",
"type": 1,
"value": 0.18069696426392
},
{
"name": "mod_passive_power",
"type": 1,
"value": 0.033788848668337
},
{
"name": "mod_health",
"type": 1,
"value": -0.056404989212751
},
{
"name": "mod_engine_mass_curve",
"type": 1,
"value": -0.027384582906961
},
{
"name": "mod_engine_heat",
"type": 2,
"value": -0.072683908045292
}
],
"moduleTags": [
17
],
"recipeID": 128673655,
"slotIndex": 52
},
"name": "Int_Engine_Size7_Class2",
"on": true,
"priority": 0,
"recipeLevel": 1,
"recipeName": "Engine_Dirty",
"recipeValue": 0,
"unloaned": 0,
"value": 1709638
}
},
"MediumHardpoint1": {
"module": {
"free": false,
"id": 128049436,
"name": "Hpt_BeamLaser_Turret_Medium",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 1889910
}
},
"MediumHardpoint2": {
"module": {
"free": false,
"id": 128049436,
"name": "Hpt_BeamLaser_Turret_Medium",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 1889910
}
},
"MediumHardpoint3": {
"module": {
"free": false,
"id": 128049460,
"name": "Hpt_MultiCannon_Gimbal_Medium",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 51300
}
},
"MediumHardpoint4": {
"module": {
"free": false,
"id": 128049460,
"name": "Hpt_MultiCannon_Gimbal_Medium",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 51300
}
},
"MediumHardpoint5": {
"module": {
"free": false,
"id": 128049460,
"name": "Hpt_MultiCannon_Gimbal_Medium",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 51300
}
},
"PaintJob": {
"module": {
"free": false,
"id": 128732290,
"name": "PaintJob_BelugaLiner_Tactical_White",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"PlanetaryApproachSuite": {
"module": {
"free": false,
"id": 128672317,
"name": "Int_PlanetApproachSuite",
"on": true,
"priority": 1,
"unloaned": 450,
"value": 450
}
},
"PowerDistributor": {
"module": {
"free": false,
"id": 128064207,
"name": "Int_PowerDistributor_Size6_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 3128120
}
},
"PowerPlant": {
"module": {
"free": false,
"id": 128064057,
"modifiers": {
"engineerID": 300100,
"id": 277,
"modifiers": [
{
"name": "mod_powerplant_power",
"type": 1,
"value": 0.054692290723324
},
{
"name": "mod_health",
"type": 1,
"value": -0.033690698444843
},
{
"name": "mod_powerplant_heat",
"type": 1,
"value": 0.027470717206597
},
{
"name": "mod_powerplant_heat",
"type": 2,
"value": -0.056317910552025
}
],
"moduleTags": [
18
],
"recipeID": 128673765,
"slotIndex": 51
},
"name": "Int_Powerplant_Size6_Class5",
"on": true,
"priority": 1,
"recipeLevel": 1,
"recipeName": "PowerPlant_Boosted",
"recipeValue": 0,
"unloaned": 0,
"value": 14561578
}
},
"Radar": {
"module": {
"free": false,
"id": 128064239,
"name": "Int_Sensors_Size5_Class2",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 71500
}
},
"Slot01_Size6": {
"module": {
"free": false,
"id": 128666681,
"name": "Int_FuelScoop_Size6_Class5",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 25887249
}
},
"Slot02_Size6": {
"module": {
"free": false,
"id": 128064287,
"name": "Int_ShieldGenerator_Size6_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 14561578
}
},
"Slot03_Size6": {
"module": {
"free": false,
"id": 128727927,
"name": "Int_PassengerCabin_Size6_Class2",
"on": true,
"priority": 1,
"unloaned": 165808,
"value": 165808
}
},
"Slot04_Size6": {
"module": {
"free": false,
"id": 128727928,
"name": "Int_PassengerCabin_Size6_Class3",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 497429
}
},
"Slot05_Size5": {
"module": {
"free": false,
"id": 128727925,
"name": "Int_PassengerCabin_Size5_Class4",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 1492286
}
},
"Slot06_Size5": {
"module": {
"free": false,
"id": 128064342,
"name": "Int_CargoRack_Size5_Class1",
"on": true,
"priority": 1,
"unloaned": 100409,
"value": 100409
}
},
"Slot07_Size4": {
"module": {
"free": false,
"id": 128727922,
"name": "Int_PassengerCabin_Size4_Class1",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 17059
}
},
"Slot08_Size3": {
"module": {
"free": false,
"id": 128667632,
"name": "Int_Repairer_Size3_Class5",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 2361960
}
},
"Slot09_Size3": {
"module": {
"free": false,
"id": 128672289,
"name": "Int_BuggyBay_Size2_Class2",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 19440
}
},
"Slot10_Size3": {
"module": {
"free": false,
"id": 128666634,
"name": "Int_DetailedSurfaceScanner_Tiny",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 225000
}
},
"Slot11_Size3": {
"module": {
"free": false,
"id": 128663561,
"name": "Int_StellarBodyDiscoveryScanner_Advanced",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 1390500
}
},
"TinyHardpoint1": {
"module": {
"free": false,
"id": 128049513,
"name": "Hpt_ChaffLauncher_Tiny",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 7650
}
},
"TinyHardpoint2": {
"module": {
"free": false,
"id": 128668536,
"name": "Hpt_ShieldBooster_Size0_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 252900
}
},
"TinyHardpoint3": {
"module": {
"free": false,
"id": 128668536,
"name": "Hpt_ShieldBooster_Size0_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 252900
}
},
"TinyHardpoint4": {
"module": {
"free": false,
"id": 128668536,
"name": "Hpt_ShieldBooster_Size0_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 281000
}
},
"TinyHardpoint5": {
"module": {
"free": false,
"id": 128668536,
"name": "Hpt_ShieldBooster_Size0_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 281000
}
},
"TinyHardpoint6": {
"module": {
"free": false,
"id": 128668536,
"name": "Hpt_ShieldBooster_Size0_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 281000
}
},
"WeaponColour": {
"module": {
"free": false,
"id": 128732194,
"name": "WeaponCustomisation_Purple",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
}
},
"name": "BelugaLiner",
"value": {
"hull": 71688743,
"modules": 120812762,
"unloaned": 1869489
}
}

View File

@@ -0,0 +1,314 @@
{
"cargo": {
"capacity": 264
},
"free": false,
"fuel": {
"main": {
"capacity": 32
},
"reserve": {
"capacity": 0.52
}
},
"id": 4,
"modules": {
"Armour": {
"module": {
"free": false,
"id": 128049298,
"name": "Type7_Armour_Grade1",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"Bobble01": [],
"Bobble02": [],
"Bobble03": [],
"Bobble04": [],
"Bobble05": [],
"Bobble06": [],
"Bobble07": [],
"Bobble08": [],
"Bobble09": [],
"Bobble10": [],
"Decal1": {
"module": {
"free": false,
"id": 128667746,
"name": "Decal_Trade_Dealer",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"Decal2": {
"module": {
"free": false,
"id": 128667738,
"name": "Decal_Combat_Competent",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"Decal3": {
"module": {
"free": false,
"id": 128667753,
"name": "Decal_Explorer_Scout",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"EngineColour": [],
"FrameShiftDrive": {
"module": {
"free": false,
"id": 128064122,
"name": "Int_Hyperdrive_Size5_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 5103953
}
},
"FuelTank": {
"module": {
"free": false,
"id": 128064350,
"name": "Int_FuelTank_Size5_Class3",
"on": true,
"priority": 1,
"unloaned": 97754,
"value": 97754
}
},
"LifeSupport": {
"module": {
"free": false,
"id": 128064154,
"name": "Int_LifeSupport_Size4_Class2",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 28373
}
},
"MainEngines": {
"module": {
"free": false,
"id": 128064087,
"name": "Int_Engine_Size5_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 5103953
}
},
"PaintJob": {
"module": {
"free": false,
"id": 128671422,
"name": "PaintJob_Type7_Tactical_White",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"PlanetaryApproachSuite": {
"module": {
"free": false,
"id": 128672317,
"name": "Int_PlanetApproachSuite",
"on": true,
"priority": 1,
"unloaned": 500,
"value": 500
}
},
"PowerDistributor": {
"module": {
"free": false,
"id": 128064192,
"name": "Int_PowerDistributor_Size3_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 158331
}
},
"PowerPlant": {
"module": {
"free": false,
"id": 128064047,
"name": "Int_Powerplant_Size4_Class5",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 1610080
}
},
"Radar": {
"module": {
"free": false,
"id": 128064229,
"name": "Int_Sensors_Size3_Class2",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 10133
}
},
"Slot01_Size6": {
"module": {
"free": false,
"id": 128064343,
"name": "Int_CargoRack_Size6_Class1",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 362591
}
},
"Slot02_Size6": {
"module": {
"free": false,
"id": 128064343,
"name": "Int_CargoRack_Size6_Class1",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 362591
}
},
"Slot03_Size5": {
"module": {
"free": false,
"id": 128064343,
"name": "Int_CargoRack_Size6_Class1",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 362591
}
},
"Slot04_Size5": {
"module": {
"free": false,
"id": 128064342,
"name": "Int_CargoRack_Size5_Class1",
"on": true,
"priority": 1,
"unloaned": 111566,
"value": 111566
}
},
"Slot05_Size4": {
"module": {
"free": false,
"id": 128064342,
"name": "Int_CargoRack_Size5_Class1",
"on": true,
"priority": 1,
"unloaned": 111566,
"value": 111566
}
},
"Slot06_Size4": {
"module": {
"free": false,
"id": 128064279,
"name": "Int_ShieldGenerator_Size5_Class2",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 189035
}
},
"Slot07_Size2": {
"module": {
"free": false,
"id": 128049549,
"name": "Int_DockingComputer_Standard",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 4500
}
},
"Slot08_Size2": {
"module": {
"free": false,
"id": 128064340,
"name": "Int_CargoRack_Size3_Class1",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 10563
}
},
"SmallHardpoint1": [],
"SmallHardpoint2": [],
"SmallHardpoint3": [],
"SmallHardpoint4": [],
"TinyHardpoint1": {
"module": {
"free": false,
"id": 128668536,
"name": "Hpt_ShieldBooster_Size0_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 281000
}
},
"TinyHardpoint2": {
"module": {
"free": false,
"id": 128668536,
"name": "Hpt_ShieldBooster_Size0_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 281000
}
},
"TinyHardpoint3": {
"module": {
"free": false,
"id": 128668536,
"name": "Hpt_ShieldBooster_Size0_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 281000
}
},
"TinyHardpoint4": {
"module": {
"free": false,
"id": 128049513,
"name": "Hpt_ChaffLauncher_Tiny",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 8500
}
},
"WeaponColour": []
},
"name": "Type7",
"value": {
"hull": 16780009,
"modules": 14479580,
"unloaned": 321386
}
}

View File

@@ -0,0 +1,225 @@
{
"free": false,
"id": 2,
"modules": {
"Armour": {
"module": {
"free": false,
"id": 128049280,
"name": "CobraMkIII_Armour_Grade1",
"on": true,
"priority": 1,
"value": 0
}
},
"FrameShiftDrive": {
"module": {
"free": false,
"id": 128064117,
"name": "Int_Hyperdrive_Size4_Class5",
"on": true,
"priority": 4,
"value": 1610080
}
},
"FuelTank": {
"module": {
"free": false,
"id": 128064349,
"name": "Int_FuelTank_Size4_Class3",
"on": true,
"priority": 1,
"value": 24734
}
},
"LifeSupport": {
"module": {
"free": false,
"id": 128064149,
"name": "Int_LifeSupport_Size3_Class2",
"on": true,
"priority": 0,
"value": 10133
}
},
"MainEngines": {
"module": {
"free": false,
"id": 128064079,
"name": "Int_Engine_Size4_Class2",
"on": true,
"priority": 0,
"value": 59633
}
},
"PaintJob": {
"module": {
"free": false,
"id": 128741033,
"name": "PaintJob_CobraMKIII_Corrosive_05",
"on": true,
"priority": 1,
"value": 0
}
},
"PlanetaryApproachSuite": {
"module": {
"free": false,
"id": 128672317,
"name": "Int_PlanetApproachSuite",
"on": true,
"priority": 1,
"value": 500
}
},
"PowerDistributor": {
"module": {
"free": false,
"id": 128064179,
"name": "Int_PowerDistributor_Size1_Class2",
"on": true,
"priority": 2,
"value": 1293
}
},
"PowerPlant": {
"module": {
"free": false,
"id": 128064037,
"name": "Int_Powerplant_Size2_Class5",
"on": true,
"priority": 1,
"value": 160224
}
},
"Radar": {
"module": {
"free": false,
"id": 128064229,
"name": "Int_Sensors_Size3_Class2",
"on": true,
"priority": 0,
"value": 10133
}
},
"ShipID0": {
"module": {
"free": false,
"id": 128758976,
"name": "Nameplate_ShipID_Black",
"on": true,
"priority": 1,
"value": 0
}
},
"ShipID1": {
"module": {
"free": false,
"id": 128758976,
"name": "Nameplate_ShipID_Black",
"on": true,
"priority": 1,
"value": 0
}
},
"ShipKitBumper": {
"module": {
"free": false,
"id": 128740698,
"name": "CobraMkIII_ShipkitRaider1_Bumper1",
"on": true,
"priority": 1,
"value": 0
}
},
"ShipKitSpoiler": {
"module": {
"free": false,
"id": 128740701,
"name": "CobraMkIII_ShipkitRaider1_Spoiler1",
"on": true,
"priority": 1,
"value": 0
}
},
"ShipKitTail": {
"module": {
"free": false,
"id": 128740705,
"name": "CobraMkIII_ShipkitRaider1_Tail2",
"on": true,
"priority": 1,
"value": 0
}
},
"ShipKitWings": {
"module": {
"free": false,
"id": 128740707,
"name": "CobraMkIII_ShipkitRaider1_Wings1",
"on": true,
"priority": 1,
"value": 0
}
},
"ShipName0": {
"module": {
"free": false,
"id": 128758944,
"name": "Nameplate_Explorer01_Black",
"on": true,
"priority": 1,
"value": 0
}
},
"ShipName1": {
"module": {
"free": false,
"id": 128758944,
"name": "Nameplate_Explorer01_Black",
"on": true,
"priority": 1,
"value": 0
}
},
"Slot01_Size4": {
"module": {
"free": false,
"id": 128666663,
"name": "Int_FuelScoop_Size4_Class3",
"on": true,
"priority": 2,
"value": 178898
}
},
"Slot02_Size4": [],
"Slot03_Size4": [],
"Slot04_Size2": [],
"Slot05_Size2": {
"module": {
"free": false,
"id": 128663561,
"name": "Int_StellarBodyDiscoveryScanner_Advanced",
"on": true,
"priority": 2,
"value": 1545000
}
},
"Slot06_Size2": {
"module": {
"free": false,
"id": 128666634,
"name": "Int_DetailedSurfaceScanner_Tiny",
"on": true,
"priority": 2,
"value": 250000
}
}
},
"name": "CobraMkIII",
"value": {
"hull": 205287,
"modules": 3850628,
"unloaned": 1751109
}
}

View File

@@ -0,0 +1,327 @@
{
"$schema": "http://cdn.coriolis.io/schemas/ship-loadout/4.json#",
"name": "Multi-purpose Imperial Courier",
"ship": "Imperial Courier",
"references": [
{
"name": "Coriolis.io",
"url": "https://coriolis.edcd.io/outfit/imperial_courier?code=0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OP0tCYRjFj9fuVbvF1du9ekkT8s%2FkIg4NElyIBBd321yaGvwUQTS3N7UFfYygIT9EoyQUJA36ns47XJCWA%2B%2Fz%2Bz3Pe3ImBbDNKaqNPSBoGrL4ngfomKpFGiJ%2BLgHteR1IPjxJT5pF11uSeXNsJVcRfgdC92syWUuK0iMdKZqrjJ%2F0aoA71lJ5oKf38knWcCiptCPdhJIerdS00vlK0qktlqoj983UmqqHjQ33VsW8eazFmaTyULP2hQ4lX8LBme6g%2F6v0TTdbxJ2KhdEIaCw15MF%2FNB0L%2BS2hwEwyFM8KgP%2BqEpWWA3Qu9Z3z9kPWHzakt7Dt%2BAeD7ghSTgEAAA%3D%3D&bn=Multi-purpose%20Imperial%20Courier",
"code": "0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ=.H4sIAAAAAAAAA12OP0tCYRjFj9fuVbvF1du9ekkT8s/kIg4NElyIBBd321yaGvwUQTS3N7UFfYygIT9EoyQUJA36ns47XJCWA+/z+z3Pe3ImBbDNKaqNPSBoGrL4ngfomKpFGiJ+LgHteR1IPjxJT5pF11uSeXNsJVcRfgdC92syWUuK0iMdKZqrjJ/0aoA71lJ5oKf38knWcCiptCPdhJIerdS00vlK0qktlqoj983UmqqHjQ33VsW8eazFmaTyULP2hQ4lX8LBme6g/6v0TTdbxJ2KhdEIaCw15MF/NB0L+S2hwEwyFM8KgP+qEpWWA3Qu9Z3z9kPWHzakt7Dt+AeD7ghSTgEAAA==",
"shipId": "imperial_courier"
}
],
"components": {
"standard": {
"bulkheads": "Lightweight Alloy",
"cargoHatch": {
"enabled": false,
"priority": 5
},
"powerPlant": {
"class": 4,
"rating": "A",
"enabled": true,
"priority": 2,
"modifications": {
"pgen": 1052,
"integrity": -482,
"eff": 974
},
"blueprint": {
"id": 63,
"name": "Overcharged",
"grade": 1
}
},
"thrusters": {
"class": 3,
"rating": "A",
"enabled": true,
"priority": 1,
"name": "Enhanced Performance",
"modifications": {
"optmul": 2476,
"thermload": 7023,
"power": 1763,
"integrity": 165,
"optmass": -667
},
"blueprint": {
"id": 22,
"name": "Dirty",
"grade": 4
}
},
"frameShiftDrive": {
"class": 3,
"rating": "A",
"enabled": true,
"priority": 1,
"modifications": {
"mass": 4082,
"integrity": -2422,
"power": 1782,
"optmass": 4927
},
"blueprint": {
"id": 26,
"name": "Increased range",
"grade": 5
}
},
"lifeSupport": {
"class": 1,
"rating": "A",
"enabled": true,
"priority": 1
},
"powerDistributor": {
"class": 3,
"rating": "A",
"enabled": true,
"priority": 1
},
"sensors": {
"class": 2,
"rating": "D",
"enabled": true,
"priority": 1
},
"fuelTank": {
"class": 3,
"rating": "C",
"enabled": true,
"priority": 1
}
},
"hardpoints": [
{
"class": 2,
"rating": "F",
"enabled": true,
"priority": 1,
"group": "Pulse Laser",
"mount": "Fixed",
"modifications": {
"rof": 5931,
"damage": -184,
"jitter": 50,
"distdraw": -4689,
"piercing": 3328
},
"blueprint": {
"id": 89,
"name": "Rapid fire",
"grade": 5
}
},
{
"class": 2,
"rating": "F",
"enabled": true,
"priority": 1,
"group": "Pulse Laser",
"mount": "Fixed",
"modifications": {
"rof": 4715,
"damage": -97,
"jitter": 30,
"distdraw": -4548,
"piercing": 1057,
"integrity": 319
},
"blueprint": {
"id": 89,
"name": "Rapid fire",
"grade": 5
}
},
{
"class": 2,
"rating": "F",
"enabled": true,
"priority": 1,
"group": "Multi-cannon",
"mount": "Gimballed",
"modifications": {
"damage": 2437,
"distdraw": 5487,
"rof": 1120,
"jitter": 58,
"thermload": 1346,
"power": 1009,
"integrity": -202,
"ammo": -2000
},
"blueprint": {
"id": 88,
"name": "Overcharged",
"grade": 3,
"special": {
"id": 3,
"name": "Corrosive shell"
}
}
}
],
"utility": [
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Heat Sink Launcher",
"name": "Heat Sink Launcher",
"modifications": {
"ammo": 5000,
"mass": 17684,
"reload": 9707
},
"blueprint": {
"id": 37,
"name": "Ammo capacity",
"grade": 3
}
},
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Heat Sink Launcher",
"name": "Heat Sink Launcher",
"modifications": {
"ammo": 5000,
"mass": 18520,
"reload": 8715
},
"blueprint": {
"id": 37,
"name": "Ammo capacity",
"grade": 3
}
},
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Chaff Launcher",
"name": "Chaff Launcher"
},
{
"class": 0,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Frame Shift Wake Scanner"
}
],
"internal": [
{
"class": 3,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Shield Generator",
"modifications": {
"optmul": 1888,
"explres": 455,
"kinres": 546,
"thermres": 1092,
"brokenregen": -2614,
"regen": -876,
"distdraw": 463
},
"blueprint": {
"id": 77,
"name": "Reinforced",
"grade": 3
}
},
{
"class": 3,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Fuel Scoop"
},
{
"class": 2,
"rating": "E",
"enabled": true,
"priority": 2,
"group": "Cargo Rack"
},
{
"class": 2,
"rating": "E",
"enabled": true,
"priority": 2,
"group": "Cargo Rack"
},
null,
{
"class": 1,
"rating": "C",
"enabled": true,
"priority": 2,
"group": "Scanner",
"name": "Advanced Discovery Scanner"
}
]
},
"stats": {
"class": 1,
"hullCost": 2481550,
"speed": 280,
"boost": 380,
"boostEnergy": 10,
"agility": 6,
"baseShieldStrength": 200,
"baseArmour": 80,
"hullMass": 35,
"masslock": 7,
"pipSpeed": 0.05,
"moduleCostMultiplier": 1,
"fuelCapacity": 8,
"cargoCapacity": 8,
"ladenMass": 104.25,
"armour": 144,
"shield": 404.19,
"shieldCells": 0,
"totalCost": 14059860,
"unladenMass": 88.25,
"totalDpe": 32.25,
"totalExplDpe": 0,
"totalKinDpe": 9.41,
"totalThermDpe": 22.84,
"totalDps": 53.8,
"totalExplDps": 0,
"totalKinDps": 17.44,
"totalThermDps": 36.35,
"totalSDps": 48.99,
"totalExplSDps": 0,
"totalKinSDps": 12.64,
"totalThermSDps": 36.35,
"totalEps": 9.84,
"totalHps": 12.28,
"shieldExplRes": 0.48,
"shieldKinRes": 0.55,
"shieldThermRes": 1.09,
"hullExplRes": 1.4,
"hullKinRes": 1.2,
"hullThermRes": 1,
"powerAvailable": 17.24,
"powerRetracted": 11.3,
"powerDeployed": 16.41,
"unladenRange": 25.57,
"fullTankRange": 23.92,
"ladenRange": 22.09,
"unladenFastestRange": 116.23,
"ladenFastestRange": 107,
"maxJumpCount": 5,
"topSpeed": 386.56,
"topBoost": 524.62
}
}

View File

@@ -2,31 +2,31 @@
{
"shipId": "anaconda",
"buildName": "Imported Anaconda",
"buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=",
"buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=.",
"buildText": "[Anaconda]\nS: 1F/F Pulse Laser\nS: 1F/F Pulse Laser\n\nBH: 1I Lightweight Alloy\nRB: 8E Power Plant\nTM: 7E Thrusters\nFH: 6E Frame Shift Drive\nEC: 5E Life Support\nPC: 8E Power Distributor\nSS: 8E Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n7: 6E Cargo Rack (Capacity: 64)\n6: 5E Cargo Rack (Capacity: 32)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 1E Basic Discovery Scanner\n2: 1E Cargo Rack (Capacity: 2)\n"
},
{
"shipId": "anaconda",
"buildName": "Imported Anaconda",
"buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=",
"buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=.",
"buildText": "\n\n \t[Anaconda]\nS: 1F/F Pulse Laser\nS: 1F/F Pulse Laser\n\nBH: 1I Lightweight Alloy\nRB: 8E Power Plant\nTM: 7E Thrusters\nFH: 6E Frame Shift Drive\nEC: 5E Life Support\nPC: 8E Power Distributor\nSS: 8E Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n7: 6E Cargo Rack (Capacity: 64)\n6: 5E Cargo Rack (Capacity: 32)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 1E Basic Discovery Scanner\n2: 1E Cargo Rack (Capacity: 2)\n"
},
{
"shipId": "cobra_mk_iii",
"buildName": "Imported Cobra Mk III",
"buildCode": "0patcFeldd5sdf41712222503040202490f242h.Iw1-kA==.Aw1-kA==",
"buildCode": "0patcFeldd5sdf41712222503040202490f242h.Iw1-kA==.Aw1-kA==.",
"buildText": "[Cobra Mk III]\nM: 1F/F Pulse Laser\nM: 1G/G Burst Laser\nS: 1E/T Fragment Cannon\nS: 1G/T Multi-cannon\nU: 0I Point Defence\nU: 0A Shield Booster\n\nBH: 1I Lightweight Alloy\nRB: 4A Power Plant\nTM: 4C Thrusters\nFH: 4E Frame Shift Drive\nEC: 3D Life Support\nPC: 2A Power Distributor\nSS: 3D Sensors\nFS: 4C Fuel Tank (Capacity: 16)\n\n4: 3E Cargo Rack (Capacity: 8)\n4: 3E Cargo Rack (Capacity: 8)\n4: 4E Shield Generator\n2: 2C Auto Field-Maintenance Unit\n2: 1E Standard Docking Computer\n2: 1E Basic Discovery Scanner\n---\nShield: 112.29 MJ\nPower : 10.45 MW retracted (67%)\n 12.16 MW deployed (78%)\n 15.60 MW available\nCargo : 16 T\nFuel : 16 T\nMass : 235.5 T empty\n 267.5 T full\nRange : 10.69 LY unladen\n 10.05 LY laden\nPrice : 2,929,040 CR\nRe-Buy: 146,452 CR @ 95% insurance\n"
},
{
"shipId": "type_9_heavy",
"buildName": "Imported Type-9 Heavy",
"buildCode": "3pftsFklkdisif57e2k2f2h110001020306054j03022f01242i.Iw18eQ==.Aw18eQ==",
"buildCode": "3pftsFklkdisif57e2k2f2h110001020306054j03022f01242i.Iw18eQ==.Aw18eQ==.",
"buildText": "[Type-9 Heavy]\nM: 2D/G Fragment Cannon\nM: 2I/F Mine Launcher\nM: 2B/FD Missile Rack\nS: 1I/FS Torpedo Pylon\nS: 1F/F Burst Laser\nU: 0I Chaff Launcher\nU: 0F Electronic Countermeasure\nU: 0I Heat Sink Launcher\nU: 0I Point Defence\n\nBH: 1I Mirrored Surface Composite\nRB: 5A Power Plant\nTM: 7D Thrusters\nFH: 6A Frame Shift Drive\nEC: 5A Life Support\nPC: 4D Power Distributor\nSS: 4D Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n8: 7E Cargo Rack (Capacity: 128)\n7: 6E Cargo Rack (Capacity: 64)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 3E Cargo Rack (Capacity: 8)\n4: 1C Advanced Discovery Scanner\n3: 2E Cargo Rack (Capacity: 4)\n3: 1E Standard Docking Computer\n2: 1C Detailed Surface Scanner\n"
},
{
"shipId": "vulture",
"buildName": "Imported Vulture",
"buildCode": "4patfFalddksif31e1e0e0j04044a0n532jf1.Iw19kA==.Aw19kA==",
"buildCode": "4patfFalddksif31e1e0e0j04044a0n532jf1.Iw19kA==.Aw19kA==.",
"buildText": "[Vulture]\nL: 3E/G Pulse Laser\nL: 3E/G Pulse Laser\nU: 0A Frame Shift Wake Scanner\nU: 0A Kill Warrant Scanner\nU: 0A Shield Booster\nU: 0A Shield Booster\n\nBH: 1I Reactive Surface Composite\nRB: 4A Power Plant\nTM: 5A Thrusters\nFH: 4A Frame Shift Drive\nEC: 3D Life Support\nPC: 5A Power Distributor\nSS: 4D Sensors\nFS: 3C Fuel Tank (Capacity: 8)\n\n5: 5A Shield Generator\n4: 4A Auto Field-Maintenance Unit\n2: 2A Shield Cell Bank\n1: 1A Fuel Scoop\n1: 1C Fuel Tank (Capacity: 2)"
}
]
]

View File

@@ -1,50 +1,50 @@
{
"type_6_transporter": {
"Cargo": "0p0tdFal8d8s8f4-----04040303430101.Iw1-kA==.Aw1-kA==",
"Miner": "0p5tdFal8d8s8f42l2l---040403451q0101.Iw1-kA==.Aw1-kA==",
"Hopper": "0p0tdFal8d0s8f41717---030302024300-.Iw1-kA==.Aw1-kA=="
"Cargo": "A0p0tdFal8d8s8f4-----04040303430101.Iw1/kA==.Aw1/kA==.",
"Miner": "A0p5tdFal8d8s8f42l2l---040403451q0101.Iw1/kA==.Aw1/kA==.",
"Hopper": "A0p0tdFal8d0s8f41717---030302024300-.Iw1/kA==.Aw1/kA==."
},
"type_7_transport": {
"Cargo": "0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==",
"Miner": "0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ=="
"Cargo": "A0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==.",
"Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==."
},
"federal_dropship": {
"Cargo": "0pdtiFflnddsif4-1717------05040448020201.Iw18aQ==.Aw18aQ=="
"Cargo": "A0pdtiFflnddsif4-1717------05040448--020201.Iw18eQ==.Aw18eQ==."
},
"asp": {
"Miner": "2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ=="
"Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==."
},
"imperial_clipper": {
"Cargo": "0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==",
"Dream": "2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.Iw18aQ==.Aw18aQ==",
"Current": "0patkFflndfskf4----------------.Iw18aQ==.Aw18aQ=="
"Cargo": "A0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==.",
"Dream": "A2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.AwRj4yWU1I==.CwBhCYy6YRigzLIA.",
"Current": "A0patkFflndfskf4----------------.AwRj4yWU1I==.CwBhCYy6YRigzLIA."
},
"type_9_heavy": {
"Current": "0patsFklndnsif6---------0706054a0303020224.Iw18eQ==.Aw18eQ=="
"Current": "A0patsFklndnsif6---------0706054a0303020224.AwRj4yoo.EwBhEYy6dsg=."
},
"python": {
"Cargo": "0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==",
"Miner": "0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.Aw18eQ==",
"Dream": "2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw18eQ==.Aw18eQ==",
"Missile": "0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ=="
"Cargo": "A0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==.",
"Miner": "A0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.IwBhBYy6dkCYg===.",
"Dream": "A2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw1+gDBxA===.EwBhEYy6e0WEA===.",
"Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==."
},
"anaconda": {
"Dream": "4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d040303326b.Iw18ZlA=.Aw18ZlA=",
"Cargo": "0patnFklndnsxf5----------------0605050504040445030301.Iw18ZlA=.Aw18ZlA=",
"Current": "0patnFklndksxf5----------------0605050504040403034524.Iw18ZlA=.Aw18ZlA=",
"Explorer": "0patnFklndksxf5--------0202------f7050505040s372f2i4524.Iw18ZlA=.Aw18ZlA=",
"Test": "4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.Iw18ZlA=.Aw18ZlA="
"Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04-0303326b.AwRj4yo5dyg=.MwBhCYy6duvARiA=.",
"Cargo": "A0patnFklndnsxf5----------------06050505040404-45030301.Iw18ZVA=.Aw18ZVA=.",
"Current": "A0patnFklndksxf5----------------06050505040404-03034524.Iw18ZVA=.Aw18ZVA=.",
"Explorer": "A0patnFklndksxf5--------0202------f7050505040s37-2f2i4524.AwRj4yVKJ9hA.AwhMIyumQRhEA===.",
"Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.Iw18ZVA=.Aw18ZVA=."
},
"diamondback_explorer": {
"Explorer": "0p0tdFfldddsdf5---0202--320p432i2f.Iw1-kA==.Aw1-kA=="
"Explorer": "A0p0tdFfldddsdf5---0202--320p432i2f-.AwRj4zTYg===.AwiMIyoo."
},
"vulture": {
"Bounty Hunter": "3patcFalddksff31e1e0404-0l4a5d27662j.Iw19kA==.Aw19kA=="
"Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j.AwRj4z2I.MwBhBYy6oJmAjLIA."
},
"fer_de_lance": {
"Attack": "2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.Aw18aQ=="
"Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.CwBhrSu8EZyA."
},
"eagle": {
"Figther": "4p0t5F5l3d5s5f20p0p24-40532j-.Iw1-EA==.Aw1-EA=="
"Figther": "A4p0t5F5l3d5s5f20p0p24-4053-2j-.Iw18kA==.Aw18kA==."
}
}
}

View File

@@ -33,7 +33,8 @@
},
"anaconda": {
"Dream": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404040l0b0100034k5n05050404040303326b.AwRj4yo5dig=.MwBhEYy6duwEziA=",
"Cargo": "03A7D6A5D4D8D5C----------------060505054d040403030301.AwRj4yuqg===.Aw18ZlA="
"Cargo": "03A7D6A5D4D8D5C----------------060505054d040403030301.AwRj4yuqg===.Aw18ZlA=",
"Modified": "0pyttFolodDsyf5------1717--------05044j-03----2h00.Iw18ZlA=.Aw18ZlA=.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA=="
},
"diamondback_explorer": {
"Explorer": "02A4D5A3D3D3D5C-------320p432i2f.AwRj4zTI.AwiMIypI"
@@ -63,4 +64,4 @@
1,
1
]
}
}

90
__tests__/test-agility.js Normal file
View File

@@ -0,0 +1,90 @@
import Ship from '../src/app/shipyard/Ship';
import { Ships } from 'coriolis-data/dist';
import * as ModuleUtils from '../src/app/shipyard/ModuleUtils';
describe("Agility", function() {
it("correctly calculates speed", function() {
let agilityData = require('./fixtures/agility-data');
for (let shipId in agilityData) {
for (let thrusterId in agilityData[shipId]) {
const thrusterData = agilityData[shipId][thrusterId];
let shipData = Ships[shipId];
let ship = new Ship(shipId, shipData.properties, shipData.slots);
ship.buildWith(shipData.defaults);
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
expect(Math.round(ship.topSpeed)).toBe(thrusterData.speed);
}
}
});
it("correctly calculates boost", function() {
let agilityData = require('./fixtures/agility-data');
for (let shipId in agilityData) {
for (let thrusterId in agilityData[shipId]) {
const thrusterData = agilityData[shipId][thrusterId];
let shipData = Ships[shipId];
let ship = new Ship(shipId, shipData.properties, shipData.slots);
ship.buildWith(shipData.defaults);
// Turn off internals to ensure we have enough power to boost
for (let internal in ship.internal) {
ship.internal[internal].enabled = 0;
}
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
expect(Math.round(ship.topBoost)).toBe(thrusterData.boost);
}
}
});
it("correctly calculates pitch", function() {
let agilityData = require('./fixtures/agility-data');
for (let shipId in agilityData) {
for (let thrusterId in agilityData[shipId]) {
const thrusterData = agilityData[shipId][thrusterId];
let shipData = Ships[shipId];
let ship = new Ship(shipId, shipData.properties, shipData.slots);
ship.buildWith(shipData.defaults);
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
expect(Math.round(ship.pitches[4] * 100) / 100).toBeCloseTo(thrusterData.pitch, 1);
}
}
});
it("correctly calculates roll", function() {
let agilityData = require('./fixtures/agility-data');
for (let shipId in agilityData) {
for (let thrusterId in agilityData[shipId]) {
const thrusterData = agilityData[shipId][thrusterId];
let shipData = Ships[shipId];
let ship = new Ship(shipId, shipData.properties, shipData.slots);
ship.buildWith(shipData.defaults);
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
expect(Math.round(ship.rolls[4] * 100) / 100).toBeCloseTo(thrusterData.roll, 1);
}
}
});
it("correctly calculates yaw", function() {
let agilityData = require('./fixtures/agility-data');
for (let shipId in agilityData) {
for (let thrusterId in agilityData[shipId]) {
const thrusterData = agilityData[shipId][thrusterId];
let shipData = Ships[shipId];
let ship = new Ship(shipId, shipData.properties, shipData.slots);
ship.buildWith(shipData.defaults);
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
expect(Math.round(ship.yaws[4] * 100) / 100).toBeCloseTo(thrusterData.yaw, 1);
}
}
});
});

View File

@@ -1,8 +1,10 @@
jest.dontMock('../src/app/stores/Persist');
jest.dontMock('../src/app/components/TranslatedComponent');
jest.dontMock('../src/app/components/ModalImport');
jest.unmock('../src/app/stores/Persist');
jest.unmock('../src/app/components/TranslatedComponent');
jest.unmock('../src/app/components/ModalImport');
jest.unmock('prop-types');
import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import TU from 'react-testutils-additions';
import Utils from './testUtils';
@@ -129,7 +131,7 @@ describe('Import Modal', function() {
});
});
describe('Import Detailed Build', function() {
describe('Import Detailed V3 Build', function() {
beforeEach(reset);
@@ -142,18 +144,64 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda/4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test%20My%20Ship');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.&bn=Test%20My%20Ship');
});
it('catches an invalid build', function() {
const importData = require('./fixtures/anaconda-test-detailed-export-v3');
pasteText(JSON.stringify(importData).replace('components', 'comps'));
pasteText(JSON.stringify(importData).replace('references', 'refs'));
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('Anaconda Build "Test My Ship": Invalid data');
});
});
describe('Import Detailed V4 Build', function() {
beforeEach(reset);
it('imports a valid v4 build', function() {
const importData = require('./fixtures/anaconda-test-detailed-export-v4');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.H4sIAAAAAAAAA2MUe8HMwPD%2FPwMAAGvB0AkAAAA%3D&bn=Test%20My%20Ship');
});
});
describe('Import Detailed Engineered V4 Build', function() {
beforeEach(reset);
it('imports a valid v4 build', function() {
const importData = require('./fixtures/asp-test-detailed-export-v4');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FA7kMAExxqlSAAAAA&bn=Multi-purpose%20Asp%20Explorer');
});
it('imports a valid v4 build with modifications', function() {
const importData = require('./fixtures/courier-test-detailed-export-v4');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OPUvDYBSFT1OTfkRJjUkbbC3Yj8mlODgUISAtdOlety5ODv0Vgji7O7kJ%2FgzBQX%2BEY7Gg0NKhfY%2FnHQLFDBdynufe9%2BRMCmCb06g29oCgacjiRx6gY6oWKUT8UgLaszqQfHmSnpVFN1uSeXNsJVcj%2FA2EHlZkspIUpUc6UjTXGT85qwHuSEuVc%2F16r99kDQeSSjvSbSjpyUpNK10uJJ3aYqk6smwm1lQ9bOxw71TMm8VanEqq9JW1r3Qo%2BREOLnQHvbWmb7rZIu5VLIyGQGOukPv%2F0WQk5LeEAjPOUDwtAP6bShy2HKAz0HPO%2B5KsP25I79O2I7LvD%2Bz4Il1XAQAA&bn=Multi-purpose%20Imperial%20Courier');
});
});
describe('Import Detaild Builds Array', function() {
beforeEach(reset);
@@ -179,22 +227,75 @@ describe('Import Modal', function() {
});
});
describe('Import Companion API Build', function() {
beforeEach(reset);
it('imports a valid companion API build', function() {
const importData = require('./fixtures/companion-api-import-1');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g2f.AwRj4zNaqA%3D%3D.CwRgDBldUExuBiIlUA%3D%3D.H4sIAAAAAAAAA12STy8DURTFb1szU53Ga8dg2qqqDmJDIoKFxJImumYjVrVqfAALC4lNbcUnkLCoDbEQu0bSlQVhI8JHsJBIQ73rXMkwMYuT9%2Bb87nl%2F7ovoRSL6ikD6TYNINZg5XsWUo7pfrBikr2USlRyXyDuLAhr6ZHanNLOzD5tjOiskysk5dOBvfTB7bjeRW0MNG3ohSBq1bKKxKwyLLUAjmwjpPu4wJx4xVbNI57heDfbUKUAy2xaRUQZpllHoHMHxKqjhhF4LgjtJiFHDmqbrEeVnUJOax7%2FSdRfRwBNotv9wo5kAuZMD2egKyDYcdYl1OBki6z%2BZQjaFnBPyFCM1LefF%2BcgrY0es9FKwbW8ZYj9gmBbxRVRdglMh6BNqnwsk4ouoO4HSIehNoBuBRHwR1QOmsBvHmk6IfMbd2fdCEka%2BjNSexPWGoEkcyX6CnxbxRZQtd%2BPpym%2B31xFtn0iSFPkf%2BBkttZlzB9KDFyBuFRfAGV0Ogoff8SSsCfjjD5hGWtLIwZB%2FgX5Zt%2BLHMI9My7sp6nzgZzekswTxVvCOkq%2FSXqb%2F3zfLxh6HrwIAAA%3D%3D&bn=Imported%20Federal%20Corvette');
});
it('imports a valid companion API build', function() {
const importData = require('./fixtures/companion-api-import-2');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i2f.AwRj4yukg%3D%3D%3D.CwRgDBldHi8IUA%3D%3D.H4sIAAAAAAAAA2P8Z8%2FAwPCXEUiIKTMxMPCv%2F%2Ff%2FP8cFIPGf6Z8YTEr0GjMDg%2FJWICERBOTzn%2Fn7%2F7%2FIO5Ai5n9SIEWsQEIoSxAolfbt%2F3%2BJPk4GBhE7YQYGYVmgcuVnf4Aq%2FwOVAAAyiFctbgAAAA%3D%3D&bn=Imported%20Beluga%20Liner');
});
it('imports a valid companion API build', function() {
const importData = require('./fixtures/companion-api-import-3');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/type_7_transport?code=A0patfFflidasdf5----0404040005050504044d2402.AwRj4yrI.CwRgDBlVK7EiA%3D%3D%3D.&bn=Imported%20Type-7%20Transporter');
});
it('imports a valid companion API build', function() {
const importData = require('./fixtures/companion-api-import-4');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/cobra_mk_iii?code=A0p0tdFaldd3sdf4------34---2f2i.AwRj4yKA.CwRgDMYExrezBUg%3D.&bn=Imported%20Cobra%20Mk%20III');
});
});
describe('Import E:D Shipyard Builds', function() {
it('imports a valid builds', function() {
const imports = require('./fixtures/ed-shipyard-import-valid');
for (let i = 0; i < imports.length; i++ ) {
reset();
let fixture = imports[i];
pasteText(fixture.buildText);
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/' + fixture.shipId + '/' + fixture.buildCode + '?bn=' + encodeURIComponent(fixture.buildName));
}
});
// it('imports a valid build', function() {
// const imports = require('./fixtures/ed-shipyard-import-valid');
//
// for (let i = 0; i < imports.length; i++ ) {
// reset();
// let fixture = imports[i];
// pasteText(fixture.buildText);
// expect(modal.state.importValid).toBeTruthy();
// expect(modal.state.errorMsg).toEqual(null);
// clickProceed();
// expect(MockRouter.go.mock.calls.length).toBe(1);
// expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/' + fixture.shipId + '?code=' + encodeURIComponent(fixture.buildCode) + '&bn=' + encodeURIComponent(fixture.buildName));
// }
// });
it('catches invalid builds', function() {
const imports = require('./fixtures/ed-shipyard-import-invalid');

View File

@@ -1,4 +1,4 @@
jest.dontMock('../src/app/stores/Persist');
jest.unmock('../src/app/stores/Persist');
import React from 'react';
import ReactDOM from 'react-dom';

View File

@@ -4,16 +4,16 @@ import * as Serializer from '../src/app/shipyard/Serializer';
import jsen from 'jsen';
describe("Serializer", function() {
const anacondaTestExport = require.requireActual('./fixtures/anaconda-test-detailed-export-v3');
const anacondaTestExport = require.requireActual('./fixtures/anaconda-test-detailed-export-v4');
const code = anacondaTestExport.references[0].code;
const anaconda = Ships.anaconda;
const validate = jsen(require('../src/schemas/ship-loadout/3'));
const validate = jsen(require('../src/schemas/ship-loadout/4'));
describe("To Detailed Build", function() {
let testBuild = new Ship('anaconda', anaconda.properties, anaconda.slots).buildFrom(code);
let exportData = Serializer.toDetailedBuild('Test My Ship', testBuild);
it("conforms to the v3 ship-loadout schema", function() {
it("conforms to the v4 ship-loadout schema", function() {
expect(validate(exportData)).toBe(true);
});
@@ -31,7 +31,7 @@ describe("Serializer", function() {
const builds = require('./fixtures/expected-builds');
const exportData = Serializer.toDetailedExport(builds);
it("conforms to the v3 ship-loadout schema", function() {
it("conforms to the v4 ship-loadout schema", function() {
expect(exportData instanceof Array).toBe(true);
for (let detailedBuild of exportData) {

View File

@@ -24,7 +24,7 @@ describe("Ship", function() {
expect(ship.fuelCapacity).toBeGreaterThan(0, s + ' fuelCapacity');
expect(ship.unladenFastestRange).toBeGreaterThan(0, s + ' unladenFastestRange');
expect(ship.ladenFastestRange).toBeGreaterThan(0, s + ' ladenFastestRange');
expect(ship.shieldStrength).toBeGreaterThan(0, s + ' shieldStrength');
expect(ship.shield).toBeGreaterThan(0, s + ' shield');
expect(ship.armour).toBeGreaterThan(0, s + ' armour');
expect(ship.topSpeed).toBeGreaterThan(0, s + ' topSpeed');
}

View File

@@ -1,11 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
const TestUtils = {
createContextProvider: function(context) {
var _contextTypes = {};
Object.keys(context).forEach(function(key) {
_contextTypes[key] = React.PropTypes.any;
_contextTypes[key] = PropTypes.any;
});
return React.createClass({
@@ -21,4 +22,4 @@ const TestUtils = {
};
export default TestUtils;
export default TestUtils;

11
d3-funcs.js vendored Normal file
View File

@@ -0,0 +1,11 @@
export {
axisBottom,
axisLeft,
axisTop,
formatLocale,
line,
scaleBand,
scaleLinear,
scaleOrdinal,
select
} from 'd3';

7312
d3.js vendored Normal file

File diff suppressed because it is too large Load Diff

3
d3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
devServer.js Normal file → Executable file
View File

@@ -5,6 +5,7 @@ var config = require('./webpack.config.dev');
new WebpackDevServer(webpack(config), {
publicPath: config.output.publicPath,
hot: true,
disableHostCheck: true,
headers: { "Access-Control-Allow-Origin": "*" },
historyApiFallback: {
rewrites: [

12862
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

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

9
rollup.config.js Normal file
View File

@@ -0,0 +1,9 @@
import nodeResolve from "rollup-plugin-node-resolve";
export default {
entry: "d3-funcs.js",
format: "umd",
moduleName: "d3",
plugins: [nodeResolve({jsnext: true})],
dest: "d3.js"
};

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import Router from './Router';
import { EventEmitter } from 'fbemitter';
import { getLanguage } from './i18n/Language';
@@ -6,7 +7,12 @@ import Persist from './stores/Persist';
import Header from './components/Header';
import Tooltip from './components/Tooltip';
import ModalExport from './components/ModalExport';
import ModalHelp from './components/ModalHelp';
import ModalImport from './components/ModalImport';
import ModalPermalink from './components/ModalPermalink';
import * as CompanionApiUtils from './utils/CompanionApiUtils';
import * as JournalUtils from './utils/JournalUtils';
import AboutPage from './pages/AboutPage';
import NotFoundPage from './pages/NotFoundPage';
@@ -15,24 +21,26 @@ import ComparisonPage from './pages/ComparisonPage';
import ShipyardPage from './pages/ShipyardPage';
import ErrorDetails from './pages/ErrorDetails';
const zlib = require('pako');
/**
* Coriolis App
*/
export default class Coriolis extends React.Component {
static childContextTypes = {
closeMenu: React.PropTypes.func.isRequired,
hideModal: React.PropTypes.func.isRequired,
language: React.PropTypes.object.isRequired,
noTouch: React.PropTypes.bool.isRequired,
onCommand: React.PropTypes.func.isRequired,
onWindowResize: React.PropTypes.func.isRequired,
openMenu: React.PropTypes.func.isRequired,
route: React.PropTypes.object.isRequired,
showModal: React.PropTypes.func.isRequired,
sizeRatio: React.PropTypes.number.isRequired,
termtip: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired
closeMenu: PropTypes.func.isRequired,
hideModal: PropTypes.func.isRequired,
language: PropTypes.object.isRequired,
noTouch: PropTypes.bool.isRequired,
onCommand: PropTypes.func.isRequired,
onWindowResize: PropTypes.func.isRequired,
openMenu: PropTypes.func.isRequired,
route: PropTypes.object.isRequired,
showModal: PropTypes.func.isRequired,
sizeRatio: PropTypes.number.isRequired,
termtip: PropTypes.func.isRequired,
tooltip: PropTypes.func.isRequired
};
/**
@@ -52,6 +60,7 @@ export default class Coriolis extends React.Component {
this._onLanguageChange = this._onLanguageChange.bind(this);
this._onSizeRatioChange = this._onSizeRatioChange.bind(this);
this._keyDown = this._keyDown.bind(this);
this._importBuild = this._importBuild.bind(this);
this.emitter = new EventEmitter();
this.state = {
@@ -63,13 +72,43 @@ export default class Coriolis extends React.Component {
};
Router('', (r) => this._setPage(ShipyardPage, r));
Router('/import?', (r) => this._importBuild(r));
Router('/import/:data', (r) => this._importBuild(r));
Router('/outfit/?', (r) => this._setPage(OutfittingPage, r));
Router('/outfit/:ship/?', (r) => this._setPage(OutfittingPage, r));
Router('/outfit/:ship/:code?', (r) => this._setPage(OutfittingPage, r));
Router('/compare/:name?', (r) => this._setPage(ComparisonPage, r));
Router('/comparison?', (r) => this._setPage(ComparisonPage, r));
Router('/comparison/:code', (r) => this._setPage(ComparisonPage, r));
Router('/about', (r) => this._setPage(AboutPage, r));
Router('*', (r) => this._setPage(null, r));
}
/**
* Import a build directly
* @param {Object} r The current route
*/
_importBuild(r) {
try {
// Need to decode and gunzip the data, then build the ship
const data = zlib.inflate(new Buffer(r.params.data, 'base64'), { to: 'string' });
const json = JSON.parse(data);
console.info('Ship import data: ');
console.info(json);
let ship;
if (json && json.modules) {
ship = CompanionApiUtils.shipFromJson(json);
} else if (json && json.Modules) {
ship = JournalUtils.shipFromLoadoutJSON(json);
}
r.params.ship = ship.id;
r.params.code = ship.toString();
this._setPage(OutfittingPage, r);
} catch (err) {
this._onError('Failed to import ship', r.path, 0, 0, err);
}
}
/**
* Updates / Sets the page and route context
* @param {[type]} page The page to be shown
@@ -93,6 +132,13 @@ export default class Coriolis extends React.Component {
*/
_onError(msg, scriptUrl, line, col, errObj) {
console && console.error && console.error(arguments); // eslint-disable-line no-console
if (errObj) {
if (errObj instanceof Error) {
bugsnagClient.notify(errObj) // eslint-disable-line
} else if (errObj instanceof String) {
bugsnagClient.notify(msg, errObj) // eslint-disable-line
}
}
this.setState({
error: <ErrorDetails error={{ message: msg, details: { scriptUrl, line, col, error: JSON.stringify(errObj) } }}/>,
page: null,
@@ -131,14 +177,26 @@ export default class Coriolis extends React.Component {
this._hideModal();
this._closeMenu();
break;
case 72: // 'h'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + h
e.preventDefault();
this._showModal(<ModalHelp />);
}
break;
case 73: // 'i'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
e.preventDefault();
this._showModal(<ModalImport />);
}
break;
case 101010: // 's'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
case 79: // 'o'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + o
e.preventDefault();
this._showModal(<ModalPermalink url={window.location.href}/>);
}
break;
case 83: // 's'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + s
e.preventDefault();
this.emitter.emit('command', 'save');
}
@@ -199,14 +257,19 @@ export default class Coriolis extends React.Component {
/**
* Show the term tip
* @param {string} term Term or Phrase
* @param {Object} opts Options - dontCap, orientation (n,e,s,w)
* @param {Object} opts Options - dontCap, orientation (n,e,s,w) (can also be the event if no options supplied)
* @param {SyntheticEvent} event Event
* @param {SyntheticEvent} e2 Alternative location for synthetic event from charts (where 'Event' is actually a chart index)
*/
_termtip(term, opts, event) {
if (opts && opts.nativeEvent) { // Opts is a SyntheticEvent
_termtip(term, opts, event, e2) {
if (opts && opts.nativeEvent) { // Opts is the SyntheticEvent
event = opts;
opts = { cap: true };
}
if (e2 instanceof Object && e2.nativeEvent) { // E2 is the SyntheticEvent
event = e2;
}
this._tooltip(
<div className={'cen' + (opts.cap ? ' cap' : '')}>{this.state.language.translate(term)}</div>,
event.currentTarget.getBoundingClientRect(),
@@ -292,6 +355,8 @@ export default class Coriolis extends React.Component {
<footer>
<div className="right cap">
<a href="https://github.com/EDCD/coriolis" target="_blank" title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
<br/>
<a href={'https://github.com/EDCD/coriolis/compare/edcd:develop@{' + window.CORIOLIS_DATE + '}...edcd:develop'} target="_blank" title={'Coriolis Commits since' + window.CORIOLIS_DATE}>Commits since last release ({window.CORIOLIS_DATE})</a>
</div>
</footer>
</div>;

View File

@@ -1,5 +1,6 @@
import Persist from './stores/Persist';
import ReactGA from 'react-ga';
ReactGA.initialize('UA-55840909-18');
let standalone = undefined;
/**
@@ -76,7 +77,16 @@ Router.go = function(path, state) {
if (isStandAlone()) {
Persist.setState(ctx);
}
history.pushState(ctx.state, ctx.title, ctx.canonicalPath);
try {
history.pushState(ctx.state, ctx.title, ctx.canonicalPath);
} catch (ex) {
sessionStorage.setItem('__safari_history_fix', JSON.stringify({
state: ctx.state,
title: ctx.title,
path: ctx.canonicalPath
}));
location.reload();
}
}
return ctx;
};
@@ -99,7 +109,16 @@ Router.replace = function(path, state, dispatch) {
if (isStandAlone()) {
Persist.setState(ctx);
}
history.replaceState(ctx.state, ctx.title, ctx.canonicalPath);
try {
history.replaceState(ctx.state, ctx.title, ctx.canonicalPath);
} catch (ex) {
sessionStorage.setItem('__safari_history_fix', JSON.stringify({
state: ctx.state,
title: ctx.title,
path: ctx.canonicalPath
}));
location.reload();
}
return ctx;
};
@@ -239,9 +258,16 @@ Route.prototype.match = function(path, params) {
* @param {string} path Path to track
*/
function gaTrack(path) {
if (window.ga) {
window.ga('send', 'pageview', path);
const match = path.match(/\/outfit\/(.*)(\?code=.*)/);
if (match) {
if (match[1]) {
ReactGA.ga('set', 'contentGroup1', match[1]);
}
if (match[2]) {
ReactGA.ga('set', 'contentGroup2', match[2]);
}
}
ReactGA.pageview(path);
}
/**

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import TranslatedComponent from './TranslatedComponent';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames';
@@ -7,18 +8,107 @@ import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
/*
* Categorisation of module groups
*/
const GRPCAT = {
'sg': 'shields',
'bsg': 'shields',
'psg': 'shields',
'scb': 'shields',
'cc': 'limpet controllers',
'fx': 'limpet controllers',
'hb': 'limpet controllers',
'pc': 'limpet controllers',
'rpl': 'limpet controllers',
'pce': 'passenger cabins',
'pci': 'passenger cabins',
'pcm': 'passenger cabins',
'pcq': 'passenger cabins',
'fh': 'hangars',
'pv': 'hangars',
'fs': 'fuel',
'ft': 'fuel',
'hr': 'structural reinforcement',
'mrp': 'structural reinforcement',
'bl': 'lasers',
'pl': 'lasers',
'ul': 'lasers',
'ml': 'lasers',
'c': 'projectiles',
'mc': 'projectiles',
'axmc': 'experimental',
'fc': 'projectiles',
'rfl': 'experimental',
'pa': 'projectiles',
'rg': 'projectiles',
'mr': 'ordnance',
'axmr': 'experimental',
'rcpl': 'experimental',
'tp': 'ordnance',
'nl': 'ordnance',
'sc': 'scanners',
'ss': 'scanners',
// Utilities
'cs': 'scanners',
'kw': 'scanners',
'ws': 'scanners',
'xs': 'scanners',
'ch': 'defence',
'po': 'defence',
'ec': '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
const CATEGORIES = {
// Internals
'am': ['am'],
'cr': ['cr'],
'fi': ['fi'],
'fuel': ['ft', 'fs'],
'hangars': ['fh', 'pv'],
'limpet controllers': ['cc', 'fx', 'hb', 'pc', 'rpl'],
'passenger cabins': ['pce', 'pci', 'pcm', 'pcq'],
'rf': ['rf'],
'shields': ['sg', 'bsg', 'psg', 'scb'],
'structural reinforcement': ['hr', 'mrp'],
'dc': ['dc'],
// Hardpoints
'lasers': ['pl', 'ul', 'bl', 'ml'],
'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'],
'ordnance': ['mr', 'tp', 'nl'],
// Utilities
'sb': ['sb'],
'hs': ['hs'],
'defence': ['ch', 'po', 'ec'],
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
// Experimental
'experimental': ['axmc', 'axmr', 'rfl', 'xs', 'sfn', 'rcpl'],
// Guardian
'guardian': ['gpp', 'gpc', 'ggc']
};
/**
* Available modules menu
*/
export default class AvailableModulesMenu extends TranslatedComponent {
static propTypes = {
modules: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.array]).isRequired,
onSelect: React.PropTypes.func.isRequired,
diffDetails: React.PropTypes.func,
m: React.PropTypes.object,
shipMass: React.PropTypes.number,
warning: React.PropTypes.func
modules: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
onSelect: PropTypes.func.isRequired,
diffDetails: PropTypes.func,
m: PropTypes.object,
shipMass: PropTypes.number,
warning: PropTypes.func,
firstSlotId: PropTypes.string,
lastSlotId: PropTypes.string,
activeSlotId: PropTypes.string,
slotDiv: PropTypes.object
};
static defaultProps = {
@@ -34,6 +124,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
super(props);
this._hideDiff = this._hideDiff.bind(this);
this.state = this._initState(props, context);
this.slotItems = [];// Array to hold <li> refs.
}
/**
@@ -44,8 +135,9 @@ export default class AvailableModulesMenu extends TranslatedComponent {
*/
_initState(props, context) {
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 buildGroup = this._buildGroup.bind(
this,
translate,
@@ -56,48 +148,109 @@ export default class AvailableModulesMenu extends TranslatedComponent {
this._hideDiff(event);
onSelect(m);
}
);
);
if (modules instanceof Array) {
list = buildGroup(modules[0].grp, modules);
} else {
list = [];
// At present time slots with grouped options (Hardpoints and Internal) can be empty
list.push(<div className='empty-c upp' key='empty' onClick={onSelect.bind(null, null)} >{translate('empty')}</div>);
for (let g in modules) {
if (m && g == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={g} className={'select-group cap'}>{translate(g)}</div>);
} else {
list.push(<div key={g} className={'select-group cap'}>{translate(g)}</div>);
}
if (m) {
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>);
}
list.push(buildGroup(g, modules[g]));
// Need to regroup the modules by our own categorisation
let catmodules = {};
// Pre-create to preserve ordering
for (let cat in CATEGORIES) {
catmodules[cat] = [];
}
for (let g in modules) {
const moduleCategory = GRPCAT[g] || g;
const existing = catmodules[moduleCategory] || [];
catmodules[moduleCategory] = existing.concat(modules[g]);
}
for (let category in catmodules) {
let categoryHeader = false;
// Order through CATEGORIES if present
const categories = CATEGORIES[category] || [category];
if (categories && categories.length) {
for (let n in categories) {
const grp = categories[n];
// We now have the group and the category. We might not have any modules, though...
if (modules[grp]) {
// Decide if we need a category header as well as a group header
if (categories.length === 1) {
// Show category header instead of group header
if (m && grp == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={category} className={'select-category upp'}>{translate(category)}</div>);
} else {
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
}
} else {
// Show category header as well as group header
if (!categoryHeader) {
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
categoryHeader = true;
}
if (m && grp == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={grp} className={'select-group cap'}>{translate(grp)}</div>);
} else {
list.push(<div key={grp} className={'select-group cap'}>{translate(grp)}</div>);
}
}
list.push(buildGroup(grp, modules[grp]));
}
}
}
}
}
return { list, currentGroup };
let trackingFocus = false;
return { list, currentGroup, trackingFocus };
}
/**
* Generate React Components for Module Group
* @param {Function} translate Translate function
* @param {Objecy} mountedModule Mounted Module
* @param {Funciton} warningFunc Warning function
* @param {Object} mountedModule Mounted Module
* @param {Function} warningFunc Warning function
* @param {number} mass Mass
* @param {function} onSelect Select/Mount callback
* @param {string} grp Group name
* @param {Array} modules Available modules
* @param {string} firstSlotId id of first slot item
* @param {string} lastSlotId id of last slot item
* @return {React.Component} Available Module Group contents
*/
_buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules) {
let prevClass = null, prevRating = null;
_buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules, firstSlotId, lastSlotId) {
let prevClass = null, prevRating = null, prevName;
let elems = [];
for (let i = 0; i < modules.length; i++) {
let m = modules[i];
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
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]));
let itemsOnThisRow = 0;
for (let i = 0; i < sortedModules.length; i++) {
let m = sortedModules[i];
let mount = null;
let disabled = m.maxmass && (mass + (m.mass ? m.mass : 0)) > m.maxmass;
let active = mountedModule && mountedModule === m;
let disabled = false;
prevName = m.name;
if (ModuleUtils.isShieldGenerator(m.grp)) {
// Shield generators care about maximum hull mass
disabled = mass > m.maxmass;
} else if (m.maxmass) {
// Thrusters care about total mass
disabled = mass + m.mass > m.maxmass;
}
let active = mountedModule && mountedModule.id === m.id;
let classes = cn(m.name ? 'lc' : 'c', {
warning: !disabled && warningFunc && warningFunc(m),
active,
@@ -105,9 +258,21 @@ export default class AvailableModulesMenu extends TranslatedComponent {
});
let eventHandlers;
if (disabled || active) {
eventHandlers = {};
if (disabled) {
eventHandlers = {
onKeyDown: this._keyDown.bind(this, null),
onKeyUp: this._keyUp.bind(this, null)
};
} 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;
if (active) this.activeSlotId = sortedModules[i].id;
this.lastSlotId = sortedModules[i].id;
let showDiff = this._showDiff.bind(this, mountedModule, m);
let select = onSelect.bind(null, m);
@@ -116,7 +281,9 @@ export default class AvailableModulesMenu extends TranslatedComponent {
onTouchStart: this._touchStart.bind(this, showDiff),
onTouchEnd: this._touchEnd.bind(this, select),
onMouseLeave: this._hideDiff,
onClick: select
onClick: select,
onKeyDown: this._keyDown.bind(this, select),
onKeyUp: this._keyUp.bind(this, select)
};
}
@@ -125,22 +292,28 @@ export default class AvailableModulesMenu extends TranslatedComponent {
case 'G': mount = <MountGimballed className={'lg'}/>; break;
case 'T': mount = <MountTurret className={'lg'}/>; break;
}
if (i > 0 && modules.length > 3 && m.class != prevClass && (m.rating != prevRating || m.mount) && m.grp != 'pa') {
elems.push(<br key={'b' + m.grp + i} />);
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)) {
elems.push(<br key={'b' + m.grp + i} />);
itemsOnThisRow = 0;
}
let tbIdx = (classes.indexOf('disabled') < 0) ? 0 : undefined;
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 ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
</li>
);
itemsOnThisRow++;
prevClass = m.class;
prevRating = m.rating;
prevName = m.name;
}
return <ul key={'modules' + grp} >{elems}</ul>;
return <ul key={'modules' + grp}>{elems}</ul>;
}
/**
@@ -191,6 +364,41 @@ export default class AvailableModulesMenu extends TranslatedComponent {
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) {
let className = event.currentTarget.attributes['class'].value;
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
select();
return;
}
let 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
* @param {Function} select Select module callback
* @param {SytheticEvent} event Event
*/
_keyUp(select,event) {
// nothing here yet
}
/**
* Hide diff tooltip
* @param {SyntheticEvent} event Event
@@ -201,12 +409,69 @@ export default class AvailableModulesMenu extends TranslatedComponent {
this.context.tooltip();
}
/**
* Order two modules suitably for display in module selection
* @param {Object} a the first module
* @param {Object} b the second module
* @return {int} -1 if the first module should go first, 1 if the second module should go first
*/
_moduleOrder(a, b) {
// Named modules go last
if (!a.name && b.name) {
return -1;
}
if (a.name && !b.name) {
return 1;
}
// Class ordered from highest (8) to lowest (1)
if (a.class < b.class) {
return 1;
}
if (a.class > b.class) {
return -1;
}
// Mount type, if applicable
if (a.mount && b.mount && a.mount !== b.mount) {
if (a.mount === 'F' || (a.mount === 'G' && b.mount === 'T')) {
return -1;
} else {
return 1;
}
}
// Rating ordered from highest (A) to lowest (E)
if (a.rating < b.rating) {
return -1;
}
if (a.rating > b.rating) {
return 1;
}
// Do not attempt to order by name at this point, as that mucks up the order of armour
return 0;
}
/**
* Scroll to mounted (if it exists) module group on mount
*/
componentDidMount() {
if (this.groupElem) { // Scroll to currently selected group
findDOMNode(this).scrollTop = this.groupElem.offsetTop;
this.node.scrollTop = this.groupElem.offsetTop;
}
/**
* Set focus on active or first slot element, if applicable.
*/
if (this.slotItems[this.activeSlotId]) {
this.slotItems[this.activeSlotId].focus();
} else if (this.slotItems[this.firstSlotId]) {
this.slotItems[this.firstSlotId].focus();
}
}
/**
* Handle focus if the component updates
*
*/
componentWillUnmount() {
if(this.props.slotDiv) {
this.props.slotDiv.focus();
}
}
@@ -225,7 +490,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
*/
render() {
return (
<div
<div ref={node => this.node = node}
className={cn('select', this.props.className)}
onScroll={this._hideDiff}
onClick={(e) => e.stopPropagation() }

View File

@@ -1,5 +1,6 @@
import React from 'react';
import d3 from 'd3';
import PropTypes from 'prop-types';
import * as d3 from 'd3';
import TranslatedComponent from './TranslatedComponent';
const MARGIN = { top: 15, right: 20, bottom: 40, left: 150 };
@@ -44,17 +45,17 @@ export default class BarChart extends TranslatedComponent {
unit: ''
};
static PropTypes = {
colors: React.PropTypes.array,
data: React.PropTypes.array.isRequired,
desc: React.PropTypes.bool,
format: React.PropTypes.string.isRequired,
labels: React.PropTypes.array,
predicate: React.PropTypes.string,
properties: React.PropTypes.array,
title: React.PropTypes.string.isRequired,
unit: React.PropTypes.string.isRequired,
width: React.PropTypes.number.isRequired
static propTypes = {
colors: PropTypes.array,
data: PropTypes.array.isRequired,
desc: PropTypes.bool,
format: PropTypes.string.isRequired,
labels: PropTypes.array,
predicate: PropTypes.string,
properties: PropTypes.array,
title: PropTypes.string.isRequired,
unit: PropTypes.string.isRequired,
width: PropTypes.number.isRequired
};
/**
@@ -68,13 +69,13 @@ export default class BarChart extends TranslatedComponent {
this._updateDimensions = this._updateDimensions.bind(this);
this._hideTip = this._hideTip.bind(this);
let scale = d3.scale.linear();
let y0 = d3.scale.ordinal();
let y1 = d3.scale.ordinal();
let scale = d3.scaleLinear();
let y0 = d3.scaleBand();
let y1 = d3.scaleBand();
this.xAxis = d3.svg.axis().scale(scale).ticks(5).outerTickSize(0).orient('bottom').tickFormat(context.language.formats.s2);
this.yAxis = d3.svg.axis().scale(y0).outerTickSize(0).orient('left');
this.state = { scale, y0, y1, color: d3.scale.ordinal().range(props.colors) };
this.xAxis = d3.axisBottom(scale).ticks(5).tickSizeOuter(0).tickFormat(context.language.formats.s2);
this.yAxis = d3.axisLeft(y0).tickSizeOuter(0);
this.state = { scale, y0, y1, color: d3.scaleOrdinal().range(props.colors) };
}
/**
@@ -131,8 +132,8 @@ export default class BarChart extends TranslatedComponent {
let max = data.reduce((max, build) => (properties.reduce(((m, p) => (m > build[p] ? m : build[p])), max)), 0);
this.state.scale.range([0, innerWidth]).domain([0, max]);
this.state.y0.domain(data.map(bName)).rangeRoundBands([0, innerHeight], 0.3);
this.state.y1.domain(properties).rangeRoundBands([0, this.state.y0.rangeBand()]);
this.state.y0.domain(data.map(bName)).range([0, innerHeight], 0.3).padding(0.4);
this.state.y1.domain(properties).range([0, this.state.y0.bandwidth()]).padding(0.1);
this.setState({
barHeight,
@@ -192,7 +193,7 @@ export default class BarChart extends TranslatedComponent {
x={0}
y={y1(p)}
width={scale(build[p])}
height={y1.rangeBand()}
height={y1.bandwidth()}
fill={color(p)}
onMouseOver={this._showTip.bind(this, build, p, propIndex)}
onMouseOut={this._hideTip}

View File

@@ -0,0 +1,90 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import { Pip } from './SvgIcons';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
/**
* Boost displays a boost button that toggles bosot
* Requires an onChange() function of the form onChange(boost) which is triggered whenever the boost changes.
*/
export default class Boost extends TranslatedComponent {
static propTypes = {
marker: PropTypes.string.isRequired,
ship: PropTypes.object.isRequired,
boost: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
const { ship, boost } = props;
this._keyDown = this._keyDown.bind(this);
this._toggleBoost = this._toggleBoost.bind(this);
}
/**
* Add listeners after mounting
*/
componentDidMount() {
document.addEventListener('keydown', this._keyDown);
}
/**
* Remove listeners before unmounting
*/
componentWillUnmount() {
document.removeEventListener('keydown', this._keyDown);
}
/**
* Handle Key Down
* @param {Event} e Keyboard Event
*/
_keyDown(e) {
if (e.ctrlKey || e.metaKey) { // CTRL/CMD
switch (e.keyCode) {
case 66: // b == boost
if (this.props.ship.canBoost()) {
e.preventDefault();
this._toggleBoost();
}
break;
}
}
}
/**
* Toggle the boost feature
*/
_toggleBoost() {
this.props.onChange(!this.props.boost);
}
/**
* Render boost
* @return {React.Component} contents
*/
render() {
const { formats, translate, units } = this.context.language;
const { ship, boost } = this.props;
// TODO disable if ship cannot boost
return (
<span id='boost'>
<button id='boost' className={boost ? 'selected' : null} onClick={this._toggleBoost}>{translate('boost')}</button>
</span>
);
}
}

View File

@@ -0,0 +1,76 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import Slider from '../components/Slider';
/**
* Cargo slider
* Requires an onChange() function of the form onChange(cargo), providing the cargo in tonnes, which is triggered on cargo level change
*/
export default class Cargo extends TranslatedComponent {
static propTypes = {
cargo: PropTypes.number.isRequired,
cargoCapacity: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this._cargoChange = this._cargoChange.bind(this);
}
/**
* Update cargo level
* @param {number} cargoLevel percentage level from 0 to 1
*/
_cargoChange(cargoLevel) {
const { cargo, cargoCapacity } = this.props;
if (cargoCapacity > 0) {
// We round the cargo to whole number of tonnes
const newCargo = Math.round(cargoLevel * cargoCapacity);
if (newCargo != cargo) {
this.props.onChange(newCargo);
}
}
}
/**
* Render cargo slider
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { cargo, cargoCapacity } = this.props;
return (
<span>
<h3>{translate('cargo carried')}: {formats.int(cargo)}{units.T}</h3>
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody >
<tr>
<td>
<Slider
axis={true}
onChange={this._cargoChange}
axisUnit={translate('T')}
percent={cargo / cargoCapacity}
max={cargoCapacity}
scale={sizeRatio}
onResize={onWindowResize}
/>
</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -1,8 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import Link from './Link';
import cn from 'classnames';
import { SizeMap } from '../shipyard/Constants';
import { outfitURL } from '../utils/UrlGenerators';
/**
@@ -11,11 +12,11 @@ import { SizeMap } from '../shipyard/Constants';
export default class ComparisonTable extends TranslatedComponent {
static propTypes = {
facets: React.PropTypes.array.isRequired,
builds: React.PropTypes.array.isRequired,
onSort: React.PropTypes.func.isRequired,
predicate: React.PropTypes.string.isRequired, // Used only to test again prop changes for shouldRender
desc: React.PropTypes.oneOfType([React.PropTypes.bool.isRequired, React.PropTypes.number.isRequired]), // Used only to test again prop changes for shouldRender
facets: PropTypes.array.isRequired,
builds: PropTypes.array.isRequired,
onSort: PropTypes.func.isRequired,
predicate: PropTypes.string.isRequired, // Used only to test again prop changes for shouldRender
desc: PropTypes.oneOfType([PropTypes.bool.isRequired, PropTypes.number.isRequired]), // Used only to test again prop changes for shouldRender
};
/**
@@ -71,21 +72,22 @@ export default class ComparisonTable extends TranslatedComponent {
* @return {React.Component} Table row
*/
_buildRow(build, facets, formats, units) {
let url = `/outfit/${build.id}/${build.toString()}?bn=${build.buildName}`;
let cells = [
<td key='s' className='tl'><Link href={url}>{build.name}</Link></td>,
<td key='bn' className='tl'><Link href={url}>{build.buildName}</Link></td>
];
if (build && build.id && build.buildName) {
let url = outfitURL(build.id, build.toString(), build.buildName);
let cells = [
<td key='s' className='tl'><Link href={url}>{build.name}</Link></td>,
<td key='bn' className='tl'><Link href={url}>{build.buildName}</Link></td>
];
for (let f of facets) {
if (f.active) {
for (let p of f.props) {
cells.push(<td key={p}>{formats[f.fmt](build[p])}{f.unit ? units[f.unit] : null}</td>);
for (let f of facets) {
if (f.active) {
for (let p of f.props) {
cells.push(<td key={p}>{formats[f.fmt](build[p])}{f.unit ? units[f.unit] : null}</td>);
}
}
}
return <tr key={build.id + build.buildName} className='tr'>{cells}</tr>;
}
return <tr key={build.id + build.buildName} className='tr'>{cells}</tr>;
}
/**

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist';
@@ -6,16 +7,17 @@ import Ship from '../shipyard/Ship';
import { Insurance } from '../shipyard/Constants';
import { slotName, slotComparator } from '../utils/SlotFunctions';
import TranslatedComponent from './TranslatedComponent';
import { ShoppingIcon } from '../components/SvgIcons';
/**
* Cost Section
*/
export default class CostSection extends TranslatedComponent {
static PropTypes = {
ship: React.PropTypes.object.isRequired,
code: React.PropTypes.string.isRequired,
buildName: React.PropTypes.string
static propTypes = {
ship: PropTypes.object.isRequired,
code: PropTypes.string.isRequired,
buildName: PropTypes.string
};
/**
@@ -31,6 +33,7 @@ export default class CostSection extends TranslatedComponent {
this._buildRetrofitShip = this._buildRetrofitShip.bind(this);
this._onBaseRetrofitChange = this._onBaseRetrofitChange.bind(this);
this._defaultRetrofitName = this._defaultRetrofitName.bind(this);
this._eddbShoppingList = this._eddbShoppingList.bind(this);
let data = Ships[props.ship.id]; // Retrieve the basic ship properties, slots and defaults
let retrofitName = this._defaultRetrofitName(props.ship.id, props.buildName);
@@ -325,12 +328,27 @@ export default class CostSection extends TranslatedComponent {
</div>;
}
/**
* Open up a window for EDDB with a shopping list of our retrofit components
*/
_eddbShoppingList() {
const { retrofitCosts } = this.state;
const { ship } = this.props;
// Provide unique list of non-PP module EDDB IDs to buy
const modIds = retrofitCosts.filter(item => item.retroItem.incCost && item.buyId && !item.buyPp).map(item => item.buyId).filter((v, i, a) => a.indexOf(v) === i);
// Open up the relevant URL
window.open('https://eddb.io/station?m=' + modIds.join(','));
}
/**
* Render the retofit tab
* @return {React.Component} Tab contents
*/
_retrofitTab() {
let { retrofitTotal, retrofitCosts, moduleDiscount, retrofitName } = this.state;
const { termtip, tooltip } = this.context;
let { translate, formats, units } = this.context.language;
let int = formats.int;
let rows = [], options = [<option key='stock' value=''>{translate('Stock')}</option>];
@@ -370,7 +388,8 @@ export default class CostSection extends TranslatedComponent {
<tbody>
{rows}
<tr className='ri'>
<td colSpan='4' className='lbl' >{translate('cost')}</td>
<td className='lbl' ><button onClick={this._eddbShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_REFIT_SHOPPING_LIST')} onMouseOut={tooltip.bind(null, null)}><ShoppingIcon className='lg' style={{ fill: 'black' }}/></button></td>
<td colSpan='3' className='lbl' >{translate('cost')}</td>
<td colSpan='2' className={cn('val', retrofitTotal > 0 ? 'warning' : 'secondary-disabled')} style={{ borderBottom:'none' }}>
{int(retrofitTotal)}{units.CR}
</td>
@@ -379,7 +398,7 @@ export default class CostSection extends TranslatedComponent {
<td colSpan='4' className='lbl cap' >{translate('retrofit from')}</td>
<td className='val cen' style={{ borderRight: 'none', width: '1em' }}><u className='primary-disabled'>&#9662;</u></td>
<td className='val' style={{ borderLeft:'none', padding: 0 }}>
<select style={{ width: '100%', padding: 0 }} value={retrofitName} onChange={this._onBaseRetrofitChange}>
<select style={{ width: '100%', padding: 0 }} value={retrofitName || translate('Stock')} onChange={this._onBaseRetrofitChange}>
{options}
</select>
</td>
@@ -403,6 +422,8 @@ export default class CostSection extends TranslatedComponent {
if (ship.bulkheads.m.index != retrofitShip.bulkheads.m.index) {
item = {
buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating,
buyId: ship.bulkheads.m.eddbID,
buyPp: ship.bulkheads.m.pp,
buyName: ship.bulkheads.m.name,
sellClassRating: retrofitShip.bulkheads.m.class + retrofitShip.bulkheads.m.rating,
sellName: retrofitShip.bulkheads.m.name,
@@ -419,9 +440,13 @@ export default class CostSection extends TranslatedComponent {
let retroSlotGroup = retrofitShip[g];
let slotGroup = ship[g];
for (i = 0, l = slotGroup.length; i < l; i++) {
if (slotGroup[i].m != retroSlotGroup[i].m) {
const modId = slotGroup[i].m ? slotGroup[i].m.eddbID : null;
const retroModId = retroSlotGroup[i].m ? retroSlotGroup[i].m.eddbID : null;
if (modId != retroModId) {
item = { netCost: 0, retroItem: retroSlotGroup[i] };
if (slotGroup[i].m) {
item.buyId = slotGroup[i].m.eddbID,
item.buyPp = slotGroup[i].m.pp,
item.buyName = slotGroup[i].m.name || slotGroup[i].m.grp;
item.buyClassRating = slotGroup[i].m.class + slotGroup[i].m.rating;
item.netCost = slotGroup[i].discountedCost;
@@ -505,19 +530,19 @@ export default class CostSection extends TranslatedComponent {
scoop = true;
break;
case 'scb':
q = slotGroup[i].m.cells;
q = slotGroup[i].m.getAmmo() + 1;
break;
case 'am':
q = slotGroup[i].m.ammo;
q = slotGroup[i].m.getAmmo();
break;
case 'pv':
srvs += slotGroup[i].m.vehicles;
srvs += slotGroup[i].m.getBays();
break;
case 'fx': case 'hb': case 'cc': case 'pc':
limpets = ship.cargoCapacity;
break;
default:
q = slotGroup[i].m.clip + slotGroup[i].m.ammo;
q = slotGroup[i].m.getClip() + slotGroup[i].m.getAmmo();
}
// Calculate ammo costs only if a cost is specified
if (slotGroup[i].m.ammocost > 0) {
@@ -530,6 +555,17 @@ export default class CostSection extends TranslatedComponent {
ammoCosts.push(item);
ammoTotal += item.total;
}
// Add fighters
if (slotGroup[i].m.grp === 'fh') {
item = {
m: slotGroup[i].m,
max: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays(),
cost: slotGroup[i].m.fightercost,
total: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays() * slotGroup[i].m.fightercost
};
ammoCosts.push(item);
ammoTotal += item.total;
}
}
}
}
@@ -550,12 +586,13 @@ export default class CostSection extends TranslatedComponent {
item = {
m: { name: 'SRVs', class: '', rating: '' },
max: srvs,
cost: 6005,
total: srvs * 6005
cost: 1030,
total: srvs * 1030
};
ammoCosts.push(item);
ammoTotal += item.total;
}
// Calculate refuel costs if no scoop present
if (!scoop) {
item = {

View File

@@ -0,0 +1,262 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import * as Calc from '../shipyard/Calculations';
import PieChart from './PieChart';
import VerticalBarChart from './VerticalBarChart';
/**
* Defence information
* Shield information consists of four panels:
* - textual information (time to lose shields etc.)
* - breakdown of shield sources (pie chart)
* - comparison of shield resistances (bar chart)
* - effective shield (bar chart)
*/
export default class Defence extends TranslatedComponent {
static propTypes = {
marker: PropTypes.string.isRequired,
ship: PropTypes.object.isRequired,
opponent: PropTypes.object.isRequired,
engagementrange: PropTypes.number.isRequired,
sys: PropTypes.number.isRequired,
opponentWep: PropTypes.number.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(props.ship, props.opponent, props.sys, props.opponentWep, props.engagementrange);
this.state = { shield, armour, shielddamage, armourdamage };
}
/**
* Update the state if our properties change
* @param {Object} nextProps Incoming/Next properties
* @return {boolean} Returns true if the component should be rerendered
*/
componentWillReceiveProps(nextProps) {
if (this.props.marker != nextProps.marker || this.props.sys != nextProps.sys) {
const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(nextProps.ship, nextProps.opponent, nextProps.sys, nextProps.opponentWep, nextProps.engagementrange);
this.setState({ shield, armour, shielddamage, armourdamage });
}
return true;
}
/**
* Render defence
* @return {React.Component} contents
*/
render() {
const { ship, sys, opponentWep } = this.props;
const { language, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { shield, armour, shielddamage, armourdamage } = this.state;
const pd = ship.standard[4].m;
const shieldSourcesData = [];
const effectiveShieldData = [];
const shieldDamageTakenData = [];
const shieldSourcesTt = [];
const shieldDamageTakenAbsoluteTt = [];
const shieldDamageTakenExplosiveTt = [];
const shieldDamageTakenKineticTt = [];
const shieldDamageTakenThermalTt = [];
const effectiveShieldAbsoluteTt = [];
const effectiveShieldExplosiveTt = [];
const effectiveShieldKineticTt = [];
const effectiveShieldThermalTt = [];
let maxEffectiveShield = 0;
if (shield.total) {
shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') });
shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
if (shield.generator > 0) {
shieldSourcesTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
effectiveShieldAbsoluteTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
effectiveShieldExplosiveTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
effectiveShieldKineticTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
effectiveShieldThermalTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
if (shield.boosters > 0) {
shieldSourcesTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
effectiveShieldAbsoluteTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
effectiveShieldExplosiveTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
effectiveShieldKineticTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
effectiveShieldThermalTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
}
if (shield.cells > 0) {
shieldSourcesTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
effectiveShieldAbsoluteTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
effectiveShieldExplosiveTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
effectiveShieldKineticTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
effectiveShieldThermalTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
}
// Add effective shield from resistances
const rawMj = shield.generator + shield.boosters + shield.cells;
const explosiveMj = rawMj / (shield.explosive.generator * shield.explosive.boosters) - rawMj;
if (explosiveMj != 0) effectiveShieldExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(explosiveMj)}{units.MJ}</div>);
const kineticMj = rawMj / (shield.kinetic.generator * shield.kinetic.boosters) - rawMj;
if (kineticMj != 0) effectiveShieldKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(kineticMj)}{units.MJ}</div>);
const thermalMj = rawMj / (shield.thermal.generator * shield.thermal.boosters) - rawMj;
if (thermalMj != 0) effectiveShieldThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}</div>);
// Add effective shield from power distributor SYS pips
if (shield.absolute.sys != 1) {
effectiveShieldAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.sys - rawMj)}{units.MJ}</div>);
effectiveShieldExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.explosive.sys - rawMj)}{units.MJ}</div>);
effectiveShieldKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.sys - rawMj)}{units.MJ}</div>);
effectiveShieldThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.thermal.sys - rawMj)}{units.MJ}</div>);
}
}
shieldDamageTakenAbsoluteTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}</div>);
shieldDamageTakenAbsoluteTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}</div>);
shieldDamageTakenAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.absolute.sys)}</div>);
shieldDamageTakenExplosiveTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.explosive.generator)}</div>);
shieldDamageTakenExplosiveTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.explosive.boosters)}</div>);
shieldDamageTakenExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.explosive.sys)}</div>);
shieldDamageTakenKineticTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.kinetic.generator)}</div>);
shieldDamageTakenKineticTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.kinetic.boosters)}</div>);
shieldDamageTakenKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.kinetic.sys)}</div>);
shieldDamageTakenThermalTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.thermal.generator)}</div>);
shieldDamageTakenThermalTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.thermal.boosters)}</div>);
shieldDamageTakenThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.thermal.sys)}</div>);
const effectiveAbsoluteShield = shield.total / shield.absolute.total;
effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute'), tooltip: effectiveShieldAbsoluteTt });
const effectiveExplosiveShield = shield.total / shield.explosive.total;
effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive'), tooltip: effectiveShieldExplosiveTt });
const effectiveKineticShield = shield.total / shield.kinetic.total;
effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic'), tooltip: effectiveShieldKineticTt });
const effectiveThermalShield = shield.total / shield.thermal.total;
effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal'), tooltip: effectiveShieldThermalTt });
shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute'), tooltip: shieldDamageTakenAbsoluteTt });
shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive'), tooltip: shieldDamageTakenExplosiveTt });
shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic'), tooltip: shieldDamageTakenKineticTt });
shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal'), tooltip: shieldDamageTakenThermalTt });
maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max);
}
const armourSourcesData = [];
armourSourcesData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') });
armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
const armourSourcesTt = [];
const effectiveArmourAbsoluteTt = [];
const effectiveArmourExplosiveTt = [];
const effectiveArmourKineticTt = [];
const effectiveArmourThermalTt = [];
if (armour.bulkheads > 0) {
armourSourcesTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourAbsoluteTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
if (armour.reinforcement > 0) {
armourSourcesTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourAbsoluteTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
}
}
const rawArmour = armour.bulkheads + armour.reinforcement;
const armourDamageTakenTt = [];
armourDamageTakenTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}</div>);
armourDamageTakenTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.absolute.reinforcement)}</div>);
const armourDamageTakenExplosiveTt = [];
armourDamageTakenExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}</div>);
armourDamageTakenExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}</div>);
if (armour.explosive.bulkheads * armour.explosive.reinforcement != 1) effectiveArmourExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.explosive.bulkheads * armour.explosive.reinforcement) - rawArmour)}</div>);
const armourDamageTakenKineticTt = [];
armourDamageTakenKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}</div>);
armourDamageTakenKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}</div>);
if (armour.kinetic.bulkheads * armour.kinetic.reinforcement != 1) effectiveArmourKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.kinetic.bulkheads * armour.kinetic.reinforcement) - rawArmour)}</div>);
const armourDamageTakenThermalTt = [];
armourDamageTakenThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}</div>);
armourDamageTakenThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}</div>);
if (armour.thermal.bulkheads * armour.thermal.reinforcement != 1) effectiveArmourThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.thermal.bulkheads * armour.thermal.reinforcement) - rawArmour)}</div>);
const effectiveArmourData = [];
const effectiveAbsoluteArmour = armour.total / armour.absolute.total;
effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute'), tooltip: effectiveArmourAbsoluteTt });
const effectiveExplosiveArmour = armour.total / armour.explosive.total;
effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive'), tooltip: effectiveArmourExplosiveTt });
const effectiveKineticArmour = armour.total / armour.kinetic.total;
effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic'), tooltip: effectiveArmourKineticTt });
const effectiveThermalArmour = armour.total / armour.thermal.total;
effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal'), tooltip: effectiveArmourThermalTt });
const armourDamageTakenData = [];
armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourDamageTakenTt });
armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive'), tooltip: armourDamageTakenExplosiveTt });
armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic'), tooltip: armourDamageTakenKineticTt });
armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourDamageTakenThermalTt });
return (
<span id='defence'>
{shield.total ? <span>
<div className='group quarter'>
<h2>{translate('shield metrics')}</h2>
<br/>
<h2 onMouseOver={termtip.bind(null, <div>{shieldSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}<br/>{formats.int(shield.total)}{units.MJ}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}<br/>{shielddamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(shield.total, shielddamage.totalsdps, shielddamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECOVER'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}<br/>{shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECHARGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}<br/>{shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}</h2>
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
<PieChart data={shieldSourcesData} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
<VerticalBarChart data={shieldDamageTakenData} yMax={140} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_SHIELD'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective shield')}(MJ)</h2>
<VerticalBarChart data={effectiveShieldData} yMax={maxEffectiveShield}/>
</div>
</span> : null }
<div className='group quarter'>
<h2>{translate('armour metrics')}</h2>
<h2 onMouseOver={termtip.bind(null, <div>{armourSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')}<br/>{formats.int(armour.total)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_ARMOUR')}<br/>{armourdamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(armour.total, armourdamage.totalsdps, armourdamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('raw module armour')}<br/>{formats.int(armour.modulearmour)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_EXTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')}<br/>{formats.pct1(armour.moduleprotection / 2)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_INTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_INTERNAL')}<br/>{formats.pct1(armour.moduleprotection)}</h2>
<br/>
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_ARMOUR_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('armour sources')}</h2>
<PieChart data={armourSourcesData} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
<VerticalBarChart data={armourDamageTakenData} yMax={100} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective armour')}</h2>
<VerticalBarChart data={effectiveArmourData} />
</div>
</span>);
}
}

View File

@@ -0,0 +1,102 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import Slider from '../components/Slider';
/**
* Engagement range slider
* Requires an onChange() function of the form onChange(range), providing the range in metres, which is triggered on range change
*/
export default class EngagementRange extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
engagementRange: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
const { ship } = props;
const maxRange = Math.round(this._calcMaxRange(ship));
this.state = {
maxRange
};
}
/**
* Calculate the maximum range of a ship's weapons
* @param {Object} ship The ship
* @returns {int} The maximum range, in metres
*/
_calcMaxRange(ship) {
let maxRange = 1000;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const thisRange = ship.hardpoints[i].m.getRange();
if (thisRange > maxRange) {
maxRange = thisRange;
}
}
}
return maxRange;
}
/**
* Update range
* @param {number} rangeLevel percentage level from 0 to 1
*/
_rangeChange(rangeLevel) {
const { maxRange } = this.state;
// We round the range to an integer value
const range = Math.round(rangeLevel * maxRange);
if (range !== this.props.engagementRange) {
this.props.onChange(range);
}
}
/**
* Render range slider
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { engagementRange } = this.props;
const { maxRange } = this.state;
return (
<span>
<h3>{translate('engagement range')}: {formats.int(engagementRange)}{translate('m')}</h3>
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody >
<tr>
<td>
<Slider
axis={true}
onChange={this._rangeChange.bind(this)}
axisUnit={translate('m')}
percent={engagementRange / maxRange}
max={maxRange}
scale={sizeRatio}
onResize={onWindowResize}
/>
</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -0,0 +1,106 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
import * as Calc from '../shipyard/Calculations';
/**
* Engine profile for a given ship
*/
export default class EngineProfile extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
cargo: PropTypes.number.isRequired,
fuel: PropTypes.number.isRequired,
eng: PropTypes.number.isRequired,
boost: PropTypes.bool.isRequired,
marker: PropTypes.string.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
const ship = this.props.ship;
this.state = {
calcMaxSpeedFunc: this.calcMaxSpeed.bind(this, ship, this.props.eng, this.props.boost)
};
}
/**
* Update the state if our ship changes
* @param {Object} nextProps Incoming/Next properties
* @param {Object} nextContext Incoming/Next conext
* @return {boolean} Returns true if the component should be rerendered
*/
componentWillReceiveProps(nextProps, nextContext) {
if (nextProps.marker != this.props.marker) {
this.setState({ calcMaxSpeedFunc: this.calcMaxSpeed.bind(this, nextProps.ship, nextProps.eng, nextProps.boost) });
}
return true;
}
/**
* Calculate the top speed for this ship given thrusters, mass and pips to ENG
* @param {Object} ship The ship
* @param {Object} eng The number of pips to ENG
* @param {Object} boost If boost is enabled
* @param {Object} mass The mass at which to calculate the top speed
* @return {number} The maximum speed
*/
calcMaxSpeed(ship, eng, boost, mass) {
// Obtain the top speed
return Calc.calcSpeed(mass, ship.speed, ship.standard[1].m, ship.pipSpeed, eng, ship.boost / ship.speed, boost);
}
/**
* Render engine profile
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { ship, cargo, eng, fuel, boost } = this.props;
// Calculate bounds for our line chart
const thrusters = ship.standard[1].m;
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
const maxMass = thrusters.getMaxMass();
const mass = ship.unladenMass + fuel + cargo;
const minSpeed = Calc.calcSpeed(maxMass, ship.speed, thrusters, ship.pipSpeed, 0, ship.boost / ship.speed, false);
const maxSpeed = Calc.calcSpeed(minMass, ship.speed, thrusters, ship.pipSpeed, 4, ship.boost / ship.speed, true);
// Add a mark at our current mass
const mark = Math.min(mass, maxMass);
const code = `${ship.toString()}:${cargo}:${fuel}:${eng}:${boost}`;
// This graph can have a precipitous fall-off so we use lots of points to make it look a little smoother
return (
<LineChart
xMin={minMass}
xMax={maxMass}
yMin={minSpeed}
yMax={maxSpeed}
xMark={mark}
xLabel={translate('mass')}
xUnit={translate('T')}
yLabel={translate('maximum speed')}
yUnit={translate('m/s')}
func={this.state.calcMaxSpeedFunc}
points={1000}
code={code}
aspect={0.7}
/>
);
}
}

View File

@@ -0,0 +1,104 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
import * as Calc from '../shipyard/Calculations';
/**
* FSD profile for a given ship
*/
export default class FSDProfile extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
cargo: PropTypes.number.isRequired,
fuel: PropTypes.number.isRequired,
marker: PropTypes.string.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
const ship = this.props.ship;
this.state = {
calcMaxRangeFunc: this._calcMaxRange.bind(this, ship, this.props.fuel)
};
}
/**
* Update the state if our ship changes
* @param {Object} nextProps Incoming/Next properties
* @param {Object} nextContext Incoming/Next conext
* @return {boolean} Returns true if the component should be rerendered
*/
componentWillReceiveProps(nextProps, nextContext) {
if (nextProps.marker != this.props.marker) {
this.setState({ calcMaxRangeFunc: this._calcMaxRange.bind(this, nextProps.ship, nextProps.fuel) });
}
return true;
}
/**
* Calculate the maximum range for this ship across its applicable mass
* @param {Object} ship The ship
* @param {Object} fuel The fuel on the ship
* @param {Object} mass The mass at which to calculate the maximum range
* @return {number} The maximum range
*/
_calcMaxRange(ship, fuel, mass) {
// Obtain the maximum range
return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump()));
}
/**
* Render FSD profile
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { ship, cargo, fuel } = this.props;
// Calculate bounds for our line chart - use thruster info for X
const thrusters = ship.standard[1].m;
const fsd = ship.standard[2].m;
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
const maxMass = thrusters.getMaxMass();
const mass = ship.unladenMass + fuel + cargo;
const minRange = 0;
const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump());
// Add a mark at our current mass
const mark = Math.min(mass, maxMass);
const code = ship.name + ship.toString() + '.' + fuel;
return (
<LineChart
xMin={minMass}
xMax={maxMass}
yMin={minRange}
yMax={maxRange}
xMark={mark}
xLabel={translate('mass')}
xUnit={translate('T')}
yLabel={translate('maximum range')}
yUnit={translate('LY')}
func={this.state.calcMaxRangeFunc}
points={200}
code={code}
aspect={0.7}
/>
);
}
}

View File

@@ -0,0 +1,75 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import Slider from '../components/Slider';
/**
* Fuel slider
* Requires an onChange() function of the form onChange(fuel), providing the fuel in tonnes, which is triggered on fuel level change
*/
export default class Fuel extends TranslatedComponent {
static propTypes = {
fuel: PropTypes.number.isRequired,
fuelCapacity: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this._fuelChange = this._fuelChange.bind(this);
}
/**
* Update fuel level
* @param {number} fuelLevel percentage level from 0 to 1
*/
_fuelChange(fuelLevel) {
const { fuel, fuelCapacity } = this.props;
const newFuel = fuelLevel * fuelCapacity;
// Only send an update if the fuel has changed significantly
if (Math.round(fuel * 10) != Math.round(newFuel * 10)) {
this.props.onChange(Math.round(newFuel * 10) / 10);
}
}
/**
* Render fuel slider
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { fuel, fuelCapacity } = this.props;
return (
<span>
<h3>{translate('fuel carried')}: {formats.f1(fuel)}{units.T}</h3>
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody >
<tr>
<td>
<Slider
axis={true}
onChange={this._fuelChange}
axisUnit={translate('T')}
percent={fuel / fuelCapacity}
max={fuelCapacity}
scale={sizeRatio}
onResize={onWindowResize}
/>
</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -1,6 +1,12 @@
import React from 'react';
import cn from 'classnames';
import Slot from './Slot';
import { DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import Persist from '../stores/Persist';
import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { blueprintTooltip } from '../utils/BlueprintFunctions';
/**
* Hardpoint / Utility Slot
@@ -27,42 +33,76 @@ export default class HardpointSlot extends Slot {
/**
* Generate the slot contents
* @param {Object} m Mounted Module
* @param {Boolean} enabled Slot enabled
* @param {Function} translate Translate function
* @param {Object} formats Localized Formats map
* @param {Object} u Localized Units Map
* @return {React.Component} Slot contents
*/
_getSlotDetails(m, translate, formats, u) {
_getSlotDetails(m, enabled, translate, formats, u) {
if (m) {
let classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
let { drag, drop } = this.props;
let { termtip, tooltip } = this.context;
let validMods = Modifications.modules[m.grp].modifications || [];
let showModuleResistances = Persist.showModuleResistances();
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
// Modifications tooltip shows blueprint and grade, if available
let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) {
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 = (
<div>
<div>{modTT}</div>
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
</div>
);
}
const className = cn('details', enabled ? '' : 'disabled');
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}>
<div className={'l'}>
{m.mount && m.mount == 'F' ? <MountFixed /> : ''}
{m.mount && m.mount == 'G' ? <MountGimballed /> : ''}
{m.mount && m.mount == 'T' ? <MountTurret /> : ''}
{m.type && m.type == 'K' ? <DamageKinetic /> : ''}
{m.type && m.type == 'T' ? <DamageThermal /> : ''}
{m.type && m.type == 'KT' ? <span><DamageKinetic /><DamageThermal /></span> : ''}
{m.type && m.type == 'E' ? <DamageExplosive /> : ''}
{classRating} {translate(m.name || m.grp)}</div>
<div className={'r'}>{m.mass}{u.T}</div>
{m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed /></span> : ''}
{m.mount && m.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : ''}
{m.mount && m.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : ''}
{m.getDamageDist() && m.getDamageDist().K ? <span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span> : ''}
{m.getDamageDist() && m.getDamageDist().T ? <span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span> : ''}
{m.getDamageDist() && m.getDamageDist().E ? <span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span> : ''}
{m.getDamageDist() && m.getDamageDist().A ? <span onMouseOver={termtip.bind(null, 'absolute')} onMouseOut={tooltip.bind(null, null)}><DamageAbsolute /></span> : ''}
{classRating} {translate(m.name || m.grp)}{ m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }
</div>
<div className={'r'}>{formats.round(m.getMass())}{u.T}</div>
</div>
<div className={'cb'}>
{ m.dps ? <div className={'l'}>{translate('DPS')}: {formats.round1(m.dps)} { m.clip ? <span>({formats.round1((m.clip * m.dps / m.rof) / ((m.clip / m.rof) + m.reload)) })</span> : null }</div> : null }
{ m.eps ? <div className={'l'}>{translate('EPS')}: {formats.round1(m.eps)} { m.clip ? <span>({formats.round1((m.clip * m.eps / m.rof) / ((m.clip / m.rof) + m.reload)) })</span> : null }</div> : null }
{ m.hps ? <div className={'l'}>{translate('HPS')}: {formats.round1(m.hps)} { m.clip ? <span>({formats.round1((m.clip * m.hps / m.rof) / ((m.clip / m.rof) + m.reload)) })</span> : null }</div> : null }
{ m.dps && m.eps ? <div className={'l'}>{translate('DPE')}: {formats.round1(m.dps / m.eps)}</div> : null }
{ m.rof ? <div className={'l'}>{translate('ROF')}: {m.rof}{u.ps}</div> : null }
{ m.range && !m.dps ? <div className={'l'}>{translate('Range')} : {formats.round(m.range / 1000)}{u.km}</div> : null }
{ m.shieldmul ? <div className={'l'}>+{formats.rPct(m.shieldmul)}</div> : null }
{ m.ammo >= 0 ? <div className={'l'}>{translate('ammo')}: {formats.int(m.clip)}/{formats.int(m.ammo)}</div> : null }
{ m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
{ m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')} onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) }{u.MW})</span> : null }</div> : null }
{ m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')} onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
{ m.getDps() && m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, 'dpe')} onMouseOut={tooltip.bind(null, null)}>{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}</div> : null }
{ m.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')} onMouseOut={tooltip.bind(null, null)}>{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}</div> : null }
{ m.getRange() ? <div className={'l'}>{translate('range', m.grp)} {formats.f1(m.getRange() / 1000)}{u.km}</div> : null }
{ m.getScanTime() ? <div className={'l'}>{translate('scantime')} {formats.f1(m.getScanTime())}{u.s}</div> : null }
{ m.getFalloff() ? <div className={'l'}>{translate('falloff')} {formats.round(m.getFalloff() / 1000)}{u.km}</div> : null }
{ m.getShieldBoost() ? <div className={'l'}>+{formats.pct1(m.getShieldBoost())}</div> : null }
{ m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}</div> : null }
{ m.getReload() ? <div className={'l'}>{translate('reload')}: {formats.round(m.getReload())}{u.s}</div> : null }
{ m.getShotSpeed() ? <div className={'l'}>{translate('shotspeed')}: {formats.int(m.getShotSpeed())}{u.mps}</div> : null }
{ m.getPiercing() ? <div className={'l'}>{translate('piercing')}: {formats.int(m.getPiercing())}</div> : null }
{ m.getJitter() ? <div className={'l'}>{translate('jitter')}: {formats.f2(m.getJitter())}°</div> : null }
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ m && validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
</div>
</div>;
} else {
return <div className={'empty'}>{translate('empty')}</div>;
}
}
}

View File

@@ -0,0 +1,160 @@
import React from 'react';
import SlotSection from './SlotSection';
import HardpointSlot from './HardpointSlot';
import cn from 'classnames';
import { MountFixed, MountGimballed, MountTurret } from '../components/SvgIcons';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
/**
* Hardpoint slot section
*/
export default class HardpointSlotSection extends SlotSection {
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props, context, 'hardpoints', 'hardpoints');
this._empty = this._empty.bind(this);
this.selectedRefId = null;
this.firstRefId = 'emptyall';
this.lastRefId = 'nl-F';
}
/**
* Handle focus when component updates
* @param {Object} prevProps React Component properties
*/
componentDidUpdate(prevProps) {
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
}
/**
* Empty all slots
*/
_empty() {
this.selectedRefId = 'emptyall';
this.props.ship.emptyWeapons();
this.props.onChange();
this._close();
}
/**
* Fill slots with specified module
* @param {string} group Group name
* @param {string} mount Mount Type - F, G, T
* @param {SyntheticEvent} event Event
*/
_fill(group, mount, event) {
this.selectedRefId = group + '-' + mount;
this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt'));
this.props.onChange();
this._close();
}
/**
* Empty all on section header right click
*/
_contextMenu() {
this._empty();
}
/**
* Generate the slot React Components
* @return {Array} Array of Slots
*/
_getSlots() {
let { ship, currentMenu } = this.props;
let { originSlot, targetSlot } = this.state;
let slots = [];
let hardpoints = ship.hardpoints;
let availableModules = ship.getAvailableModules();
for (let i = 0, l = hardpoints.length; i < l; i++) {
let h = hardpoints[i];
if (h.maxClass) {
slots.push(<HardpointSlot
key={i}
maxClass={h.maxClass}
availableModules={() => availableModules.getHps(h.maxClass)}
onOpen={this._openMenu.bind(this, h)}
onSelect={this._selectModule.bind(this, h)}
onChange={this.props.onChange}
selected={currentMenu == h}
drag={this._drag.bind(this, h)}
dragOver={this._dragOverSlot.bind(this, h)}
drop={this._drop}
dropClass={this._dropClass(h, originSlot, targetSlot)}
ship={ship}
m={h.m}
enabled={h.enabled ? true : false}
/>);
}
}
return slots;
}
/**
* Generate the section drop-down menu
* @param {Function} translate Translate function
* @return {React.Component} Section menu
*/
_getSectionMenu(translate) {
let _fill = this._fill;
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul>
<div className='select-group cap'>{translate('pl')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'pl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'pl', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'pl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-T'] = smRef}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('ul')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'ul', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'ul', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'ul', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-T'] = smRef}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('bl')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'bl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'bl', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'bl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-T'] = smRef}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('mc')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'mc', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'mc', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'mc', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-T'] = smRef}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('c')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'c', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['c-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'c', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['c-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'c', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['c-T'] = smRef}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('fc')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'fc', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['fc-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'fc', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['fc-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'fc', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['fc-T'] = smRef}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('pa')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'pa', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pa-F'] = smRef}>{translate('pa')}</li>
</ul>
<div className='select-group cap'>{translate('nl')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'nl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['nl-F'] = smRef}>{translate('nl')}</li>
</ul>
</div>;
}
}

View File

@@ -1,132 +0,0 @@
import React from 'react';
import SlotSection from './SlotSection';
import HardpointSlot from './HardpointSlot';
import cn from 'classnames';
import { MountFixed, MountGimballed, MountTurret } from '../components/SvgIcons';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
/**
* Hardpoint slot section
*/
export default class HardpointsSlotSection extends SlotSection {
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props, context, 'hardpoints', 'hardpoints');
this._empty = this._empty.bind(this);
}
/**
* Empty all slots
*/
_empty() {
this.props.ship.emptyWeapons();
this.props.onChange();
this._close();
}
/**
* Fill slots with specified module
* @param {string} group Group name
* @param {string} mount Mount Type - F, G, T
* @param {SyntheticEvent} event Event
*/
_fill(group, mount, event) {
this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt'));
this.props.onChange();
this._close();
}
/**
* Empty all on section header right click
*/
_contextMenu() {
this._empty();
}
/**
* Generate the slot React Components
* @return {Array} Array of Slots
*/
_getSlots() {
let { ship, currentMenu } = this.props;
let { originSlot, targetSlot } = this.state;
let slots = [];
let hardpoints = ship.hardpoints;
let availableModules = ship.getAvailableModules();
for (let i = 0, l = hardpoints.length; i < l; i++) {
let h = hardpoints[i];
if (h.maxClass) {
slots.push(<HardpointSlot
key={i}
maxClass={h.maxClass}
availableModules={() => availableModules.getHps(h.maxClass)}
onOpen={this._openMenu.bind(this, h)}
onSelect={this._selectModule.bind(this, h)}
selected={currentMenu == h}
drag={this._drag.bind(this, h)}
dragOver={this._dragOverSlot.bind(this, h)}
drop={this._drop}
dropClass={this._dropClass(h, originSlot, targetSlot)}
ship={ship}
m={h.m}
/>);
}
}
return slots;
}
/**
* Generate the section drop-down menu
* @param {Function} translate Translate function
* @return {React.Component} Section menu
*/
_getSectionMenu(translate) {
let _fill = this._fill;
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul>
<div className='select-group cap'>{translate('pl')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'pl', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'pl', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'pl', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('ul')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'ul', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'ul', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'ul', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('bl')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'bl', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'bl', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'bl', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('mc')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'mc', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'mc', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'mc', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('c')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'c', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'c', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'c', 'T')}><MountTurret className='lg'/></li>
</ul>
</div>;
}
}

View File

@@ -5,12 +5,13 @@ import { Insurance } from '../shipyard/Constants';
import Link from './Link';
import ActiveLink from './ActiveLink';
import cn from 'classnames';
import { Cogs, CoriolisLogo, Hammer, Rocket, StatsBars } from './SvgIcons';
import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons';
import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist';
import { toDetailedExport } from '../shipyard/Serializer';
import ModalDeleteAll from './ModalDeleteAll';
import ModalExport from './ModalExport';
import ModalHelp from './ModalHelp';
import ModalImport from './ModalImport';
import Slider from './Slider';
import { outfitURL } from '../utils/UrlGenerators';
@@ -27,7 +28,7 @@ function normalizePercent(val) {
if (val === '' || isNaN(val)) {
return 0;
}
val = Math.round(val * 100) / 100;
val = Math.round(val * 1000) / 1000;
return val >= 100 ? 100 : val;
}
@@ -53,11 +54,11 @@ function selectAll(e) {
*/
export default class Header extends TranslatedComponent {
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this.shipOrder = Object.keys(Ships).sort();
@@ -74,6 +75,7 @@ export default class Header extends TranslatedComponent {
this._openBuilds = this._openMenu.bind(this, 'b');
this._openComp = this._openMenu.bind(this, 'comp');
this._openSettings = this._openMenu.bind(this, 'settings');
this._showHelp = this._showHelp.bind(this);
this.languageOptions = [];
this.insuranceOptions = [];
this.state = {
@@ -203,6 +205,13 @@ export default class Header extends TranslatedComponent {
Persist.showTooltips(!Persist.showTooltips());
}
/**
* Toggle module resistances setting
*/
_toggleModuleResistances() {
Persist.showModuleResistances(!Persist.showModuleResistances());
}
/**
* Show delete all modal
* @param {SyntheticEvent} e Event
@@ -241,6 +250,17 @@ export default class Header extends TranslatedComponent {
/>);
}
/**
* Show help modal
* @param {SyntheticEvent} e Event
*/
_showHelp(e) {
let translate = this.context.language.translate;
e.preventDefault();
this.context.showModal(<ModalHelp title={translate('help')} />);
}
/**
* Show import modal
* @param {SyntheticEvent} e Event
@@ -336,7 +356,7 @@ export default class Header extends TranslatedComponent {
let comps = Object.keys(Persist.getComparisons()).sort();
for (let name of comps) {
comparisons.push(<ActiveLink key={name} href={'/compare/' + name} className='block name'>{name}</ActiveLink>);
comparisons.push(<ActiveLink key={name} href={'/compare/' + encodeURIComponent(name)} className='block name'>{name}</ActiveLink>);
}
} else {
comparisons = <span className='cap'>{translate('none created')}</span>;
@@ -359,6 +379,7 @@ export default class Header extends TranslatedComponent {
_getSettingsMenu() {
let translate = this.context.language.translate;
let tips = Persist.showTooltips();
let moduleResistances = Persist.showModuleResistances();
return (
<div className='menu-list no-wrap cap' onClick={ (e) => e.stopPropagation() }>
@@ -376,6 +397,10 @@ export default class Header extends TranslatedComponent {
<td>{translate('tooltips')}</td>
<td className={cn('ri', { disabled: !tips, 'primary-disabled': tips })}>{(tips ? '✓' : '✗')}</td>
</tr>
<tr className='cap ptr' onClick={this._toggleModuleResistances} >
<td>{translate('module resistances')}</td>
<td className={cn('ri', { disabled: !moduleResistances, 'primary-disabled': moduleResistances })}>{(moduleResistances ? '✓' : '✗')}</td>
</tr>
<tr>
<td>{translate('insurance')}</td>
<td className='ri'>
@@ -438,6 +463,7 @@ export default class Header extends TranslatedComponent {
Persist.addListener('deletedAll', update);
Persist.addListener('builds', update);
Persist.addListener('tooltips', update);
Persist.addListener('moduleresistances', update);
}
/**
@@ -479,6 +505,9 @@ export default class Header extends TranslatedComponent {
return (
<header>
{this.props.appCacheUpdate && <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>}
{this.props.appCacheUpdate ? <a className={'view-changes'} href={'https://github.com/EDCD/coriolis/compare/edcd:develop@{' + window.CORIOLIS_DATE + '}...edcd:develop'} target="_blank">
{'View Release Changes'}
</a> : null}
<Link className='l' href='/' style={{ marginRight: '1em' }} title='Home'><CoriolisLogo className='icon xl' /></Link>
<div className='l menu'>
@@ -508,8 +537,14 @@ export default class Header extends TranslatedComponent {
</div>
{openedMenu == 'settings' ? this._getSettingsMenu() : null}
</div>
<div className='r menu'>
<div className={cn('menu-header')} onClick={this._showHelp}>
<Help className='xl warning'/>
</div>
</div>
</header>
);
}
}
}

View File

@@ -1,6 +1,11 @@
import React from 'react';
import cn from 'classnames';
import Slot from './Slot';
import { Infinite } from './SvgIcons';
import Persist from '../stores/Persist';
import { ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { blueprintTooltip } from '../utils/BlueprintFunctions';
/**
* Internal Slot
@@ -10,38 +15,77 @@ export default class InternalSlot extends Slot {
/**
* Generate the slot contents
* @param {Object} m Mounted Module
* @param {Boolean} enabled Slot enabled
* @param {Function} translate Translate function
* @param {Object} formats Localized Formats map
* @param {Object} u Localized Units Map
* @return {React.Component} Slot contents
*/
_getSlotDetails(m, translate, formats, u) {
_getSlotDetails(m, enabled, translate, formats, u) {
if (m) {
let classRating = m.class + m.rating;
let { drag, drop } = this.props;
let { drag, drop, ship } = this.props;
let { termtip, tooltip } = this.context;
let validMods = (Modifications.modules[m.grp] ? Modifications.modules[m.grp].modifications : []);
let showModuleResistances = Persist.showModuleResistances();
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
// Modifications tooltip shows blueprint and grade, if available
let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) {
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 = (
<div>
<div>{modTT}</div>
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
</div>
);
}
let mass = m.getMass() || m.cargo || m.fuel || 0;
const className = cn('details', enabled ? '' : 'disabled');
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}>
<div className={'l'}>{classRating} {translate(m.name || m.grp)}</div>
<div className={'r'}>{m.mass || m.cargo || m.fuel || 0}{u.T}</div>
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : ''}</div>
<div className={'r'}>{formats.round(mass)}{u.T}</div>
</div>
<div className={'cb'}>
{ m.optmass ? <div className={'l'}>{translate('optimal mass')}: {m.optmass}{u.T}</div> : null }
{ m.maxmass ? <div className={'l'}>{translate('max mass')}: {m.maxmass}{u.T}</div> : null }
{ m.getOptMass() ? <div className={'l'}>{translate('optmass', 'sg')}: {formats.int(m.getOptMass())}{u.T}</div> : null }
{ m.getMaxMass() ? <div className={'l'}>{translate('maxmass', 'sg')}: {formats.int(m.getMaxMass())}{u.T}</div> : null }
{ m.bins ? <div className={'l'}>{m.bins} <u>{translate('bins')}</u></div> : null }
{ m.bays ? <div className={'l'}>{translate('bays')}: {m.bays}</div> : null }
{ m.rebuildsperbay ? <div className={'l'}>{translate('rebuildsperbay')}: {m.rebuildsperbay}</div> : null }
{ m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs}&nbsp;&nbsp;&nbsp;{translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null }
{ m.ammo ? <div className={'l'}>{translate('ammo')}: {formats.gen(m.ammo)}</div> : null }
{ m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null }
{ m.recharge ? <div className={'l'}>{translate('recharge')}: {m.recharge} <u>MJ</u>&nbsp;&nbsp;&nbsp;{translate('total')}: {m.cells * m.recharge}{u.MJ}</div> : null }
{ m.getAmmo() && m.grp !== 'scb' ? <div className={'l'}>{translate('ammunition')}: {formats.gen(m.getAmmo())}</div> : null }
{ m.getSpinup() ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}</div> : null }
{ m.getDuration() ? <div className={'l'}>{translate('duration')}: {formats.f1(m.getDuration())}{u.s}</div> : null }
{ m.grp === 'scb' ? <div className={'l'}>{translate('cells')}: {formats.int(m.getAmmo() + 1)}</div> : null }
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.f1(m.getDuration() * m.getShieldReinforcement())}{u.MJ}</div> : null }
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('total')}: {formats.int((m.getAmmo() + 1) * (m.getDuration() * m.getShieldReinforcement()))}{u.MJ}</div> : null }
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
{ m.range ? <div className={'l'}>{translate('range')} {m.range}{u.km}</div> : null }
{ m.time ? <div className={'l'}>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.getFacingLimit() ? <div className={'l'}>{translate('facinglimit')} {formats.f1(m.getFacingLimit())}°</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.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.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
{ m.rangeLS === null ? <div className={'l'}>{u.Ls}</div> : null }
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
{ m.armouradd ? <div className={'l'}>+{m.armouradd} <u className='cap'>{translate('armour')}</u></div> : null }
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
{ m.passengers ? <div className={'l'}>{translate('passengers')}: {m.passengers}</div> : null }
{ m.getRegenerationRate() ? <div className='l'>{translate('regen')}: {formats.round1(m.getRegenerationRate())}{u.ps}</div> : null }
{ m.getBrokenRegenerationRate() ? <div className='l'>{translate('brokenregen')}: {formats.round1(m.getBrokenRegenerationRate())}{u.ps}</div> : null }
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ m.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.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ m && validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
</div>
</div>;
} else {

View File

@@ -4,6 +4,7 @@ import SlotSection from './SlotSection';
import InternalSlot from './InternalSlot';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { canMount } from '../utils/SlotFunctions';
/**
* Internal slot section
@@ -16,18 +17,35 @@ export default class InternalSlotSection extends SlotSection {
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props, context, 'internal', 'internal compartments');
super(props, context, 'internal', 'optional internal');
this._empty = this._empty.bind(this);
this._fillWithCargo = this._fillWithCargo.bind(this);
this._fillWithCells = this._fillWithCells.bind(this);
this._fillWithArmor = this._fillWithArmor.bind(this);
this._fillWithModuleReinforcementPackages = this._fillWithModuleReinforcementPackages.bind(this);
this._fillWithFuelTanks = this._fillWithFuelTanks.bind(this);
this._fillWithLuxuryCabins = this._fillWithLuxuryCabins.bind(this);
this._fillWithFirstClassCabins = this._fillWithFirstClassCabins.bind(this);
this._fillWithBusinessClassCabins = this._fillWithBusinessClassCabins.bind(this);
this._fillWithEconomyClassCabins = this._fillWithEconomyClassCabins.bind(this);
this.selectedRefId = null;
this.firstRefId = 'emptyall';
this.lastRefId = this.sectionRefArr['pcq'] ? 'pcq' : 'pcm';
}
/**
* Handle focus when component updates
* @param {Object} prevProps React Component properties
*/
componentDidUpdate(prevProps) {
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
}
/**
* Empty all slots
*/
_empty() {
this.selectedRefId = 'emptyall';
this.props.ship.emptyInternal();
this.props.onChange();
this._close();
@@ -38,10 +56,11 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithCargo(event) {
this.selectedRefId = 'cargo';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
if (clobber || !slot.m) {
if ((clobber || !slot.m) && canMount(ship, slot, 'cr')) {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
}
});
@@ -49,16 +68,102 @@ export default class InternalSlotSection extends SlotSection {
this._close();
}
/**
* Fill all slots with fuel tanks
* @param {SyntheticEvent} event Event
*/
_fillWithFuelTanks(event) {
this.selectedRefId = 'ft';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
if ((clobber || !slot.m) && canMount(ship, slot, 'ft')) {
ship.use(slot, ModuleUtils.findInternal('ft', slot.maxClass, 'C'));
}
});
this.props.onChange();
this._close();
}
/**
* Fill all slots with luxury passenger cabins
* @param {SyntheticEvent} event Event
*/
_fillWithLuxuryCabins(event) {
this.selectedRefId = 'pcq';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
if ((clobber || !slot.m) && canMount(ship, slot, 'pcq')) {
ship.use(slot, ModuleUtils.findInternal('pcq', Math.min(slot.maxClass, 6), 'B')); // Passenger cabins top out at 6
}
});
this.props.onChange();
this._close();
}
/**
* Fill all slots with first class passenger cabins
* @param {SyntheticEvent} event Event
*/
_fillWithFirstClassCabins(event) {
this.selectedRefId = 'pcm';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
if ((clobber || !slot.m) && canMount(ship, slot, 'pcm')) {
ship.use(slot, ModuleUtils.findInternal('pcm', Math.min(slot.maxClass, 6), 'C')); // Passenger cabins top out at 6
}
});
this.props.onChange();
this._close();
}
/**
* Fill all slots with business class passenger cabins
* @param {SyntheticEvent} event Event
*/
_fillWithBusinessClassCabins(event) {
this.selectedRefId = 'pci';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
if ((clobber || !slot.m) && canMount(ship, slot, 'pci')) {
ship.use(slot, ModuleUtils.findInternal('pci', Math.min(slot.maxClass, 6), 'D')); // Passenger cabins top out at 6
}
});
this.props.onChange();
this._close();
}
/**
* Fill all slots with economy class passenger cabins
* @param {SyntheticEvent} event Event
*/
_fillWithEconomyClassCabins(event) {
this.selectedRefId = 'pce';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
if ((clobber || !slot.m) && canMount(ship, slot, 'pce')) {
ship.use(slot, ModuleUtils.findInternal('pce', Math.min(slot.maxClass, 6), 'E')); // Passenger cabins top out at 6
}
});
this.props.onChange();
this._close();
}
/**
* Fill all slots with Shield Cell Banks
* @param {SyntheticEvent} event Event
*/
_fillWithCells(event) {
this.selectedRefId = 'scb';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
let chargeCap = 0; // Capacity of single activation
ship.internal.forEach(function(slot) {
if ((!slot.m || (clobber && !ModuleUtils.isShieldGenerator(slot.m.grp))) && (!slot.eligible || slot.eligible.scb)) { // Check eligibility due to passenger ships special case
if ((clobber && !(slot.m && ModuleUtils.isShieldGenerator(slot.m.grp)) || !slot.m) && canMount(ship, slot, 'scb')) {
ship.use(slot, ModuleUtils.findInternal('scb', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, chargeCap <= ship.shieldStrength); // Don't waste cell capacity on overcharge
chargeCap += slot.m.recharge;
@@ -73,10 +178,11 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithArmor(event) {
this.selectedRefId = 'hr';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
if (clobber || !slot.m) {
if ((clobber || !slot.m) && canMount(ship, slot, 'hr')) {
ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D
}
});
@@ -84,6 +190,23 @@ export default class InternalSlotSection extends SlotSection {
this._close();
}
/**
* Fill all slots with Module Reinforcement Packages
* @param {SyntheticEvent} event Event
*/
_fillWithModuleReinforcementPackages(event) {
this.selectedRefId = 'mrp';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
if ((clobber || !slot.m) && canMount(ship, slot, 'mrp')) {
ship.use(slot, ModuleUtils.findInternal('mrp', Math.min(slot.maxClass, 5), 'D')); // Module reinforcements top out at 5D
}
});
this.props.onChange();
this._close();
}
/**
* Empty all on section header right click
*/
@@ -99,7 +222,7 @@ export default class InternalSlotSection extends SlotSection {
let slots = [];
let { currentMenu, ship } = this.props;
let { originSlot, targetSlot } = this.state;
let { internal, fuelCapacity, ladenMass } = ship;
let { internal, fuelCapacity } = ship;
let availableModules = ship.getAvailableModules();
for (let i = 0, l = internal.length; i < l; i++) {
@@ -110,9 +233,10 @@ export default class InternalSlotSection extends SlotSection {
maxClass={s.maxClass}
availableModules={() => availableModules.getInts(ship, s.maxClass, s.eligible)}
onOpen={this._openMenu.bind(this,s)}
onChange={this.props.onChange}
onSelect={this._selectModule.bind(this, s)}
selected={currentMenu == s}
enabled={s.enabled}
eligible={s.eligible}
m={s.m}
drag={this._drag.bind(this, s)}
dragOver={this._dragOverSlot.bind(this, s)}
@@ -120,6 +244,7 @@ export default class InternalSlotSection extends SlotSection {
dropClass={this._dropClass(s, originSlot, targetSlot)}
fuel={fuelCapacity}
ship={ship}
enabled={s.enabled ? true : false}
/>);
}
@@ -129,15 +254,22 @@ export default class InternalSlotSection extends SlotSection {
/**
* Generate the section drop-down menu
* @param {Function} translate Translate function
* @param {Function} ship The ship
* @return {React.Component} Section menu
*/
_getSectionMenu(translate) {
_getSectionMenu(translate, ship) {
return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='lc' onClick={this._fillWithCargo}>{translate('cargo')}</li>
<li className='lc' onClick={this._fillWithCells}>{translate('scb')}</li>
<li className='lc' onClick={this._fillWithArmor}>{translate('hr')}</li>
<li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithCargo} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['cargo'] = smRef}>{translate('cargo')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithCells} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['scb'] = smRef}>{translate('scb')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithArmor} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['hr'] = smRef}>{translate('hr')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithModuleReinforcementPackages} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mrp'] = smRef}>{translate('mrp')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithFuelTanks} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ft'] = smRef}>{translate('ft')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithEconomyClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pce'] = smRef}>{translate('pce')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithBusinessClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pci'] = smRef}>{translate('pci')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithFirstClassCabins} onKeyDown={ship.luxuryCabins ? '' : this._keyDown} ref={smRef => this.sectionRefArr['pcm'] = smRef}>{translate('pcm')}</li>
{ ship.luxuryCabins ? <li className='lc' tabIndex='0' onClick={this._fillWithLuxuryCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pcq'] = smRef}>{translate('pcq')}</li> : ''}
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul>
</div>;

View File

@@ -0,0 +1,124 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
import * as Calc from '../shipyard/Calculations';
/**
* Jump range for a given ship
*/
export default class JumpRange extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
code: PropTypes.string.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
const ship = this.props.ship;
this.state = {
fuelLevel: 1,
calcJumpRangeFunc: this._calcJumpRange.bind(this, ship)
};
}
/**
* Update the state if our ship changes
* @param {Object} nextProps Incoming/Next properties
* @param {Object} nextContext Incoming/Next conext
* @return {boolean} Returns true if the component should be rerendered
*/
componentWillReceiveProps(nextProps, nextContext) {
if (nextProps.code != this.props.code) {
this.setState({ fuelLevel: 1,
calcJumpRangeFunc: this._calcJumpRange.bind(this, nextProps.ship) });
}
return true;
}
/**
* Calculate the jump range this ship at a given cargo
* @param {Object} ship The ship
* @param {Object} cargo The cargo
* @return {number} The jump range
*/
_calcJumpRange(ship, cargo) {
// Obtain the FSD for this ship
const fsd = ship.standard[2].m;
const fuel = this.state.fuelLevel * ship.fuelCapacity;
// Obtain the jump range
return Calc.jumpRange(ship.unladenMass + fuel + cargo, fsd, fuel);
}
/**
* Update fuel level
* @param {number} fuelLevel Fuel level 0 - 1
*/
_fuelChange(fuelLevel) {
this.setState({
fuelLevel,
});
}
/**
* Render engine profile
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { ship } = this.props;
const { fuelLevel } = this.state;
const code = ship.toString() + '.' + ship.getModificationsString() + '.' + fuelLevel;
return (
<span>
<h1>{translate('jump range')}</h1>
<LineChart
xMax={ship.cargoCapacity}
yMax={ship.unladenRange}
xLabel={translate('cargo')}
xUnit={translate('T')}
yLabel={translate('jump range')}
yUnit={translate('LY')}
func={this.state.calcJumpRangeFunc}
points={200}
code={code}
/>
<h3>{translate('fuel carried')}: {formats.f2(fuelLevel * ship.fuelCapacity)}{units.T}</h3>
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody >
<tr>
<td>
<Slider
axis={true}
onChange={this._fuelChange.bind(this)}
axisUnit={translate('T')}
percent={fuelLevel}
max={ship.fuelCapacity}
scale={sizeRatio}
onResize={onWindowResize}
/>
</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -1,8 +1,9 @@
import React from 'react';
import d3 from 'd3';
import PropTypes from 'prop-types';
import Measure from 'react-measure';
import * as d3 from 'd3';
import TranslatedComponent from './TranslatedComponent';
const RENDER_POINTS = 20; // Only render 20 points on the graph
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
/**
@@ -11,24 +12,30 @@ const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
export default class LineChart extends TranslatedComponent {
static defaultProps = {
code: '',
xMin: 0,
yMin: 0,
colors: ['#ff8c0d']
points: 20,
colors: ['#ff8c0d'],
aspect: 0.5
};
static PropTypes = {
width: React.PropTypes.number.isRequired,
func: React.PropTypes.func.isRequired,
xLabel: React.PropTypes.string.isRequired,
xMin: React.PropTypes.number,
xMax: React.PropTypes.number.isRequired,
xUnit: React.PropTypes.string.isRequired,
yLabel: React.PropTypes.string.isRequired,
yMin: React.PropTypes.number,
yMax: React.PropTypes.number.isRequired,
yUnit: React.PropTypes.string.isRequired,
series: React.PropTypes.array,
colors: React.PropTypes.array,
static propTypes = {
func: PropTypes.func.isRequired,
xLabel: PropTypes.string.isRequired,
xMin: PropTypes.number,
xMax: PropTypes.number.isRequired,
xUnit: PropTypes.string.isRequired,
xMark: PropTypes.number,
yLabel: PropTypes.string.isRequired,
yMin: PropTypes.number,
yMax: PropTypes.number.isRequired,
yUnit: PropTypes.string,
series: PropTypes.array,
colors: PropTypes.array,
points: PropTypes.number,
aspect: PropTypes.number,
code: PropTypes.string,
};
/**
@@ -40,38 +47,30 @@ export default class LineChart extends TranslatedComponent {
super(props);
this._updateDimensions = this._updateDimensions.bind(this);
this._updateSeriesData = this._updateSeriesData.bind(this);
this._updateSeries = this._updateSeries.bind(this);
this._tooltip = this._tooltip.bind(this);
this._showTip = this._showTip.bind(this);
this._hideTip = this._hideTip.bind(this);
this._moveTip = this._moveTip.bind(this);
let markerElems = [];
let detailElems = [<text key='lbl' className='text-tip x' y='1.25em'/>];
let xScale = d3.scale.linear();
let xAxisScale = d3.scale.linear();
let yScale = d3.scale.linear();
let series = props.series;
let seriesLines = [];
const series = props.series;
this.xAxis = d3.svg.axis().scale(xAxisScale).outerTickSize(0).orient('bottom');
this.yAxis = d3.svg.axis().scale(yScale).ticks(6).outerTickSize(0).orient('left');
let xScale = d3.scaleLinear();
let yScale = d3.scaleLinear();
let xAxisScale = d3.scaleLinear();
for(let i = 0, l = series ? series.length : 1; i < l; i++) {
let yAccessor = series ? function(d) { return yScale(d[1][this]); }.bind(series[i]) : (d) => yScale(d[1]);
seriesLines.push(d3.svg.line().x((d) => xScale(d[0])).y(yAccessor));
detailElems.push(<text key={i} className='text-tip y' y={1.25 * (i + 2) + 'em'}/>);
markerElems.push(<circle key={i} className='marker' r='4' />);
}
this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0);
this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0);
this.state = {
xScale,
xAxisScale,
yScale,
seriesLines,
detailElems,
markerElems,
tipHeight: 2 + (1.2 * (series ? series.length : 0.8))
tipHeight: 2 + (1.2 * (series ? series.length : 0.8)),
dimensions: {
width: 100,
height: 100
}
};
}
@@ -81,13 +80,14 @@ export default class LineChart extends TranslatedComponent {
*/
_tooltip(xPos) {
let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props;
let { xScale, yScale, innerWidth } = this.state;
let { xScale, yScale } = this.state;
let { width } = this.state.dimensions;
let { formats, translate } = this.context.language;
let x0 = xScale.invert(xPos),
y0 = func(x0),
tips = this.tipContainer,
yTotal = 0,
flip = (xPos / innerWidth > 0.60),
flip = (xPos / width > 0.50),
tipWidth = 0,
tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
@@ -98,7 +98,7 @@ export default class LineChart extends TranslatedComponent {
let yVal = series ? y0[series[i]] : y0;
yTotal += yVal;
return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal);
}).append('tspan').attr('class', 'metric').text(' ' + yUnit);
}).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : '');
tips.selectAll('text').each(function() {
if (this.getBBox().width > tipWidth) {
@@ -118,19 +118,21 @@ export default class LineChart extends TranslatedComponent {
/**
* Update dimensions based on properties and scale
* @param {Object} props React Component properties
* @param {Object} props React Component properties
* @param {number} scale size ratio / scale
* @returns {Object} calculated dimensions
*/
_updateDimensions(props, scale) {
let { width, xMax, xMin, yMin, yMax } = props;
let innerWidth = width - MARGIN.left - MARGIN.right;
let outerHeight = Math.round(width * 0.5 * scale);
let innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
const { xMax, xMin, yMin, yMax } = props;
const { width, height } = this.state.dimensions;
const innerWidth = width - MARGIN.left - MARGIN.right;
const outerHeight = Math.round(width * props.aspect);
const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true);
this.state.xAxisScale.range([0, innerWidth]).domain([xMin, xMax]).clamp(true);
this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax]);
this.setState({ innerWidth, outerHeight, innerHeight });
this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax + (yMax - yMin) * 0.1]); // 10% higher than maximum value for tooltip visibility
return { innerWidth, outerHeight, innerHeight };
}
/**
@@ -164,35 +166,47 @@ export default class LineChart extends TranslatedComponent {
}
/**
* Update series data generated from props
* @param {Object} props React Component properties
* Update series generated from props
* @param {Object} props React Component properties
* @param {Object} state React Component state
*/
_updateSeriesData(props) {
let { func, xMin, xMax, series } = props;
let delta = (xMax - xMin) / RENDER_POINTS;
let seriesData = new Array(RENDER_POINTS);
_updateSeries(props, state) {
let { func, xMin, xMax, series, points } = props;
let delta = (xMax - xMin) / points;
let seriesData = new Array(points);
if (delta) {
seriesData = new Array(RENDER_POINTS);
for (let i = 0, x = xMin; i < RENDER_POINTS; i++) {
seriesData = new Array(points);
for (let i = 0, x = xMin; i < points; i++) {
seriesData[i] = [x, func(x)];
x += delta;
}
seriesData[RENDER_POINTS - 1] = [xMax, func(xMax)];
seriesData[points - 1] = [xMax, func(xMax)];
} else {
let yVal = func(xMin);
seriesData = [[0, yVal], [1, yVal]];
}
this.setState({ seriesData });
const markerElems = [];
const detailElems = [<text key='lbl' className='text-tip x' y='1.25em'/>];
const seriesLines = [];
for (let i = 0, l = series ? series.length : 1; i < l; i++) {
const yAccessor = series ? function(d) { return state.yScale(d[1][this]); }.bind(series[i]) : (d) => state.yScale(d[1]);
seriesLines.push(d3.line().x((d, i) => this.state.xScale(d[0])).y(yAccessor));
detailElems.push(<text key={i} className='text-tip y' strokeWidth={0} fill={props.colors[i]} y={1.25 * (i + 2) + 'em'}/>);
markerElems.push(<circle key={i} className='marker' r='4' />);
}
const tipHeight = 2 + (1.2 * (seriesLines ? seriesLines.length : 0.8));
this.setState({ markerElems, detailElems, seriesLines, seriesData, tipHeight });
}
/**
* Update dimensions and series data based on props and context.
*/
componentWillMount() {
this._updateDimensions(this.props, this.context.sizeRatio);
this._updateSeriesData(this.props);
this._updateSeries(this.props, this.state);
}
/**
@@ -201,17 +215,10 @@ export default class LineChart extends TranslatedComponent {
* @param {Object} nextContext Incoming/Next conext
*/
componentWillReceiveProps(nextProps, nextContext) {
let { func, xMin, xMax, yMin, yMax, width } = nextProps;
let props = this.props;
const props = this.props;
let domainChanged = xMax != props.xMax || xMin != props.xMin || yMax != props.yMax || yMin != props.yMin || func != props.func;
if (width != props.width || domainChanged || this.context.sizeRatio != nextContext.sizeRatio) {
this._updateDimensions(nextProps, nextContext.sizeRatio);
}
if (domainChanged) {
this._updateSeriesData(nextProps);
if (props.code != nextProps.code) {
this._updateSeries(nextProps, this.state);
}
}
@@ -220,49 +227,57 @@ export default class LineChart extends TranslatedComponent {
* @return {React.Component} Chart SVG
*/
render() {
if (!this.props.width) {
return null;
}
const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio);
const { width, height } = this.state.dimensions;
const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props;
const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
const line = this.line;
const lines = seriesLines.map((line, i) => <path key={i} className='line' fill='none' stroke={colors[i]} strokeWidth='1' d={line(seriesData)} />).reverse();
let { xLabel, yLabel, xUnit, yUnit, colors } = this.props;
let { innerWidth, outerHeight, innerHeight, tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
let line = this.line;
let lines = seriesLines.map((line, i) => <path key={i} className='line' stroke={colors[i]} strokeWidth='2' d={line(seriesData)} />);
const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0;
const xmark = xMark ? <path key={'mark'} className='line' fill='none' strokeDasharray='5,5' stroke={'#ff8c0d'} strokeWidth='1' d={'M ' + markX + ' ' + innerHeight + ' L ' + markX + ' 0'} /> : '';
return <svg style={{ width: '100%', height: outerHeight }}>
<g transform={`translate(${MARGIN.left},${MARGIN.top})`}>
<g>{lines}</g>
<g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
<tspan>{xLabel}</tspan>
<tspan className='metric'> ({xUnit})</tspan>
</text>
</g>
<g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}>
<text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}>
<tspan>{yLabel}</tspan>
<tspan className='metric'> ({yUnit})</tspan>
</text>
</g>
<g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}>
<rect className='tooltip' height={tipHeight + 'em'}></rect>
{detailElems}
</g>
<g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}>
{markerElems}
</g>
<rect
fillOpacity='0'
height={innerHeight}
width={innerWidth + 1}
onMouseEnter={this._showTip}
onTouchStart={this._showTip}
onMouseLeave={this._hideTip}
onTouchEnd={this._hideTip}
onMouseMove={this._moveTip}
onTouchMove={this._moveTip}
/>
</g>
</svg>;
return (
<Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}>
<div width={width} height={height}>
<svg style={{ width: '100%', height: outerHeight }}>
<g transform={`translate(${MARGIN.left},${MARGIN.top})`}>
<g>{xmark}</g>
<g>{lines}</g>
<g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
<tspan>{xLabel}</tspan>
<tspan className='metric'> ({xUnit})</tspan>
</text>
</g>
<g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}>
<text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}>
<tspan>{yLabel}</tspan>
{ yUnit && <tspan className='metric'> ({yUnit})</tspan> }
</text>
</g>
<g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}>
<rect className='tooltip' height={tipHeight + 'em'}></rect>
{detailElems}
</g>
<g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}>
{markerElems}
</g>
<rect
fillOpacity='0'
height={innerHeight}
width={innerWidth + 1}
onMouseEnter={this._showTip}
onTouchStart={this._showTip}
onMouseLeave={this._hideTip}
onTouchEnd={this._hideTip}
onMouseMove={this._moveTip}
onTouchMove={this._moveTip}
/>
</g>
</svg>
</div>
</Measure>
);
}
}

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import Router from '../Router';
import { shallowEqual } from '../utils/UtilityFunctions';
@@ -8,9 +9,9 @@ import { shallowEqual } from '../utils/UtilityFunctions';
export default class Link extends React.Component {
static propTypes = {
children: React.PropTypes.any,
href: React.PropTypes.string.isRequired,
onClick: React.PropTypes.func
children: PropTypes.any,
href: PropTypes.string.isRequired,
onClick: PropTypes.func
};
/**
@@ -56,4 +57,4 @@ export default class Link extends React.Component {
return <a {...this.props} onClick={this.handler}>{this.props.children}</a>;
}
}
}

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist';
@@ -22,8 +23,8 @@ function buildComparator(a, b) {
export default class ModalCompare extends TranslatedComponent {
static propTypes = {
onSelect: React.PropTypes.func.isRequired,
builds: React.PropTypes.array
onSelect: PropTypes.func.isRequired,
builds: PropTypes.array
};
static defaultProps = {

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
/**
@@ -8,9 +8,9 @@ import TranslatedComponent from './TranslatedComponent';
export default class ModalExport extends TranslatedComponent {
static propTypes = {
title: React.PropTypes.string,
generator: React.PropTypes.func,
data: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object, React.PropTypes.array])
title: PropTypes.string,
generator: PropTypes.func,
data: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.array])
};
/**
@@ -45,10 +45,9 @@ export default class ModalExport extends TranslatedComponent {
* Focus on textarea and select all
*/
componentDidMount() {
let e = findDOMNode(this.refs.exportField);
if (e) {
e.focus();
e.select();
if (this.exportField) {
this.exportField.focus();
this.exportField.select();
}
}
@@ -68,7 +67,7 @@ export default class ModalExport extends TranslatedComponent {
<h2>{translate(this.props.title || 'Export')}</h2>
{description}
<div>
<textarea className='cb json' ref='exportField' readOnly value={this.state.exportJson} />
<textarea className='cb json' ref={node => this.exportField = node} readOnly value={this.state.exportJson} />
</div>
<button className='r dismiss cap' onClick={this.context.hideModal}>{translate('close')}</button>
</div>;

View File

@@ -0,0 +1,37 @@
/* eslint react/no-danger: 0 */
import PropTypes from 'prop-types';
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
/**
* Help Modal
*/
export default class ModalHelp extends TranslatedComponent {
static propTypes = {
title: PropTypes.string
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
}
/**
* Render the modal
* @return {React.Component} Modal Content
*/
render() {
const translate = this.context.language.translate;
const text = translate('HELP_TEXT');
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate(this.props.title || 'Help')}</h2>
<div dangerouslySetInnerHTML={{ __html: text }} />
<button className='r dismiss cap' onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import Router from '../Router';
@@ -11,6 +11,7 @@ import * as ModuleUtils from '../shipyard/ModuleUtils';
import { fromDetailedBuild } from '../shipyard/Serializer';
import { Download } from './SvgIcons';
import { outfitURL } from '../utils/UrlGenerators';
import * as CompanionApiUtils from '../utils/CompanionApiUtils';
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
@@ -85,7 +86,7 @@ export default class ModalImport extends TranslatedComponent {
static propTypes = {
builds: React.PropTypes.object, // Optional: Import object
builds: PropTypes.object, // Optional: Import object
};
/**
@@ -112,6 +113,7 @@ export default class ModalImport extends TranslatedComponent {
this._importBackup = this._importBackup.bind(this);
this._importDetailedArray = this._importDetailedArray.bind(this);
this._importTextBuild = this._importTextBuild.bind(this);
this._importCompanionApiBuild = this._importCompanionApiBuild.bind(this);
this._validateImport = this._validateImport.bind(this);
}
@@ -183,6 +185,21 @@ export default class ModalImport extends TranslatedComponent {
this.setState({ builds });
}
/**
* Import a build direct from the companion API
* @param {string} build JSON from the companion API information
* @throws {string} if parse/import fails
*/
_importCompanionApiBuild(build) {
const shipModel = CompanionApiUtils.shipModelFromJson(build);
const ship = CompanionApiUtils.shipFromJson(build);
let builds = {};
builds[shipModel] = {};
builds[shipModel]['Imported ' + Ships[shipModel].properties.name] = ship.toString();
this.setState({ builds, singleBuild: true });
}
/**
* Import a text build from ED Shipyard
* @param {string} buildStr Build string
@@ -315,7 +332,11 @@ export default class ModalImport extends TranslatedComponent {
throw 'Must be an object or array!';
}
if (importData instanceof Array) { // Must be detailed export json
if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information
this._importCompanionApiBuild(importData); // Single sihp definition
} else if (importData.ship != null && importData.ship.modules != null && importData.ship.modules.Armour != null) { // Only the companion API has this information
this._importCompanionApiBuild(importData.ship); // Complete API dump
} else if (importData instanceof Array) { // Must be detailed export json
this._importDetailedArray(importData);
} else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export
this._importDetailedArray([importData]); // Convert to array with singleobject
@@ -325,6 +346,7 @@ export default class ModalImport extends TranslatedComponent {
}
}
} catch (e) {
// console.log(e.stack);
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
return;
}
@@ -437,8 +459,8 @@ export default class ModalImport extends TranslatedComponent {
* If textarea is shown focus on mount
*/
componentDidMount() {
if (!this.props.builds && findDOMNode(this.refs.importField)) {
findDOMNode(this.refs.importField).focus();
if (!this.props.builds && this.importField) {
this.importField.focus();
}
}
@@ -454,7 +476,7 @@ export default class ModalImport extends TranslatedComponent {
if (!state.processed) {
importStage = (
<div>
<textarea className='cb json' ref='importField' onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
<textarea className='cb json' ref={node => this.importField = node} onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
<button id='proceed' className='l cap' onClick={this._process} disabled={!state.importValid} >{translate('proceed')}</button>
<div className='l warning' style={{ marginLeft:'3em' }}>{state.errorMsg}</div>
</div>

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import ShortenUrl from '../utils/ShortenUrl';
@@ -8,7 +9,7 @@ import ShortenUrl from '../utils/ShortenUrl';
export default class ModalPermalink extends TranslatedComponent {
static propTypes = {
url: React.PropTypes.string.isRequired
url: PropTypes.string.isRequired
};
/**
@@ -49,6 +50,7 @@ export default class ModalPermalink extends TranslatedComponent {
<h3 >{translate('shortened')}</h3>
<input value={this.state.shortenedUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
<br/><br/>
<p>s.orbis.zone is the new URL shortener domain, old eddp.co urls are considered end of life and could go down at any moment. Sorry for any inconvenience.</p>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}

View File

@@ -0,0 +1,120 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import ShortenUrl from '../utils/ShortenUrl';
import Persist from '../stores/Persist';
/**
* Permalink modal
*/
export default class ModalShoppingList extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this.state = {
matsList: '',
mats: {},
matsPerGrade: Persist.getRolls()
};
}
/**
* React component did mount
*/
componentDidMount() {
this.renderMats();
}
/**
* Convert mats object to string
*/
renderMats() {
const ship = this.props.ship;
let mats = {};
for (const module of ship.costList) {
if (module.type === 'SHIP') {
continue;
}
if (module.m && module.m.blueprint) {
if (!module.m.blueprint.grade || !module.m.blueprint.grades) {
continue;
}
for (const g in module.m.blueprint.grades) {
if (g > module.m.blueprint.grade) {
continue;
}
for (const i in module.m.blueprint.grades[g].components) {
if (mats[i]) {
mats[i] += module.m.blueprint.grades[g].components[i] * this.state.matsPerGrade[g];
} else {
mats[i] = module.m.blueprint.grades[g].components[i] * this.state.matsPerGrade[g];
}
}
}
}
}
let matsString = '';
for (const i in mats) {
if (!mats.hasOwnProperty(i)) {
continue;
}
if (mats[i] === 0) {
delete mats[i];
continue;
}
matsString += `${i}: ${mats[i]}\n`;
}
this.setState({ matsList: matsString, mats });
}
/**
* Handler for changing roll amounts
* @param {SyntheticEvent} e React Event
*/
changeHandler(e) {
let grade = e.target.id;
let newState = this.state.matsPerGrade;
newState[grade] = parseInt(e.target.value);
this.setState({ matsPerGrade: newState });
Persist.setRolls(newState);
this.renderMats();
}
/**
* Render the modal
* @return {React.Component} Modal Content
*/
render() {
let translate = this.context.language.translate;
this.changeHandler = this.changeHandler.bind(this);
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate('PHRASE_SHOPPING_MATS')}</h2>
<label>Grade 1 rolls </label>
<input id={1} type={'number'} min={0} defaultValue={this.state.matsPerGrade[1]} onChange={this.changeHandler} />
<br/>
<label>Grade 2 rolls </label>
<input id={2} type={'number'} min={0} defaultValue={this.state.matsPerGrade[2]} onChange={this.changeHandler} />
<br/>
<label>Grade 3 rolls </label>
<input id={3} type={'number'} min={0} value={this.state.matsPerGrade[3]} onChange={this.changeHandler} />
<br/>
<label>Grade 4 rolls </label>
<input id={4} type={'number'} min={0} value={this.state.matsPerGrade[4]} onChange={this.changeHandler} />
<br/>
<label>Grade 5 rolls </label>
<input id={5} type={'number'} min={0} value={this.state.matsPerGrade[5]} onChange={this.changeHandler} />
<div>
<textarea className='cb json' readOnly value={this.state.matsList} />
</div>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -0,0 +1,103 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import NumberEditor from 'react-number-editor';
/**
* Modification
*/
export default class Modification extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
m: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired,
onKeyDown: PropTypes.func.isRequired,
modItems: PropTypes.array.isRequired,
handleModChange: PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this.state = {};
this.state.value = props.value;
}
/**
* Update modification given a value.
* @param {Number} value The value to set. This comes in as a string and must be stored in state as a string,
* because it needs to allow illegal 'numbers' ('-', '1.', etc) when the user is typing
* in a value by hand
*/
_updateValue(value) {
const name = this.props.name;
let scaledValue = Math.round(Number(value) * 100);
// Limit to +1000% / -99.99%
if (scaledValue > 100000) {
scaledValue = 100000;
value = 1000;
}
if (scaledValue < -9999) {
scaledValue = -9999;
value = -99.99;
}
let m = this.props.m;
let ship = this.props.ship;
ship.setModification(m, name, scaledValue, true);
this.setState({ value });
}
/**
* 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() {
if (this.props.value != this.state.value) {
this.props.handleModChange(true);
this.props.onChange();
}
}
/**
* Render the modification
* @return {React.Component} modification
*/
render() {
let translate = this.context.language.translate;
let { m, name } = this.props;
if (name === 'damagedist') {
// We don't show damage distribution
return null;
}
let symbol;
if (name === 'jitter') {
symbol = '°';
} else if (name !== 'burst' && name != 'burstrof') {
symbol = '%';
}
if (symbol) {
symbol = ' (' + symbol + ')';
}
return (
<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>
<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>
);
}
}

View File

@@ -0,0 +1,483 @@
import React from 'react';
import PropTypes from 'prop-types';
import * as _ from 'lodash';
import TranslatedComponent from './TranslatedComponent';
import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames';
import { Modifications } from 'coriolis-data/dist';
import Modification from './Modification';
import {
getBlueprint,
blueprintTooltip,
setPercent,
getPercent,
setRandom,
specialToolTip
} from '../utils/BlueprintFunctions';
/**
* Modifications menu
*/
export default class ModificationsMenu extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
m: PropTypes.object.isRequired,
marker: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
modButton:PropTypes.object
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this);
this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this);
this._rollFifty = this._rollFifty.bind(this);
this._rollRandom = this._rollRandom.bind(this);
this._rollBest = this._rollBest.bind(this);
this._rollWorst = this._rollWorst.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.selectedModId = null;
this.selectedSpecialId = 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 = {
blueprintMenuOpened: !(props.m.blueprint && props.m.blueprint.name),
specialMenuOpened: false
};
}
/**
* Render the blueprints
* @param {Object} props React component properties
* @param {Object} context React component context
* @return {Object} list: Array of React Components
*/
_renderBlueprints(props, context) {
const { m } = props;
const { language, tooltip, termtip } = context;
const translate = language.translate;
const blueprints = [];
for (const blueprintName in Modifications.modules[m.grp].blueprints) {
const blueprint = getBlueprint(blueprintName, m);
let blueprintGrades = [];
for (let grade in Modifications.modules[m.grp].blueprints[blueprintName].grades) {
// Grade is a string in the JSON so make it a number
grade = Number(grade);
const classes = cn('c', {
active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade
});
const close = this._blueprintSelected.bind(this, blueprintName, grade);
const key = blueprintName + ':' + grade;
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
if (classes.indexOf('active') >= 0) this.selectedModId = key;
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) {
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(<ul key={blueprintName}>{blueprintGrades}</ul>);
}
}
return blueprints;
}
/**
* Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc
* @param {SyntheticEvent} event Event
*
*/
_keyDown(event) {
let className = null;
let 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
* @param {Object} props React component properties
* @param {Object} context React component context
* @return {Object} list: Array of React Components
*/
_renderSpecials(props, context) {
const { m } = props;
const { language, tooltip, termtip } = context;
const translate = language.translate;
const 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) {
const close = this._specialSelected.bind(this, null);
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]) {
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
});
if (classes.indexOf('active') >= 0) this.selectedSpecialId = specialName;
const close = this._specialSelected.bind(this, specialName);
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>);
}
}
}
return specials;
}
/**
* Render the modifications
* @param {Object} props React Component properties
* @return {Object} list: Array of React Components
*/
_renderModifications(props) {
const { m, onChange, ship } = props;
const modifications = [];
for (const modName of Modifications.modules[m.grp].modifications) {
if (!Modifications.modifications[modName].hidden) {
const key = modName + (m.getModValue(modName) / 100 || 0);
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} />);
}
}
return modifications;
}
/**
* Toggle the blueprints menu
*/
_toggleBlueprintsMenu() {
const blueprintMenuOpened = !this.state.blueprintMenuOpened;
this.setState({ blueprintMenuOpened });
}
/**
* Activated when a blueprint is selected
* @param {int} fdname The Frontier name of the blueprint
* @param {int} grade The grade of the selected blueprint
*/
_blueprintSelected(fdname, grade) {
this.context.tooltip(null);
const { m, ship } = this.props;
const blueprint = getBlueprint(fdname, m);
blueprint.grade = grade;
ship.setModuleBlueprint(m, blueprint);
setPercent(ship, m, 100);
this.setState({ blueprintMenuOpened: false, specialMenuOpened: true });
this.props.onChange();
}
/**
* Toggle the specials menu
*/
_toggleSpecialsMenu() {
const specialMenuOpened = !this.state.specialMenuOpened;
this.setState({ specialMenuOpened });
}
/**
* Activated when a special is selected
* @param {int} special The name of the selected special
*/
_specialSelected(special) {
this.context.tooltip(null);
const { m, ship } = this.props;
if (special === null) {
ship.clearModuleSpecial(m);
} else {
ship.setModuleSpecial(m, Modifications.specials[special]);
}
this.setState({ specialMenuOpened: false });
this.props.onChange();
}
/**
* Provide a '50%' roll within the information we have
*/
_rollFifty() {
const { m, ship } = this.props;
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();
}
/**
* Provide a random roll within the information we have
*/
_rollRandom() {
const { m, ship } = this.props;
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();
}
/**
* Provide a 'best' roll within the information we have
*/
_rollBest() {
const { m, ship } = this.props;
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();
}
/**
* Provide a 'worst' roll within the information we have
*/
_rollWorst() {
const { m, ship } = this.props;
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();
}
/**
* Reset modification information
*/
_reset() {
const { m, ship } = this.props;
ship.clearModifications(m);
ship.clearModuleBlueprint(m);
this.selectedModId = null;
this.selectedSpecialId = null;
this.props.onChange();
}
/**
* set mod did change boolean
* @param {boolean} b Boolean to determine if a change has been made to a module
*/
_handleModChange(b) {
this.modValDidChange = b;
}
/**
* Set focus on first element in modifications menu
* after it first mounts
*/
componentDidMount() {
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();
}
}
/**
* Set focus on first element in modifications menu
* if component updates, unless update is due to value change
* in a modification
*/
componentDidUpdate() {
if (!this.modValDidChange) {
if (this.modItems['modMainDiv'].children.length > 0) {
if (this.modItems[this.selectedModId]) {
this.modItems[this.selectedModId].focus();
return;
} else if (this.modItems[this.selectedSpecialId]) {
this.modItems[this.selectedSpecialId].focus();
return;
}
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
}
}
/**
* set focus to the modification menu icon after mod menu is unmounted.
*/
componentWillUnmount() {
if (this.props.modButton) {
this.props.modButton.focus();
}
}
/**
* Render the list
* @return {React.Component} List
*/
render() {
const { language, tooltip, termtip } = this.context;
const translate = language.translate;
const { m } = this.props;
const { blueprintMenuOpened, specialMenuOpened } = this.state;
const _toggleBlueprintsMenu = this._toggleBlueprintsMenu;
const _toggleSpecialsMenu = this._toggleSpecialsMenu;
const _rollFull = this._rollBest;
const _rollWorst = this._rollWorst;
const _rollFifty = this._rollFifty;
const _rollRandom = this._rollRandom;
const _reset = this._reset;
let blueprintLabel;
let haveBlueprint = false;
let blueprintTt;
let blueprintCv;
// TODO: Fix this to actually find the correct blueprint.
if (!m.blueprint || !m.blueprint.name || !m.blueprint.fdname || !Modifications.modules[m.grp].blueprints || !Modifications.modules[m.grp].blueprints[m.blueprint.fdname]) {
this.props.ship.clearModuleBlueprint(m);
this.props.ship.clearModuleSpecial(m);
}
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;
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);
blueprintCv = getPercent(m);
}
let specialLabel;
let specialTt;
if (m.blueprint && m.blueprint.special) {
specialLabel = m.blueprint.special.name;
specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, m.blueprint.special.edname);
} else {
specialLabel = translate('PHRASE_SELECT_SPECIAL');
}
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 showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened;
const showSpecialsMenu = specialMenuOpened && specials.length;
const showRolls = haveBlueprint && !blueprintMenuOpened && (!specialMenuOpened || !specials.length);
const showReset = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
const showMods = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
if (haveBlueprint) {
this.firstBPLabel = blueprintLabel;
} else {
this.firstBPLabel = 'selectBP';
}
return (
<div
className={cn('select', this.props.className)}
onClick={(e) => e.stopPropagation() }
onContextMenu={stopCtxPropagation}
ref={modItem => this.modItems['modMainDiv'] = modItem}
>
{ showBlueprintsMenu | showSpecialsMenu ? '' : haveBlueprint ?
<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 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 }
{ 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 }
{ 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' }}>
<tbody>
{ showRolls ?
<tr>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: false }) }> { translate('roll') }: </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 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 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 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 }
</tbody>
</table> : null }
{ showMods ? <hr /> : null }
{ showMods ?
<span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} >
{ this._renderModifications(this.props) }
</span> : null }
</div>
);
}
}

View File

@@ -0,0 +1,71 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
/**
* Movement
*/
export default class Movement extends TranslatedComponent {
static propTypes = {
marker: PropTypes.string.isRequired,
ship: PropTypes.object.isRequired,
boost: PropTypes.bool.isRequired,
eng: PropTypes.number.isRequired,
fuel: PropTypes.number.isRequired,
cargo: PropTypes.number.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
}
/**
* Render movement
* @return {React.Component} contents
*/
render() {
const { ship, boost, eng, cargo, fuel } = this.props;
const { language } = this.context;
const { formats, translate, units } = language;
return (
<span id='movement'>
<svg viewBox='0 0 600 600' fillRule="evenodd" clipRule="evenodd">
// Axes
<path d="M150 250v300" strokeWidth='1'/>
<path d="M150 250l236 236" strokeWidth='1'/>
<path d="M150 250l350 -200" strokeWidth='1'/>
// End Arrow
<path d="M508 43.3L487 67l-10-17.3 31-6.4z"/>
// Axes arcs and arrows
<path d="M71.7 251.7C64.2 259.2 60 269.4 60 280c0 22 18 40 40 40s40-18 40-40c0-10.6-4.2-20.8-11.7-28.3 7.5 7.5 11.7 17.7 11.7 28.3 0 22-18 40-40 40s-40-18-40-40c0-10.6 4.2-20.8 11.7-28.3z" strokeWidth='4' transform="matrix(.6 0 0 .3 87.5 376.3)"/>
<path d="M142.8 453l-13.2 8.7-2.6-9.7 15.8 1z"/>
<path d="M144.7 451.6l.5 1.6-16.2 10.6h-.4l-3.5-13 .7-.4 19.3 1.2zm-14.2 7.7l7.7-5-9.2-.7 1.5 5.7zm25.7-6.3l15.8-1-2.6 9.7-13.2-8.8z"/>
<path d="M174 450.8l-3.6 13h-.4l-16.2-10.6.5-1.6 19.3-1.2.3.4zm-13.2 3.4l7.7 5 1.5-5.6-9.2.6z"/>
<path d="M407.7 119c2 .7 4.3 1 6.4 1 14 0 25-11.2 25-25s-11-25-25-25c-11 0-21 7.6-24 18.5 3-11 13-18.5 24-18.5 14 0 25 11.2 25 25s-11 25-25 25c-2 0-4-.3-6-1z" strokeWidth='2'/>
<path d="M388 99.7L387 84l9.8 2.5-8.7 13.2z"/>
<path d="M398.8 85.5l.2.5-10.7 16-1.6-.3-1.2-19.3.4-.3 12.5 3.8zm-9.5 9.7l5-7.7-5.6-1.6.6 9zm10 20.8l15.7-1-2.6 9.7-13.2-8.8z"/>
<path d="M417 113.8l-3.6 13h-.4l-16.2-10.6.5-1.6 19.3-1.2.3.4zm-13.2 3.4l7.7 5 1.5-5.6-9.2.6z"/>
<path d="M355 430c0-13.8-11.2-25-25-25s-25 11.2-25 25 11.2 25 25 25c-13.8 0-25-11.2-25-25s11.2-25 25-25 25 11.2 25 25z" strokeWidth='2'/>
<path d="M357 439.7l-8.8-13 9.7-2.7-1 15.7z"/>
<path d="M359.5 422.4l-1.2 19.3-1.6.4-10.7-16 .2-.2 13-3.4.3.4zm-9 5l5.2 7.8.6-9.3-5.7 1.2zm-10.5 24l-13.2 8.6-2.6-9.7 15.8 1z"/>
<path d="M342 450l.4 1.5-16.2 10.7-.4-.2-3.5-13 .3-.3L342 450zm-14.3 7.6l7.7-5-9.2-.6 1.5 5.6z"/>
// Speed
<text x="470" y="30" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcSpeed(eng, fuel, cargo, boost)) + 'm/s' : '-'}</text>
// Pitch
<text x="355" y="410" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcPitch(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
// Roll
<text x="450" y="110" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcRoll(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
// Yaw
<text x="160" y="430" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcYaw(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
</svg>
</span>);
}
}

View File

@@ -0,0 +1,267 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import * as Calc from '../shipyard/Calculations';
import PieChart from './PieChart';
import { nameComparator } from '../utils/SlotFunctions';
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import VerticalBarChart from './VerticalBarChart';
/**
* Generates an internationalization friendly weapon comparator that will
* sort by specified property (if provided) then by name/group, class, rating
* @param {function} translate Translation function
* @param {function} propComparator Optional property comparator
* @param {boolean} desc Use descending order
* @return {function} Comparator function for names
*/
export function weaponComparator(translate, propComparator, desc) {
return (a, b) => {
if (!desc) { // Flip A and B if ascending order
let t = a;
a = b;
b = t;
}
// If a property comparator is provided use it first
let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a, b);
if (diff) {
return diff;
}
// Property matches so sort by name / group, then class, rating
if (a.name === b.name && a.grp === b.grp) {
if(a.class == b.class) {
return a.rating > b.rating ? 1 : -1;
}
return a.class - b.class;
}
return nameComparator(translate, a, b);
};
}
/**
* Offence information
* Offence information consists of four panels:
* - textual information (time to drain cap, time to take down shields etc.)
* - breakdown of damage sources (pie chart)
* - comparison of shield resistances (table chart)
* - effective sustained DPS of weapons (bar chart)
*/
export default class Offence extends TranslatedComponent {
static propTypes = {
marker: PropTypes.string.isRequired,
ship: PropTypes.object.isRequired,
opponent: PropTypes.object.isRequired,
engagementrange: PropTypes.number.isRequired,
wep: PropTypes.number.isRequired,
opponentSys: PropTypes.number.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._sort = this._sort.bind(this);
const damage = Calc.offenceMetrics(props.ship, props.opponent, props.wep, props.opponentSys, props.engagementrange);
this.state = {
predicate: 'n',
desc: true,
damage
};
}
/**
* Update the state if our properties change
* @param {Object} nextProps Incoming/Next properties
* @return {boolean} Returns true if the component should be rerendered
*/
componentWillReceiveProps(nextProps) {
if (this.props.marker != nextProps.marker || this.props.eng != nextProps.eng) {
const damage = Calc.offenceMetrics(nextProps.ship, nextProps.opponent, nextProps.wep, nextProps.opponentSys, nextProps.engagementrange);
this.setState({ damage });
}
return true;
}
/**
* Set the sort order and sort
* @param {string} predicate Sort predicate
*/
_sortOrder(predicate) {
let desc = this.state.desc;
if (predicate == this.state.predicate) {
desc = !desc;
} else {
desc = true;
}
this._sort(predicate, desc);
this.setState({ predicate, desc });
}
/**
* Sorts the weapon list
* @param {string} predicate Sort predicate
* @param {Boolean} desc Sort order descending
*/
_sort(predicate, desc) {
let comp = weaponComparator.bind(null, this.context.language.translate);
switch (predicate) {
case 'n': comp = comp(null, desc); break;
case 'esdpss': comp = comp((a, b) => a.sdps.shields.total - b.sdps.shields.total, desc); break;
case 'es': comp = comp((a, b) => a.effectiveness.shields.total - b.effectiveness.shields.total, desc); break;
case 'esdpsh': comp = comp((a, b) => a.sdps.armour.total - b.sdps.armour.total, desc); break;
case 'eh': comp = comp((a, b) => a.effectiveness.armour.total - b.effectiveness.armour.total, desc); break;
}
this.state.damage.sort(comp);
}
/**
* Render offence
* @return {React.Component} contents
*/
render() {
const { ship, opponent, wep, engagementrange } = this.props;
const { language, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { damage } = this.state;
const sortOrder = this._sortOrder;
const pd = ship.standard[4].m;
const opponentShields = Calc.shieldMetrics(opponent, 4);
const opponentArmour = Calc.armourMetrics(opponent);
const timeToDrain = Calc.timeToDrainWep(ship, wep);
let absoluteShieldsSDps = 0;
let explosiveShieldsSDps = 0;
let kineticShieldsSDps = 0;
let thermalShieldsSDps = 0;
let absoluteArmourSDps = 0;
let explosiveArmourSDps = 0;
let kineticArmourSDps = 0;
let thermalArmourSDps = 0;
let totalSEps = 0;
const rows = [];
for (let i = 0; i < damage.length; i++) {
const weapon = damage[i];
totalSEps += weapon.seps;
absoluteShieldsSDps += weapon.sdps.shields.absolute;
explosiveShieldsSDps += weapon.sdps.shields.explosive;
kineticShieldsSDps += weapon.sdps.shields.kinetic;
thermalShieldsSDps += weapon.sdps.shields.thermal;
absoluteArmourSDps += weapon.sdps.armour.absolute;
explosiveArmourSDps += weapon.sdps.armour.explosive;
kineticArmourSDps += weapon.sdps.armour.kinetic;
thermalArmourSDps += weapon.sdps.armour.thermal;
const effectivenessShieldsTooltipDetails = [];
effectivenessShieldsTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.shields.range)}</div>);
effectivenessShieldsTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.shields.resistance)}</div>);
effectivenessShieldsTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(weapon.effectiveness.shields.sys)}</div>);
const effectiveShieldsSDpsTooltipDetails = [];
if (weapon.sdps.shields.absolute) effectiveShieldsSDpsTooltipDetails.push(<div key='absolute'>{translate('absolute') + ' ' + formats.f1(weapon.sdps.shields.absolute)}</div>);
if (weapon.sdps.shields.explosive) effectiveShieldsSDpsTooltipDetails.push(<div key='explosive'>{translate('explosive') + ' ' + formats.f1(weapon.sdps.shields.explosive)}</div>);
if (weapon.sdps.shields.kinetic) effectiveShieldsSDpsTooltipDetails.push(<div key='kinetic'>{translate('kinetic') + ' ' + formats.f1(weapon.sdps.shields.kinetic)}</div>);
if (weapon.sdps.shields.thermal) effectiveShieldsSDpsTooltipDetails.push(<div key='thermal'>{translate('thermal') + ' ' + formats.f1(weapon.sdps.shields.thermal)}</div>);
const effectivenessArmourTooltipDetails = [];
effectivenessArmourTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.armour.range)}</div>);
effectivenessArmourTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.armour.resistance)}</div>);
effectivenessArmourTooltipDetails.push(<div key='hardness'>{translate('hardness') + ' ' + formats.pct1(weapon.effectiveness.armour.hardness)}</div>);
const effectiveArmourSDpsTooltipDetails = [];
if (weapon.sdps.armour.absolute) effectiveArmourSDpsTooltipDetails.push(<div key='absolute'>{translate('absolute') + ' ' + formats.f1(weapon.sdps.armour.absolute)}</div>);
if (weapon.sdps.armour.explosive) effectiveArmourSDpsTooltipDetails.push(<div key='explosive'>{translate('explosive') + ' ' + formats.f1(weapon.sdps.armour.explosive)}</div>);
if (weapon.sdps.armour.kinetic) effectiveArmourSDpsTooltipDetails.push(<div key='kinetic'>{translate('kinetic') + ' ' + formats.f1(weapon.sdps.armour.kinetic)}</div>);
if (weapon.sdps.armour.thermal) effectiveArmourSDpsTooltipDetails.push(<div key='thermal'>{translate('thermal') + ' ' + formats.f1(weapon.sdps.armour.thermal)}</div>);
rows.push(
<tr key={weapon.id}>
<td className='ri'>
{weapon.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed className='icon'/></span> : null}
{weapon.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : null}
{weapon.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
{weapon.classRating} {translate(weapon.name)}
{weapon.engineering ? ' (' + weapon.engineering + ')' : null }
</td>
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.shields.total)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessShieldsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.shields.total)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.armour.total)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessArmourTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.armour.total)}</span></td>
</tr>);
}
const totalShieldsSDps = absoluteShieldsSDps + explosiveShieldsSDps + kineticShieldsSDps + thermalShieldsSDps;
const totalArmourSDps = absoluteArmourSDps + explosiveArmourSDps + kineticArmourSDps + thermalArmourSDps;
const shieldsSDpsData = [];
shieldsSDpsData.push({ value: Math.round(absoluteShieldsSDps), label: translate('absolute') });
shieldsSDpsData.push({ value: Math.round(explosiveShieldsSDps), label: translate('explosive') });
shieldsSDpsData.push({ value: Math.round(kineticShieldsSDps), label: translate('kinetic') });
shieldsSDpsData.push({ value: Math.round(thermalShieldsSDps), label: translate('thermal') });
const armourSDpsData = [];
armourSDpsData.push({ value: Math.round(absoluteArmourSDps), label: translate('absolute') });
armourSDpsData.push({ value: Math.round(explosiveArmourSDps), label: translate('explosive') });
armourSDpsData.push({ value: Math.round(kineticArmourSDps), label: translate('kinetic') });
armourSDpsData.push({ value: Math.round(thermalArmourSDps), label: translate('thermal') });
const timeToDepleteShields = Calc.timeToDeplete(opponentShields.total, totalShieldsSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
const timeToDepleteArmour = Calc.timeToDeplete(opponentArmour.total, totalArmourSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
return (
<span id='offence'>
<div className='group full'>
<table>
<thead>
<tr className='main'>
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
<th colSpan='2'>{translate('opponent\'s shields')}</th>
<th colSpan='2'>{translate('opponent\'s armour')}</th>
</tr>
<tr>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'es')}>{'eft'}</th>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
</div>
<div className='group quarter'>
<h2>{translate('offence metrics')}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_DRAIN_WEP'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_DRAIN_WEP')}<br/>{timeToDrain === Infinity ? translate('never') : formats.time(timeToDrain)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_SHIELDS')}<br/>{formats.f1(totalShieldsSDps)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_SHIELDS')}<br/>{timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')}<br/>{formats.f1(totalArmourSDps)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')}<br/>{timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}</h2>
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield damage sources')}</h2>
<PieChart data={shieldsSDpsData} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_ARMOUR_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('armour damage sources')}</h2>
<PieChart data={armourSDpsData} />
</div>
</span>);
}
}

View File

@@ -0,0 +1,187 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { Ships } from 'coriolis-data/dist';
import Ship from '../shipyard/Ship';
import Persist from '../stores/Persist';
import TranslatedComponent from './TranslatedComponent';
import PowerManagement from './PowerManagement';
import CostSection from './CostSection';
import EngineProfile from './EngineProfile';
import FSDProfile from './FSDProfile';
import Movement from './Movement';
import Offence from './Offence';
import Defence from './Defence';
import WeaponDamageChart from './WeaponDamageChart';
/**
* Outfitting subpages
*/
export default class OutfittingSubpages extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
code: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
buildName: PropTypes.string,
sys: PropTypes.number.isRequired,
eng: PropTypes.number.isRequired,
wep: PropTypes.number.isRequired,
cargo: PropTypes.number.isRequired,
fuel: PropTypes.number.isRequired,
boost: PropTypes.bool.isRequired,
engagementRange: PropTypes.number.isRequired,
opponent: PropTypes.object.isRequired,
opponentBuild: PropTypes.string,
opponentSys: PropTypes.number.isRequired,
opponentEng: PropTypes.number.isRequired,
opponentWep: PropTypes.number.isRequired,
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._powerTab = this._powerTab.bind(this);
this._profilesTab = this._profilesTab.bind(this);
this._offenceTab = this._offenceTab.bind(this);
this._defenceTab = this._defenceTab.bind(this);
this.state = {
tab: Persist.getOutfittingTab() || 'power',
};
}
/**
* Show selected tab
* @param {string} tab Tab name
*/
_showTab(tab) {
this.setState({ tab });
}
/**
* Render the power tab
* @return {React.Component} Tab contents
*/
_powerTab() {
let { ship, buildName, code, onChange } = this.props;
Persist.setOutfittingTab('power');
const powerMarker = `${ship.toString()}`;
const costMarker = `${ship.toString().split('.')[0]}`;
return <div>
<PowerManagement ship={ship} code={powerMarker} onChange={onChange} />
<CostSection ship={ship} buildName={buildName} code={costMarker} />
</div>;
}
/**
* Render the profiles tab
* @return {React.Component} Tab contents
*/
_profilesTab() {
const { ship, opponent, cargo, fuel, eng, boost, engagementRange, opponentSys } = this.props;
const { translate } = this.context.language;
let realBoost = boost && ship.canBoost(cargo, fuel);
Persist.setOutfittingTab('profiles');
const engineProfileMarker = `${ship.toString()}:${cargo}:${fuel}:${eng}:${realBoost}`;
const fsdProfileMarker = `${ship.toString()}:${cargo}:${fuel}`;
const movementMarker = `${ship.topSpeed}:${ship.pitch}:${ship.roll}:${ship.yaw}:${ship.canBoost(cargo, fuel)}`;
const damageMarker = `${ship.toString()}:${opponent.toString()}:${engagementRange}:${opponentSys}`;
return <div>
<div className='group third'>
<h1>{translate('engine profile')}</h1>
<EngineProfile ship={ship} marker={engineProfileMarker} fuel={fuel} cargo={cargo} eng={eng} boost={realBoost} />
</div>
<div className='group third'>
<h1>{translate('fsd profile')}</h1>
<FSDProfile ship={ship} marker={fsdProfileMarker} fuel={fuel} cargo={cargo} />
</div>
<div className='group third'>
<h1>{translate('movement profile')}</h1>
<Movement marker={movementMarker} ship={ship} boost={boost} eng={eng} cargo={cargo} fuel={fuel} />
</div>
<div className='group half'>
<h1>{translate('damage to opponent\'s shields')}</h1>
<WeaponDamageChart marker={damageMarker} ship={ship} opponent={opponent} opponentSys={opponentSys} hull={false} engagementRange={engagementRange} />
</div>
<div className='group half'>
<h1>{translate('damage to opponent\'s hull')}</h1>
<WeaponDamageChart marker={damageMarker} ship={ship} opponent={opponent} opponentSys={opponentSys} hull={true} engagementRange={engagementRange} />
</div>
</div>;
}
/**
* Render the offence tab
* @return {React.Component} Tab contents
*/
_offenceTab() {
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild, opponentSys } = this.props;
Persist.setOutfittingTab('offence');
const marker = `${ship.toString()}${opponent.toString()}${opponentBuild}${engagementRange}${opponentSys}`;
return <div>
<Offence marker={marker} ship={ship} opponent={opponent} wep={wep} opponentSys={opponentSys} engagementrange={engagementRange}/>
</div>;
}
/**
* Render the defence tab
* @return {React.Component} Tab contents
*/
_defenceTab() {
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild, opponentWep } = this.props;
Persist.setOutfittingTab('defence');
const marker = `${ship.toString()}${opponent.toString()}{opponentBuild}${engagementRange}${opponentWep}`;
return <div>
<Defence marker={marker} ship={ship} opponent={opponent} sys={sys} opponentWep={opponentWep} engagementrange={engagementRange}/>
</div>;
}
/**
* Render the section
* @return {React.Component} Contents
*/
render() {
const tab = this.state.tab;
const translate = this.context.language.translate;
let tabSection;
switch (tab) {
case 'power': tabSection = this._powerTab(); break;
case 'profiles': tabSection = this._profilesTab(); break;
case 'offence': tabSection = this._offenceTab(); break;
case 'defence': tabSection = this._defenceTab(); break;
}
return (
<div className='group full' style={{ minHeight: '1000px' }}>
<table className='tabs'>
<thead>
<tr>
<th style={{ width:'25%' }} className={cn({ active: tab == 'power' })} onClick={this._showTab.bind(this, 'power')} >{translate('power and costs')}</th>
<th style={{ width:'25%' }} className={cn({ active: tab == 'profiles' })} onClick={this._showTab.bind(this, 'profiles')} >{translate('profiles')}</th>
<th style={{ width:'25%' }} className={cn({ active: tab == 'offence' })} onClick={this._showTab.bind(this, 'offence')} >{translate('offence')}</th>
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })} onClick={this._showTab.bind(this, 'defence')} >{translate('defence')}</th>
</tr>
</thead>
</table>
{tabSection}
</div>
);
}
}

View File

@@ -0,0 +1,97 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Measure from 'react-measure';
import * as d3 from 'd3';
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
const LABEL_COLOUR = '#000000';
/**
* A pie chart
*/
export default class PieChart extends Component {
static propTypes = {
data : PropTypes.array.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this.pie = d3.pie().value((d) => d.value);
this.colors = CORIOLIS_COLOURS;
this.arc = d3.arc();
this.arc.innerRadius(0);
this.state = {
dimensions: {
width: 100,
height: 100
}
};
}
/**
* Generate a slice of the pie chart
* @param {Object} d the data for this slice
* @param {number} i the index of this slice
* @returns {Object} the SVG for the slice
*/
sliceGenerator(d, i) {
if (!d || d.value == 0) {
// Ignore 0 values
return null;
}
const { width, height } = this.state.dimensions;
const { data } = this.props;
// Push the labels further out from the centre of the slice
let [labelX, labelY] = this.arc.centroid(d);
const labelTranslate = `translate(${labelX * 1.5}, ${labelY * 1.5})`;
// Put the keys in a line with equal spacing
const nonZeroItems = data.filter(d => d.value != 0).length;
const thisItemIndex = data.slice(0, i + 1).filter(d => d.value != 0).length - 1;
const keyX = -width / 2 + (width / nonZeroItems) * (thisItemIndex + 0.5);
const keyTranslate = `translate(${keyX}, ${width * 0.45})`;
return (
<g key={`group-${i}`}>
<path key={`arc-${i}`} d={this.arc(d)} style={{ fill: this.colors[i] }} />
<text key={`label-${i}`} transform={labelTranslate} style={{ strokeWidth: '0px', fill: LABEL_COLOUR }} textAnchor='middle'>{d.value}</text>
<text key={`key-${i}`} transform={keyTranslate} style={{ strokeWidth:'0px', fill: this.colors[i] }} textAnchor='middle'>{d.data.label}</text>
</g>
);
}
/**
* Render the component
* @returns {object} Markup
*/
render() {
const { width, height } = this.state.dimensions;
const pie = this.pie(this.props.data),
translate = `translate(${width / 2}, ${width * 0.4})`;
this.arc.outerRadius(width * 0.4);
return (
<Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}>
<div width={width} height={width}>
<svg style={{ stroke: 'None' }} width={width} height={width * 0.9}>
<g transform={translate}>
{pie.map((d, i) => this.sliceGenerator(d, i))}
</g>
</svg>
</div>
</Measure>
);
}
}

299
src/app/components/Pips.jsx Normal file
View File

@@ -0,0 +1,299 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import { Pip } from './SvgIcons';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
/**
* Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area.
* Requires an onChange() function of the form onChange(sys, eng, wep) which is triggered whenever the pips change.
*/
export default class Pips extends TranslatedComponent {
static propTypes = {
sys: PropTypes.number.isRequired,
eng: PropTypes.number.isRequired,
wep: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
const { sys, eng, wep } = props;
this._keyDown = this._keyDown.bind(this);
}
/**
* Add listeners after mounting
*/
componentDidMount() {
document.addEventListener('keydown', this._keyDown);
}
/**
* Remove listeners before unmounting
*/
componentWillUnmount() {
document.removeEventListener('keydown', this._keyDown);
}
/**
* Handle Key Down
* @param {Event} e Keyboard Event
*/
_keyDown(e) {
if (e.ctrlKey || e.metaKey) { // CTRL/CMD
switch (e.keyCode) {
case 37: // Left arrow == increase SYS
e.preventDefault();
this._incSys();
break;
case 38: // Up arrow == increase ENG
e.preventDefault();
this._incEng();
break;
case 39: // Right arrow == increase WEP
e.preventDefault();
this._incWep();
break;
case 40: // Down arrow == reset
e.preventDefault();
this._reset();
break;
}
}
}
/**
* Handle a click
* @param {string} which Which item was clicked
*/
onClick(which) {
if (which == 'SYS') {
this._incSys();
} else if (which == 'ENG') {
this._incEng();
} else if (which == 'WEP') {
this._incWep();
} else if (which == 'RST') {
this._reset();
}
}
/**
* Reset the capacitor
*/
_reset() {
let { sys, eng, wep } = this.props;
if (sys != 2 || eng != 2 || wep != 2) {
sys = eng = wep = 2;
this.props.onChange(sys, eng, wep);
}
}
/**
* Increment the SYS capacitor
*/
_incSys() {
let { sys, eng, wep } = this.props;
const required = Math.min(1, 4 - sys);
if (required > 0) {
if (required == 0.5) {
// Take from whichever is larger
if (eng > wep) {
eng -= 0.5;
sys += 0.5;
} else {
wep -= 0.5;
sys += 0.5;
}
} else {
// Required is 1 - take from both if possible
if (eng == 0) {
wep -= 1;
sys += 1;
} else if (wep == 0) {
eng -= 1;
sys += 1;
} else {
eng -= 0.5;
wep -= 0.5;
sys += 1;
}
}
this.props.onChange(sys, eng, wep);
}
}
/**
* Increment the ENG capacitor
*/
_incEng() {
let { sys, eng, wep } = this.props;
const required = Math.min(1, 4 - eng);
if (required > 0) {
if (required == 0.5) {
// Take from whichever is larger
if (sys > wep) {
sys -= 0.5;
eng += 0.5;
} else {
wep -= 0.5;
eng += 0.5;
}
} else {
// Required is 1 - take from both if possible
if (sys == 0) {
wep -= 1;
eng += 1;
} else if (wep == 0) {
sys -= 1;
eng += 1;
} else {
sys -= 0.5;
wep -= 0.5;
eng += 1;
}
}
this.props.onChange(sys, eng, wep);
}
}
/**
* Increment the WEP capacitor
*/
_incWep() {
let { sys, eng, wep } = this.props;
const required = Math.min(1, 4 - wep);
if (required > 0) {
if (required == 0.5) {
// Take from whichever is larger
if (sys > eng) {
sys -= 0.5;
wep += 0.5;
} else {
eng -= 0.5;
wep += 0.5;
}
} else {
// Required is 1 - take from both if possible
if (sys == 0) {
eng -= 1;
wep += 1;
} else if (eng == 0) {
sys -= 1;
wep += 1;
} else {
sys -= 0.5;
eng -= 0.5;
wep += 1;
}
}
this.props.onChange(sys, eng, wep);
}
}
/**
* Set up the rendering for pips
* @param {int} sys the SYS pips
* @param {int} eng the ENG pips
* @param {int} wep the WEP pips
* @returns {Object} Object containing the rendering for the pips
*/
_renderPips(sys, eng, wep) {
const pipsSvg = {};
// SYS
pipsSvg['SYS'] = [];
for (let i = 0; i < Math.floor(sys); i++) {
pipsSvg['SYS'].push(<Pip className='full' key={i} />);
}
if (sys > Math.floor(sys)) {
pipsSvg['SYS'].push(<Pip className='half' key={'half'} />);
}
for (let i = Math.floor(sys + 0.5); i < 4; i++) {
pipsSvg['SYS'].push(<Pip className='empty' key={i} />);
}
// ENG
pipsSvg['ENG'] = [];
for (let i = 0; i < Math.floor(eng); i++) {
pipsSvg['ENG'].push(<Pip className='full' key={i} />);
}
if (eng > Math.floor(eng)) {
pipsSvg['ENG'].push(<Pip className='half' key={'half'} />);
}
for (let i = Math.floor(eng + 0.5); i < 4; i++) {
pipsSvg['ENG'].push(<Pip className='empty' key={i} />);
}
// WEP
pipsSvg['WEP'] = [];
for (let i = 0; i < Math.floor(wep); i++) {
pipsSvg['WEP'].push(<Pip className='full' key={i} />);
}
if (wep > Math.floor(wep)) {
pipsSvg['WEP'].push(<Pip className='half' key={'half'} />);
}
for (let i = Math.floor(wep + 0.5); i < 4; i++) {
pipsSvg['WEP'].push(<Pip className='empty' key={i} />);
}
return pipsSvg;
}
/**
* Render pips
* @return {React.Component} contents
*/
render() {
const { formats, translate, units } = this.context.language;
const { sys, eng, wep } = this.props;
const onSysClicked = this.onClick.bind(this, 'SYS');
const onEngClicked = this.onClick.bind(this, 'ENG');
const onWepClicked = this.onClick.bind(this, 'WEP');
const onRstClicked = this.onClick.bind(this, 'RST');
const pipsSvg = this._renderPips(sys, eng, wep);
return (
<span id='pips'>
<table>
<tbody>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td className='clickable' onClick={onEngClicked}>{pipsSvg['ENG']}</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td className='clickable' onClick={onSysClicked}>{pipsSvg['SYS']}</td>
<td className='clickable' onClick={onEngClicked}>{translate('ENG')}</td>
<td className='clickable' onClick={onWepClicked}>{pipsSvg['WEP']}</td>
</tr>
<tr>
<td>&nbsp;</td>
<td className='clickable' onClick={onSysClicked}>{translate('SYS')}</td>
<td className='clickable' onClick={onRstClicked}>{translate('RST')}</td>
<td className='clickable' onClick={onWepClicked}>{translate('WEP')}</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -1,5 +1,6 @@
import React from 'react';
import d3 from 'd3';
import PropTypes from 'prop-types';
import * as d3 from 'd3';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import { wrapCtxMenu } from '../utils/UtilityFunctions';
@@ -33,10 +34,10 @@ function bandText(val, index, wattScale) {
export default class PowerBands extends TranslatedComponent {
static propTypes = {
bands: React.PropTypes.array.isRequired,
available: React.PropTypes.number.isRequired,
width: React.PropTypes.number.isRequired,
code: React.PropTypes.string,
bands: PropTypes.array.isRequired,
available: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
code: PropTypes.string,
};
/**
@@ -46,10 +47,10 @@ export default class PowerBands extends TranslatedComponent {
*/
constructor(props, context) {
super(props);
this.wattScale = d3.scale.linear();
this.pctScale = d3.scale.linear().domain([0, 1]);
this.wattAxis = d3.svg.axis().scale(this.wattScale).outerTickSize(0).orient('top').tickFormat(context.language.formats.r2);
this.pctAxis = d3.svg.axis().scale(this.pctScale).outerTickSize(0).orient('bottom').tickFormat(context.language.formats.rPct);
this.wattScale = d3.scaleLinear();
this.pctScale = d3.scaleLinear().domain([0, 1]);
this.wattAxis = d3.axisTop(this.wattScale).tickSizeOuter(0).tickFormat(context.language.formats.r2);
this.pctAxis = d3.axisBottom(this.pctScale).tickSizeOuter(0).tickFormat(context.language.formats.rPct);
this._updateDimensions = this._updateDimensions.bind(this);
this._updateScales = this._updateScales.bind(this);
@@ -186,10 +187,10 @@ export default class PowerBands extends TranslatedComponent {
let { wattScale, pctScale, context, props, state } = this;
let { translate, formats } = context.language;
let { f2, pct1, rPct, r2 } = formats; // wattFmt, pctFmt, pctAxis, wattAxis
let { available, bands, width } = props;
let { innerWidth, maxPwr, ret, dep } = state;
let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum * 2 >= available });
let { f2, pct1 } = formats; // wattFmt, pctFmt
let { available, bands } = props;
let { innerWidth, ret, dep } = state;
let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum > available * 0.4 });
let deployed = [];
let retracted = [];
let retSelected = Object.keys(ret).length > 0;
@@ -268,7 +269,7 @@ export default class PowerBands extends TranslatedComponent {
axis.call(this.pctAxis);
axis.select('g:nth-child(6)').selectAll('line, text').attr('class', pwrWarningClass);
}} className='pct axis' transform={`translate(0,${state.innerHeight})`}></g>
<line x1={pctScale(0.5)} x2={pctScale(0.5)} y1='0' y2={state.innerHeight} className={pwrWarningClass} />
<line x1={pctScale(0.4)} x2={pctScale(0.4)} y1='0' y2={state.innerHeight} className={pwrWarningClass} />
<text dy='0.5em' x='-3' y={state.retY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'retracted')} onMouseLeave={this._hidetip}>{translate('ret')}</text>
<text dy='0.5em' x='-3' y={state.depY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'deployed', { orientation: 's', cap: 1 })} onMouseLeave={this._hidetip}>{translate('dep')}</text>
<text dy='0.5em' x={innerWidth + 5} y={state.retY} className={getClass(retSelected, retSum, available)}>{f2(Math.max(0, retSum))} ({pct1(Math.max(0, retSum / available))})</text>

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import PowerBands from './PowerBands';
@@ -17,10 +17,10 @@ const POWER = [
* Power Management Section
*/
export default class PowerManagement extends TranslatedComponent {
static PropTypes = {
ship: React.PropTypes.object.isRequired,
code: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired
static propTypes = {
ship: PropTypes.object.isRequired,
code: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
};
/**
@@ -34,8 +34,8 @@ export default class PowerManagement extends TranslatedComponent {
this._sort = this._sort.bind(this);
this.state = {
predicate: 'n',
desc: true,
predicate: 'pwr',
desc: false,
width: 0
};
}
@@ -71,7 +71,7 @@ export default class PowerManagement extends TranslatedComponent {
case 'n': comp = comp(null, desc); break;
case 't': comp = comp((a, b) => a.type.localeCompare(b.type), desc); break;
case 'pri': comp = comp((a, b) => a.priority - b.priority, desc); break;
case 'pwr': comp = comp((a, b) => a.m.power - b.m.power, desc); break;
case 'pwr': comp = comp((a, b) => a.m.getPowerUsage() - b.m.getPowerUsage(), desc); break;
case 'r': comp = comp((a, b) => ship.getSlotStatus(a) - ship.getSlotStatus(b), desc); break;
case 'd': comp = comp((a, b) => ship.getSlotStatus(a, true) - ship.getSlotStatus(b, true), desc); break;
}
@@ -113,7 +113,7 @@ export default class PowerManagement extends TranslatedComponent {
for (let i = 0, l = ship.powerList.length; i < l; i++) {
let slot = ship.powerList[i];
if (slot.m && slot.m.power) {
if (slot.m && slot.m.getPowerUsage() > 0) {
let m = slot.m;
let toggleEnabled = this._toggleEnabled.bind(this, slot);
let retractedElem = null, deployedElem = null;
@@ -134,8 +134,8 @@ export default class PowerManagement extends TranslatedComponent {
{' ' + (slot.priority + 1) + ' '}
<span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>&#9658;</span>
</td>
<td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.power)}</td>
<td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.power / ship.powerAvailable)}</u></td>
<td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.getPowerUsage())}</td>
<td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.getPowerUsage() / ship.powerAvailable)}</u></td>
{retractedElem}
{deployedElem}
</tr>);
@@ -148,7 +148,7 @@ export default class PowerManagement extends TranslatedComponent {
* Update power bands width from DOM
*/
_updateWidth() {
this.setState({ width: findDOMNode(this).offsetWidth });
this.setState({ width: this.node.offsetWidth });
}
/**
@@ -196,7 +196,7 @@ export default class PowerManagement extends TranslatedComponent {
let sortOrder = this._sortOrder;
return (
<div className='group half' id='componentPriority'>
<div ref={node => this.node = node} className='group half' id='componentPriority'>
<table style={{ width: '100%' }}>
<thead>
<tr className='main'>
@@ -214,7 +214,7 @@ export default class PowerManagement extends TranslatedComponent {
<td className='le shorten cap' >{translate('pp')}</td>
<td><u >{translate('SYS')}</u></td>
<td>1</td>
<td className='ri'>{pwr(pp.pGen)}</td>
<td className='ri'>{pwr(pp.getPowerGeneration())}</td>
<td className='ri'><u>100%</u></td>
<td></td>
<td></td>
@@ -223,7 +223,7 @@ export default class PowerManagement extends TranslatedComponent {
{this._renderPowerRows(ship, translate, pwr, formats.pct1)}
</tbody>
</table>
<PowerBands width={this.state.width} code={code} available={ship.standard[0].m.pGen} bands={ship.priorityBands} />
<PowerBands width={this.state.width} code={code} available={pp.getPowerGeneration()} bands={ship.priorityBands} />
</div>
);
}

View File

@@ -0,0 +1,127 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import Ship from '../shipyard/Ship';
import { Ships } from 'coriolis-data/dist';
import { Rocket } from './SvgIcons';
import Persist from '../stores/Persist';
import cn from 'classnames';
/**
* Ship picker
* Requires an onChange() function of the form onChange(ship), providing the ship, which is triggered on ship change
*/
export default class ShipPicker extends TranslatedComponent {
static propTypes = {
onChange: PropTypes.func.isRequired,
ship: PropTypes.string.isRequired,
build: PropTypes.string
};
static defaultProps = {
ship: 'eagle'
}
/**
* constructor
* @param {object} props Properties react
* @param {object} context react context
*/
constructor(props, context) { // eslint-disable-line
super(props);
this.shipOrder = Object.keys(Ships).sort();
this._toggleMenu = this._toggleMenu.bind(this);
this._closeMenu = this._closeMenu.bind(this);
this.state = { menuOpen: false };
}
/**
* Update ship
* @param {object} ship the ship
* @param {string} build the build, if present
*/
_shipChange(ship, build) {
this._closeMenu();
// Ensure that the ship has changed
if (ship !== this.props.ship || build !== this.props.build) {
this.props.onChange(ship, build);
}
}
/**
* Render the menu for the picker
* @returns {object} the picker menu
*/
_renderPickerMenu() {
const { ship, build } = this.props;
const _shipChange = this._shipChange;
const builds = Persist.getBuilds();
const buildList = [];
for (let shipId of this.shipOrder) {
const shipBuilds = [];
// Add stock build
const stockSelected = (ship == shipId && !build);
shipBuilds.push(<li key={shipId} className={ cn({ 'selected': stockSelected })} onClick={_shipChange.bind(this, shipId, null)}>Stock</li>);
if (builds[shipId]) {
let buildNameOrder = Object.keys(builds[shipId]).sort();
for (let buildName of buildNameOrder) {
const buildSelected = ship === shipId && build === buildName;
shipBuilds.push(<li key={shipId + '-' + buildName} className={ cn({ 'selected': buildSelected })} onClick={_shipChange.bind(this, shipId, buildName)}>{buildName}</li>);
}
}
buildList.push(<ul key={shipId} className='block'>{Ships[shipId].properties.name}{shipBuilds}</ul>);
}
return buildList;
}
/**
* Toggle the menu state
*/
_toggleMenu() {
const { menuOpen } = this.state;
this.setState({ menuOpen: !menuOpen });
}
/**
* Close the menu
*/
_closeMenu() {
const { menuOpen } = this.state;
if (menuOpen) {
this._toggleMenu();
}
}
/**
* Render picker
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { ship, build } = this.props;
const { menuOpen } = this.state;
const shipString = ship + ': ' + (build ? build : translate('stock'));
return (
<div className='shippicker' onClick={ (e) => e.stopPropagation() }>
<div className='menu'>
<div className={cn('menu-header', { selected: menuOpen })} onClick={this._toggleMenu}>
<span><Rocket className='warning' /></span>
<span className='menu-item-label'>{shipString}</span>
</div>
{ menuOpen ?
<div className='menu-list' onClick={ (e) => e.stopPropagation() }>
<div className='quad'>
{this._renderPickerMenu()}
</div>
</div> : null }
</div>
</div>
);
}
}

View File

@@ -1,8 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import { SizeMap } from '../shipyard/Constants';
import { Warning } from './SvgIcons';
import * as Calc from '../shipyard/Calculations';
/**
* Ship Summary Table / Stats
@@ -10,98 +11,182 @@ import { Warning } from './SvgIcons';
export default class ShipSummaryTable extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired
ship: PropTypes.object.isRequired,
cargo: PropTypes.number.isRequired,
fuel: PropTypes.number.isRequired,
marker: PropTypes.string.isRequired,
pips: PropTypes.object.isRequired
};
/**
* The ShipSummaryTable constructor
* @param {Object} props The props
*/
constructor(props) {
super(props);
this.didContextChange = this.didContextChange.bind(this);
this.state = {
shieldColour: 'blue'
};
}
/**
* Render the table
* @return {React.Component} Summary table
*/
render() {
let ship = this.props.ship;
const { ship, cargo, fuel, pips } = this.props;
let { language, tooltip, termtip } = this.context;
let translate = language.translate;
let u = language.units;
let formats = language.formats;
let round = formats.round;
let { time, int } = formats;
let armourDetails = null;
let sgClassNames = cn({ warning: ship.sgSlot && !ship.shieldStrength, muted: !ship.sgSlot });
let sgRecover = '-';
let sgRecharge = '-';
let { time, int, round, f1, f2 } = formats;
let hide = tooltip.bind(null, null);
if (ship.armourMultiplier > 1 || ship.armourAdded) {
armourDetails = <u>({
(ship.armourMultiplier > 1 ? formats.rPct(ship.armourMultiplier) : '') +
(ship.armourAdded ? ' + ' + ship.armourAdded : '')
})</u>;
const shieldGenerator = ship.findInternalByGroup('sg') || ship.findInternalByGroup('psg');
const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
const timeToDrain = Calc.timeToDrainWep(ship, 4);
const canThrust = ship.canThrust(cargo, ship.fuelCapacity);
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const canBoost = ship.canBoost(cargo, ship.fuelCapacity);
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const sgMetrics = Calc.shieldMetrics(ship, pips.sys || 2);
const shipBoost = canBoost ? Calc.calcBoost(ship) : 'No Boost';
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';
}
if (ship.shieldStrength) {
sgRecover = time(ship.calcShieldRecovery());
sgRecharge = time(ship.calcShieldRecharge());
}
this.state = {
shieldColour
};
return <div id='summary'>
<table id='summaryTable'>
<table className={'summaryTable'}>
<thead>
<tr className='main'>
<th rowSpan={2}>{translate('size')}</th>
<th onMouseEnter={termtip.bind(null, 'maneuverability')} onMouseLeave={hide} rowSpan={2}>{translate('MNV')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canThrust() }) }>{translate('speed')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canBoost() }) }>{translate('boost')}</th>
<th onMouseEnter={termtip.bind(null, 'damage per second')} onMouseLeave={hide} rowSpan={2}>{translate('DPS')}</th>
<th rowSpan={2}>{translate('armour')}</th>
<th colSpan={3}>{translate('shields')}</th>
<th colSpan={3}>{translate('mass')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
<th colSpan={5}>{translate('jump range')}</th>
<th rowSpan={2}>{translate('shield')}</th>
<th rowSpan={2}>{translate('integrity')}</th>
<th rowSpan={2}>{translate('DPS')}</th>
<th rowSpan={2}>{translate('EPS')}</th>
<th rowSpan={2}>{translate('TTD')}</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('pax')}</th>
<th rowSpan={2}>{translate('fuel')}</th>
<th colSpan={3}>{translate('jump range')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_FASTEST_RANGE')} onMouseLeave={hide} colSpan={3}>{translate('fastest range')}</th>
<th onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
<th colSpan={3}>{translate('mass')}</th>
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
<th rowSpan={2}>{translate('crew')}</th>
<th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_TIME', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost time')}</th>
</tr>
<tr>
<th className='lft'>{translate('strength')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide}>{translate('recovery')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide}>{translate('recharge')}</th>
<th className='lft'>{translate('hull')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_UNLADEN', { cap: 0 })} onMouseLeave={hide}>{translate('unladen')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_LADEN', { cap: 0 })} onMouseLeave={hide}>{translate('laden')}</th>
<th className='lft'>{translate('max')}</th>
<th>{translate('full tank')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
<th className='lft'>{translate('jumps')}</th>
<th>{translate('total unladen')}</th>
<th>{translate('total laden')}</th>
<th className='lft'>{translate('hull')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
</tr>
</thead>
<tbody>
<tr>
<td className='cap'>{translate(SizeMap[ship.class])}</td>
<td>{ship.agility}/10</td>
<td>{ ship.canThrust() ? <span>{int(ship.topSpeed)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td>{ ship.canBoost() ? <span>{int(ship.topBoost)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td>{round(ship.totalDps)}</td>
<td>{int(ship.armour)} {armourDetails}</td>
<td className={sgClassNames}>{int(ship.shieldStrength)} {u.MJ} { ship.shieldMultiplier > 1 && ship.shieldStrength > 0 ? <u>({formats.rPct(ship.shieldMultiplier)})</u> : null }</td>
<td className={sgClassNames}>{sgRecover}</td>
<td className={sgClassNames}>{sgRecharge}</td>
<td>{ship.hullMass} {u.T}</td>
<td>{round(ship.unladenMass)} {u.T}</td>
<td>{round(ship.ladenMass)} {u.T}</td>
<td>{round(ship.cargoCapacity)} {u.T}</td>
<td>{round(ship.fuelCapacity)} {u.T}</td>
<td>{round(ship.unladenRange)} {u.LY}</td>
<td>{round(ship.fullTankRange)} {u.LY}</td>
<td>{round(ship.ladenRange)} {u.LY}</td>
<td>{round(ship.maxJumpCount)}</td>
<td>{round(ship.unladenFastestRange)} {u.LY}</td>
<td>{round(ship.ladenFastestRange)} {u.LY}</td>
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })} onMouseLeave={hide}>{ canThrust ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })} onMouseLeave={hide}>{ canBoost ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, true))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump()))}{u.LY}</span></td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</span></td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</span></td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</td>
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
{/* <td>{f1(ship.totalHps)}</td> */}
<td>{round(ship.cargoCapacity)}{u.T}</td>
<td>{ship.passengerCapacity}</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_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</td>
<td>{int(ship.hardness)}</td>
<td>{ship.crew}</td>
<td>{ship.masslock}</td>
<td>{shipBoost !== 'No Boost' ? formats.time(shipBoost) : 'No Boost'}</td>
</tr>
</tbody>
</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>;
}
}

View File

@@ -1,166 +1,386 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
const MARGIN_LR = 8; // Left/ Right margin
/**
* Horizontal Slider
*/
export default class Slider extends React.Component {
static defaultProps = {
axis: false,
min: 0,
max: 1,
scale: 1 // SVG render scale
};
static propTypes = {
axis: React.PropTypes.bool,
axisUnit: React.PropTypes.string,
max: React.PropTypes.number,
min: React.PropTypes.number,
onChange: React.PropTypes.func.isRequired,
onResize: React.PropTypes.func,
percent: React.PropTypes.number.isRequired,
scale: React.PropTypes.number
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._down = this._down.bind(this);
this._move = this._move.bind(this);
this._up = this._up.bind(this);
this._updatePercent = this._updatePercent.bind(this);
this._updateDimensions = this._updateDimensions.bind(this);
this.state = { width: 0 };
}
/**
* On Mouse/Touch down handler
* @param {SyntheticEvent} event Event
*/
_down(event) {
let rect = event.currentTarget.getBoundingClientRect();
this.left = rect.left;
this.width = rect.width;
this._move(event);
}
/**
* Update the slider percentage on move
* @param {SyntheticEvent} event Event
*/
_move(event) {
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);
}
}
/**
* On Mouse/Touch up handler
* @param {Event} event DOM Event
*/
_up(event) {
event.preventDefault();
this.left = null;
this.width = null;
}
/**
* Determine if the user is still dragging
* @param {SyntheticEvent} event Event
*/
_enter(event) {
if(event.buttons !== 1) {
this.left = null;
this.width = null;
}
}
/**
* Update the slider percentage
* @param {number} pos Slider drag position
* @param {number} width Slider width
* @param {Event} event DOM Event
*/
_updatePercent(pos, width) {
this.props.onChange(Math.min(Math.max(pos / width, 0), 1));
}
/**
* Update dimenions from rendered DOM
*/
_updateDimensions() {
this.setState({
outerWidth: findDOMNode(this).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} />;
}
let margin = MARGIN_LR * scale;
let width = outerWidth - (margin * 2);
let pctPos = width * this.props.percent;
return <svg onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onTouchEnd={this._up} style={style}>
<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} />
{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>;
}
}
import React from 'react';
import PropTypes from 'prop-types';
const MARGIN_LR = 8; // Left/ Right margin
/**
* Horizontal Slider
*/
export default class Slider extends React.Component {
static defaultProps = {
axis: false,
min: 0,
max: 1,
scale: 1 // SVG render scale
};
static propTypes = {
axis: PropTypes.bool,
axisUnit: PropTypes.string,// units (T, M, etc.)
max: PropTypes.number,
min: PropTypes.number,
onChange: PropTypes.func.isRequired,// function which determins percent value
onResize: PropTypes.func,
percent: PropTypes.number.isRequired,// value of slider
scale: PropTypes.number
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._down = this._down.bind(this);
this._move = this._move.bind(this);
this._up = this._up.bind(this);
this._keyup = this._keyup.bind(this);
this._keydown = this._keydown.bind(this);
this._touchstart = this._touchstart.bind(this);
this._touchend = this._touchend.bind(this);
this._updatePercent = this._updatePercent.bind(this);
this._updateDimensions = this._updateDimensions.bind(this);
this.state = { width: 0 };
}
/**
* On Mouse/Touch down handler
* @param {SyntheticEvent} event Event
*/
_down(event) {
let rect = event.currentTarget.getBoundingClientRect();
this.left = rect.left;
this.width = rect.width;
this._move(event);
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
}
/**
* Update the slider percentage on move
* @param {SyntheticEvent} event Event
*/
_move(event) {
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);
}
}
/**
* On Mouse/Touch up handler
* @param {Event} event DOM Event
*/
_up(event) {
this.sliderInputBox.sliderVal.focus();
clearTimeout(this.touchStartTimer);
event.preventDefault();
this.left = null;
this.width = null;
}
/**
* Key up handler for keyboard.
* display the number field then set focus to it
* when "Enter" key is pressed
* @param {Event} event Keyboard event
*/
_keyup(event) {
switch (event.key) {
case 'Enter':
event.preventDefault();
this.sliderInputBox._setDisplay('block');
return;
default:
return;
}
}
/**
* Key down handler
* increment slider position by +/- 1 when right/left arrow key is pressed or held
* @param {Event} event Keyboard even
*/
_keydown(event) {
let newVal = this.props.percent * this.props.max;
switch (event.key) {
case 'ArrowRight':
newVal += 1;
if (newVal <= this.props.max) this.props.onChange(newVal / this.props.max);
return;
case 'ArrowLeft':
newVal -= 1;
if (newVal >= 0) this.props.onChange(newVal / this.props.max);
return;
default:
return;
}
}
/**
* Touch start handler
* @param {Event} event DOM Event
*
*/
_touchstart(event) {
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
}
/**
* Touch end handler
* @param {Event} event DOM Event
*
*/
_touchend(event) {
this.sliderInputBox.sliderVal.focus();
clearTimeout(this.touchStartTimer);
}
/**
* Determine if the user is still dragging
* @param {SyntheticEvent} event Event
*/
_enter(event) {
if(event.buttons !== 1) {
this.left = null;
this.width = null;
}
}
/**
* Update the slider percentage
* @param {number} pos Slider drag position
* @param {number} width Slider width
* @param {Event} event DOM Event
*/
_updatePercent(pos, width) {
this.props.onChange(Math.min(Math.max(pos / width, 0), 1));
}
/**
* Update dimenions from rendered DOM
*/
_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
};
/**
* Determine if the user is still dragging
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._handleFocus = this._handleFocus.bind(this);
this._handleBlur = this._handleBlur.bind(this);
this._handleChange = this._handleChange.bind(this);
this._keyup = this._keyup.bind(this);
this.state = this._getInitialState();
}
/**
* Update input value if slider changes will change props/state
* @param {Object} nextProps React Component properites
* @param {Object} nextState React Component state values
*/
componentWillReceiveProps(nextProps, nextState) {
let 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 });
}
}
/**
* Update slider textbox visibility/values if changes are made to slider
* @param {Object} prevProps React Component properites
* @param {Object} prevState React Component state values
*/
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);
}
}
/**
* Set initial state for the textbox.
* We may want to rethink this to
* try and make it a stateless component
* @returns {object} React state object with initial values set
*/
_getInitialState() {
return {
divStyle: { display:'none' },
inputStyle: { width:'4em' },
labelStyle: { marginLeft: '.1em' },
maxLength:5,
size:5,
min:0,
tabIndex:-1,
type:'number',
readOnly: true,
inputValue: this.props.percent * this.props.max
};
}
/**
*
* @param {string} val block or none
*/
_setDisplay(val) {
this.setState({
divStyle: { display:val }
});
}
/**
* Update the input value
* when textbox gets focus
*/
_handleFocus() {
this.setState({
inputValue:this._getValue()
});
}
/**
* Update inputValue when textbox loses focus
*/
_handleBlur() {
this._setDisplay('none');
if (this.state.inputValue !== '') {
this.props.onChange(this.state.inputValue / this.props.max);
} else {
this.setState({
inputValue: this.props.percent * this.props.max
});
}
}
/**
* Get the value in the text box
* @returns {number} inputValue Value of the input box
*/
_getValue() {
return this.state.inputValue;
}
/**
* Update and set limits on input box
* values depending on what user
* has selected
*
* @param {SyntheticEvent} event ReactJs onChange event
*/
_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;
}
}
/**
* Get the value in the text box
* @return {React.Component} Text Input component for Slider
*/
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

@@ -1,9 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu';
import { diffDetails } from '../utils/SlotFunctions';
import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
/**
* Abstract Slot
@@ -11,17 +14,19 @@ import { wrapCtxMenu } from '../utils/UtilityFunctions';
export default class Slot extends TranslatedComponent {
static propTypes = {
availableModules: React.PropTypes.func.isRequired,
onSelect: React.PropTypes.func.isRequired,
onOpen: React.PropTypes.func.isRequired,
maxClass: React.PropTypes.number.isRequired,
selected: React.PropTypes.bool,
m: React.PropTypes.object,
ship: React.PropTypes.object.isRequired,
warning: React.PropTypes.func,
drag: React.PropTypes.func,
drop: React.PropTypes.func,
dropClass: React.PropTypes.string
availableModules: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired,
onOpen: PropTypes.func.isRequired,
maxClass: PropTypes.number.isRequired,
selected: PropTypes.bool,
m: PropTypes.object,
enabled: PropTypes.bool.isRequired,
ship: PropTypes.object.isRequired,
eligible: PropTypes.object,
warning: PropTypes.func,
drag: PropTypes.func,
drop: PropTypes.func,
dropClass: PropTypes.string
};
/**
@@ -31,8 +36,12 @@ export default class Slot extends TranslatedComponent {
constructor(props) {
super(props);
this._modificationsSelected = false;
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
this._keyDown = this._keyDown.bind(this);
this.slotDiv = null;
}
// Must be implemented by subclasses:
@@ -66,6 +75,22 @@ export default class Slot extends TranslatedComponent {
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') {
this._toggleModifications();
}
this.props.onOpen(event);
}
}
/**
* Render the slot
* @return {React.Component} The slot
@@ -73,37 +98,64 @@ export default class Slot extends TranslatedComponent {
render() {
let language = this.context.language;
let translate = language.translate;
let { ship, m, dropClass, dragOver, onOpen, selected, onSelect, warning, shipMass, availableModules } = this.props;
let slotDetails, menu;
let { ship, m, enabled, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
let slotDetails, modificationsMarker, menu;
if (m) {
slotDetails = this._getSlotDetails(m, translate, language.formats, language.units); // Must be implemented by sub classes
} else {
slotDetails = <div className={'empty'}>{translate('empty')}</div>;
if (!selected) {
// If not selected then sure that modifications flag is unset
this._modificationsSelected = false;
}
if (this.props.selected) {
menu = <AvailableModulesMenu
className={this._getClassNames()}
modules={availableModules()}
shipMass={ship.hullMass}
m={m}
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}
/>;
if (m) {
slotDetails = this._getSlotDetails(m, enabled, translate, language.formats, language.units); // Must be implemented by sub classes
modificationsMarker = JSON.stringify(m);
} else {
slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
modificationsMarker = '';
}
if (selected) {
if (this._modificationsSelected) {
menu = <ModificationsMenu
className={this._getClassNames()}
onChange={onChange}
ship={ship}
m={m}
marker={modificationsMarker}
modButton = {this.modButton}
/>;
} else {
menu = <AvailableModulesMenu
className={this._getClassNames()}
modules={availableModules()}
shipMass={ship.hullMass}
m={m}
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}
slotDiv = {this.slotDiv}
/>;
}
}
// TODO: implement touch dragging
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='sz'>{this._getMaxClassLabel(translate)}</div>
{slotDetails}
</div>
{slotDetails}
</div>
{menu}
</div>
);
}
/**
* Toggle the modifications flag when selecting the modifications icon
*/
_toggleModifications() {
this._modificationsSelected = !this._modificationsSelected;
}
}

View File

@@ -1,9 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { canMount } from '../utils/SlotFunctions';
import { Equalizer } from '../components/SvgIcons';
import cn from 'classnames';
const browser = require('detect-browser');
/**
* Abstract Slot Section
@@ -11,10 +13,13 @@ import cn from 'classnames';
export default class SlotSection extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired,
onChange: React.PropTypes.func.isRequired,
code: React.PropTypes.string.isRequired,
togglePwr: React.PropTypes.func
ship: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
onCargoChange: PropTypes.func.isRequired,
onFuelChange: PropTypes.func.isRequired,
code: PropTypes.string.isRequired,
togglePwr: PropTypes.func,
sectionMenuRefs: PropTypes.object
};
/**
@@ -28,7 +33,10 @@ export default class SlotSection extends TranslatedComponent {
super(props);
this.sectionId = sectionId;
this.sectionName = sectionName;
this.ssHeadRef = null;
this.sectionRefArr = this.props.sectionMenuRefs[this.sectionId] = [];
this.sectionRefArr['selectedRef'] = null;
this._getSlots = this._getSlots.bind(this);
this._selectModule = this._selectModule.bind(this);
this._getSectionMenu = this._getSectionMenu.bind(this);
@@ -36,6 +44,8 @@ export default class SlotSection extends TranslatedComponent {
this._drop = this._drop.bind(this);
this._dragOverNone = this._dragOverNone.bind(this);
this._close = this._close.bind(this);
this._keyDown = this._keyDown.bind(this);
this._handleSectionFocus = this._handleSectionFocus.bind(this);
this.state = {};
}
@@ -43,7 +53,59 @@ export default class SlotSection extends TranslatedComponent {
// _getSlots()
// _getSectionMenu()
// _contextMenu()
// componentDidUpdate(prevProps)
/**
* TODO: May either need to send the function to be triggered when Enter key is pressed, or else
* may need a separate keyDown handler for each subclass (StandardSlotSection, HardpointSlotSection, etc.)
* ex: _keyDown(_keyDownfn, event)
*
* @param {SyntheticEvent} event KeyDown event
*/
_keyDown(event) {
if (event.key == 'Enter') {
event.stopPropagation();
if (event.currentTarget.nodeName === 'H1') {
this._openMenu(this.sectionName, event);
} else {
event.currentTarget.click();
}
return;
}
if (event.key == 'Tab') {
if (event.shiftKey) {
if ((event.currentTarget === this.sectionRefArr[this.firstRefId]) && this.sectionRefArr[this.lastRefId]) {
event.preventDefault();
this.sectionRefArr[this.lastRefId].focus();
}
} else {
if ((event.currentTarget === this.sectionRefArr[this.lastRefId]) && this.sectionRefArr[this.firstRefId]) {
event.preventDefault();
this.sectionRefArr[this.firstRefId].focus();
}
}
}
}
/**
* Set focus on appropriate Slot Section Menu element
* @param {Object} focusPrevProps prevProps for componentDidUpdate() from ...SlotSection.jsx
* @param {String} firstRef id of the first ref in ...SlotSection.jsx
* @param {String} lastRef id of the last ref in ...SlotSection.jsx
*
*/
_handleSectionFocus(focusPrevProps, firstRef, lastRef) {
if (this.selectedRefId !== null && this.sectionRefArr[this.selectedRefId]) {
// set focus on the previously selected option for the currently open section menu
this.sectionRefArr[this.selectedRefId].focus();
} else if (this.sectionRefArr[firstRef] && this.sectionRefArr[firstRef] != null) {
// set focus on the first option in the currently open section menu if none have been selected previously
this.sectionRefArr[firstRef].focus();
} else if (this.props.currentMenu == null && focusPrevProps.currentMenu == this.sectionName && this.sectionRefArr['ssHeadRef']) {
// set focus on the section menu header when section menu is closed
this.sectionRefArr['ssHeadRef'].focus();
}
}
/**
* Open a menu
* @param {string} menu Menu name
@@ -64,7 +126,7 @@ export default class SlotSection extends TranslatedComponent {
* @param {Object} m Selected module
*/
_selectModule(slot, m) {
this.props.ship.use(slot, m);
this.props.ship.use(slot, m, false);
this.props.onChange();
this._close();
}
@@ -75,9 +137,11 @@ export default class SlotSection extends TranslatedComponent {
* @param {Event} e Drag Event
*/
_drag(originSlot, e) {
e.dataTransfer.setData('text/html', e.currentTarget);
e.dataTransfer.effectAllowed = 'all';
this.setState({ originSlot });
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
e.dataTransfer.setData('text/html', e.currentTarget);
}
e.dataTransfer.effectAllowed = 'copyMove';
this.setState({ originSlot, copy: e.getModifierState('Alt') });
this._close();
}
@@ -91,10 +155,16 @@ export default class SlotSection extends TranslatedComponent {
e.stopPropagation();
let os = this.state.originSlot;
if (os) {
e.dataTransfer.dropEffect = os != targetSlot && canMount(this.props.ship, targetSlot, os.m.grp, os.m.class) ? 'copyMove' : 'none';
// Show correct icon
const effect = this.state.copy ? 'copy' : 'move';
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
e.dataTransfer.dropEffect = os != targetSlot && canMount(this.props.ship, targetSlot, os.m.grp, os.m.class) ? effect : 'none';
}
this.setState({ targetSlot });
} else {
e.dataTransfer.dropEffect = 'none';
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
e.dataTransfer.dropEffect = 'none';
}
}
}
@@ -104,7 +174,9 @@ export default class SlotSection extends TranslatedComponent {
*/
_dragOverNone(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'none';
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
e.dataTransfer.dropEffect = 'none';
}
this.setState({ targetSlot: null });
}
@@ -114,20 +186,52 @@ export default class SlotSection extends TranslatedComponent {
* the origin slot will be empty.
*/
_drop() {
let { originSlot, targetSlot } = this.state;
let { originSlot, targetSlot, copy } = this.state;
let m = originSlot.m;
if (targetSlot && m && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
// Swap modules if possible
if (targetSlot.m && canMount(this.props.ship, originSlot, targetSlot.m.grp, targetSlot.m.class)) {
this.props.ship.use(originSlot, targetSlot.m, true);
} else { // Otherwise empty the origin slot
this.props.ship.use(originSlot, null, true); // Empty but prevent summary update
if (targetSlot && originSlot != targetSlot) {
if (copy) {
// We want to copy the module in to the target slot
if (targetSlot && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
const mCopy = m.clone();
this.props.ship.use(targetSlot, mCopy, false);
// Copy power info
targetSlot.enabled = originSlot.enabled;
targetSlot.priority = originSlot.priority;
this.props.onChange();
}
} else {
// Store power info
const originEnabled = targetSlot.enabled;
const originPriority = targetSlot.priority;
const targetEnabled = originSlot.enabled;
const targetPriority = originSlot.priority;
// We want to move the module in to the target slot, and swap back any module that was originally in the target slot
if (targetSlot && m && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
// Swap modules if possible
if (targetSlot.m && canMount(this.props.ship, originSlot, targetSlot.m.grp, targetSlot.m.class)) {
this.props.ship.use(originSlot, targetSlot.m, true);
this.props.ship.use(targetSlot, m);
// Swap power
originSlot.enabled = originEnabled;
originSlot.priority = originPriority;
targetSlot.enabled = targetEnabled;
targetSlot.priority = targetPriority;
} else { // Otherwise empty the origin slot
// Store power
const targetEnabled = originSlot.enabled;
this.props.ship.use(originSlot, null, true); // Empty but prevent summary update
this.props.ship.use(targetSlot, m);
originSlot.enabled = 0;
originSlot.priority = 0;
targetSlot.enabled = targetEnabled;
targetSlot.priority = targetPriority;
}
this.props.onChange();
}
}
this.props.ship.use(targetSlot, m); // update target slot
this.props.onChange();
}
this.setState({ originSlot: null, targetSlot: null });
this.setState({ originSlot: null, targetSlot: null, copy: null });
}
/**
@@ -179,8 +283,8 @@ export default class SlotSection extends TranslatedComponent {
return (
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}>
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}>
<h1>{translate(this.sectionName)} <Equalizer/></h1>
{sectionMenuOpened ? this._getSectionMenu(translate) : null }
<h1 tabIndex="0" onKeyDown={this._keyDown} ref={ssHead => this.sectionRefArr['ssHeadRef'] = ssHead}>{translate(this.sectionName)} <Equalizer/></h1>
{sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null }
</div>
{this._getSlots()}
</div>

View File

@@ -1,9 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import Persist from '../stores/Persist';
import TranslatedComponent from './TranslatedComponent';
import { jumpRange } from '../shipyard/Calculations';
import { diffDetails } from '../utils/SlotFunctions';
import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import { ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { blueprintTooltip } from '../utils/BlueprintFunctions';
/**
* Standard Slot
@@ -11,57 +18,130 @@ import AvailableModulesMenu from './AvailableModulesMenu';
export default class StandardSlot extends TranslatedComponent {
static propTypes = {
slot: React.PropTypes.object,
modules: React.PropTypes.array.isRequired,
onSelect: React.PropTypes.func.isRequired,
onOpen: React.PropTypes.func.isRequired,
ship: React.PropTypes.object.isRequired,
selected: React.PropTypes.bool,
warning: React.PropTypes.func,
slot: PropTypes.object,
modules: PropTypes.array.isRequired,
onSelect: PropTypes.func.isRequired,
onOpen: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
ship: PropTypes.object.isRequired,
selected: PropTypes.bool,
warning: PropTypes.func,
};
/**
* Construct the slot
* @param {object} props Object properties
*/
constructor(props) {
super(props);
this._modificationsSelected = false;
this._keyDown = this._keyDown.bind(this);
this.modButton = null;
this.slotDiv = null;
}
/**
* Handle Enter key
* @param {SyntheticEvent} event KeyDown event
*/
_keyDown(event) {
if (event.key == 'Enter') {
if(event.target.className == 'r') {
this._toggleModifications();
}
this.props.onOpen(event);
}
}
/**
* Render the slot
* @return {React.Component} Slot component
*/
render() {
let { termtip, tooltip } = this.context;
let { translate, formats, units } = this.context.language;
let { modules, slot, warning, onSelect, ladenMass, ship } = this.props;
let { modules, slot, selected, warning, onSelect, onChange, ship } = this.props;
let m = slot.m;
let classRating = m.class + m.rating;
let menu;
let validMods = m == null || !Modifications.modules[m.grp] ? [] : (Modifications.modules[m.grp].modifications || []);
if (m && m.name && m.name === 'Guardian Hybrid Power Plant') {
validMods = [];
}
let showModuleResistances = Persist.showModuleResistances();
let mass = m.getMass() || m.cargo || m.fuel || 0;
if (this.props.selected) {
menu = <AvailableModulesMenu
className='standard'
modules={modules}
shipMass={ship.ladenMass}
m={m}
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}
/>;
// Modifications tooltip shows blueprint and grade, if available
let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) {
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 = (
<div>
<div>{modTT}</div>
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
</div>
);
}
if (!selected) {
// If not selected then sure that modifications flag is unset
this._modificationsSelected = false;
}
const modificationsMarker = JSON.stringify(m);
if (selected) {
if (this._modificationsSelected) {
menu = <ModificationsMenu
className='standard'
onChange={onChange}
ship={ship}
m={m}
marker={modificationsMarker}
modButton = {this.modButton}
/>;
} else {
menu = <AvailableModulesMenu
className='standard'
modules={modules}
shipMass={ModuleUtils.isShieldGenerator(m.grp) ? ship.hullMass : ship.unladenMass}
m={m}
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}
slotDiv = {this.slotDiv}
/>;
}
}
return (
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen}>
<div className={cn('details-container', { warning: warning && warning(slot.m) })}>
<div className={'sz'}>{slot.maxClass}</div>
<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={'sz'}>{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}</div>
<div>
<div className='l'>{classRating} {translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}</div>
<div className={'r'}>{m.mass || m.fuel || 0}{units.T}</div>
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div>
<div className={'r'}>{formats.round(mass)}{units.T}</div>
<div/>
<div className={'cb'}>
{ m.grp == 'bh' && m.name ? <div className='l'>{translate(m.name)}</div> : null }
{ m.optmass ? <div className='l'>{translate('optimal mass')}: {m.optmass}{units.T}</div> : null }
{ m.maxmass ? <div className='l'>{translate('max mass')}: {m.maxmass}{units.T}</div> : null }
{ m.range ? <div className='l'>{translate('range')}: {m.range}{units.km}</div> : null }
{ m.getMinMass() ? <div className='l'>{translate('minimum mass')}: {formats.int(m.getMinMass())}{units.T}</div> : null }
{ m.getOptMass() ? <div className='l'>{translate('optimal mass')}: {formats.int(m.getOptMass())}{units.T}</div> : null }
{ m.getMaxMass() ? <div className='l'>{translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}</div> : null }
{ m.getOptMul() ? <div className='l'>{translate('optimal multiplier')}: {formats.rPct(m.getOptMul())}</div> : null }
{ m.getRange() ? <div className='l'>{translate('range', m.grp)}: {formats.f2(m.getRange())}{units.km}</div> : null }
{ m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.eff ? <div className='l'>{translate('efficiency')}: {m.eff}</div> : null }
{ m.pGen ? <div className='l'>{translate('power')}: {m.pGen}{units.MW}</div> : null }
{ m.maxfuel ? <div className='l'>{translate('max')} {translate('fuel')}: {m.maxfuel}{units.T}</div> : null }
{ m.weaponcapacity ? <div className='l'>{translate('WEP')}: {m.weaponcapacity}{units.MJ} / {m.weaponrecharge}{units.MW}</div> : null }
{ m.systemcapacity ? <div className='l'>{translate('SYS')}: {m.systemcapacity}{units.MJ} / {m.systemrecharge}{units.MW}</div> : null }
{ m.enginecapacity ? <div className='l'>{translate('ENG')}: {m.enginecapacity}{units.MJ} / {m.enginerecharge}{units.MW}</div> : null }
{ m.getThermalEfficiency() ? <div className='l'>{translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}</div> : null }
{ m.getPowerGeneration() > 0 ? <div className='l'>{translate('pgen')}: {formats.f1(m.getPowerGeneration())}{units.MW}</div> : null }
{ m.getMaxFuelPerJump() ? <div className='l'>{translate('max')} {translate('fuel')}: {formats.f1(m.getMaxFuelPerJump())}{units.T}</div> : null }
{ m.getWeaponsCapacity() ? <div className='l'>{translate('WEP')}: {formats.f1(m.getWeaponsCapacity())}{units.MJ} / {formats.f1(m.getWeaponsRechargeRate())}{units.MW}</div> : null }
{ m.getSystemsCapacity() ? <div className='l'>{translate('SYS')}: {formats.f1(m.getSystemsCapacity())}{units.MJ} / {formats.f1(m.getSystemsRechargeRate())}{units.MW}</div> : null }
{ m.getEnginesCapacity() ? <div className='l'>{translate('ENG')}: {formats.f1(m.getEnginesCapacity())}{units.MJ} / {formats.f1(m.getEnginesRechargeRate())}{units.MW}</div> : null }
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ 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>
@@ -69,4 +149,11 @@ export default class StandardSlot extends TranslatedComponent {
</div>
);
}
/**
* Toggle the modifications flag when selecting the modifications icon
*/
_toggleModifications() {
this._modificationsSelected = !this._modificationsSelected;
}
}

View File

@@ -2,8 +2,7 @@ import React from 'react';
import cn from 'classnames';
import SlotSection from './SlotSection';
import StandardSlot from './StandardSlot';
import { diffDetails } from '../utils/SlotFunctions';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
import * as ShipRoles from '../shipyard/ShipRoles';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
@@ -18,17 +17,30 @@ export default class StandardSlotSection extends SlotSection {
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props, context, 'standard', 'standard');
super(props, context, 'standard', 'core internal');
this._optimizeStandard = this._optimizeStandard.bind(this);
this._selectBulkhead = this._selectBulkhead.bind(this);
this.selectedRefId = null;
this.firstRefId = 'maxjump';
this.lastRefId = 'racer';
}
/**
* Handle focus if the component updates
* @param {Object} prevProps React Component properties
*/
componentDidUpdate(prevProps) {
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
}
/**
* Use the lightest/optimal available standard modules
*/
_optimizeStandard() {
this.selectedRefId = 'maxjump';
this.props.ship.useLightestStandard();
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -38,8 +50,12 @@ export default class StandardSlotSection extends SlotSection {
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
*/
_multiPurpose(shielded, bulkheadIndex) {
this.selectedRefId = 'multipurpose';
if (bulkheadIndex === 2) this.selectedRefId = 'combat';
ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -48,8 +64,24 @@ export default class StandardSlotSection extends SlotSection {
* @param {Boolean} shielded True if shield generator should be included
*/
_optimizeCargo(shielded) {
this.selectedRefId = 'trader';
ShipRoles.trader(this.props.ship, shielded);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
/**
* Miner Build
* @param {Boolean} shielded True if shield generator should be included
*/
_optimizeMiner(shielded) {
this.selectedRefId = 'miner';
ShipRoles.miner(this.props.ship, shielded);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -58,8 +90,24 @@ export default class StandardSlotSection extends SlotSection {
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
*/
_optimizeExplorer(planetary) {
this.selectedRefId = 'explorer';
if (planetary) this.selectedRefId = 'planetary';
ShipRoles.explorer(this.props.ship, planetary);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
/**
* Racer role
*/
_optimizeRacer() {
this.selectedRefId = 'racer';
ShipRoles.racer(this.props.ship);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -86,12 +134,10 @@ export default class StandardSlotSection extends SlotSection {
* @return {Array} Array of Slots
*/
_getSlots() {
let { translate, units, formats } = this.context.language;
let { ship, currentMenu } = this.props;
let { ship, currentMenu, cargo, fuel } = this.props;
let slots = new Array(8);
let open = this._openMenu;
let select = this._selectModule;
let selBulkhead = this._selectBulkhead;
let st = ship.standard;
let avail = ship.getAvailableModules().standard;
let bh = ship.bulkheads;
@@ -103,6 +149,7 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, bh)}
onSelect={this._selectBulkhead}
selected={currentMenu == bh}
onChange={this.props.onChange}
ship={ship}
/>;
@@ -113,8 +160,9 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[0])}
onSelect={select.bind(this, st[0])}
selected={currentMenu == st[0]}
onChange={this.props.onChange}
ship={ship}
warning={m => m.pGen < ship.powerRetracted}
warning={m => m instanceof Module ? m.getPowerGeneration() < ship.powerRetracted : m.pgen < ship.powerRetracted}
/>;
slots[2] = <StandardSlot
@@ -124,8 +172,9 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[1])}
onSelect={select.bind(this, st[1])}
selected={currentMenu == st[1]}
onChange={this.props.onChange}
ship={ship}
warning={m => m.maxmass < (ship.ladenMass - st[1].mass + m.mass)}
warning={m => m instanceof Module ? m.getMaxMass() < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass) : m.maxmass < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass)}
/>;
@@ -135,6 +184,7 @@ export default class StandardSlotSection extends SlotSection {
modules={avail[2]}
onOpen={open.bind(this, st[2])}
onSelect={select.bind(this, st[2])}
onChange={this.props.onChange}
ship={ship}
selected={currentMenu == st[2]}
/>;
@@ -145,6 +195,7 @@ export default class StandardSlotSection extends SlotSection {
modules={avail[3]}
onOpen={open.bind(this, st[3])}
onSelect={select.bind(this, st[3])}
onChange={this.props.onChange}
ship={ship}
selected={currentMenu == st[3]}
/>;
@@ -156,8 +207,9 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[4])}
onSelect={select.bind(this, st[4])}
selected={currentMenu == st[4]}
onChange={this.props.onChange}
ship={ship}
warning= {m => m.enginecapacity < ship.boostEnergy}
warning={m => m instanceof Module ? m.getEnginesCapacity() <= ship.boostEnergy : m.engcap <= ship.boostEnergy}
/>;
slots[6] = <StandardSlot
@@ -167,8 +219,8 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[5])}
onSelect={select.bind(this, st[5])}
selected={currentMenu == st[5]}
onChange={this.props.onChange}
ship={ship}
warning= {m => m.enginecapacity < ship.boostEnergy}
/>;
slots[7] = <StandardSlot
@@ -178,6 +230,7 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[6])}
onSelect={select.bind(this, st[6])}
selected={currentMenu == st[6]}
onChange={this.props.onChange}
ship={ship}
warning= {m => m.fuel < st[2].m.maxfuel} // Show warning when fuel tank is smaller than FSD Max Fuel
/>;
@@ -194,16 +247,17 @@ export default class StandardSlotSection extends SlotSection {
let planetaryDisabled = this.props.ship.internal.length < 4;
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' onClick={this._optimizeStandard}>{translate('Maximize Jump Range')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeStandard} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['maxjump'] = smRef}>{translate('Maximize Jump Range')}</li>
</ul>
<div className='select-group cap'>{translate('roles')}</div>
<ul>
<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._optimizeCargo.bind(this, false)}>{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={cn('lc', { disabled: planetaryDisabled })} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)}>{translate('Planetary Explorer')}</li>
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, false, 0)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['multipurpose'] = smRef}>{translate('Multi-purpose')}</li>
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, true, 2)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['combat'] = smRef}>{translate('Combat')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeCargo.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['trader'] = smRef}>{translate('Trader')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeExplorer.bind(this, false)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['explorer'] = smRef}>{translate('Explorer')}</li>
<li className={cn('lc', { disabled: planetaryDisabled })} tabIndex={planetaryDisabled ? '' : '0'} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['planetary'] = smRef}>{translate('Planetary Explorer')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeMiner.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['miner'] = smRef}>{translate('Miner')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeRacer.bind(this)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['racer'] = smRef}>{translate('Racer')}</li>
</ul>
</div>;
}

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { shallowEqual } from '../utils/UtilityFunctions';
@@ -8,8 +9,8 @@ import { shallowEqual } from '../utils/UtilityFunctions';
class SvgIcon extends React.Component {
static propTypes = {
className: React.PropTypes.any,
style: React.PropTypes.object
className: PropTypes.any,
style: PropTypes.object
};
/**
@@ -227,6 +228,92 @@ export class LinkIcon extends SvgIcon {
}
}
/**
* Material
*/
export class MatIcon extends SvgIcon {
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return<g xmlns="http://www.w3.org/2000/svg">
<path fill="#FF7100" d="M 24.86,4.18
C 24.86,4.18 17.17,7.82 17.17,7.82
17.17,7.82 15.35,14.55 15.35,14.55
15.35,14.55 24.70,9.75 24.70,9.75
24.70,9.75 24.86,4.18 24.86,4.18 Z
M 32.21,17.45
C 32.21,17.45 26.41,11.18 26.41,11.18
26.41,11.18 19.51,11.51 19.51,11.51
19.51,11.51 26.92,19.01 26.92,19.01
26.92,19.01 32.21,17.45 32.21,17.45 Z
M 21.99,28.62
C 21.99,28.62 26.10,21.10 26.10,21.10
26.10,21.10 23.66,14.57 23.66,14.57
23.66,14.57 18.89,24.01 18.89,24.01
18.89,24.01 21.99,28.62 21.99,28.62 Z
M 8.33,22.24
C 8.33,22.24 16.67,23.87 16.67,23.87
16.67,23.87 22.06,19.51 22.06,19.51
22.06,19.51 11.70,17.84 11.70,17.84
11.70,17.84 8.33,22.24 8.33,22.24 Z
M 10.11,7.14
C 10.11,7.14 11.15,15.66 11.15,15.66
11.15,15.66 16.92,19.49 16.92,19.49
16.92,19.49 15.29,9.02 15.29,9.02
15.29,9.02 10.11,7.14 10.11,7.14 Z
M 27.69,2.67
C 27.69,2.67 35.89,16.00 35.89,16.00
35.89,16.00 27.69,29.33 27.69,29.33
27.69,29.33 11.31,29.33 11.31,29.33
11.31,29.33 3.11,16.00 3.11,16.00
3.11,16.00 11.31,2.67 11.31,2.67
11.31,2.67 27.67,2.67 27.67,2.67M 29.16,0.00
C 29.16,0.00 27.69,0.00 27.69,0.00
27.69,0.00 11.31,0.00 11.31,0.00
11.31,0.00 9.84,0.00 9.84,0.00
9.84,0.00 9.06,1.25 9.06,1.25
9.06,1.25 0.87,14.57 0.87,14.57
0.87,14.57 0.00,15.98 0.00,15.98
0.00,15.98 0.87,17.39 0.87,17.39
0.87,17.39 9.06,30.73 9.06,30.73
9.06,30.73 9.84,32.00 9.84,32.00
9.84,32.00 11.31,32.00 11.31,32.00
11.31,32.00 27.69,32.00 27.69,32.00
27.69,32.00 29.16,32.00 29.16,32.00
29.16,32.00 29.94,30.73 29.94,30.73
29.94,30.73 38.13,17.39 38.13,17.39
38.13,17.39 39.00,15.98 39.00,15.98
39.00,15.98 38.13,14.57 38.13,14.57
38.13,14.57 29.94,1.25 29.94,1.25
29.94,1.25 29.16,0.00 29.16,0.00
29.16,0.00 29.16,0.00 29.16,0.00 Z" />
</g>;
}
}
/**
* Shopping icon (dollar sign)
*/
export class ShoppingIcon extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<path d='M94 188v-17c-9-1-16-3-21-6-6-3-11-7-15-14-4-6-6-14-6-23l17-3c2 9 4 16 7 21 5 6 11 9 18 10v-56c-7-1-14-4-22-8-6-3-10-8-13-13-3-6-4-12-4-19 0-13 4-23 13-31 6-5 15-8 26-9v-8h11v8c10 1 18 4 24 9 8 6 12 15 14 26l-18 3c-1-7-4-12-7-16s-8-6-13-7v50l17 6c6 2 10 5 13 8 4 4 7 8 8 13 2 4 3 10 3 15 0 12-4 22-11 31-8 8-18 12-30 13v17H94zm0-153c-7 1-12 3-16 8-4 4-6 9-6 15s2 11 5 16c4 4 9 7 17 9V35zm11 121a28 28 0 0 0 24-28c-1-6-2-11-6-15-3-4-9-7-18-10v53z'/>
</g>;
}
}
/**
* No Power - Lightning bolt + no entry
*/
@@ -319,6 +406,24 @@ export class Warning extends SvgIcon {
}
}
/**
* Absolute damage
*/
export class DamageAbsolute extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <ellipse cx='100' cy='100' rx='90' ry='90' fillOpacity='0' />;
}
}
/**
* Thermal damage
*/
@@ -475,6 +580,52 @@ export class MountTurret extends SvgIcon {
}
}
/**
* Collapse section
*/
export class CollapseSection extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<path d='m 100,180 0,-140' />
<path d='m 100,40 25,45' />
<path d='m 100,40 -25,45' />
<path d='m 20,20 160,0' />
</g>;
}
}
/**
* Expand section
*/
export class ExpandSection extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<path d='m 100,20 0,140' />
<path d='m 100,160 25,-45' />
<path d='m 100,160 -25,-45' />
<path d='m 20,180 160,0' />
</g>;
}
}
/**
* Rocket ship
*/
@@ -488,6 +639,90 @@ export class Rocket extends SvgIcon {
}
}
/**
* Help
*/
export class Help extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <path d="M90.381 140.644a225.03 225.03 0 0 1-.106-5.706c0-7.47 1.058-13.92 3.172-19.346 1.55-4.087 4.052-8.21 7.505-12.367 2.536-3.031 7.1-7.453 13.688-13.267 6.59-5.815 10.872-10.448 12.844-13.9 1.974-3.453 2.96-7.224 2.96-11.312 0-7.4-2.89-13.9-8.667-19.503-5.779-5.602-12.862-8.403-21.248-8.403-8.104 0-14.87 2.536-20.296 7.61-5.427 5.074-8.986 13.003-10.677 23.784L50 65.91c1.762-14.448 6.995-25.511 15.698-33.194 8.704-7.68 20.208-11.522 34.514-11.522 15.152 0 27.237 4.124 36.258 12.369C145.49 41.807 150 51.779 150 63.477c0 6.766-1.585 13.003-4.756 18.712-3.17 5.708-9.372 12.65-18.605 20.824-6.201 5.496-10.253 9.55-12.156 12.156-1.904 2.609-3.312 5.603-4.23 8.986-.915 3.383-1.443 8.88-1.584 16.49H90.38zm-1.163 38.162v-21.67h21.67v21.67h-21.67z"/>;
}
}
/**
* ListModifications (engineers)
*/
export class ListModifications extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Render the Icon
* @return {React.Component} SVG Icon
*/
render() {
return (
<svg className={cn('modicon', this.props.className)} style={this.props.style} viewBox={this.viewBox()}>
{this.svg()}
</svg>
);
}
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <path d='M20 180l90-100-90 100zM176 40a40 40 0 1 1-27-28'/>;
}
}
/**
* Modified (engineers)
*/
export class Modified extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Render the Icon
* @return {React.Component} SVG Icon
*/
render() {
return (
<svg className={cn('modicon', this.props.className)} style={this.props.style} viewBox={this.viewBox()}>
{this.svg()}
</svg>
);
}
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<path d="M100,5L18,52.5L18,147.5L100,195L182,147.5L182,52.5L100,5Z"/>
<path d="M100,70L74,85L74,115L100,130L126,115L126,85L100,70Z"/>
</g>;
}
}
/**
* Hammer
*/
@@ -540,6 +775,24 @@ export class Switch extends SvgIcon {
}
}
/**
* Pip
*/
export class Pip extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <rect x='10' y='10' width='180' height='180'/>;
}
}
/**
* In-game Coriolis Station logo
*/

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
/**
@@ -7,8 +8,8 @@ import TranslatedComponent from './TranslatedComponent';
export default class Tooltip extends TranslatedComponent {
static propTypes = {
rect: React.PropTypes.object.isRequired,
options: React.PropTypes.object
rect: PropTypes.object.isRequired,
options: PropTypes.object
};
static defaultProps = {
@@ -127,4 +128,4 @@ export default class Tooltip extends TranslatedComponent {
</div>;
}
}
}

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import { shallowEqual } from '../utils/UtilityFunctions';
/**
@@ -7,15 +8,15 @@ import { shallowEqual } from '../utils/UtilityFunctions';
export default class TranslatedComponent extends React.Component {
static contextTypes = {
language: React.PropTypes.object.isRequired,
sizeRatio: React.PropTypes.number.isRequired,
openMenu: React.PropTypes.func.isRequired,
closeMenu: React.PropTypes.func.isRequired,
showModal: React.PropTypes.func.isRequired,
hideModal: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired,
termtip: React.PropTypes.func.isRequired,
onWindowResize: React.PropTypes.func.isRequired
language: PropTypes.object.isRequired,
sizeRatio: PropTypes.number.isRequired,
openMenu: PropTypes.func.isRequired,
closeMenu: PropTypes.func.isRequired,
showModal: PropTypes.func.isRequired,
hideModal: PropTypes.func.isRequired,
tooltip: PropTypes.func.isRequired,
termtip: PropTypes.func.isRequired,
onWindowResize: PropTypes.func.isRequired
};
/**

View File

@@ -17,12 +17,23 @@ export default class UtilitySlotSection extends SlotSection {
constructor(props, context) {
super(props, context, 'utility', 'utility mounts');
this._empty = this._empty.bind(this);
this.selectedRefId = null;
this.firstRefId = 'emptyall';
this.lastRefId = 'po';
}
/**
* Handle focus if the component updates
* @param {Object} prevProps React Component properties
*/
componentDidUpdate(prevProps) {
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
}
/**
* Empty all utility slots and close the menu
*/
_empty() {
this.selectedRefId = this.firstRefId;
this.props.ship.emptyUtility();
this.props.onChange();
this._close();
@@ -36,6 +47,9 @@ export default class UtilitySlotSection extends SlotSection {
* @param {Synthetic} event Event
*/
_use(group, rating, name, event) {
this.selectedRefId = group;
if (rating !== null) this.selectedRefId += '-' + rating;
this.props.ship.useUtility(group, rating, name, event.getModifierState('Alt'));
this.props.onChange();
this._close();
@@ -68,14 +82,15 @@ export default class UtilitySlotSection extends SlotSection {
availableModules={() => availableModules.getHps(h.maxClass)}
onOpen={this._openMenu.bind(this,h)}
onSelect={this._selectModule.bind(this, h)}
onChange={this.props.onChange}
selected={currentMenu == h}
drag={this._drag.bind(this, h)}
dragOver={this._dragOverSlot.bind(this, h)}
drop={this._drop}
dropClass={this._dropClass(h, originSlot, targetSlot)}
enabled={h.enabled}
ship={ship}
m={h.m}
enabled={h.enabled ? true : false}
/>);
}
}
@@ -93,20 +108,28 @@ export default class UtilitySlotSection extends SlotSection {
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul>
<div className='select-group cap'>{translate('sb')}</div>
<ul>
<li className='c' onClick={_use.bind(this, 'sb', 'E', null)}>E</li>
<li className='c' onClick={_use.bind(this, 'sb', 'D', null)}>D</li>
<li className='c' onClick={_use.bind(this, 'sb', 'C', null)}>C</li>
<li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li>
<li className='c' onClick={_use.bind(this, 'sb', 'A', null)}>A</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'A', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-A'] = smRef}>A</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'B', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-B'] = smRef}>B</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'C', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-C'] = smRef}>C</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'D', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-D'] = smRef}>D</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'E', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-E'] = smRef}>E</li>
</ul>
<div className='select-group cap'>{translate('cm')}</div>
<div className='select-group cap'>{translate('hs')}</div>
<ul>
<li className='lc' onClick={_use.bind(this, 'cm', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li>
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['hs'] = smRef}>{translate('Heat Sink Launcher')}</li>
</ul>
<div className='select-group cap'>{translate('ch')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'ch', null, 'Chaff Launcher')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ch'] = smRef}>{translate('Chaff Launcher')}</li>
</ul>
<div className='select-group cap'>{translate('po')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'po', null, 'Point Defence')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['po'] = smRef}>{translate('Point Defence')}</li>
</ul>
</div>;
}

View File

@@ -0,0 +1,111 @@
import TranslatedComponent from './TranslatedComponent';
import React, { PropTypes } from 'react';
import Measure from 'react-measure';
import { BarChart, Bar, XAxis, YAxis } from 'recharts';
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
const LABEL_COLOUR = '#000000';
const AXIS_COLOUR = '#C06400';
const ASPECT = 1;
const merge = function(one, two) {
return Object.assign({}, one, two);
};
/**
* A vertical bar chart
*/
export default class VerticalBarChart extends TranslatedComponent {
static propTypes = {
data : PropTypes.array.isRequired,
yMax : PropTypes.number
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this._termtip = this._termtip.bind(this);
this.state = {
dimensions: {
width: 300,
height: 300
}
};
}
/**
* Render the bar chart
* @returns {Object} the markup
*/
render() {
const { width, height } = this.state.dimensions;
const { tooltip, termtip } = this.context;
// Calculate maximum for Y
let dataMax = Math.max(...this.props.data.map(d => d.value));
if (dataMax == -Infinity) dataMax = 0;
let yMax = this.props.yMax ? Math.round(this.props.yMax) : 0;
const localMax = Math.max(dataMax, yMax);
return (
<Measure whitelist={['width', 'top']} onMeasure={ (dimensions) => this.setState({ dimensions }) }>
<div width='100%'>
<BarChart width={width} height={width * ASPECT} data={this.props.data} margin={{ top: 5, right: 5, left: 5, bottom: 5 }}>
<XAxis interval={0} fontSize='0.8em' stroke={AXIS_COLOUR} dataKey='label' />
<YAxis interval={'preserveStart'} tickCount={11} fontSize='0.8em' stroke={AXIS_COLOUR} type='number' domain={[0, localMax]}/>
<Bar dataKey='value' label={<ValueLabel />} fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}/>
</BarChart>
</div>
</Measure>
);
}
/**
* Generate a term tip
* @param {Object} d the data
* @param {number} i the index
* @param {Object} e the event
* @returns {Object} termtip markup
*/
_termtip(d, i, e) {
if (this.props.data[i].tooltip) {
return this.context.termtip(this.props.data[i].tooltip, e);
} else {
return null;
}
}
}
/**
* A label that displays the value within the bar of the chart
*/
class ValueLabel extends React.Component {
static propTypes = {
x: PropTypes.number,
y: PropTypes.number,
payload: PropTypes.object,
value: PropTypes.number
};
/**
* Render offence
* @return {React.Component} contents
*/
render() {
const { x, y, payload, value } = this.props;
const em = value < 1000 ? '1em' : value < 1000 ? '0.8em' : '0.7em';
return (
<text x={x} y={y} fill="#000000" textAnchor="middle" dy={20} style={{ fontSize: em }}>{value}</text>
);
}
};

View File

@@ -0,0 +1,204 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as Calc from '../shipyard/Calculations';
import Module from '../shipyard/Module';
const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777'];
/**
* Weapon damage chart
*/
export default class WeaponDamageChart extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
opponent: PropTypes.object.isRequired,
hull: PropTypes.bool.isRequired,
engagementRange: PropTypes.number.isRequired,
opponentSys: PropTypes.number.isRequired,
marker: PropTypes.string.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
}
/**
* Set the initial weapons state
*/
componentWillMount() {
const weaponNames = this._weaponNames(this.props.ship, this.context);
const opponentShields = Calc.shieldMetrics(this.props.opponent, this.props.opponentSys);
const opponentArmour = Calc.armourMetrics(this.props.opponent);
const maxRange = this._calcMaxRange(this.props.ship);
const maxDps = this._calcMaxSDps(this.props.ship, this.props.opponent, opponentShields, opponentArmour);
this.setState({ maxRange, maxDps, weaponNames, opponentShields, opponentArmour, calcSDpsFunc: this._calcSDps.bind(this, this.props.ship, weaponNames, this.props.opponent, opponentShields, opponentArmour, this.props.hull) });
}
/**
* Set the updated weapons state if our ship changes
* @param {Object} nextProps Incoming/Next properties
* @param {Object} nextContext Incoming/Next conext
* @return {boolean} Returns true if the component should be rerendered
*/
componentWillReceiveProps(nextProps, nextContext) {
if (nextProps.marker != this.props.marker) {
const weaponNames = this._weaponNames(nextProps.ship, nextContext);
const opponentShields = Calc.shieldMetrics(nextProps.opponent, nextProps.opponentSys);
const opponentArmour = Calc.armourMetrics(nextProps.opponent);
const maxRange = this._calcMaxRange(nextProps.ship);
const maxDps = this._calcMaxSDps(nextProps.ship, nextProps.opponent, opponentShields, opponentArmour);
this.setState({ weaponNames,
opponentShields,
opponentArmour,
maxRange,
maxDps,
calcSDpsFunc: this._calcSDps.bind(this, nextProps.ship, weaponNames, nextProps.opponent, opponentShields, opponentArmour, nextProps.hull)
});
}
return true;
}
/**
* Calculate the maximum range of a ship's weapons
* @param {Object} ship The ship
* @returns {int} The maximum range, in metres
*/
_calcMaxRange(ship) {
let maxRange = 1000; // Minimum
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const thisRange = ship.hardpoints[i].m.getRange();
if (thisRange > maxRange) {
maxRange = thisRange;
}
}
}
return maxRange;
}
/**
* Calculate the maximum sustained single-weapon DPS for this ship
* @param {Object} ship The ship
* @param {Object} opponent The opponent ship
* @param {Object} opponentShields The opponent's shields
* @param {Object} opponentArmour The opponent's armour
* @return {number} The maximum sustained single-weapon DPS
*/
_calcMaxSDps(ship, opponent, opponentShields, opponentArmour) {
// Additional information to allow effectiveness calculations
let maxSDps = 0;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const m = ship.hardpoints[i].m;
const sustainedDps = Calc._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, 0);
const thisSDps = sustainedDps.damage.armour.total > sustainedDps.damage.shields.total ? sustainedDps.damage.armour.total : sustainedDps.damage.shields.total;
if (thisSDps > maxSDps) {
maxSDps = thisSDps;
}
}
}
return maxSDps;
}
/**
* Obtain the weapon names for this ship
* @param {Object} ship The ship
* @param {Object} context The context
* @return {array} The weapon names
*/
_weaponNames(ship, context) {
const translate = context.language.translate;
let names = [];
let num = 1;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const m = ship.hardpoints[i].m;
let name = '' + num++ + ': ' + m.class + m.rating + (m.missile ? '/' + m.missile : '') + ' ' + translate(m.name || m.grp);
let engineering;
if (m.blueprint && m.blueprint.name) {
engineering = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id) {
engineering += ', ' + translate(m.blueprint.special.name);
}
}
if (engineering) {
name = name + ' (' + engineering + ')';
}
names.push(name);
}
}
return names;
}
/**
* Calculate the per-weapon sustained DPS for this ship against another ship at a given range
* @param {Object} ship The ship
* @param {Object} weaponNames The names of the weapons for which to calculate DPS
* @param {Object} opponent The target
* @param {Object} opponentShields The opponent's shields
* @param {Object} opponentArmour The opponent's armour
* @param {bool} hull true if to calculate against hull, false if to calculate against shields
* @param {Object} engagementRange The engagement range
* @return {array} The array of weapon DPS
*/
_calcSDps(ship, weaponNames, opponent, opponentShields, opponentArmour, hull, engagementRange) {
let results = {};
let weaponNum = 0;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const m = ship.hardpoints[i].m;
const sustainedDps = Calc._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementRange);
results[weaponNames[weaponNum++]] = hull ? sustainedDps.damage.armour.total : sustainedDps.damage.shields.total;
}
}
return results;
}
/**
* Render damage dealt
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { maxRange } = this.state;
const { ship, opponent, engagementRange } = this.props;
const sortOrder = this._sortOrder;
const onCollapseExpand = this._onCollapseExpand;
const code = `${ship.toString()}:${opponent.toString()}`;
return (
<span>
<LineChart
xMax={maxRange}
yMax={this.state.maxDps}
xLabel={translate('range')}
xUnit={translate('m')}
yLabel={translate('sdps')}
series={this.state.weaponNames}
xMark={this.props.engagementRange}
colors={DAMAGE_DEALT_COLORS}
func={this.state.calcSDpsFunc}
points={200}
code={code}
/>
</span>
);
}
}

View File

@@ -5,7 +5,9 @@ import * as ES from './es';
import * as FR from './fr';
import * as IT from './it';
import * as RU from './ru';
import d3 from 'd3';
import * as PL from './pl';
import * as PT from './pt';
import * as d3 from 'd3';
let fallbackTerms = EN.terms;
@@ -23,49 +25,56 @@ export function getLanguage(langCode) {
case 'fr': lang = FR; break;
case 'it': lang = IT; break;
case 'ru': lang = RU; break;
case 'pl': lang = PL; break;
case 'pt': lang = PT; break;
default:
lang = EN;
}
let currentTerms = lang.terms;
let d3Locale = d3.locale(lang.formats);
let gen = d3Locale.numberFormat('n');
let d3Locale = d3.formatLocale(lang.formats);
let gen = d3Locale.format('');
const round = function(x, n) { const ten_n = Math.pow(10,n); return Math.round(x * ten_n) / ten_n; };
if(lang === EN) {
translate = (t) => { return currentTerms[t] || t; };
translate = (t, x) => { return currentTerms[t + '_' + x] || currentTerms[t] || t; };
} else {
translate = (t) => { return currentTerms[t] || fallbackTerms[t] || t; };
translate = (t, x) => { return currentTerms[t + '_' + x] || currentTerms[t] || fallbackTerms[t + '_' + x] || fallbackTerms[t] || t; };
}
return {
formats: {
gen, // General number format (.e.g 1,001,001.1234)
int: d3Locale.numberFormat(',.0f'), // Fixed to 0 decimal places (.e.g 1,001)
f1: d3Locale.numberFormat(',.1f'), // Fixed to 1 decimal place (.e.g 1,001.1)
f2: d3Locale.numberFormat(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10)
s2: d3Locale.numberFormat('.2s'), // SI Format to 2 decimal places (.e.g 1.1k)
pct: d3Locale.numberFormat('.2%'), // % to 2 decimal places (.e.g 5.40%)
pct1: d3Locale.numberFormat('.1%'), // % to 1 decimal places (.e.g 5.4%)
r2: d3Locale.numberFormat('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1)
rPct: d3.format('%'), // % to 0 decimal places (.e.g 5%)
round1: (d) => gen(d3.round(d, 1)), // Rounded to 0-1 decimal places (.e.g 5.1, 4)
round: (d) => gen(d3.round(d, 2)), // Rounded to 0-2 decimal places (.e.g 5.12, 4.1)
gen, // General number format (.e.g 1,001,001.1234)
int: d3Locale.format(',.0f'), // Fixed to 0 decimal places (.e.g 1,001)
f1: d3Locale.format(',.1f'), // Fixed to 1 decimal place (.e.g 1,001.1)
f2: d3Locale.format(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10)
s2: d3Locale.format('.2s'), // SI Format to 2 decimal places (.e.g 1.1k)
pct: d3Locale.format('.2%'), // % to 2 decimal places (.e.g 5.40%)
pct1: d3Locale.format('.1%'), // % to 1 decimal places (.e.g 5.4%)
r1: d3Locale.format('.1r'), // Rounded to 1 significant number (.e.g 512 => 500, 4.122 => 4)
r2: d3Locale.format('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1)
rPct: d3Locale.format('.0%'), // % to 0 decimal places (.e.g 5%)
round1: (d) => gen(round(d, 1)), // Round to 0-1 decimal places (e.g. 5.1, 4)
round: (d) => gen(round(d, 2)), // Rounded to 0-2 decimal places (.e.g 5.12, 4.1)
time: (d) => (d < 0 ? '-' : '') + Math.floor(Math.abs(d) / 60) + ':' + ('00' + Math.floor(Math.abs(d) % 60)).substr(-2, 2)
},
translate,
units: {
CR: <u> {translate('CR')}</u>, // Credits
kg: <u> {translate('kg')}</u>, // Kilograms
kgs: <u> {translate('kg/s')}</u>, // Kilograms per second
km: <u> {translate('km')}</u>, // Kilometers
Ls: <u> {translate('Ls')}</u>, // Light Seconds
LY: <u> {translate('LY')}</u>, // Light Years
MJ: <u> {translate('MJ')}</u>, // Mega Joules
'm/s': <u> {translate('m/s')}</u>, // Meters per second
MW: <u> {translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
CR: <u>{translate('CR')}</u>, // Credits
kg: <u>{translate('kg')}</u>, // Kilograms
kgs: <u>{translate('kg/s')}</u>, // Kilograms per second
km: <u>{translate('km')}</u>, // Kilometers
Ls: <u>{translate('Ls')}</u>, // Light Seconds
LY: <u>{translate('LY')}</u>, // Light Years
MJ: <u>{translate('MJ')}</u>, // Mega Joules
'm/s': <u>{translate('m/s')}</u>, // Meters per second
'°/s': <u>{translate('°/s')}</u>, // Degrees per second
MW: <u>{translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
mps: <u>{translate('m/s')}</u>, // Metres per second
ps: <u>{translate('/s')}</u>, // per second
pm: <u>{translate('/min')}</u>, // per minute
T: <u> {translate('T')}</u>, // Metric Tons
s: <u>{translate('secs')}</u>, // Seconds
T: <u>{translate('T')}</u>, // Metric Tons
}
};
}
@@ -80,5 +89,7 @@ export const Languages = {
it: 'Italiano',
es: 'Español',
fr: 'Français',
ru: 'ру́сский'
ru: 'ру́сский',
pl: 'polski',
pt: 'português'
};

View File

@@ -1,166 +1,16 @@
export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
currency: ['', ' €'],
dateTime: '%A, der %e. %B %Y, %X',
date: '%d.%m.%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'], // unused
days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
shortDays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
shortMonths: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
};
export const terms = {
// Phrases
PHRASE_BACKUP_DESC: 'Export aller Coriolis-Daten, um sie zu sichern oder um sie zu einem anderen Browser/Gerät zu übertragen.', // Backup of all Coriolis data to save or transfer to another browser/device
PHRASE_CONFIRMATION: 'Sind Sie sicher?', // Are You Sure?
PHRASE_EXPORT_DESC: 'Ein detaillierter JSON-Export Ihrer Konfiguration für die Verwendung in anderen Websites und Tools', // A detailed JSON export of your build for use in other sites and tools
PHRASE_FASTEST_RANGE: 'aufeinanderfolgende maximale Reichweite/Sprünge', // Consecutive max range jumps
PHRASE_IMPORT: 'JSON hier einfügen oder importieren', // Paste JSON or import here
PHRASE_LADEN: 'Schiffsmasse + Treibstoff + Fracht', // Ship Mass + Fuel + Cargo
PHRASE_NO_BUILDS: 'Keine Konfigurationen zum Vergleich ausgewählt!', // No builds added to comparison!
PHRASE_NO_RETROCH: 'Keine Umrüständerungen', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Ausstattung zum Vergleich auswählen', // Select Builds to Compare
PHRASE_SG_RECHARGE: 'Zeit von 50% bis 100% der Ladung', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: 'Erneuerung (zu 50%) nach Zusammenbruch', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: 'Schiffsmasse ohne Treibstoff und Fracht', // Ship Mass excluding Fuel and Cargo
PHRASE_UPDATE_RDY: 'Update verfügbar! Klicken zum Aktualisieren', // Update Available! Click to Refresh
// Units / Metrics
LY: 'Lj', // Light Years
T: 't', // Tons (Metric Ton - 1000kg)
// Sizes
S: 'K', // Small Hardpoint (single Character)
L: 'G', // Large Hardpoint size (single character)
H: 'R', // Huge Hardpoint size (single character)
U: 'W', // Utility Hardpoint size (single character) - Kill warrant scanner, etc
small: 'klein', // Small ship size
medium: 'mittel', // Medium ship size
large: 'groß', // Large Ship Size
// Terms
about: 'über', // Link to about page / about Coriolis.io
action: 'Aktion',
added: 'hinzugefügt',
ammo: 'Munition', // Ammunition
armour: 'Panzerung',
available: 'verfügbar', // Available options
backup: 'Sicherungsdatei',
base: 'Basis', // Base speed, boost, etc - Base ship stats
bays: 'Lagerraum',
bins: 'Behälter', // Number of Mining Refinery bins
build: 'Ausstattung', // Shorthand for the build/configuration/design name
'build name': 'Ausstattungsname', // Ship build/configuration/design name
builds: 'Ausstattungen', // Ship build/configuration/design names
buy: 'kaufen',
cancel: 'abbrechen',
cargo: 'Fracht',
cells: 'Zellen', // Number of cells in a shield cell bank
close: 'schließen',
compare: 'vergleichen',
'compare all': 'alles vergleichen',
comparison: 'Vergleich',
comparisons: 'Vergleiche',
cost: 'Preis', // Cost / price of a module or price of a ship
costs: 'Kosten', // Costs / prices of a modules or prices of ships
create: 'erstellen',
'create new': 'neu erstellen',
credits: 'Credits',
damage: 'Schaden',
'damage per second': 'Schaden pro Sekunde',
delete: 'löschen',
'delete all': 'alles löschen',
dep: 'ausg', // Weapons/Hardpoints Deployed abbreviation
deployed: 'ausgefahren', // Weapons/Hardpoints Deployed
'detailed export': 'detailierter Export',
disabled: 'deaktiviert',
discount: 'Rabatt',
'edit data': 'bearbeiten',
efficiency: 'Effizienz', // Power Plant efficiency
empty: 'leer',
'empty all': 'alles entfernen',
ENG: 'ANT', // Abbreviation - Engine recharge rate for power distributor
'Enter Name': 'Namen eingeben',
Explorer: 'Forscher',
export: 'Export',
'fastest range': 'maximale Reichweite', // Fastet totaljump range - sum of succesive jumps
forum: 'Forum',
fuel: 'Treibstoff',
'fuel level': 'Tankfüllstand', // Percent of fuel (T) in the tank
'full tank': 'Tank voll',
hardpoints: 'Waffenaufhängungen',
hull: 'Rumpf', // Ships hull
import: 'importieren',
insurance: 'Versicherung',
'internal compartments': 'Innenbereich',
jump: 'Sprung', // Single jump range
'jump range': 'Sprungreichweite',
jumps: 'Sprünge',
laden: 'beladen',
language: 'Sprache',
maneuverability: 'Manövrierbarkeit',
manufacturer: 'Hersteller',
mass: 'Masse',
'mass lock factor': 'Massensperrefaktor',
'max mass': 'maximale Masse',
MLF: 'MSF', // Mass Lock Factor Abbreviation
module: 'Modul',
modules: 'Module',
'net cost': 'Nettokosten',
no: 'Nein',
'none created': 'nicht erstellt',
ok: 'OK',
'optimal mass': 'optimale Masse', // Lowest weight / best weight for jump distance, etc
optimize: 'optimieren',
pen: 'Durchdr.', // Armour peneration abbreviation
permalink: 'Permanentlink',
power: 'Energie', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section
pri: 'Prio', // Priority abbreviation for power management
proceed: 'fortfahren',
qty: 'Menge', // Quantity abbreviation
range: 'Reichweite',
rate: 'Rate',
recharge: 'aufladen', // Shield Recharge time from 50% -> 100%
recovery: 'Erneuerung', // Shield recovery time (after losing shields/turning on -> 50%)
'refuel time': 'Auftankzeit', // Time to refuel the tank when scooping
reload: 'aktualisieren', // Reload weapon/hardpoint
'reload costs': 'Nachladekosten',
rename: 'umbenennen',
repair: 'reparieren',
reset: 'zurücksetzen',
ret: 'eing', // Retracted abbreviation
retracted: 'eingefahren', // Weapons/Hardpoints retracted
'retrofit costs': 'Änderungskosten', // The cost difference when upgrading / downgrading a component
'retrofit from': 'nachrüsten von', // Retrofit from Build A against build B
ROF: 'Kad', // Rate of Fire abbreviation
roles: 'Rollen', // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc
save: 'speichern',
sell: 'verkaufen',
settings: 'Einstellungen', // Coriolis application settings
shields: 'Schilde',
ship: 'Schiff',
ships: 'Schiffe',
shortened: 'gekürzt', // Standard/Stock build of a ship when purchased new
size: 'Größe',
skip: 'überspringen', // Skip past something / ignore it
speed: 'Geschwindigkeit',
standard: 'Grundausstattung', // Standard / Common modules (FSD, power plant, life support, etc)
Stock: 'Standard', // Thermal-load abbreviation
strength: 'Stärke', // Strength in reference to Shield Strength
subtotal: 'Zwischensumme',
time: 'Dauer', // time it takes to complete something
tooltips: 'Tooltips', // Tooltips setting - show/hide
total: 'gesamt',
'total range': 'Gesamtbereich',
Trader: 'Händler', // Trader role
type: 'Typ',
'unit cost': 'Kosten pro Einheit',
unladen: 'unbeladen', // No cargo or fuel
'utility mounts': 'Werkzeug-Steckplätze',
WEP: 'WAF', // Abbreviation - Weapon recharge rate for power distributor
yes: 'ja'
};
export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
currency: ['', ' €'],
dateTime: '%A, der %e. %B %Y, %X',
date: '%d.%m.%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'], // unused
days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
shortDays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
shortMonths: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
};
export { default as terms } from './de.json';

358
src/app/i18n/de.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -13,70 +13,4 @@ export const formats = {
shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
};
export const terms = {
PHRASE_ALT_ALL: 'Alt + Click to fill all slots',
PHRASE_BACKUP_DESC: 'Backup of all Coriolis data to save or transfer to another browser/device',
PHRASE_CONFIRMATION: 'Are you sure?',
PHRASE_EXPORT_DESC: 'A detailed JSON export of your build for use in other sites and tools',
PHRASE_FASTEST_RANGE: 'Consecutive max range jumps',
PHRASE_IMPORT: 'Paste JSON or import here',
PHRASE_LADEN: 'Ship mass + fuel + cargo',
PHRASE_NO_BUILDS: 'No builds added to comparison!',
PHRASE_NO_RETROCH: 'No Retrofitting changes',
PHRASE_SELECT_BUILDS: 'Select builds to compare',
PHRASE_SG_RECHARGE: 'Time from 50% to 100% charge',
PHRASE_SG_RECOVER: 'Recovery (to 50%) after collapse',
PHRASE_UNLADEN: 'Ship mass excluding fuel and cargo',
PHRASE_UPDATE_RDY: 'Update Available! Click to refresh',
// Other languages fallback to these values
// Only Translate to other languages if the name is different in-game
am: 'Auto Field-Maintenance Unit',
bh: 'Bulkheads',
bl: 'Beam Laser',
bsg: 'Bi-Weave Shield Generator',
c: 'Cannon',
cc: 'Collector Limpet Controller',
cm: 'Countermeasure',
cr: 'Cargo Rack',
cs: 'Cargo Scanner',
dc: 'Docking Computer',
fc: 'Fragment Cannon',
fh: 'Fighter Hangar',
fi: 'FSD Interdictor',
fs: 'Fuel Scoop',
fsd: 'Frame Shift Drive',
ft: 'Fuel Tank',
fx: 'Fuel Transfer Limpet Controller',
hb: 'Hatch Breaker Limpet Controller',
hr: 'Hull Reinforcement Package',
kw: 'Kill Warrant Scanner',
ls: 'Life Support',
mc: 'Multi-cannon',
ml: 'Mining Laser',
mr: 'Missile Rack',
nl: 'Mine Launcher',
pa: 'Plasma Accelerator',
pas: 'Planetary Approach Suite',
pc: 'Prospector Limpet Controller',
pce: 'Economy Class Passenger Cabin',
pci: 'Business Class Passenger Cabin',
pcm: 'First Class Passenger Cabin',
pcq: 'Luxury Passenger Cabin',
pd: 'power distributor',
pl: 'Pulse Laser',
pp: 'Power Plant',
psg: 'Prismatic Shield Generator',
pv: 'Planetary Vehicle Hangar',
rf: 'Refinery',
rg: 'Rail Gun',
s: 'Sensors',
sb: 'Shield Booster',
sc: 'Scanner',
scb: 'Shield Cell Bank',
sg: 'Shield Generator',
t: 'thrusters',
tp: 'Torpedo Pylon',
ul: 'Burst Laser',
ws: 'Frame Shift Wake Scanner'
};
export { default as terms } from './en.json';

331
src/app/i18n/en.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -13,196 +13,4 @@ export const formats = {
shortMonths: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']
};
export const terms = {
'PHRASE_EXPORT_DESC': 'Una detallada exportaci\u00f3n JSON de tu construcci\u00f3n para usarlo en otros sitios web y herramientas',
'A-Rated': 'Calidad-A',
'about': 'Acerca',
'action': 'Acci\u00f3n',
'added': 'A\u00f1adido',
'Advanced Discovery Scanner': 'Esc\u00e1ner de exploraci\u00f3n avanzado',
maneuverability: 'Maniobrabilidad',
'alpha': 'Alfa',
'ammo': 'Munici\u00f3n',
'PHRASE_CONFIRMATION': '\u00bfEst\u00e1s seguro?',
'armour': 'Blindaje',
'am': 'Unidad de auto-reparaciones',
'available': 'Disponible',
'backup': 'Reserva',
'Basic Discovery Scanner': 'Esc\u00e1ner de exploraci\u00f3n b\u00e1sico',
'bl': 'L\u00e1ser de haz',
'bins': 'contenedores',
'boost': 'incrementar',
'build': 'Construcci\u00f3n',
'build name': 'Nombre de la construcci\u00f3n',
'builds': 'Construcciones',
'bh': 'mamparos',
'ul': 'Laser de r\u00e1fagas',
'buy': 'Comprar',
'cancel': 'Cancelar',
'c': 'Ca\u00f1\u00f3n',
'capital': 'capital',
'cargo': 'Carga',
'Cargo Hatch': 'Compuerta de carga',
'cr': 'Compartimento de carga',
'cs': 'Esc\u00e1ner de carga',
'cells': 'celdas',
'Chaff Launcher': 'Lanzador de birutas',
'close': 'Cerrar',
'cc': 'Controlador de Drones de Recogida',
'compare': 'Comparar',
'compare all': 'comparar todas',
'comparison': 'Comparativa',
'comparisons': 'Comparativas',
'component': 'Componente',
'cost': 'Coste',
'costs': 'Costes',
'cm': 'Contramedidas',
'create': 'Crear',
'create new': 'Crear nuevo',
'credits': 'Cr\u00e9ditos',
'damage': 'Da\u00f1o',
'delete': 'Borrar',
'delete all': 'Borrar todo',
'dep': 'desp',
'deployed': 'Desplegado',
'detailed export': 'Exportacion detallada',
'Detailed Surface Scanner': 'Escaner de exploraci\u00f3n detallada',
'disabled': 'Desactivado',
'discount': 'Descuento',
'dc': 'Ordenador de aterrizaje',
'done': 'Hecho',
'DPS': 'DPS (Da\u00f1o Por Segundo)',
'edit data': 'Editar datos',
'efficiency': 'Eficiencia',
'Electronic Countermeasure': 'Contramedidas electr\u00f3nicas',
'empty': 'Vac\u00edo',
'ENG': 'MOT',
'enter name': 'Introduce el nombre',
'export': 'exportar',
'fixed': 'fijo',
'forum': 'Foro',
'fc': 'Ca\u00f1\u00f3n de fragmentaci\u00f3n',
'fsd': 'Motor de salto',
'ws': 'Esc\u00e1ner de Salto',
'fi': 'Interdictor FSD',
'fuel': 'Combustible',
'fs': 'Recolector de Combustible',
'ft': 'Tanque de combustible',
'fx': 'Sistema de Transferencia de Combustilble',
'full tank': 'Tanque lleno',
'Gimballed': 'Card\u00e1n',
'H': 'E',
'hardpoints': 'Montura de armas',
'hb': 'Controlador de Apertura de Bah\u00eda de Carga',
'Heat Sink Launcher': 'Eyector de Acumulador de Calor',
'huge': 'enorme',
'hull': 'Casco',
'hr': 'Sistema de Casco Reforzado',
'import': 'Importar',
'import all': 'Importar todo',
'insurance': 'Seguro',
'Intermediate Discovery Scanner': 'Esc\u00e1ner de exploraci\u00f3n media',
'internal compartments': 'Compartimentos internos',
'jump range': 'Rango de salto',
'jumps': 'Saltos',
'kw': 'Esc\u00e1ner Detector de Recompensas',
'L': 'G',
'laden': 'Cargada',
'language': 'Idioma',
'large': 'Grande',
'ls': 'Soporte vital',
'Lightweight Alloy': 'Aleaci\u00f3n ligera',
'limpets': 'Drones',
'lock factor': 'factor de bloqueo',
'mass': 'Masa',
'max': 'm\u00e1x',
'max mass': 'Masa m\u00e1xima',
'medium': 'medio',
'Military Grade Composite': 'Blindaje Militar',
'nl': 'Lanzaminas',
'Mining Lance': 'Lanza de miner\u00eda',
'ml': 'L\u00e1ser de miner\u00eda',
'Mirrored Surface Composite': 'Blindaje Reflectante',
'mr': 'Bah\u00eda de Misiles',
'mc': 'Ca\u00f1\u00f3n m\u00faltiple',
'net cost': 'Coste neto',
'PHRASE_NO_BUILDS': '\u00a1No se a\u00f1adieron plantillas para comparaci\u00f3n!',
'PHRASE_NO_RETROCH': 'No hay cambios en los ajutes',
'none': 'Nada',
'none created': 'Nada creado',
'off': 'apagado',
'on': 'encendido',
'optimal': '\u00f3ptimo',
'optimal mass': 'masa \u00f3ptima',
'optimize mass': 'optimizar masa',
'overwrite': 'Sobreescribir',
'PHRASE_IMPORT': 'Pega el JSON o imp\u00f3rtalo aqu\u00ed',
'penetration': 'penetraci\u00f3n',
'permalink': 'enlace permanente',
'pa': 'Acelerador de Plasma',
'Point Defence': 'Punto de Defensa',
'power': 'energ\u00eda',
'pd': 'distribuidor de energ\u00eda',
'pp': 'Planta de Energ\u00eda',
'priority': 'prioridad',
'proceed': 'Proceder',
'pc': 'Controlador de drones de prospecci\u00f3n',
'pl': 'L\u00e1ser de Pulso',
'PWR': 'POT',
'rg': 'Ca\u00f1\u00f3n de Riel',
'range': 'rango',
'rate': 'ratio',
'Reactive Surface Composite': 'Blindaje Reactivo',
'recharge': 'recargar',
'rf': 'Refineria',
'refuel time': 'Tiempo para repostar',
'Reinforced Alloy': 'Armadura reforzada',
'reload': 'Recargar',
'rename': 'Renombrar',
'repair': 'Reparar',
'reset': 'Reiniciar',
'ret': 'PLE',
'retracted': 'plegadas',
'retrofit costs': 'costes de equipamiento',
'retrofit from': 'equipamiento desde',
'ROF': 'RDF',
'S': 'P',
'save': 'guardar',
'sc': 'sc\u00e1ner',
'PHRASE_SELECT_BUILDS': 'Selecciona equipamientos para comparar',
'sell': 'Vender',
's': 'Sensores',
'settings': 'Configuraci\u00f3n',
'sb': 'Potenciador de Escudos',
'scb': 'C\u00e9lula de Energ\u00eda de Escudos',
'sg': 'Generador de escudos',
'shields': 'Escudos',
'ship': 'Nave ',
'ships': 'Naves',
'shortened': 'Abreviado',
'size': 'Tama\u00f1o',
'skip': 'omitir',
'small': 'Peque\u00f1o',
'speed': 'velocidad',
'standard': 'est\u00e1ndar',
'Standard Docking Computer': 'Computador de Atraque Est\u00e1ndar',
'Stock': 'De serie',
'SYS': 'SIS',
'T_LOAD': 'c-t\u00e9rmica',
't': 'Propulsores',
'time': 'Tiempo',
'tp': 'Anclaje de torpedo',
'total': 'Total',
'total range': 'Rango total',
'turret': 'torreta',
'type': 'Tipo',
'unladen': 'Sin carga',
'PHRASE_UPDATE_RDY': 'Actualizacion disponible! Haz click para recargar',
'URL': 'Enlace',
'utility': 'utilidad',
'utility mounts': 'monturas de utilidad',
'version': 'Versi\u00f3n',
'WEP': 'ARM',
'yes': 'si',
'PHRASE_BACKUP_DESC': 'Copia de seguridad de todos los datos de Coriolis para guardarlos o transferirlos a otro navegador\/dispositivo'
};
export { default as terms } from './es.json';

193
src/app/i18n/es.json Normal file
View File

@@ -0,0 +1,193 @@
{
"PHRASE_EXPORT_DESC": "Una detallada exportación JSON de tu construcción para usarlo en otros sitios web y herramientas",
"A-Rated": "Calidad-A",
"about": "Acerca",
"action": "Acción",
"added": "Añadido",
"Advanced Discovery Scanner": "Escáner de exploración avanzado",
"maneuverability": "Maniobrabilidad",
"alpha": "Alfa",
"ammo": "Munición",
"PHRASE_CONFIRMATION": "¿Estás seguro?",
"armour": "Blindaje",
"am": "Unidad de auto-reparaciones",
"available": "Disponible",
"backup": "Reserva",
"Basic Discovery Scanner": "Escáner de exploración básico",
"bl": "Láser de haz",
"bins": "contenedores",
"boost": "incrementar",
"build": "Construcción",
"build name": "Nombre de la construcción",
"builds": "Construcciones",
"bh": "mamparos",
"ul": "Laser de ráfagas",
"buy": "Comprar",
"cancel": "Cancelar",
"c": "Cañón",
"capital": "capital",
"cargo": "Carga",
"Cargo Hatch": "Compuerta de carga",
"cr": "Compartimento de carga",
"cs": "Escáner de carga",
"cells": "celdas",
"Chaff Launcher": "Lanzador de virutas",
"close": "Cerrar",
"cc": "Controlador de Drones de Recogida",
"compare": "Comparar",
"compare all": "comparar todas",
"comparison": "Comparativa",
"comparisons": "Comparativas",
"component": "Componente",
"cost": "Coste",
"costs": "Costes",
"cm": "Contramedidas",
"create": "Crear",
"create new": "Crear nuevo",
"credits": "Créditos",
"damage": "Daño",
"delete": "Borrar",
"delete all": "Borrar todo",
"dep": "desp",
"deployed": "Desplegado",
"detailed export": "Exportacion detallada",
"Detailed Surface Scanner": "Escaner de exploración detallada",
"disabled": "Desactivado",
"discount": "Descuento",
"dc": "Ordenador de aterrizaje",
"done": "Hecho",
"DPS": "DPS (Daño Por Segundo)",
"edit data": "Editar datos",
"efficiency": "Eficiencia",
"Electronic Countermeasure": "Contramedidas electrónicas",
"empty": "Vacío",
"ENG": "MOT",
"enter name": "Introduce el nombre",
"export": "exportar",
"fixed": "fijo",
"forum": "Foro",
"fc": "Cañón de fragmentación",
"fsd": "Motor de salto",
"ws": "Escáner de Salto",
"fi": "Interdictor FSD",
"fuel": "Combustible",
"fs": "Recolector de Combustible",
"ft": "Tanque de combustible",
"fx": "Sistema de Transferencia de Combustilble",
"full tank": "Tanque lleno",
"Gimballed": "Cardán",
"H": "E",
"hardpoints": "Montura de armas",
"hb": "Controlador de Apertura de Bahía de Carga",
"Heat Sink Launcher": "Eyector de Acumulador de Calor",
"huge": "enorme",
"hull": "Casco",
"hr": "Sistema de Casco Reforzado",
"import": "Importar",
"import all": "Importar todo",
"insurance": "Seguro",
"Intermediate Discovery Scanner": "Escáner de exploración media",
"internal compartments": "Compartimentos internos",
"jump range": "Rango de salto",
"jumps": "Saltos",
"kw": "Escáner Detector de Recompensas",
"L": "G",
"laden": "Cargada",
"language": "Idioma",
"large": "Grande",
"ls": "Soporte vital",
"Lightweight Alloy": "Aleación ligera",
"limpets": "Drones",
"lock factor": "factor de bloqueo",
"mass": "Masa",
"max": "máx",
"max mass": "Masa máxima",
"medium": "medio",
"Military Grade Composite": "Blindaje Militar",
"nl": "Lanzaminas",
"Mining Lance": "Lanza de minería",
"ml": "Láser de minería",
"Mirrored Surface Composite": "Blindaje Reflectante",
"mr": "Bahía de Misiles",
"mc": "Cañón múltiple",
"net cost": "Coste neto",
"PHRASE_NO_BUILDS": "¡No se añadieron plantillas para comparación!",
"PHRASE_NO_RETROCH": "No hay cambios en los ajutes",
"none": "Nada",
"none created": "Nada creado",
"off": "apagado",
"on": "encendido",
"optimal": "óptimo",
"optimal mass": "masa óptima",
"optimize mass": "optimizar masa",
"overwrite": "Sobreescribir",
"PHRASE_IMPORT": "Pega el JSON o impórtalo aquí",
"penetration": "penetración",
"permalink": "enlace permanente",
"pa": "Acelerador de Plasma",
"Point Defence": "Punto de Defensa",
"power": "energía",
"pd": "distribuidor de energía",
"pp": "Planta de Energía",
"priority": "prioridad",
"proceed": "Proceder",
"pc": "Controlador de drones de prospección",
"pl": "Láser de Pulso",
"PWR": "POT",
"rg": "Cañón de Riel",
"range": "rango",
"rate": "ratio",
"Reactive Surface Composite": "Blindaje Reactivo",
"recharge": "recargar",
"rf": "Refineria",
"refuel time": "Tiempo para repostar",
"Reinforced Alloy": "Armadura reforzada",
"reload": "Recargar",
"rename": "Renombrar",
"repair": "Reparar",
"reset": "Reiniciar",
"ret": "PLE",
"retracted": "plegadas",
"retrofit costs": "costes de equipamiento",
"retrofit from": "equipamiento desde",
"ROF": "RDF",
"S": "P",
"save": "guardar",
"sc": "scáner",
"PHRASE_SELECT_BUILDS": "Selecciona equipamientos para comparar",
"sell": "Vender",
"s": "Sensores",
"settings": "Configuración",
"sb": "Potenciador de Escudos",
"scb": "Célula de Energía de Escudos",
"sg": "Generador de escudos",
"shields": "Escudos",
"ship": "Nave ",
"ships": "Naves",
"shortened": "Abreviado",
"size": "Tamaño",
"skip": "omitir",
"small": "Pequeño",
"speed": "velocidad",
"standard": "estándar",
"Standard Docking Computer": "Computador de Atraque Estándar",
"Stock": "De serie",
"SYS": "SIS",
"T_LOAD": "c-térmica",
"t": "Propulsores",
"time": "Tiempo",
"tp": "Anclaje de torpedo",
"total": "Total",
"total range": "Rango total",
"turret": "torreta",
"type": "Tipo",
"unladen": "Sin carga",
"PHRASE_UPDATE_RDY": "Actualizacion disponible! Haz click para recargar",
"URL": "Enlace",
"utility": "utilidad",
"utility mounts": "monturas de utilidad",
"version": "Versión",
"WEP": "ARM",
"yes": "si",
"PHRASE_BACKUP_DESC": "Copia de seguridad de todos los datos de Coriolis para guardarlos o transferirlos a otro navegador/dispositivo"
}

View File

@@ -13,143 +13,4 @@ export const formats = {
shortMonths: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.']
};
export const terms = {
// Phrases
PHRASE_BACKUP_DESC: 'Copie des données Coriolis pour l\'utilisation dans d\'autres sites et outils', // Backup of all Coriolis data to save or transfer to another browser/device
PHRASE_CONFIRMATION: 'Êtes-vous sûr?', // Are You Sure?
PHRASE_EXPORT_DESC: 'Un export détaillé en JSON de votre configuration pour l\'utilisation dans d\'autres sites et outils', // A detailed JSON export of your build for use in other sites and tools
PHRASE_FASTEST_RANGE: 'Portée maximale en cas de sauts successifs', // Consecutive max range jumps
PHRASE_IMPORT: 'Coller JSON ou importer ici', // Paste JSON or import here
PHRASE_LADEN: 'Masse du Vaisseau + Carburant + Cargo', // Ship Mass + Fuel + Cargo
PHRASE_NO_BUILDS: 'Aucune configuration ajoutée pour la comparaison', // No builds added to comparison!
PHRASE_NO_RETROCH: 'Aucun changement de configuration', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Sélectionner les configurations à comparer', // Select Builds to Compare
PHRASE_SG_RECHARGE: 'Temps de charge de 50% à 100 %', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: 'Temps de redémarrage après perte du bouclier', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: 'Masse du Vaisseau hors Carburant et Cargo', // Ship Mass excluding Fuel and Cargo
PHRASE_UPDATE_RDY: 'Mise à jour disponible ! Cliquez pour rafraichir', // Update Available! Click to Refresh
// Units / Metrics
Ls: 'SL', // Light seconds
LY: 'AL', // Light Years
// Sizes
L: 'G', // Large Hardpoint size (single character)
large: 'grand', // Large Ship Size
medium: 'moyen', // Medium ship size
S: 'P', // Small Hardpoint (single Character)
small: 'petit', // Small ship size
// Terms
about: 'à propos', // Link to about page / about Coriolis.io
added: 'ajouté',
ammo: 'munition', // Ammunition
armour: 'Armure',
available: 'Disponibilité', // Available options
backup: 'sauvegarde',
bays: 'baies',
bins: 'bacs', // Number of Mining Refinery bins
build: 'Configuration', // Shorthand for the build/configuration/design name
'build name': 'Nom de la configuration', // Ship build/configuration/design name
builds: 'Configurations', // Ship build/configuration/design names
buy: 'Acheter',
cancel: 'Annuler',
cargo: 'Soute',
cells: 'Cellule', // Number of cells in a shield cell bank
close: 'fermer',
compare: 'comparer',
'compare all': 'tout comparer',
comparison: 'comparaison',
comparisons: 'comparaisons',
cost: 'coût', // Cost / price of a module or price of a ship
costs: 'coûts', // Costs / prices of a modules or prices of ships
create: 'Créer',
'create new': 'Créer nouveau',
credits: 'crédits',
damage: 'dégât',
'damage per second': 'dégât par seconde',
delete: 'supprimer',
'delete all': 'tout supprimer',
dep: 'depl', // Weapons/Hardpoints Deployed abbreviation
deployed: 'déployé', // Weapons/Hardpoints Deployed
'detailed export': 'export détaillé',
disabled: 'désactivé',
discount: 'ristourne',
'edit data': 'Editer donnée',
efficiency: 'rendement', // Power Plant efficiency
empty: 'Vide',
'empty all': 'vide tout',
'Enter Name': 'Entrer nom',
Explorer: 'explorateur',
'fastest range': 'gamme la plus rapide', // Fastet totaljump range - sum of succesive jumps
fuel: 'carburant',
'fuel level': 'niveau de carburant', // Percent of fuel (T) in the tank
'full tank': 'Réservoir plein',
hardpoints: 'Points d\'emport',
hull: 'Coque', // Ships hull
import: 'Importer',
insurance: 'Assurance',
'internal compartments': 'compartiments internes',
jump: 'saut', // Single jump range
'jump range': 'Distance de saut',
jumps: 'Sauts',
laden: 'chargé',
language: 'Langage',
maneuverability: 'maniabilité',
manufacturer: 'fabricant',
mass: 'Masse',
'mass lock factor': 'facteur inhibition de masse',
'max mass': 'masse max',
MLF: 'FIM', // Mass Lock Factor Abbreviation
'net cost': 'coûts nets',
no: 'non',
'none created': 'Rien de créé',
ok: 'D\'accord',
'optimal mass': 'masse optimale', // Lowest weight / best weight for jump distance, etc
optimize: 'optimiser',
pen: 'pén.', // Armour peneration abbreviation
permalink: 'lien durable',
power: 'énergie', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section
proceed: 'continuer',
PWR: 'P', // Power Abbreviation. See Power
qty: 'quantité', // Quantity abbreviation
range: 'portée',
rate: 'cadence',
recharge: 'recharger', // Shield Recharge time from 50% -> 100%
recovery: 'récupération', // Shield recovery time (after losing shields/turning on -> 50%)
'refuel time': 'Temps de remplissage', // Time to refuel the tank when scooping
reload: 'recharger', // Reload weapon/hardpoint
'reload costs': 'recharger coûts',
rename: 'renommer',
repair: 'réparer',
reset: 'Réinitialisation',
ret: 'esc', // Retracted abbreviation
retracted: 'escamoté', // Weapons/Hardpoints retracted
'retrofit costs': 'Valeur de rachat', // The cost difference when upgrading / downgrading a component
'retrofit from': 'Racheter de', // Retrofit from Build A against build B
ROF: 'cadence', // Rate of Fire abbreviation
roles: 'rôles', // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc
save: 'sauvegarder',
sell: 'vendre',
settings: 'paramètres', // Coriolis application settings
shields: 'boucliers',
ship: 'vaisseau',
ships: 'vaisseaux',
shortened: 'raccourci', // Standard/Stock build of a ship when purchased new
size: 'taille',
skip: 'Suivant', // Skip past something / ignore it
speed: 'vitesse',
Stock: 'de base', // Thermal-load abbreviation
strength: 'force', // Strength in reference to Shield Strength
subtotal: 'Sous-Total',
'T-Load': 'degrés', // Thermal load abbreviation
time: 'temps', // time it takes to complete something
tooltips: 'infobulles', // Tooltips setting - show/hide
'total range': 'plage totale',
Trader: 'commerçant', // Trader role
'unit cost': 'coût unitaire',
unladen: 'Non chargé', // No cargo or fuel
'utility mounts': 'Support utilitaire',
WEP: 'ARM', // Abbreviation - Weapon recharge rate for power distributor
yes: 'oui'
};
export { default as terms } from './fr.json';

133
src/app/i18n/fr.json Normal file
View File

@@ -0,0 +1,133 @@
{
"PHRASE_BACKUP_DESC": "Copie des données Coriolis pour l'utilisation dans d'autres sites et outils",
"PHRASE_CONFIRMATION": "Êtes-vous sûr?",
"PHRASE_EXPORT_DESC": "Un export détaillé en JSON de votre configuration pour l'utilisation dans d'autres sites et outils",
"PHRASE_FASTEST_RANGE": "Portée maximale en cas de sauts successifs",
"PHRASE_IMPORT": "Coller JSON ou importer ici",
"PHRASE_LADEN": "Masse du Vaisseau + Carburant + Cargo",
"PHRASE_NO_BUILDS": "Aucune configuration ajoutée pour la comparaison",
"PHRASE_NO_RETROCH": "Aucun changement de configuration",
"PHRASE_SELECT_BUILDS": "Sélectionner les configurations à comparer",
"PHRASE_SG_RECHARGE": "Temps de charge de 50% à 100 %",
"PHRASE_SG_RECOVER": "Temps de redémarrage après perte du bouclier",
"PHRASE_UNLADEN": "Masse du Vaisseau hors Carburant et Cargo",
"PHRASE_UPDATE_RDY": "Mise à jour disponible ! Cliquez pour rafraichir",
"Ls": "SL",
"LY": "AL",
"L": "G",
"large": "grand",
"medium": "moyen",
"S": "P",
"small": "petit",
"about": "à propos",
"added": "ajouté",
"ammo": "munition",
"armour": "Armure",
"available": "Disponibilité",
"backup": "sauvegarde",
"bays": "baies",
"bins": "bacs",
"build": "Configuration",
"build name": "Nom de la configuration",
"builds": "Configurations",
"buy": "Acheter",
"cancel": "Annuler",
"cargo": "Soute",
"cells": "Cellule",
"close": "fermer",
"compare": "comparer",
"compare all": "tout comparer",
"comparison": "comparaison",
"comparisons": "comparaisons",
"cost": "coût",
"costs": "coûts",
"create": "Créer",
"create new": "Créer nouveau",
"credits": "crédits",
"damage": "dégât",
"damage per second": "dégât par seconde",
"delete": "supprimer",
"delete all": "tout supprimer",
"dep": "depl",
"deployed": "déployé",
"detailed export": "export détaillé",
"disabled": "désactivé",
"discount": "ristourne",
"edit data": "Editer donnée",
"efficiency": "rendement",
"empty": "Vide",
"empty all": "vide tout",
"Enter Name": "Entrer nom",
"Explorer": "explorateur",
"fastest range": "gamme la plus rapide",
"fuel": "carburant",
"fuel level": "niveau de carburant",
"full tank": "Réservoir plein",
"hardpoints": "Points d'emport",
"hull": "Coque",
"import": "Importer",
"insurance": "Assurance",
"internal compartments": "compartiments internes",
"jump": "saut",
"jump range": "Distance de saut",
"jumps": "Sauts",
"laden": "chargé",
"language": "Langage",
"maneuverability": "maniabilité",
"manufacturer": "fabricant",
"mass": "Masse",
"mass lock factor": "facteur inhibition de masse",
"max mass": "masse max",
"MLF": "FIM",
"net cost": "coûts nets",
"no": "non",
"none created": "Rien de créé",
"ok": "D'accord",
"optimal mass": "masse optimale",
"optimize": "optimiser",
"pen": "pén.",
"permalink": "lien durable",
"power": "énergie",
"proceed": "continuer",
"PWR": "P",
"qty": "quantité",
"range": "portée",
"rate": "cadence",
"recharge": "recharger",
"recovery": "récupération",
"refuel time": "Temps de remplissage",
"reload": "recharger",
"reload costs": "recharger coûts",
"rename": "renommer",
"repair": "réparer",
"reset": "Réinitialisation",
"ret": "esc",
"retracted": "escamoté",
"retrofit costs": "Valeur de rachat",
"retrofit from": "Racheter de",
"ROF": "cadence",
"roles": "rôles",
"save": "sauvegarder",
"sell": "vendre",
"settings": "paramètres",
"shields": "boucliers",
"ship": "vaisseau",
"ships": "vaisseaux",
"shortened": "raccourci",
"size": "taille",
"skip": "Suivant",
"speed": "vitesse",
"Stock": "de base",
"strength": "force",
"subtotal": "Sous-Total",
"T-Load": "degrés",
"time": "temps",
"tooltips": "infobulles",
"total range": "plage totale",
"Trader": "commerçant",
"unit cost": "coût unitaire",
"unladen": "Non chargé",
"utility mounts": "Support utilitaire",
"WEP": "ARM",
"yes": "oui"
}

View File

@@ -13,117 +13,4 @@ export const formats = {
shortMonths: ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic']
};
export const terms = {
PHRASE_EXPORT_DESC: 'Un export dettagliato in formato JSON della tua configurazione per essere usato in altri siti o tools',
'A-Rated': 'Classe A',
about: 'Info su Coriolis',
action: 'azione',
added: 'aggiunto',
Advanced: 'Avanzato',
maneuverability: 'agilità',
ammo: 'munizioni',
PHRASE_CONFIRMATION: 'Sei sicuro ?',
armour: 'armatura',
available: 'disponibile',
bins: 'contenitore',
build: 'configurazione',
'build name': 'Nome Configurazione',
builds: 'configurazioni',
buy: 'compra',
cancel: 'cancella',
cells: 'celle',
close: 'chiudi',
compare: 'confronta',
'compare all': 'confronta tutti',
comparison: 'comparazione',
comparisons: 'comparazioni',
component: 'componente',
cost: 'costo',
costs: 'costi',
cm: 'Contromisure',
create: 'crea',
'create new': 'crea nuovo',
credits: 'crediti',
damage: 'danno',
delete: 'elimina',
'delete all': 'elimina tutto',
dep: 'dep',
deployed: 'deployed',
'detailed export': 'esportazione dettagliata',
disabled: 'disabilita',
discount: 'sconto',
done: 'fatto',
'edit data': 'modifica i dati',
efficiency: 'efficenza',
empty: 'vuoto',
Enforcer: 'Rinforzatore',
'enter name': 'Inserisci un nome',
export: 'esporta',
fixed: 'fissi',
fuel: 'carburante',
'full tank': 'Serbatoio Pieno',
huge: 'enorme',
hull: 'corazza',
import: 'importa',
'import all': 'importa tutto',
insurance: 'assicurazione',
'internal compartments': 'compartimenti interni',
'jump range': 'distanza di salto',
jumps: 'salti',
laden: 'carico',
language: 'lingua',
large: 'largo',
mass: 'massa',
max: 'massimo',
'max mass': 'massa massimale',
medium: 'medio',
'net cost': 'costo netto',
PHRASE_NO_BUILDS: 'nessuna configurazione è stata aggiunta per la comparazione!',
PHRASE_NO_RETROCH: 'Nessun cambiamento di Retrofitting',
none: 'nessuno',
'none created': 'nessuno creato',
optimal: 'ottimale',
'optimal mass': 'massa ottimale',
'optimize mass': 'ottimizza la massa',
overwrite: 'sovrasscrivi',
PHRASE_IMPORT: 'Incolla un JSON o importalo qua',
penetration: 'penetrazione',
power: 'potenza',
priority: 'priorità',
proceed: 'procedi',
range: 'distanza',
rate: 'rateo',
recharge: 'ricarica',
reload: 'ricarica',
rename: 'rinomina',
repair: 'ripara',
reset: 'resetta',
retracted: 'retratti',
'retrofit costs': 'costi di retrofit',
'retrofit from': 'retrofit da',
save: 'salva',
sell: 'vendi',
settings: 'impostazioni',
shields: 'scudi',
ship: 'nave',
ships: 'navi',
shortened: 'accorciato',
size: 'grandezza',
skip: 'salta',
small: 'piccolo',
speed: 'velocità',
Stock: 'appena comprata',
t: 'thrusters',
time: 'tempo',
total: 'totale',
'total range': 'distanza totale',
turret: 'torrette',
type: 'tipo',
unladen: 'scarico',
PHRASE_UPDATE_RDY: 'Aggiornamenti disponibili ! Clicca per Aggiornare',
utility: 'supporti',
'utility mounts': 'supporti di utilità',
version: 'versione',
yes: 'sì',
PHRASE_BACKUP_DESC: 'Esportazione di tutti i dati su Coriolis per salvarli o trasferirli in un altro Browser/dispositivo'
};
export { default as terms } from './it.json';

114
src/app/i18n/it.json Normal file
View File

@@ -0,0 +1,114 @@
{
"PHRASE_EXPORT_DESC": "Un export dettagliato in formato JSON della tua configurazione per essere usato in altri siti o tools",
"A-Rated": "Classe A",
"about": "Info su Coriolis",
"action": "azione",
"added": "aggiunto",
"Advanced": "Avanzato",
"maneuverability": "agilità",
"ammo": "munizioni",
"PHRASE_CONFIRMATION": "Sei sicuro ?",
"armour": "armatura",
"available": "disponibile",
"bins": "contenitore",
"build": "configurazione",
"build name": "Nome Configurazione",
"builds": "configurazioni",
"buy": "compra",
"cancel": "cancella",
"cells": "celle",
"close": "chiudi",
"compare": "confronta",
"compare all": "confronta tutti",
"comparison": "comparazione",
"comparisons": "comparazioni",
"component": "componente",
"cost": "costo",
"costs": "costi",
"cm": "Contromisure",
"create": "crea",
"create new": "crea nuovo",
"credits": "crediti",
"damage": "danno",
"delete": "elimina",
"delete all": "elimina tutto",
"dep": "dep",
"deployed": "deployed",
"detailed export": "esportazione dettagliata",
"disabled": "disabilita",
"discount": "sconto",
"done": "fatto",
"edit data": "modifica i dati",
"efficiency": "efficenza",
"empty": "vuoto",
"Enforcer": "Rinforzatore",
"enter name": "Inserisci un nome",
"export": "esporta",
"fixed": "fissi",
"fuel": "carburante",
"full tank": "Serbatoio Pieno",
"huge": "enorme",
"hull": "corazza",
"import": "importa",
"import all": "importa tutto",
"insurance": "assicurazione",
"internal compartments": "compartimenti interni",
"jump range": "distanza di salto",
"jumps": "salti",
"laden": "carico",
"language": "lingua",
"large": "largo",
"mass": "massa",
"max": "massimo",
"max mass": "massa massimale",
"medium": "medio",
"net cost": "costo netto",
"PHRASE_NO_BUILDS": "nessuna configurazione è stata aggiunta per la comparazione!",
"PHRASE_NO_RETROCH": "Nessun cambiamento di Retrofitting",
"none": "nessuno",
"none created": "nessuno creato",
"optimal": "ottimale",
"optimal mass": "massa ottimale",
"optimize mass": "ottimizza la massa",
"overwrite": "sovrasscrivi",
"PHRASE_IMPORT": "Incolla un JSON o importalo qua",
"penetration": "penetrazione",
"power": "potenza",
"priority": "priorità",
"proceed": "procedi",
"range": "distanza",
"rate": "rateo",
"recharge": "ricarica",
"reload": "ricarica",
"rename": "rinomina",
"repair": "ripara",
"reset": "resetta",
"retracted": "retratti",
"retrofit costs": "costi di retrofit",
"retrofit from": "retrofit da",
"save": "salva",
"sell": "vendi",
"settings": "impostazioni",
"shields": "scudi",
"ship": "nave",
"ships": "navi",
"shortened": "accorciato",
"size": "grandezza",
"skip": "salta",
"small": "piccolo",
"speed": "velocità",
"Stock": "appena comprata",
"t": "thrusters",
"time": "tempo",
"total": "totale",
"total range": "distanza totale",
"turret": "torrette",
"type": "tipo",
"unladen": "scarico",
"PHRASE_UPDATE_RDY": "Aggiornamenti disponibili ! Clicca per Aggiornare",
"utility": "supporti",
"utility mounts": "supporti di utilità",
"version": "versione",
"yes": "sì",
"PHRASE_BACKUP_DESC": "Esportazione di tutti i dati su Coriolis per salvarli o trasferirli in un altro Browser/dispositivo"
}

16
src/app/i18n/pl.js Normal file
View File

@@ -0,0 +1,16 @@
export const formats = {
decimal: '.',
thousands: ',',
grouping: [3],
currency: ['$', ''],
dateTime: '%a %b %e %X %Y',
date: '%m/%d/%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'],
days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
shortDays: ['Nie', 'Pon', 'Wt', 'Śr', 'Czw', 'Pt', 'Sob'],
months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
shortMonths: ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru']
};
export { default as terms } from './pl.json';

59
src/app/i18n/pl.json Normal file
View File

@@ -0,0 +1,59 @@
{
"PHRASE_ALT_ALL": "Alt + kliknięcie by wypełnić wszystkie sloty",
"PHRASE_BACKUP_DESC": "Kopia zapasowa wszystkich danych Coriolis w celu zapisu lub przeniesienia na inne urządzenie/przeglądarkę",
"PHRASE_CONFIRMATION": "Czy jesteś pewien?",
"PHRASE_EXPORT_DESC": "Szczegółowy eksport schematu w formacie JSON w celu użycia na innych stronach i narzędziach",
"PHRASE_FASTEST_RANGE": "Maksymalna ilość skoków na najwyższym zasięgu",
"PHRASE_IMPORT": "Wklej tu JSON lub importuj",
"PHRASE_LADEN": "Masa statku + paliwo + ładunek",
"PHRASE_NO_BUILDS": "Nie dodano schematu do porównania!",
"PHRASE_NO_RETROCH": "Brak zmian retrofit",
"PHRASE_SELECT_BUILDS": "Wybierz schematy do porównania",
"PHRASE_SG_RECHARGE": "Czas od 50% do 100% naładowania",
"PHRASE_SG_RECOVER": "Odnowienie (do 50%) po upadku",
"PHRASE_UNLADEN": "Masa statku z wyłączeniem paliwa i ładunku",
"PHRASE_UPDATE_RDY": "Dostępna aktualizacja! Naciśnij by odświeżyć",
"am": "Auto Field-Maintenance Unit",
"bh": "Bulkheads",
"bl": "Beam Laser",
"bsg": "Bi-Weave Shield Generator",
"c": "Cannon",
"cc": "Collector Limpet Controller",
"cm": "Countermeasure",
"cr": "Cargo Rack",
"cs": "Cargo Scanner",
"dc": "Docking Computer",
"fc": "Fragment Cannon",
"fi": "FSD Interdictor",
"fs": "Fuel Scoop",
"fsd": "Frame Shift Drive",
"ft": "Fuel Tank",
"fx": "Fuel Transfer Limpet Controller",
"hb": "Hatch Breaker Limpet Controller",
"hr": "Hull Reinforcement Package",
"kw": "Kill Warrant Scanner",
"ls": "Life Support",
"mc": "Multi-cannon",
"ml": "Mining Laser",
"mr": "Missile Rack",
"nl": "Mine Launcher",
"pa": "Plasma Accelerator",
"pas": "Planetary Approach Suite",
"pc": "Prospector Limpet Controller",
"pd": "power distributor",
"pl": "Pulse Laser",
"pp": "Power Plant",
"psg": "Prismatic Shield Generator",
"pv": "Planetary Vehicle Hangar",
"rf": "Refinery",
"rg": "Rail Gun",
"s": "Sensors",
"sb": "Shield Booster",
"sc": "Scanner",
"scb": "Shield Cell Bank",
"sg": "Shield Generator",
"t": "thrusters",
"tp": "Torpedo Pylon",
"ul": "Burst Laser",
"ws": "Frame Shift Wake Scanner"
}

16
src/app/i18n/pt.js Normal file
View File

@@ -0,0 +1,16 @@
export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
currency: ['', ' €'],
dateTime: '%A, %e de %B de %Y, %X',
date: '%d/%m/%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'],
days: ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'],
shortDays: ['dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb'],
months: ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'],
shortMonths: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']
};
export { default as terms } from './pt.json';

278
src/app/i18n/pt.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -13,152 +13,4 @@ export const formats = {
shortMonths: ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек']
};
export const terms = {
// Phrases
PHRASE_BACKUP_DESC: 'Сохраните все данные перед переносом в другой браузер или устройство', // Backup of all Coriolis data to save or transfer to another browser/device
PHRASE_CONFIRMATION: 'Вы уверены?', // Are You Sure?
PHRASE_EXPORT_DESC: 'Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах', // A detailed JSON export of your build for use in other sites and tools
PHRASE_FASTEST_RANGE: 'Последовательные прыжки максимальной дальности', // Consecutive max range jumps
PHRASE_IMPORT: 'Для импорта вставьте код в эту форму', // Paste JSON or import here
PHRASE_LADEN: 'Масса корабля с учётом топлива и грузов', // Ship Mass + Fuel + Cargo
PHRASE_NO_BUILDS: 'Нечего сравнивать', // No builds added to comparison!
PHRASE_NO_RETROCH: 'нет ранних версий сборки\\конфигурации', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Выберите конфигурацию для сравнения', // Select Builds to Compare
PHRASE_SG_RECHARGE: 'восстановление с 60% до 100% объема щита', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: 'восстановление [до 60%] после снятия щита', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: 'Масса корабля без учета топлива и грузов', // Ship Mass excluding Fuel and Cargo
PHRASE_UPDATE_RDY: 'Доступно обновление. Нажмите для обновления.', // Update Available! Click to Refresh
// Units / Metrics
'/s': '/с', // Per second
'm/s': 'м/с', // Meters / Second
Ls: 'Св.сек', // Light seconds
LY: 'Св.лет', // Light Years
CR: 'кр.', // Credits abbreviation
// Sizes
S: 'M', // Small Hardpoint (single Character)
M: 'С', // Medium Hardpoint size (single character)
L: 'б', // Large Hardpoint size (single character)
H: 'O', // Huge Hardpoint size (single character)
U: 'B', // Utility Hardpoint size (single character) - Kill warrant scanner, etc
small: 'Малый', // Small ship size
medium: 'Средний', // Medium ship size
large: 'большой', // Large Ship Size
// Insurance
alpha: 'Альфа', // Alpha backer insurance level
beta: 'Бета', // Beta back insurance level
standard: 'Стандартный', // Standard insurance level
// Terms
'build name': 'название сборки', // Ship build/configuration/design name
'compare all': 'сравнить все',
'create new': 'Создать новый',
'damage per second': 'урон в секунду',
'delete all': 'Удалить все',
'detailed export': 'Подробный экспорт',
'edit data': 'Редактирование',
'empty all': 'пусто все',
'Enter Name': 'Введите имя',
'fastest range': 'быстрый диапазон', // Fastet totaljump range - sum of succesive jumps
'fuel level': 'уровень топлива', // Percent of fuel (T) in the tank
'full tank': 'Полный бак',
'internal compartments': 'внутренние отсеки',
'jump range': 'Дальность прыжка',
'mass lock factor': 'Масс. блок',
'max mass': 'Максимальная масса',
'net cost': 'разница в цене',
'none created': 'не создано',
'refuel time': 'Время дозаправки', // Time to refuel the tank when scooping
'retrofit costs': 'цена модификации', // The cost difference when upgrading / downgrading a component
'retrofit from': 'модификация от', // Retrofit from Build A against build B
'T-Load': 'Тепл.', // Thermal load abbreviation
'utility mounts': 'Вспомогательное оборудование',
about: 'О ...', // Link to about page / about Coriolis.io
action: 'Действие',
added: 'Добавлено',
ammo: 'Боекомплект', // Ammunition
armour: 'Броня',
available: 'доступно', // Available options
backup: 'Резервная копия',
bins: 'контейнеры', // Number of Mining Refinery bins
boost: 'форсаж',
build: 'cборка', // Shorthand for the build/configuration/design name
builds: 'cборки', // Ship build/configuration/design names
buy: 'купить',
cancel: 'отменить',
cargo: 'Груз',
cells: 'Ячейки', // Number of cells in a shield cell bank
close: 'закрыть',
compare: 'сравнить ',
comparison: 'сравнение',
comparisons: 'сравнения',
cost: 'Стоимость', // Cost / price of a module or price of a ship
costs: 'Расходы', // Costs / prices of a modules or prices of ships
create: 'создать',
credits: 'Кредиты',
damage: 'Урон',
delete: 'Удалить',
dep: 'Вып', // Weapons/Hardpoints Deployed abbreviation
deployed: 'Открыты', // Weapons/Hardpoints Deployed
disabled: 'Отключено',
discount: 'Скидка',
DPS: 'УВС', // Damage per second abbreviation
efficiency: 'Эффективность', // Power Plant efficiency
empty: 'пусто',
ENG: 'ДВГ', // Abbreviation - Engine recharge rate for power distributor
export: 'Экспорт',
forum: 'Форум',
fuel: 'Топливо',
hardpoints: 'Орудийные порты',
hull: 'Корпус', // Ships hull
import: 'импортировать ',
insurance: 'Страховка',
jumps: 'Прыжков',
laden: 'Груженый',
language: 'Язык',
maneuverability: 'Маневренность',
mass: 'Масса',
max: 'Макс',
no: 'Нет',
pen: 'ПБ', // Armour peneration abbreviation
permalink: 'Постоянная ссылка',
power: 'Мощность', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section
pri: 'Осн', // Priority abbreviation for power management
proceed: 'продолжить',
PWR: 'Эн', // Power Abbreviation. See Power
range: 'Дальность',
rate: 'скорость',
recharge: 'перезарядка', // Shield Recharge time from 50% -> 100%
recovery: 'включение', // Shield recovery time (after losing shields/turning on -> 50%)
reload: 'Перезагрузить', // Reload weapon/hardpoint
rename: 'Переименовать',
repair: 'Починка',
reset: 'Сброс',
ret: 'Убр.', // Retracted abbreviation
retracted: 'Убрано', // Weapons/Hardpoints retracted
ROF: 'В/сек', // Rate of Fire abbreviation
save: 'Сохранить',
sell: 'Продать',
settings: 'Настройки', // Coriolis application settings
shields: 'Щиты',
ship: 'Корабль',
ships: 'Корабли',
shortened: 'Укороченный', // Standard/Stock build of a ship when purchased new
size: 'размер',
skip: 'пропустить', // Skip past something / ignore it
speed: 'скорость',
standard: 'Стандартный', // Standard / Common modules (FSD, power plant, life support, etc)
Stock: 'Стандартная комплектация', // Thermal-load abbreviation
SYS: 'СИСТЕМЫ', // Abbreviation - System recharge rate for power distributor
time: 'Время', // time it takes to complete something
total: 'Всего',
type: 'Тип',
unladen: 'Пустой', // No cargo or fuel
URL: 'Ссылка', // Link, Uniform Resource Locator
WEP: 'ОРУДИЯ', // Abbreviation - Weapon recharge rate for power distributor
yes: 'Да'
};
export { default as terms } from './ru.json';

384
src/app/i18n/ru.json Normal file
View File

@@ -0,0 +1,384 @@
{
"PHRASE_ALT_ALL": "Alt + Нажатие для заполнения всех слотов",
"PHRASE_BACKUP_DESC": "Сохраните все данные перед переносом в другой браузер или устройство",
"PHRASE_CONFIRMATION": "Вы уверены?",
"PHRASE_EXPORT_DESC": "Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах",
"PHRASE_FASTEST_RANGE": "Последовательные прыжки максимальной дальности",
"PHRASE_IMPORT": "Для импорта вставьте код в эту форму",
"PHRASE_LADEN": "Масса корабля с учётом топлива и грузов",
"PHRASE_NO_BUILDS": "Нечего сравнивать",
"PHRASE_NO_RETROCH": "Нет ранних версий сборки",
"PHRASE_SELECT_BUILDS": "Выберите конфигурацию для сравнения",
"PHRASE_SG_RECHARGE": "Восстановление с 50% до 100% объема щита, учитывая полный аккумулятор СИС в начале",
"PHRASE_SG_RECOVER": "Восстановление с 0% до 50% объема щита, учитывая полный аккумулятор СИС в начале",
"PHRASE_UNLADEN": "Масса корабля без учета топлива и грузов",
"PHRASE_UPDATE_RDY": "Доступна новая версия. Нажмите для обновления.",
"PHRASE_ENGAGEMENT_RANGE": "Дистанция между кораблём и целью",
"PHRASE_SELECT_BLUEPRINT": "Нажмите чтобы выбрать чертёж",
"PHRASE_BLUEPRINT_WORST": "Худшие основные значения для чертежа",
"PHRASE_BLUEPRINT_RANDOM": "Случайный выбор между худшими и лучшими значениями для этого чертежа",
"PHRASE_BLUEPRINT_BEST": "Лучшие основные значения для чертежа",
"PHRASE_BLUEPRINT_EXTREME": "Лучшие положительные и худшие отрицательные основные значения для чертежа",
"PHRASE_BLUEPRINT_RESET": "Убрать все изменения и чертёж",
"PHRASE_SELECT_SPECIAL": "Нажмите чтобы выбрать экспериментальный эффект",
"PHRASE_NO_SPECIAL": "Без экспериментального эффекта",
"PHRASE_SHOPPING_LIST": "Станции что продают эту сборку",
"PHRASE_REFIT_SHOPPING_LIST": "Станции что продают необходимые модули",
"PHRASE_TOTAL_EFFECTIVE_SHIELD": "Общий урон что может быть нанесён в каждым типе, если используются все щитонакопители",
"PHRASE_TIME_TO_LOSE_SHIELDS": "Щиты продержатся",
"PHRASE_TIME_TO_RECOVER_SHIELDS": "Щиты восстановятся за",
"PHRASE_TIME_TO_RECHARGE_SHIELDS": "Щиты будут заряжены за",
"PHRASE_SHIELD_SOURCES": "Подробности энергии щита",
"PHRASE_EFFECTIVE_SHIELD": "Эффективная сила щита против разных типов урона",
"PHRASE_ARMOUR_SOURCES": "Подробности состава брони",
"PHRASE_EFFECTIVE_ARMOUR": "Эффективная сила брони против разных типов урона",
"PHRASE_DAMAGE_TAKEN": "% общих повреждений полученных в разных типах урона",
"PHRASE_TIME_TO_LOSE_ARMOUR": "Броня продержится",
"PHRASE_MODULE_PROTECTION_EXTERNAL": "Защита гнёзд",
"PHRASE_MODULE_PROTECTION_INTERNAL": "Защита всех остальных модулей",
"PHRASE_SHIELD_DAMAGE": "Подробности источников поддерживаемого ДПС против щитов",
"PHRASE_ARMOUR_DAMAGE": "Подробности источников поддерживаемого ДПС против брони",
"PHRASE_TIME_TO_REMOVE_SHIELDS": "Снимет щиты за",
"TT_TIME_TO_REMOVE_SHIELDS": "Непрерывным огнём из всех орудий",
"PHRASE_TIME_TO_REMOVE_ARMOUR": "Снимет броню за",
"TT_TIME_TO_REMOVE_ARMOUR": "Непрерывным огнём из всех орудий",
"PHRASE_TIME_TO_DRAIN_WEP": "Опустошит ОРУЖ за",
"TT_TIME_TO_DRAIN_WEP": "Время за которое опустошится аккумулятор ОРУЖ при стрельбе из всех орудий",
"TT_TIME_TO_LOSE_SHIELDS": "Против поддерживаемой стрельбы из всех орудий противника",
"TT_TIME_TO_LOSE_ARMOUR": "Против поддерживаемой стрельбы из всех орудий противника",
"TT_MODULE_ARMOUR": "Броня защищаюшае модули от урона",
"TT_MODULE_PROTECTION_EXTERNAL": "Процент урона перенаправленного от гнёзд на наборы для усиления модулей",
"TT_MODULE_PROTECTION_INTERNAL": "Процент урона перенаправленного от модулей вне гнёзд на наборы для усиления модулей",
"TT_EFFECTIVE_SDPS_SHIELDS": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст",
"TT_EFFECTIVENESS_SHIELDS": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью без пунктов в СИС на 0 метрах",
"TT_EFFECTIVE_SDPS_ARMOUR": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст",
"TT_EFFECTIVENESS_ARMOUR": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью на 0 метрах",
"PHRASE_EFFECTIVE_SDPS_SHIELDS": "ПДПС против щитов",
"PHRASE_EFFECTIVE_SDPS_ARMOUR": "ПДПС против брони",
"TT_SUMMARY_SPEED": "С полным топливным баком и 4 пунктами в ДВИ",
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "маневровые двигатели выключены или превышена максимальная масса с топливом и грузом",
"TT_SUMMARY_BOOST": "С полным топливным баком и 4 пунктами в ДВИ",
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Распределитель питания не может обеспечить достаточно энергии для форсажа",
"TT_SUMMARY_SHIELDS": "Чистая сила щита, включая усилители",
"TT_SUMMARY_SHIELDS_NONFUNCTIONAL": "Шитогенератор отсутствует или выключен",
"TT_SUMMARY_INTEGRITY": "Целостность корабля, включая переборки и наборы для усиления корпуса",
"TT_SUMMARY_HULL_MASS": "Масса корпуса без каких-либо модулей",
"TT_SUMMARY_UNLADEN_MASS": "Масса корпуса и модулей без топлива и груза",
"TT_SUMMARY_LADEN_MASS": "Масса корпуса и модулей с топливом и грузом",
"TT_SUMMARY_DPS": "Урон в секунду при стрельбе из всех орудий",
"TT_SUMMARY_EPS": "Расход аккумулятора ОРУЖ в секунду при стрельбе из всех орудий",
"TT_SUMMARY_TTD": "Время расхода аккумулятора ОРУЖ при стрельбе из всех орудий и с 4 пунктами в ОРУЖ",
"TT_SUMMARY_MAX_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с топливом достаточным только на сам прыжок",
"TT_SUMMARY_UNLADEN_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с полным топливным баком",
"TT_SUMMARY_LADEN_SINGLE_JUMP": "Самый дальний возможный прыжок с полным грузовым отсеком и с полным топливным баком",
"TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Самая дальняя общая дистанция без груза, с полным топливным баком и при прыжках на максимальное расстояние",
"TT_SUMMARY_LADEN_TOTAL_JUMP": "Самая дальняя общая дистанция с полным грузовым отсеком, с полным топливным баком и при прыжках на максимальное расстояние",
"HELP_MODIFICATIONS_MENU": "Ткните на номер чтобы ввести новое значение, или потяните вдоль полосы для малых изменений",
"am": "Блок Автом. Полевого Ремонта",
"bh": "Переборки",
"bl": "Пучковый Лазер",
"bsg": "Двухпоточный Щитогенератор",
"c": "Орудие",
"cc": "Контроллер магнитного снаряда для сбора",
"ch": "Разбрасыватель дипольных отражателей",
"cr": "Грузовой стеллаж",
"cs": "Сканер содержимого",
"dc": "Стыковочный компьютер",
"ec": "Электр. противодействие",
"fc": "Залповое орудие",
"fh": "Ангар для истребителя",
"fi": "FSD-перехватчик",
"fs": "Топливозаборник",
"fsd": "Рамочно Сместительный двигатель",
"ft": "Топливный бак",
"fx": "Контроллер магнитного снаряда для топлива",
"hb": "Контроллер магнитного снаряда для взлома трюма",
"hr": "Набор для усиления корпуса",
"hs": "Теплоотводная катапульта",
"kw": "Сканер преступников",
"ls": "Система жизнеобеспечения",
"mc": "Многоствольное орудие",
"ml": "Проходочный лазер",
"mr": "Ракетный лоток",
"mrp": "Набор для усиления модуля",
"nl": "Мины",
"pa": "Ускоритель плазмы",
"pas": "Комплект для сближения с планетой",
"pc": "Контроллер магнитного снаряда для геологоразведки",
"pce": "Каюта пассажира эконом-класса",
"pci": "Каюта пассажира бизнес-класса",
"pcm": "Каюта пассажира первого класса",
"pcq": "Каюта пассажира класса люкс",
"pd": "Распределитель питания",
"pl": "Ипмульсный лазер",
"po": "Точечная оборона",
"pp": "Силовая установка",
"psg": "Призматический щитогенератор",
"pv": "Гараж для планетарного транспорта",
"rf": "Устройство переработки",
"rg": "Электромагнитная пушка",
"s": "Сенсоры",
"sb": "Усилитель щита",
"sc": "Сканер обнаружения",
"scb": "Щитонакопитель",
"sg": "Щитогенератор",
"ss": "Сканер Поверхностей",
"t": "Маневровые двигатели",
"tp": "Торпедная стойка",
"ul": "Пульсирующие лазеры",
"ws": "Сканер следа FSD",
"emptyrestricted": "пусто (ограниченно)",
"damage dealt to": "Урон нанесён",
"damage received from": "Урон получен от",
"against shields": "Против шитов",
"against hull": "Против корпуса",
"total effective shield": "Общие эффективные щиты",
"ammunition": "Припасы",
"secs": "с",
"rebuildsperbay": "Построек за полосу",
"worst": "Худшее",
"average": "Среднее",
"random": "Случайное",
"best": "Лучшее",
"extreme": "Экстремальное",
"reset": "Сброс",
"dpe": "Урон на МДж энергии",
"dps": "Урон в Секунду",
"sdps": "Поддерживаемый урон в секунду",
"dpssdps": "Урон в секунду (поддерживаемый урон в секунду)",
"eps": "Энергия в секунду",
"epsseps": "Энергия в секунду (поддерживаемая энергия в секунду)",
"hps": "Нагрев в секунду",
"hpsshps": "Heat per second (sustained heat per second)",
"damage by": "Урон",
"damage from": "Урон от",
"shield cells": "Щитонакопители",
"recovery": "включение",
"recharge": "перезарядка",
"engine pips": "Пункты в двигателе",
"4b": "4 пункта и Форсаж",
"speed": "скорость",
"pitch": "Тангаж",
"roll": "Крен",
"yaw": "Рыскание",
"internal protection": "Внутренняя защита",
"external protection": "Внешняя защита",
"engagement range": "Боевое расстояние",
"total": "Всего",
"ammo": "Боекомплект",
"boot": "Время загрузки",
"brokenregen": "Скорость восстановления при пробое",
"burst": "Длина очереди",
"burstrof": "Скорострельность очереди",
"clip": "Боекомплект",
"damage": "Урон",
"distdraw": "Тяга распределителя",
"duration": "Продолжительность",
"eff": "Эффективность",
"engcap": "Ресурс двигателей",
"engrate": "Перезарядка двигателей",
"explres": "Сопротивление взрывам",
"facinglimit": "Ограничение по направлению",
"hullboost": "Увеличение корпуса",
"hullreinforcement": "Укрепление корпуса",
"integrity": "Целостность",
"jitter": "Дрожание",
"kinres": "Сопротивление китетическому урону",
"maxfuel": "Макс. топлива на прыжок",
"mass": "Масса",
"optmass": "Оптимизированная масса",
"optmul": "Оптимальный усилитель",
"pgen": "Мощность",
"piercing": "Бронебойность",
"power": "Мощность",
"protection": "Защита от повреждений",
"range": "Дальность",
"ranget": "Дальность",
"regen": "Скорость восстановления",
"reload": "Перезагрузить",
"rof": "Скорострельность",
"angle": "Угол сканера",
"scanrate": "Скорость сканера",
"scantime": "Время сканирования",
"shield": "Щит",
"shieldboost": "Усиление щитов",
"shieldreinforcement": "Усилитель щита",
"shotspeed": "Скорость выстрела",
"spinup": "Время раскрутки",
"syscap": "Ресурс систем",
"sysrate": "Перезарядка систем",
"thermload": "Тепловая нагрузка",
"thermres": "Сопротивление термическому урону",
"wepcap": "Орудийный ресурс",
"weprate": "Перезарядка оружия",
"minmass_sg": "Мин. масса корпуса",
"optmass_sg": "Опт. масса корпуса",
"maxmass_sg": "Макс. масса корпуса",
"minmul_sg": "Минимальная прочность",
"optmul_sg": "Оптимальная прочность",
"maxmul_sg": "Максимальная прочность",
"minmass_psg": "Мин. масса корпуса",
"optmass_psg": "Опт. масса корпуса",
"maxmass_psg": "Макс. масса корпуса",
"minmul_psg": "Минимальная прочность",
"optmul_psg": "Оптимальная прочность",
"maxmul_psg": "Максимальная прочность",
"minmass_bsg": "Мин. масса корпуса",
"optmass_bsg": "Опт. масса корпуса",
"maxmass_bsg": "Макс. масса корпуса",
"minmul_bsg": "Минимальная прочность",
"optmul_bsg": "Оптимальная прочность",
"maxmul_bsg": "Максимальная прочность",
"range_s": "Типовой диапозон выброса",
"absolute": "Общий",
"explosive": "Взрывч.",
"kinetic": "Механич.",
"thermal": "Тепл.",
"generator": "Генератор",
"boosters": "Усилители",
"cells": "Ячейки",
"bulkheads": "Переборки",
"reinforcement": "Усилители",
"power and costs": "Энергия и стоимость",
"costs": "Расходы",
"retrofit costs": "цена модификации",
"reload costs": "Стоимость перезарядки",
"profiles": "Графики",
"engine profile": "Двигатели",
"fsd profile": "FSD",
"movement profile": "Движение",
"damage to opponent's shields": "Урон щиту противника",
"damage to opponent's hull": "Урон корпусу противника",
"offence": "Нападение",
"defence": "Оборона",
"shield metrics": "Данные щита",
"raw shield strength": "Чистая мощность щита",
"shield sources": "Ресурсы щита",
"damage taken": "Полученный урон",
"effective shield": "Эффективный щит",
"armour metrics": "Данные брони",
"raw armour strength": "Чистая мощность брони",
"armour sources": "Ресурсы брони",
"raw module armour": "Чистая броня модулей",
"effective armour": "Эффективная броня",
"offence metrics": "Данные нападения",
"defence metrics": "Данные обороны",
"fuel carried": "Топливо на борту",
"cargo carried": "Груз на борту",
"ship control": "Управление кораблём",
"opponent": "Противник",
"opponent's shields": "Щит противника",
"opponent's armour": "Броня противника",
"shield damage sources": "источники урона по щиту",
"armour damage sources": "источники урона по броне",
"never": "Никогда",
"stock": "базовый",
"boost": "форсаж",
"/s": "/с",
"m/s": "м/с",
"Ls": "Св.сек",
"LY": "Св.лет",
"CR": "кр.",
"S": "M",
"M": "С",
"L": "б",
"H": "O",
"U": "B",
"small": "Малый",
"medium": "Средний",
"large": "большой",
"alpha": "Альфа",
"beta": "Бета",
"standard": "Стандартный",
"build name": "название сборки",
"compare all": "сравнить все",
"create new": "Создать новый",
"damage per second": "урон в секунду",
"delete all": "Удалить все",
"detailed export": "Подробный экспорт",
"edit data": "Редактирование",
"empty all": "пусто все",
"Enter Name": "Введите имя",
"fastest range": "быстрый диапазон",
"fuel level": "уровень топлива",
"full tank": "Полный бак",
"internal compartments": "внутренние отсеки",
"jump range": "Дальность прыжка",
"mass lock factor": "Масс. блок",
"max mass": "Максимальная масса",
"net cost": "разница в цене",
"none created": "не создано",
"refuel time": "Время дозаправки",
"retrofit from": "модификация от",
"T-Load": "Тепл.",
"utility mounts": "Вспомогательное оборудование",
"about": "О ...",
"action": "Действие",
"added": "Добавлено",
"armour": "Броня",
"available": "доступно",
"backup": "Резервная копия",
"bins": "контейнеры",
"build": "cборка",
"builds": "cборки",
"buy": "купить",
"cancel": "отменить",
"cargo": "Груз",
"close": "закрыть",
"compare": "сравнить ",
"comparison": "сравнение",
"comparisons": "сравнения",
"cost": "Стоимость",
"create": "создать",
"credits": "Кредиты",
"delete": "Удалить",
"dep": "Вып",
"deployed": "Открыты",
"disabled": "Отключено",
"discount": "Скидка",
"DPS": "УВС",
"efficiency": "Эффективность",
"empty": "пусто",
"ENG": "ДВИ",
"export": "Экспорт",
"forum": "Форум",
"fuel": "Топливо",
"hardpoints": "Орудийные порты",
"hull": "Корпус",
"import": "импортировать ",
"insurance": "Страховка",
"jumps": "Прыжков",
"laden": "Груженый",
"language": "Язык",
"maneuverability": "Маневренность",
"max": "Макс",
"no": "Нет",
"pen": "ПБ",
"permalink": "Постоянная ссылка",
"pri": "Осн",
"proceed": "продолжить",
"PWR": "Эн",
"rate": "скорость",
"rename": "Переименовать",
"repair": "Починка",
"ret": "Убр.",
"retracted": "Убрано",
"ROF": "В/сек",
"save": "Сохранить",
"sell": "Продать",
"settings": "Настройки",
"shields": "Щиты",
"ship": "Корабль",
"ships": "Корабли",
"shortened": "Укороченный",
"size": "размер",
"skip": "пропустить",
"Stock": "Стандартная комплектация",
"SYS": "СИС",
"time": "Время",
"type": "Тип",
"unladen": "Пустой",
"URL": "Ссылка",
"WEP": "ОРУЖ",
"yes": "Да"
}

View File

@@ -39,17 +39,11 @@ export default class AboutPage extends Page {
<p>Coriolis is an open source project. Checkout the list of upcoming features and to-do list on github. Any and all contributions and feedback are welcome. If you encounter any bugs please report them and provide as much detail as possible.</p>
<form action='https://www.paypal.com/cgi-bin/webscr' method='post' target='_blank'>
<input type='hidden' name='cmd' value='_s-xclick' />
<input type='hidden' name='encrypted' value='-----BEGIN PKCS7-----MIIHLwYJKoZIhvcNAQcEoIIHIDCCBxwCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYCjl3XoqQ3Q+x/qS7Va1lwvF0IgUs8gBbrwj1/uEv/xFyPSB2G0kgWqiB2c/8vvfcjjyMr4nlzLUlmQ0yl1zZaeTXFciN5a+JsvaBISThIlN9UP7PXP61TVHCECtt/hBNtlOmg8/gG8khJCj8+qi81XsNAz5bEDpdahKW3fwGHD4jELMAkGBSsOAwIaBQAwgawGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI4EVkn3RE+9qAgYhg2sTmY1Gul2yJyLYJZPRMO/PwgzEogb2NlIcshJSO+KvBea5NjjTXN2EJNqJa24h4lGA1mdrSgzTGDrVbdcnuti9+7ggn5R5s5IwEEQnN4JQx3IAqsp3UmJbti5t776Ns50nQbjA8NzxI+gwUmIvUQaVs6wC4HYXG6q8QtqUIWeVDhvbnt+H8oIIDhzCCA4MwggLsoAMCAQICAQAwDQYJKoZIhvcNAQEFBQAwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMB4XDTA0MDIxMzEwMTMxNVoXDTM1MDIxMzEwMTMxNVowgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBR07d/ETMS1ycjtkpkvjXZe9k+6CieLuLsPumsJ7QC1odNz3sJiCbs2wC0nLE0uLGaEtXynIgRqIddYCHx88pb5HTXv4SZeuv0Rqq4+axW9PLAAATU8w04qqjaSXgbGLP3NmohqM6bV9kZZwZLR/klDaQGo1u9uDb9lr4Yn+rBQIDAQABo4HuMIHrMB0GA1UdDgQWBBSWn3y7xm8XvVk/UtcKG+wQ1mSUazCBuwYDVR0jBIGzMIGwgBSWn3y7xm8XvVk/UtcKG+wQ1mSUa6GBlKSBkTCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb22CAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCBXzpWmoBa5e9fo6ujionW1hUhPkOBakTr3YCDjbYfvJEiv/2P+IobhOGJr85+XHhN0v4gUkEDI8r2/rNk1m0GA8HKddvTjyGw/XqXa+LSTlDYkqI8OwR8GEYj4efEtcRpRYBxV8KxAW93YDWzFGvruKnnLbDAF6VR5w/cCMn5hzGCAZowggGWAgEBMIGUMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbQIBADAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTUwNjA1MjEwNDQzWjAjBgkqhkiG9w0BCQQxFgQUx6Bs50H7tbYJln13pP5J7J1KiSUwDQYJKoZIhvcNAQEBBQAEgYBiOr1RX38uvwghuIZxKpjXX4LG/GoyYM6citfsBD5vjUGj0udmsamjlur+dwxJNs9dULnJO6huoTvxqxTui0Mh3n21YKoMqVE/erfNk2XygrJw9bEtW+HXjU3F+OGKR7dfD9STp2ZlvTEvZR9JRV5A/udC9/9U9eD5iLKRRwkIBg==-----END PKCS7-----' />
<input type='image' src='https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif' name='submit' alt='PayPal - Donate to Coriolis.io' style={{ border:'none' }} />
<img alt='' border='0' src='https://www.paypalobjects.com/en_US/i/scr/pixel.gif' width='1' height='1' />
</form>
<p>Help keep the lights on! Donations will be used to cover costs of running and maintaining Coriolis. Thanks for helping!</p>
<h3>Chat</h3>
<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>;
}
}

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import Page from './Page';
import Router from '../Router';
import cn from 'classnames';
@@ -17,7 +16,7 @@ import ModalImport from '../components/ModalImport';
import { FloppyDisk, Bin, Download, Embed, Rocket, LinkIcon } from '../components/SvgIcons';
import ShortenUrl from '../utils/ShortenUrl';
import { comparisonBBCode } from '../utils/BBCode';
const browser = require('detect-browser');
/**
* Creates a comparator based on the specified predicate
@@ -63,7 +62,7 @@ export default class ComparisonPage extends Page {
* @return {Object} New state object
*/
_initState(context) {
let defaultFacets = [9, 6, 4, 1, 3, 2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost
let defaultFacets = [13, 12, 11, 9, 6, 4, 1, 3, 2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost, DPS, EPS, HPS
let params = context.route.params;
let code = params.code;
let name = params.name ? decodeURIComponent(params.name) : null;
@@ -81,7 +80,9 @@ export default class ComparisonPage extends Page {
newName = name;
for (let shipId in allBuilds) {
for (let buildName in allBuilds[shipId]) {
builds.push(this._createBuild(shipId, buildName, allBuilds[shipId][buildName]));
if (buildName && allBuilds[shipId][buildName]) {
builds.push(this._createBuild(shipId, buildName, allBuilds[shipId][buildName]));
}
}
}
} else {
@@ -226,8 +227,10 @@ export default class ComparisonPage extends Page {
let placeholder = this.placeholder = document.createElement('li');
placeholder.style.width = Math.round(this.dragged.offsetWidth) + 'px';
placeholder.className = 'facet-placeholder';
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', e.currentTarget);
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', e.currentTarget);
}
}
/**
@@ -345,7 +348,7 @@ export default class ComparisonPage extends Page {
let code = fromComparison(name, builds, selectedFacets, predicate, desc);
let loc = window.location;
return `${loc.protocol}//${loc.host}/comparison/${code}`;
return loc.protocol + '//' + loc.host + '/comparison?code=' + encodeURIComponent(code);
}
/**
@@ -381,7 +384,7 @@ export default class ComparisonPage extends Page {
*/
_updateDimensions() {
this.setState({
chartWidth: findDOMNode(this.refs.chartRef).offsetWidth
chartWidth: this.chartRef.offsetWidth
});
}
@@ -496,9 +499,9 @@ export default class ComparisonPage extends Page {
<ComparisonTable builds={builds} facets={facets} onSort={this._sortShips} predicate={predicate} desc={desc} />
{!builds.length ?
<div className='chart' ref={'chartRef'}>{translate('PHRASE_NO_BUILDS')}</div> :
<div className='chart' ref={node => this.chartRef = node}>{translate('PHRASE_NO_BUILDS')}</div> :
facets.filter((f) => f.active).map((f, i) =>
<div key={f.title} className='chart' ref={ i == 0 ? 'chartRef' : null}>
<div key={f.title} className='chart' ref={ i == 0 ? node => this.chartRef = node : null}>
<h3 className='ptr' onClick={this._sortShips.bind(this, f.props[0])}>{translate(f.title)}</h3>
<BarChart
width={chartWidth}

Some files were not shown because too many files have changed in this diff Show More