mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-09 14:45:35 +00:00
Merge branch 'feature/changes' into develop
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,4 +4,5 @@ build
|
||||
*.log
|
||||
nginx.pid
|
||||
.idea
|
||||
/bin
|
||||
/bin
|
||||
env
|
||||
|
||||
28
ChangeLog.md
28
ChangeLog.md
@@ -1,3 +1,31 @@
|
||||
#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
|
||||
* Add diminishing returns for shield boosters
|
||||
* 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
|
||||
|
||||
#2.2.5
|
||||
* Calculate rate of fire for multi-burst weapons
|
||||
* Add note to disable ghostery in error situation
|
||||
|
||||
30
__tests__/fixtures/agility-data.json
Normal file
30
__tests__/fixtures/agility-data.json
Normal 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}
|
||||
}
|
||||
}
|
||||
@@ -264,22 +264,21 @@
|
||||
"topBoost": 248.62,
|
||||
"topSpeed": 186.46,
|
||||
"totalCost": 882362058,
|
||||
"totalDpe": 127.26,
|
||||
"totalDps": 97.74,
|
||||
"totalDpe": 143.01,
|
||||
"totalDps": 102.83,
|
||||
"totalEps": 22.71,
|
||||
"totalHps": 677.29,
|
||||
"totalExplDpe": 0,
|
||||
"totalExplDps": 0,
|
||||
"totalExplSDps": 0,
|
||||
"totalHps": 33.62,
|
||||
"totalKinDpe": 103.97,
|
||||
"totalKinDps": 28.92,
|
||||
"totalKinSDps": 21.23,
|
||||
"totalSDps": 85.77,
|
||||
"totalThermDpe": 23.29,
|
||||
"totalThermDps": 68.82,
|
||||
"totalThermSDps": 64.53,
|
||||
"agility": 2,
|
||||
"totalKinDpe": 119.43,
|
||||
"totalKinDps": 32.51,
|
||||
"totalKinSDps": 24.79,
|
||||
"totalSDps": 91.3,
|
||||
"totalThermDpe": 23.58,
|
||||
"totalThermDps": 70.32,
|
||||
"totalThermSDps": 66.51,
|
||||
"baseShieldStrength": 350,
|
||||
"baseArmour": 945,
|
||||
"hullExplRes": 0.78,
|
||||
@@ -288,6 +287,7 @@
|
||||
"hullThermRes": 1.37,
|
||||
"masslock": 23,
|
||||
"pipSpeed": 0.14,
|
||||
"pitch": 25,
|
||||
"moduleCostMultiplier": 1,
|
||||
"fuelCapacity": 32,
|
||||
"cargoCapacity": 128,
|
||||
@@ -297,8 +297,10 @@
|
||||
"unladenMass": 1179.2,
|
||||
"powerAvailable": 39.6,
|
||||
"powerRetracted": 23.33,
|
||||
"powerDeployed": 34.76,
|
||||
"powerDeployed": 34.13,
|
||||
"roll": 60,
|
||||
"unladenRange": 18.49,
|
||||
"yaw": 10,
|
||||
"fullTankRange": 18.12,
|
||||
"ladenRange": 16.39,
|
||||
"unladenFastestRange": 73.21,
|
||||
|
||||
@@ -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.AwRj4zTI.AwiMIypI."
|
||||
},
|
||||
"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==."
|
||||
}
|
||||
}
|
||||
|
||||
90
__tests__/test-agility.js
Normal file
90
__tests__/test-agility.js
Normal 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -142,12 +142,12 @@ 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?code=4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA%3D%3D.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');
|
||||
@@ -167,7 +167,7 @@ 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?code=4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA%3D%3D.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2P8xwAEf0GE2AtmBob%2F%2FwFvM%2BjKEgAAAA%3D%3D&bn=Test%20My%20Ship');
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.H4sIAAAAAAAAA2MUe8HMwPD%2FPwMcAABTINwTEgAAAA%3D%3D&bn=Test%20My%20Ship');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -184,7 +184,7 @@ 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/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');
|
||||
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%2FAwDFxwtofAAAAA%3D%3D&bn=Multi-purpose%20Asp%20Explorer');
|
||||
});
|
||||
|
||||
it('imports a valid v4 build with modifications', function() {
|
||||
@@ -196,7 +196,7 @@ 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/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');
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OP0tCYRjFj9fuVbvF1du9ekkT8s%2FkIg4NElyIBBd321yaGvwUQTS3N7UFfYygIT9EoyQUJA36ns47XJCWA%2B%2Fz%2Bz3Pe3ImBbDNKaqNPSBoGrL4ngfomKpFGiJ%2BLgHteR1IPjxJT5pF11uSeXNsJVcRfgdC92syWUuK0iMdKZqrjJ%2F0aoA71lJ5oKf38knWcCiptCPdhJIerdS00vlK0qktlqoj983UmqqHjQ33VsW8eazFmaTyULP2hQ4lX8LBme6g%2F6v0TTdbxJ2KhdEIaCw15MF%2FNB0L%2BS2hwEwyFM8KgP%2BqEpWWA3Qu9Z3z9kPWHzakt7Dt%2BAeD7ghSTgEAAA%3D%3D&bn=Multi-purpose%20Imperial%20Courier');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -238,7 +238,7 @@ 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/federal_corvette?code=2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifrv66g2f.AwRj4zNaKA%3D%3D.CwRgDBldUExuBiQqA%3D%3D%3D.H4sIAAAAAAAAA02Svy9DURTHT1vvtfoat30eXlvV0ufXQmLAIDHSRDcJAzHV1PgDDAaJpVbxF0gYKhFiEFuXTgbCIsKfYJCItHWP75E83vLNue%2F7Od977zs3pBeJ6DsE6TcNIlVn5lgFSw7rfrEikL6mSVS0HSL3MgxoqM3sTGtm%2BxA2R3RGSLSTfWzD32kxu043kVNFDxt6wU8ajVpEY7coh5uARrYR0n3aYY4%2FY6lmkc4xveafqZOHpHejRMb9J7NZQqN9Ascto4fjet0P7iQgRhV7mo5LlLtAUnIe34rVDaKBF9AThUJhla3%2FHqMRB76XBV7v8vEvOOoGx%2BJEgKz9BgvZEHJOyHNUakYujUuSW8KxWOkl%2F%2BzuMsR6QpkS8URUTYKTAagNta4EEvFE1INAqQD0IdCdQCKeiOoBk9%2BPYU87QL7i2tajkITKk0odSFxvAJrClawX%2BCkRT0RZYNjV5b%2BRbyLaOpMkafJa%2BBgufjFnjxBnvgFxKvgBnNYlP7jwiXcRnYQ%2F%2FoRlqCnTHAz41xha9F78CNahGXk8eZ3z%2FcyWjJcg7goeU%2BJdZsw%2FFW2pAaMCAAA%3D&bn=Imported%20Federal%20Corvette');
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr-----.AwRj4zNaqA%3D%3D.CwRgDBldUExuBjpA.H4sIAAAAAAAAA02SP0vDUBTFb1qTtE3xtTFqav0Tbfy3KHRQB8FRO7gJOuioU%2FEDODgILrqKn0DQQUEUB3Hr0smhYhcp%2BiEEEVvf9VwhmuVw3zu%2Fe959eTH0EhF9G5A%2ByyRSl8yc2saSE7pPrCSkt24RlVyPyL9JABpuM3uzmtk9hs1JPSAk2sk9deHvfjH7XprIq6KHTb0YJY3bDtHEA8rROqCxHYSkzzvMmRcs1RzSOaXXo5k6I5DCnk1kNj6YrQoa3TM4%2Fip6OKM3ouBOFmJWcabl%2BURD10jKLWCvVN0k6m%2BBngqCYI2d%2Fx6zlgG%2BXwR%2B2RXhn3DUPcbibIw8%2Bg0WsibkvJBXqFRZLo1Lkl%2FBWKz0cjS7vwJxmijzIqGIOpLgXAxqQ51bgURCEfUkUD4GvQv0KJBIKKK6wYwcpHCmGyNfcW3nWUhCFUqlDiWuJwbN4EpOC35eJBRRDhj29erfk28h2rmQJGkKv7CZKH0yF08QZ70B8bbxAbigK1Fw8IH%2Fwp6GP9nE0qjLaw7G%2FDs8mt0QP4m1UZafh38AuKZDe4MCAAA%3D&bn=Imported%20Federal%20Corvette');
|
||||
});
|
||||
|
||||
it('imports a valid v4 build', function() {
|
||||
@@ -250,7 +250,7 @@ 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/beluga?code=0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i2f.AwRj4yukg%3D%3D%3D.CwRgDBldHi8IUA%3D%3D.H4sIAAAAAAAAA2P8Z8%2FAwPCXEUiIKTMxMPCv%2F%2Ff%2FP8cFIPGf6Z8YTEr0GjMDg%2FJWICERBOTzn%2Fn7%2F7%2FIO5Ai5n9SIEWsQEIoSxAolfbt%2F3%2BJPk4GBhE7YQYGYVmgcuVnf4Aq%2FwMAIrEcGGsAAAA%3D&bn=Imported%20Beluga%20Liner');
|
||||
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%2FwMAIrEcGGsAAAA%3D&bn=Imported%20Beluga%20Liner');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "coriolis_shipyard",
|
||||
"version": "2.2.5",
|
||||
"version": "2.2.6b",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EDCD/coriolis"
|
||||
@@ -31,6 +31,7 @@
|
||||
"jsx"
|
||||
],
|
||||
"automock": true,
|
||||
"bail": false,
|
||||
"unmockedModulePathPatterns": [
|
||||
"<rootDir>/node_modules/lodash",
|
||||
"<rootDir>/node_modules/react",
|
||||
|
||||
@@ -93,8 +93,10 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
let prevClass = null, prevRating = null;
|
||||
let elems = [];
|
||||
|
||||
for (let i = 0; i < modules.length; i++) {
|
||||
let m = modules[i];
|
||||
const sortedModules = modules.sort(this._moduleOrder);
|
||||
|
||||
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.id === m.id;
|
||||
@@ -126,7 +128,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
case 'T': mount = <MountTurret className={'lg'}/>; break;
|
||||
}
|
||||
|
||||
if (i > 0 && modules.length > 3 && m.class != prevClass && (m.rating != prevRating || m.mount) && m.grp != 'pa') {
|
||||
if (i > 0 && sortedModules.length > 3 && m.class != prevClass && (m.rating != prevRating || m.mount) && m.grp != 'pa') {
|
||||
elems.push(<br key={'b' + m.grp + i} />);
|
||||
}
|
||||
|
||||
@@ -201,6 +203,46 @@ 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 lowest (E) to highest (A)
|
||||
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
|
||||
*/
|
||||
|
||||
244
src/app/components/DamageDealt.jsx
Normal file
244
src/app/components/DamageDealt.jsx
Normal file
@@ -0,0 +1,244 @@
|
||||
import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import ShipSelector from './ShipSelector';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Damage against a selected ship
|
||||
*/
|
||||
export default class DamageDealt extends TranslatedComponent {
|
||||
static PropTypes = {
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
code: React.PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
static DEFAULT_AGAINST = Ships['anaconda'];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._sort = this._sort.bind(this);
|
||||
this._onShipChange = this._onShipChange.bind(this);
|
||||
this._onCollapseExpand = this._onCollapseExpand.bind(this);
|
||||
|
||||
this.state = {
|
||||
predicate: 'n',
|
||||
desc: true,
|
||||
against: DamageDealt.DEFAULT_AGAINST,
|
||||
expanded: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the initial weapons state
|
||||
*/
|
||||
componentWillMount() {
|
||||
const weapons = this._calcWeapons(this.props.ship, this.state.against);
|
||||
this.setState({ weapons });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.code != this.props.code) {
|
||||
const weapons = this._calcWeapons(this.props.ship, this.state.against);
|
||||
this.setState({ weapons });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the damage dealt by a ship
|
||||
* @param {Object} ship The ship which will deal the damage
|
||||
* @param {Object} against The ship against which damage will be dealt
|
||||
* @return {boolean} Returns the per-weapon damage
|
||||
*/
|
||||
_calcWeapons(ship, against) {
|
||||
let weapons = [];
|
||||
|
||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
||||
if (ship.hardpoints[i].m) {
|
||||
const m = ship.hardpoints[i].m;
|
||||
const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
|
||||
const effectiveness = m.getPiercing() >= against.properties.hardness ? 1 : m.getPiercing() / against.properties.hardness;
|
||||
const effectiveDps = m.getDps() * effectiveness;
|
||||
const effectiveSDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectiveness : effectiveDps;
|
||||
|
||||
weapons.push({ id: i,
|
||||
mount: m.mount,
|
||||
name: m.name || m.grp,
|
||||
classRating,
|
||||
effectiveDps,
|
||||
effectiveSDps,
|
||||
effectiveness });
|
||||
}
|
||||
}
|
||||
|
||||
return weapons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when the collapse or expand icons are clicked
|
||||
*/
|
||||
_onCollapseExpand() {
|
||||
this.setState({ expanded: !this.state.expanded });
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when the ship we compare against changes
|
||||
* @param {string} s the new ship ID
|
||||
*/
|
||||
_onShipChange(s) {
|
||||
const against = Ships[s];
|
||||
const weapons = this._calcWeapons(this.props.ship, against);
|
||||
this.setState({ against, weapons });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(this.props.ship, predicate, desc);
|
||||
this.setState({ predicate, desc });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the weapon list
|
||||
* @param {Ship} ship Ship instance
|
||||
* @param {string} predicate Sort predicate
|
||||
* @param {Boolean} desc Sort order descending
|
||||
*/
|
||||
_sort(ship, predicate, desc) {
|
||||
let comp = weaponComparator.bind(null, this.context.language.translate);
|
||||
|
||||
switch (predicate) {
|
||||
case 'n': comp = comp(null, desc); break;
|
||||
case 'edps': comp = comp((a, b) => a.effectiveDps - b.effectiveDps, desc); break;
|
||||
case 'esdps': comp = comp((a, b) => a.effectiveSDps - b.effectiveSDps, desc); break;
|
||||
case 'e': comp = comp((a, b) => a.effectiveness - b.effectiveness, desc); break;
|
||||
}
|
||||
|
||||
this.state.weapons.sort(comp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render individual rows for hardpoints
|
||||
* @param {Function} translate Translate function
|
||||
* @param {Object} formats Localised formats map
|
||||
* @return {array} The individual rows
|
||||
*
|
||||
*/
|
||||
_renderRows(translate, formats) {
|
||||
const { termtip, tooltip } = this.context;
|
||||
|
||||
let rows = [];
|
||||
|
||||
if (this.state.weapons) {
|
||||
for (let i = 0; i < this.state.weapons.length; i++) {
|
||||
const weapon = this.state.weapons[i];
|
||||
|
||||
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)}
|
||||
</td>
|
||||
<td className='ri'>{formats.round1(weapon.effectiveDps)}</td>
|
||||
<td className='ri'>{formats.round1(weapon.effectiveSDps)}</td>
|
||||
<td className='ri'>{formats.pct(weapon.effectiveness)}</td>
|
||||
</tr>);
|
||||
}
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render damage dealt
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { language, tooltip, termtip } = this.context;
|
||||
const { formats, translate } = language;
|
||||
const { expanded } = this.state;
|
||||
|
||||
const sortOrder = this._sortOrder;
|
||||
const onCollapseExpand = this._onCollapseExpand;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<h1>{translate('damage dealt against')} {expanded ? <span onClick={onCollapseExpand}><CollapseSection className='summary'/></span> : <span onClick={onCollapseExpand}><ExpandSection className='summary'/></span>}</h1>
|
||||
{expanded ? <span>
|
||||
<ShipSelector initial={this.state.against} currentMenu={this.props.currentMenu} onChange={this._onShipChange} />
|
||||
<table className='summary' style={{ width: '100%' }}>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<td className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</td>
|
||||
<td className='sortable' onClick={sortOrder.bind(this, 'edps')}>{translate('effective dps')}</td>
|
||||
<td className='sortable' onClick={sortOrder.bind(this, 'esdps')}>{translate('effective sdps')}</td>
|
||||
<td className='sortable' onClick={sortOrder.bind(this, 'e')}>{translate('effectiveness')}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this._renderRows(translate, formats)}
|
||||
</tbody>
|
||||
</table></span> : null }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
274
src/app/components/DamageReceived.jsx
Normal file
274
src/app/components/DamageReceived.jsx
Normal file
@@ -0,0 +1,274 @@
|
||||
import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Modules } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||
import Module from '../shipyard/Module';
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Damage received by a selected ship
|
||||
*/
|
||||
export default class DamageReceived extends TranslatedComponent {
|
||||
static PropTypes = {
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
code: React.PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._sort = this._sort.bind(this);
|
||||
this._onCollapseExpand = this._onCollapseExpand.bind(this);
|
||||
|
||||
this.state = {
|
||||
predicate: 'n',
|
||||
desc: true,
|
||||
expanded: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the initial weapons state
|
||||
*/
|
||||
componentWillMount() {
|
||||
this.setState({ weapons: this._calcWeapons(this.props.ship) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the updated weapons state
|
||||
* @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) {
|
||||
this.setState({ weapons: this._calcWeapons(nextProps.ship) });
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the damage received by a ship
|
||||
* @param {Object} ship The ship which will receive the damage
|
||||
* @return {boolean} Returns the per-weapon damage
|
||||
*/
|
||||
_calcWeapons(ship) {
|
||||
let weapons = [];
|
||||
|
||||
for (let grp in Modules.hardpoints) {
|
||||
if (Modules.hardpoints[grp][0].damage && Modules.hardpoints[grp][0].type) {
|
||||
for (let mId in Modules.hardpoints[grp]) {
|
||||
const m = new Module(Modules.hardpoints[grp][mId]);
|
||||
const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
|
||||
|
||||
// Base DPS
|
||||
const baseDps = m.getDps();
|
||||
const baseSDps = m.getClip() ? (m.getClip() * baseDps / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : baseDps;
|
||||
|
||||
// Effective DPS taking in to account shield resistance
|
||||
let effectivenessShields = 0;
|
||||
if (m.getDamageType().indexOf('E') != -1) {
|
||||
effectivenessShields += ship.shieldExplRes;
|
||||
}
|
||||
if (m.getDamageType().indexOf('K') != -1) {
|
||||
effectivenessShields += ship.shieldKinRes;
|
||||
}
|
||||
if (m.getDamageType().indexOf('T') != -1) {
|
||||
effectivenessShields += ship.shieldThermRes;
|
||||
}
|
||||
effectivenessShields /= m.getDamageType().length;
|
||||
// Plasma accelerators deal absolute damage
|
||||
if (m.grp == 'pa') effectivenessShields = 1;
|
||||
const effectiveDpsShields = baseDps * effectivenessShields;
|
||||
const effectiveSDpsShields = baseSDps * effectivenessShields;
|
||||
|
||||
// Effective DPS taking in to account hull hardness and resistance
|
||||
let effectivenessHull = 0;
|
||||
if (m.getDamageType().indexOf('E') != -1) {
|
||||
effectivenessHull += ship.hullExplRes;
|
||||
}
|
||||
if (m.getDamageType().indexOf('K') != -1) {
|
||||
effectivenessHull += ship.hullKinRes;
|
||||
}
|
||||
if (m.getDamageType().indexOf('T') != -1) {
|
||||
effectivenessHull += ship.hullThermRes;
|
||||
}
|
||||
effectivenessHull /= m.getDamageType().length;
|
||||
// Plasma accelerators deal absolute damage (but could be reduced by hardness)
|
||||
if (m.grp == 'pa') effectivenessHull = 1;
|
||||
effectivenessHull *= Math.min(m.getPiercing() / ship.hardness, 1);
|
||||
const effectiveDpsHull = baseDps * effectivenessHull;
|
||||
const effectiveSDpsHull = baseSDps * effectivenessHull;
|
||||
|
||||
weapons.push({ id: m.id,
|
||||
classRating,
|
||||
name: m.name || m.grp,
|
||||
mount: m.mount,
|
||||
effectiveDpsShields,
|
||||
effectiveSDpsShields,
|
||||
effectivenessShields,
|
||||
effectiveDpsHull,
|
||||
effectiveSDpsHull,
|
||||
effectivenessHull });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return weapons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when the collapse or expand icons are clicked
|
||||
*/
|
||||
_onCollapseExpand() {
|
||||
this.setState({ expanded: !this.state.expanded });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(this.props.ship, predicate, desc);
|
||||
this.setState({ predicate, desc });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the weapon list
|
||||
* @param {Ship} ship Ship instance
|
||||
* @param {string} predicate Sort predicate
|
||||
* @param {Boolean} desc Sort order descending
|
||||
*/
|
||||
_sort(ship, predicate, desc) {
|
||||
let comp = weaponComparator.bind(null, this.context.language.translate);
|
||||
|
||||
switch (predicate) {
|
||||
case 'n': comp = comp(null, desc); break;
|
||||
case 'edpss': comp = comp((a, b) => a.effectiveDpsShields - b.effectiveDpsShields, desc); break;
|
||||
case 'esdpss': comp = comp((a, b) => a.effectiveSDpsShields - b.effectiveSDpsShields, desc); break;
|
||||
case 'es': comp = comp((a, b) => a.effectivenessShields - b.effectivenessShields, desc); break;
|
||||
case 'edpsh': comp = comp((a, b) => a.effectiveDpsHull - b.effectiveDpsHull, desc); break;
|
||||
case 'esdpsh': comp = comp((a, b) => a.effectiveSDpsHull - b.effectiveSDpsHull, desc); break;
|
||||
case 'eh': comp = comp((a, b) => a.effectivenessHull - b.effectivenessHull, desc); break;
|
||||
}
|
||||
|
||||
this.state.weapons.sort(comp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render individual rows for weapons
|
||||
* @param {Function} translate Translate function
|
||||
* @param {Object} formats Localised formats map
|
||||
* @return {array} The individual rows
|
||||
*
|
||||
*/
|
||||
_renderRows(translate, formats) {
|
||||
const { termtip, tooltip } = this.context;
|
||||
|
||||
let rows = [];
|
||||
|
||||
for (let i = 0; i < this.state.weapons.length; i++) {
|
||||
const weapon = this.state.weapons[i];
|
||||
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)}
|
||||
</td>
|
||||
<td>{formats.round1(weapon.effectiveDpsShields)}</td>
|
||||
<td>{formats.round1(weapon.effectiveSDpsShields)}</td>
|
||||
<td>{formats.pct(weapon.effectivenessShields)}</td>
|
||||
<td>{formats.round1(weapon.effectiveDpsHull)}</td>
|
||||
<td>{formats.round1(weapon.effectiveSDpsHull)}</td>
|
||||
<td>{formats.pct(weapon.effectivenessHull)}</td>
|
||||
</tr>);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render damage received
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { language, tooltip, termtip } = this.context;
|
||||
const { formats, translate } = language;
|
||||
const { expanded } = this.state;
|
||||
|
||||
const sortOrder = this._sortOrder;
|
||||
const onCollapseExpand = this._onCollapseExpand;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<h1>{translate('damage received by')} {expanded ? <span onClick={onCollapseExpand}><CollapseSection className='summary'/></span> : <span onClick={onCollapseExpand}><ExpandSection className='summary'/></span>}</h1>
|
||||
{expanded ? <span>
|
||||
<table className='summary' style={{ width: '100%' }}>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th rowSpan={2} className='sortable' onClick={sortOrder.bind(this, 'n')} >{translate('weapon')}</th>
|
||||
<th colSpan={3} >{translate('against shields')}</th>
|
||||
<th colSpan={3} >{translate('against hull')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className='sortable lft' onClick={sortOrder.bind(this, 'edpss')} onMouseOver={termtip.bind(null, 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}</th>
|
||||
<th className='sortable' onClick={sortOrder.bind(this, 'esdpss')} onMouseOver={termtip.bind(null, 'sdps')} onMouseOut={tooltip.bind(null, null)}>{translate('SDPS')}</th>
|
||||
<th className='sortable' onClick={sortOrder.bind(this, 'es')} >{translate('effectiveness')}</th>
|
||||
<th className='sortable lft' onClick={sortOrder.bind(this, 'edpsh')} onMouseOver={termtip.bind(null, 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}</th>
|
||||
<th className='sortable' onClick={sortOrder.bind(this, 'esdpsh')} onMouseOver={termtip.bind(null, 'sdps')} onMouseOut={tooltip.bind(null, null)}>{translate('SDPS')}</th>
|
||||
<th className='sortable' onClick={sortOrder.bind(this, 'eh')} >{translate('effectiveness')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this._renderRows(translate, formats)}
|
||||
</tbody>
|
||||
</table></span> : null }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@ export default class DefenceSummary extends TranslatedComponent {
|
||||
let { formats, translate, units } = language;
|
||||
let hide = tooltip.bind(null, null);
|
||||
|
||||
const shieldGenerator = ship.findShieldGenerator();
|
||||
|
||||
return (
|
||||
<span>
|
||||
<h1>{translate('defence summary')}</h1>
|
||||
@@ -48,9 +50,18 @@ export default class DefenceSummary extends TranslatedComponent {
|
||||
{ship.shield ?
|
||||
<tr>
|
||||
<td className='le'>{translate('damage from')}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /> {formats.pct1(ship.shieldExplRes || 1)}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /> {formats.pct1(ship.shieldKinRes || 1)}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /> {formats.pct1(ship.shieldThermRes || 1)}</td>
|
||||
<td className='ri'>
|
||||
<span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span>
|
||||
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - shieldGenerator.explres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(ship.shieldExplRes || 1)}</span>
|
||||
</td>
|
||||
<td className='ri'>
|
||||
<span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span>
|
||||
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - shieldGenerator.kinres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(ship.shieldKinRes || 1)}</span>
|
||||
</td>
|
||||
<td className='ri'>
|
||||
<span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span>
|
||||
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - shieldGenerator.thermres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(ship.shieldThermRes || 1)}</span>
|
||||
</td>
|
||||
</tr> : null }
|
||||
|
||||
{ ship.shield && ship.shieldCells ?
|
||||
@@ -63,10 +74,29 @@ export default class DefenceSummary extends TranslatedComponent {
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='le'>{translate('damage from')}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /> {formats.pct1(ship.hullExplRes || 1)}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /> {formats.pct1(ship.hullKinRes || 1)}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /> {formats.pct1(ship.hullThermRes || 1)}</td>
|
||||
<td className='ri'>
|
||||
<span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span>
|
||||
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - ship.bulkheads.m.explres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(ship.hullExplRes || 1)}</span></td>
|
||||
<td className='ri'>
|
||||
<span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span>
|
||||
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - ship.bulkheads.m.kinres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(ship.hullKinRes || 1)}</span>
|
||||
</td>
|
||||
<td className='ri'>
|
||||
<span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span>
|
||||
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - ship.bulkheads.m.thermres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(ship.hullThermRes || 1)}</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{ship.modulearmour > 0 ?
|
||||
<tr>
|
||||
<td colSpan='4'><h2>{translate('module armour')}: {formats.int(ship.modulearmour)}</h2></td>
|
||||
</tr> : null }
|
||||
|
||||
{ship.moduleprotection > 0 ?
|
||||
<tr>
|
||||
<td colSpan='2' className='cn'>{translate('internal protection')} {formats.pct1(ship.moduleprotection)}</td>
|
||||
<td colSpan='2' className='cn'>{translate('external protection')} {formats.pct1(ship.moduleprotection / 2)}</td>
|
||||
</tr> : null }
|
||||
</tbody>
|
||||
</table>
|
||||
</span>
|
||||
|
||||
@@ -46,7 +46,7 @@ export default class HardpointSlot extends Slot {
|
||||
|
||||
// Modifications tooltip shows blueprint and grade, if available
|
||||
let modTT = translate('modified');
|
||||
if (m && m.blueprint) {
|
||||
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) {
|
||||
modTT += ', ' + translate(m.blueprint.special.name);
|
||||
@@ -76,6 +76,7 @@ export default class HardpointSlot extends Slot {
|
||||
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f1(m.getRange() / 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.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 }
|
||||
|
||||
@@ -133,6 +133,10 @@ export default class HardpointsSlotSection extends SlotSection {
|
||||
<li className='c' onClick={_fill.bind(this, 'fc', 'G')}><MountGimballed className='lg'/></li>
|
||||
<li className='c' onClick={_fill.bind(this, 'fc', 'T')}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('pa')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={_fill.bind(this, 'pa', 'F')}>{translate('pa')}</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('nl')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={_fill.bind(this, 'nl', 'F')}>{translate('nl')}</li>
|
||||
|
||||
@@ -28,7 +28,7 @@ export default class InternalSlot extends Slot {
|
||||
|
||||
// Modifications tooltip shows blueprint and grade, if available
|
||||
let modTT = translate('modified');
|
||||
if (m && m.blueprint) {
|
||||
if (m && m.blueprint && m.blueprint.name) {
|
||||
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ export default class InternalSlot extends Slot {
|
||||
{ m.getMaxMass() ? <div className={'l'}>{translate('max mass')}: {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} {translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null }
|
||||
{ m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.gen(m.getAmmo())}</div> : null }
|
||||
{ m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null }
|
||||
@@ -58,10 +59,13 @@ export default class InternalSlot extends Slot {
|
||||
{ m.rangeLS === null ? <div className={'l'}>∞{u.Ls}</div> : null }
|
||||
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
|
||||
{ m.getHullReinforcement() ? <div className={'l'}>+{formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)} <u className='cap'>{translate('armour')}</u></div> : null }
|
||||
{ m.getProtection() ? <div className={'l'}>{formats.rPct(m.getProtection())} <u className='cap'>{translate('protection')}</u></div> : null }
|
||||
{ m.passengers ? <div className={'l'}>{translate('passengers')}: {m.passengers}</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.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 }
|
||||
|
||||
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
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);
|
||||
@@ -46,7 +47,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if (clobber || !slot.m) {
|
||||
if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.cr)) {
|
||||
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
|
||||
}
|
||||
});
|
||||
@@ -62,7 +63,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if (clobber || !slot.m) {
|
||||
if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.ft)) {
|
||||
ship.use(slot, ModuleUtils.findInternal('ft', slot.maxClass, 'C'));
|
||||
}
|
||||
});
|
||||
@@ -78,7 +79,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if (clobber || !slot.m) {
|
||||
if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.pcq)) {
|
||||
ship.use(slot, ModuleUtils.findInternal('pcq', Math.min(slot.maxClass, 6), 'B')); // Passenger cabins top out at 6
|
||||
}
|
||||
});
|
||||
@@ -94,7 +95,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if (clobber || !slot.m) {
|
||||
if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.pcm)) {
|
||||
ship.use(slot, ModuleUtils.findInternal('pcm', Math.min(slot.maxClass, 6), 'C')); // Passenger cabins top out at 6
|
||||
}
|
||||
});
|
||||
@@ -110,7 +111,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if (clobber || !slot.m) {
|
||||
if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.pci)) {
|
||||
ship.use(slot, ModuleUtils.findInternal('pci', Math.min(slot.maxClass, 6), 'D')); // Passenger cabins top out at 6
|
||||
}
|
||||
});
|
||||
@@ -126,7 +127,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if (clobber || !slot.m) {
|
||||
if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.pce)) {
|
||||
ship.use(slot, ModuleUtils.findInternal('pce', Math.min(slot.maxClass, 6), 'E')); // Passenger cabins top out at 6
|
||||
}
|
||||
});
|
||||
@@ -143,7 +144,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
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.eligible || slot.eligible.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;
|
||||
@@ -161,7 +162,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if (clobber || !slot.m) {
|
||||
if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.hr)) {
|
||||
ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D
|
||||
}
|
||||
});
|
||||
@@ -169,6 +170,22 @@ export default class InternalSlotSection extends SlotSection {
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill all slots with Module Reinforcement Packages
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithModuleReinforcementPackages(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.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
|
||||
*/
|
||||
@@ -226,6 +243,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
<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' onClick={this._fillWithModuleReinforcementPackages}>{translate('mrp')}</li>
|
||||
<li className='lc' onClick={this._fillWithFuelTanks}>{translate('ft')}</li>
|
||||
<li className='lc' onClick={this._fillWithEconomyClassCabins}>{translate('pce')}</li>
|
||||
<li className='lc' onClick={this._fillWithBusinessClassCabins}>{translate('pci')}</li>
|
||||
|
||||
92
src/app/components/MovementSummary.jsx
Normal file
92
src/app/components/MovementSummary.jsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons';
|
||||
|
||||
/**
|
||||
* Movement summary
|
||||
*/
|
||||
export default class MovementSummary extends TranslatedComponent {
|
||||
static PropTypes = {
|
||||
ship: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render movement summary
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
let ship = this.props.ship;
|
||||
let { language, tooltip, termtip } = this.context;
|
||||
let { formats, translate, units } = language;
|
||||
let hide = tooltip.bind(null, null);
|
||||
let boostMultiplier = ship.topBoost / ship.topSpeed;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<h1>{translate('movement summary')}</h1>
|
||||
<table style={{ marginLeft: 'auto', marginRight: 'auto', lineHeight: '1em', backgroundColor: 'transparent' }}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td > </td>
|
||||
<td colSpan='6'>{translate('engine pips')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
<td onMouseOver={termtip.bind(null, '4b')} onMouseOut={tooltip.bind(null, null)}>4B</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='ri'>{translate('speed')} ({units['m/s']})</td>
|
||||
<td className='ri'>{formats.int(ship.speeds[0])}</td>
|
||||
<td className='ri'>{formats.int(ship.speeds[1])}</td>
|
||||
<td className='ri'>{formats.int(ship.speeds[2])}</td>
|
||||
<td className='ri'>{formats.int(ship.speeds[3])}</td>
|
||||
<td className='ri'>{formats.int(ship.speeds[4])}</td>
|
||||
<td className='ri'>{formats.int(ship.speeds[4] * boostMultiplier)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='ri'>{translate('pitch')} ({units['°/s']})</td>
|
||||
<td className='ri'>{formats.int(ship.pitches[0])}</td>
|
||||
<td className='ri'>{formats.int(ship.pitches[1])}</td>
|
||||
<td className='ri'>{formats.int(ship.pitches[2])}</td>
|
||||
<td className='ri'>{formats.int(ship.pitches[3])}</td>
|
||||
<td className='ri'>{formats.int(ship.pitches[4])}</td>
|
||||
<td className='ri'>{formats.int(ship.pitches[4] * boostMultiplier)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='ri'>{translate('roll')} ({units['°/s']})</td>
|
||||
<td className='ri'>{formats.int(ship.rolls[0])}</td>
|
||||
<td className='ri'>{formats.int(ship.rolls[1])}</td>
|
||||
<td className='ri'>{formats.int(ship.rolls[2])}</td>
|
||||
<td className='ri'>{formats.int(ship.rolls[3])}</td>
|
||||
<td className='ri'>{formats.int(ship.rolls[4])}</td>
|
||||
<td className='ri'>{formats.int(ship.rolls[4] * boostMultiplier)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='ri'>{translate('yaw')} ({units['°/s']})</td>
|
||||
<td className='ri'>{formats.int(ship.yaws[0])}</td>
|
||||
<td className='ri'>{formats.int(ship.yaws[1])}</td>
|
||||
<td className='ri'>{formats.int(ship.yaws[2])}</td>
|
||||
<td className='ri'>{formats.int(ship.yaws[3])}</td>
|
||||
<td className='ri'>{formats.int(ship.yaws[4])}</td>
|
||||
<td className='ri'>{formats.int(ship.yaws[4] * boostMultiplier)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
90
src/app/components/ShipSelector.jsx
Normal file
90
src/app/components/ShipSelector.jsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Rocket } from './SvgIcons';
|
||||
|
||||
/**
|
||||
* Selector for ships
|
||||
*/
|
||||
export default class ShipSelector extends TranslatedComponent {
|
||||
static PropTypes = {
|
||||
initial: React.PropTypes.object.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = { ship : this.props.initial };
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the ships menu
|
||||
* @return {React.Component} Menu
|
||||
*/
|
||||
_getShipsMenu() {
|
||||
const _selectShip = this._selectShip;
|
||||
const _openMenu = this._openMenu;
|
||||
|
||||
let shipList = [];
|
||||
|
||||
for (let s in Ships) {
|
||||
shipList.push(<div key={s} onClick={_selectShip.bind(this, s)} className='block' >{Ships[s].properties.name}</div>);
|
||||
}
|
||||
|
||||
return shipList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle opening the menu
|
||||
* @param {string} menu The ID of the opened menu
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_openMenu(menu, event) {
|
||||
event.stopPropagation();
|
||||
if (this.props.currentMenu == menu) {
|
||||
menu = null;
|
||||
}
|
||||
|
||||
this.context.openMenu(menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle selection of a ship
|
||||
* @param {string} s The selected ship ID
|
||||
*/
|
||||
_selectShip(s) {
|
||||
this.setState({ ship: Ships[s] });
|
||||
|
||||
this.context.openMenu(null);
|
||||
this.props.onChange(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render ship selector
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const currentMenu = this.props.currentMenu;
|
||||
const ship = this.state.ship;
|
||||
|
||||
return (
|
||||
<div className='shipselector'>
|
||||
<div className='menu'>
|
||||
<div className={cn('menu-header', { selected: currentMenu == 'wds' })} onClick={this._openMenu.bind(this, 'wds')}>
|
||||
<Rocket className='warning' /><span className='menu-item-label'>{ship.properties.name}</span>
|
||||
{currentMenu == 'wds' ?
|
||||
<div className='menu-list quad no-wrap' onClick={ (e) => e.stopPropagation() }>
|
||||
{this._getShipsMenu()}
|
||||
</div> : null }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
||||
<th onMouseEnter={termtip.bind(null, 'damage per second')} onMouseLeave={hide} rowSpan={2}>{translate('DPS')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'energy per second')} onMouseLeave={hide} rowSpan={2}>{translate('EPS')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th>
|
||||
<th rowSpan={2}>{translate('hardness')}</th>
|
||||
<th rowSpan={2}>{translate('armour')}</th>
|
||||
<th rowSpan={2}>{translate('shields')}</th>
|
||||
<th colSpan={3}>{translate('mass')}</th>
|
||||
@@ -71,6 +72,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
||||
<td>{f1(ship.totalDps)}</td>
|
||||
<td>{f1(ship.totalEps)}</td>
|
||||
<td>{f1(ship.totalHps)}</td>
|
||||
<td>{int(ship.hardness)}</td>
|
||||
<td>{int(ship.armour)}</td>
|
||||
<td className={sgClassNames}>{int(ship.shield)} {u.MJ}</td>
|
||||
<td>{ship.hullMass} {u.T}</td>
|
||||
|
||||
@@ -77,7 +77,7 @@ export default class SlotSection extends TranslatedComponent {
|
||||
_drag(originSlot, e) {
|
||||
e.dataTransfer.setData('text/html', e.currentTarget);
|
||||
e.dataTransfer.effectAllowed = 'all';
|
||||
this.setState({ originSlot });
|
||||
this.setState({ originSlot, copy: e.getModifierState('Alt') });
|
||||
this._close();
|
||||
}
|
||||
|
||||
@@ -91,7 +91,9 @@ 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';
|
||||
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';
|
||||
@@ -114,20 +116,30 @@ 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 (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);
|
||||
this.props.onChange();
|
||||
}
|
||||
} else {
|
||||
// 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);
|
||||
} else { // Otherwise empty the origin slot
|
||||
this.props.ship.use(originSlot, null, true); // Empty but prevent summary update
|
||||
}
|
||||
this.props.ship.use(targetSlot, m); // update target slot
|
||||
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 });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -52,7 +52,7 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
|
||||
// Modifications tooltip shows blueprint and grade, if available
|
||||
let modTT = translate('modified');
|
||||
if (m && m.blueprint) {
|
||||
if (m && m.blueprint && m.blueprint.name) {
|
||||
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
||||
}
|
||||
|
||||
|
||||
@@ -475,6 +475,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
|
||||
*/
|
||||
|
||||
@@ -65,6 +65,7 @@ export function getLanguage(langCode) {
|
||||
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)
|
||||
ps: <u>{translate('/s')}</u>, // per second
|
||||
pm: <u>{translate('/min')}</u>, // per minute
|
||||
|
||||
@@ -59,6 +59,7 @@ export const terms = {
|
||||
mc: 'Multi-cannon',
|
||||
ml: 'Mining Laser',
|
||||
mr: 'Missile Rack',
|
||||
mrp: 'Module Reinforcement Package',
|
||||
nl: 'Mine Launcher',
|
||||
pa: 'Plasma Accelerator',
|
||||
pas: 'Planetary Approach Suite',
|
||||
@@ -86,15 +87,21 @@ export const terms = {
|
||||
ws: 'Frame Shift Wake Scanner',
|
||||
|
||||
// Items on the outfitting page
|
||||
// Notification of restricted slot for Orca/Beluga
|
||||
// Notification of restricted slot
|
||||
emptyrestricted: 'empty (restricted)',
|
||||
'damage dealt against': 'Damage dealt against',
|
||||
'damage received by': 'Damage received by',
|
||||
'against shields': 'Against shields',
|
||||
'against hull': 'Against hull',
|
||||
// 'ammo' was overloaded for outfitting page and modul info, so changed to ammunition for outfitting page
|
||||
ammunition: 'Ammo',
|
||||
|
||||
// Unit for seconds
|
||||
secs: 's',
|
||||
|
||||
// Weapon, offence and defence
|
||||
rebuildsperbay: 'Rebuilds per bay',
|
||||
|
||||
// Weapon, offence, defence and movement
|
||||
dpe: 'Damage per MJ of energy',
|
||||
dps: 'Damage per second',
|
||||
sdps: 'Sustained damage per second',
|
||||
@@ -106,12 +113,23 @@ export const terms = {
|
||||
'damage by': 'Damage by',
|
||||
'damage from': 'Damage from',
|
||||
'shield cells': 'Shield cells',
|
||||
'recovery': 'Recovery',
|
||||
'recharge': 'Recharge',
|
||||
'engine pips': 'Engine Pips',
|
||||
'4b': '4 pips and boost',
|
||||
'speed': 'Speed',
|
||||
'pitch': 'Pitch',
|
||||
'roll': 'Roll',
|
||||
'yaw': 'Yaw',
|
||||
'internal protection': 'Internal protection',
|
||||
'external protection': 'External protection',
|
||||
|
||||
// Modifications
|
||||
ammo: 'Ammunition maximum',
|
||||
boot: 'Boot time',
|
||||
brokenregen: 'Broken regeneration rate',
|
||||
burst: 'Burst',
|
||||
burstrof: 'Burst rate of fire',
|
||||
clip: 'Ammunition clip',
|
||||
damage: 'Damage',
|
||||
distdraw: 'Distributor draw',
|
||||
@@ -133,10 +151,11 @@ export const terms = {
|
||||
pgen: 'Power generation',
|
||||
piercing: 'Piercing',
|
||||
power: 'Power draw',
|
||||
protection: 'Protection',
|
||||
range: 'Range',
|
||||
ranget: 'Range', // Range in time (for FSD interdictor)
|
||||
regen: 'Regeneration rate',
|
||||
reload: 'Reload time',
|
||||
reload: 'Reload',
|
||||
rof: 'Rate of fire',
|
||||
shield: 'Shield',
|
||||
shieldboost: 'Shield boost',
|
||||
|
||||
@@ -39,15 +39,6 @@ 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>
|
||||
</div>;
|
||||
|
||||
@@ -16,6 +16,9 @@ import InternalSlotSection from '../components/InternalSlotSection';
|
||||
import UtilitySlotSection from '../components/UtilitySlotSection';
|
||||
import OffenceSummary from '../components/OffenceSummary';
|
||||
import DefenceSummary from '../components/DefenceSummary';
|
||||
import MovementSummary from '../components/MovementSummary';
|
||||
import DamageDealt from '../components/DamageDealt';
|
||||
import DamageReceived from '../components/DamageReceived';
|
||||
import LineChart from '../components/LineChart';
|
||||
import PowerManagement from '../components/PowerManagement';
|
||||
import CostSection from '../components/CostSection';
|
||||
@@ -335,7 +338,7 @@ export default class OutfittingPage extends Page {
|
||||
<PowerManagement ship={ship} code={code} onChange={shipUpdated} />
|
||||
<CostSection ship={ship} buildName={buildName} code={sStr + hStr + iStr} />
|
||||
|
||||
<div ref='chartThird' className='group third'>
|
||||
<div className='group third'>
|
||||
<OffenceSummary ship={ship} code={code}/>
|
||||
</div>
|
||||
|
||||
@@ -344,22 +347,21 @@ export default class OutfittingPage extends Page {
|
||||
</div>
|
||||
|
||||
<div className='group third'>
|
||||
<h1>{translate('speed')}</h1>
|
||||
<MovementSummary ship={ship} code={code}/>
|
||||
</div>
|
||||
|
||||
<div ref='chartThird' className='group third'>
|
||||
<h1>{translate('jump range')}</h1>
|
||||
<LineChart
|
||||
width={chartWidth}
|
||||
xMax={ship.cargoCapacity}
|
||||
yMax={ship.topBoost + 10}
|
||||
yMax={ship.unladenRange}
|
||||
xUnit={translate('T')}
|
||||
yUnit={translate('m/s')}
|
||||
yLabel={translate('speed')}
|
||||
series={SPEED_SERIES}
|
||||
colors={SPEED_COLORS}
|
||||
yUnit={translate('LY')}
|
||||
yLabel={translate('jump range')}
|
||||
xLabel={translate('cargo')}
|
||||
func={state.speedChartFunc}
|
||||
func={state.jumpRangeChartFunc}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='group half'>
|
||||
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
|
||||
<tbody >
|
||||
<tr>
|
||||
@@ -385,7 +387,15 @@ export default class OutfittingPage extends Page {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<DamageDealt ship={ship} code={code} currentMenu={menu}/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<DamageReceived ship={ship} code={code} currentMenu={menu}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -402,3 +412,43 @@ export default class OutfittingPage extends Page {
|
||||
// func={state.jumpRangeChartFunc}
|
||||
// />
|
||||
// </div>
|
||||
// <div className='group third'>
|
||||
// <h1>{translate('speed')}</h1>
|
||||
// <LineChart
|
||||
// width={chartWidth}
|
||||
// xMax={ship.cargoCapacity}
|
||||
// yMax={ship.topBoost + 10}
|
||||
// xUnit={translate('T')}
|
||||
// yUnit={translate('m/s')}
|
||||
// yLabel={translate('speed')}
|
||||
// series={SPEED_SERIES}
|
||||
// colors={SPEED_COLORS}
|
||||
// xLabel={translate('cargo')}
|
||||
// func={state.speedChartFunc}
|
||||
// />
|
||||
// </div>
|
||||
// <div className='group half'>
|
||||
// <table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
|
||||
// <tbody >
|
||||
// <tr>
|
||||
// <td style={{ verticalAlign: 'top', padding: 0, width: '2.5em' }} onMouseEnter={termtip.bind(null, 'fuel level')} onMouseLeave={hide}>
|
||||
// <Fuel className='xl primary-disabled' />
|
||||
// </td>
|
||||
// <td>
|
||||
// <Slider
|
||||
// axis={true}
|
||||
// onChange={this._fuelChange}
|
||||
// axisUnit={translate('T')}
|
||||
// percent={fuelLevel}
|
||||
// max={fuelCapacity}
|
||||
// scale={sizeRatio}
|
||||
// onResize={onWindowResize}
|
||||
// />
|
||||
// </td>
|
||||
// <td className='primary' style={{ width: '10em', verticalAlign: 'top', fontSize: '0.9em', textAlign: 'left' }}>
|
||||
// {formats.f2(fuelLevel * fuelCapacity)}{units.T} {formats.pct1(fuelLevel)}
|
||||
// </td>
|
||||
// </tr>
|
||||
// </tbody>
|
||||
// </table>
|
||||
// </div>
|
||||
|
||||
@@ -40,7 +40,9 @@ function shipSummary(shipId, shipData) {
|
||||
intCount: 0,
|
||||
maxCargo: 0,
|
||||
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
|
||||
int: [0, 0, 0, 0, 0, 0, 0, 0] // Sizes 1 - 8
|
||||
int: [0, 0, 0, 0, 0, 0, 0, 0], // Sizes 1 - 8
|
||||
standard: shipData.slots.standard,
|
||||
agility: shipData.properties.pitch + shipData.properties.yaw + shipData.properties.roll
|
||||
};
|
||||
Object.assign(summary, shipData.properties);
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
@@ -139,7 +141,8 @@ export default class ShipyardPage extends Page {
|
||||
>
|
||||
<td className='le'>{s.manufacturer}</td>
|
||||
<td className='cap'>{translate(SizeMap[s.class])}</td>
|
||||
<td>{s.agility}</td>
|
||||
<td className='ri'>{fInt(s.agility)}</td>
|
||||
<td className='ri'>{fInt(s.hardness)}</td>
|
||||
<td className='ri'>{fInt(s.speed)}{u['m/s']}</td>
|
||||
<td className='ri'>{fInt(s.boost)}{u['m/s']}</td>
|
||||
<td className='ri'>{fInt(s.baseArmour)}</td>
|
||||
@@ -148,6 +151,12 @@ export default class ShipyardPage extends Page {
|
||||
<td className='ri'>{fInt(s.topBoost)}{u['m/s']}</td>
|
||||
<td className='ri'>{fRound(s.maxJumpRange)}{u.LY}</td>
|
||||
<td className='ri'>{fInt(s.maxCargo)}{u.T}</td>
|
||||
<td className='cn'>{s.standard[0]}</td>
|
||||
<td className='cn'>{s.standard[1]}</td>
|
||||
<td className='cn'>{s.standard[2]}</td>
|
||||
<td className='cn'>{s.standard[3]}</td>
|
||||
<td className='cn'>{s.standard[4]}</td>
|
||||
<td className='cn'>{s.standard[5]}</td>
|
||||
<td className={cn({ disabled: !s.hp[1] })}>{s.hp[1]}</td>
|
||||
<td className={cn({ disabled: !s.hp[2] })}>{s.hp[2]}</td>
|
||||
<td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td>
|
||||
@@ -260,9 +269,11 @@ export default class ShipyardPage extends Page {
|
||||
<tr className='main'>
|
||||
<th rowSpan={2} className='sortable' onClick={sortShips('manufacturer')}>{translate('manufacturer')}</th>
|
||||
<th rowSpan={2} className='sortable' onClick={sortShips('class')}>{translate('size')}</th>
|
||||
<th rowSpan={2} className='sortable' onMouseEnter={termtip.bind(null, 'maneuverability')} onMouseLeave={hide} onClick={sortShips('agility')}>{translate('mnv')}</th>
|
||||
<th rowSpan={2} className='sortable' onClick={sortShips('agility')}>{translate('agility')}</th>
|
||||
<th rowSpan={2} className='sortable' onClick={sortShips('hardness')}>{translate('hardness')}</th>
|
||||
<th colSpan={4}>{translate('base')}</th>
|
||||
<th colSpan={4}>{translate('max')}</th>
|
||||
<th colSpan={6}>{translate('core module classes')}</th>
|
||||
<th colSpan={5} className='sortable' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>
|
||||
<th colSpan={8} className='sortable' onClick={sortShips('intCount')}>{translate('internal compartments')}</th>
|
||||
<th rowSpan={2} className='sortable' onClick={sortShips('hullMass')}>{translate('hull')}</th>
|
||||
@@ -280,6 +291,13 @@ export default class ShipyardPage extends Page {
|
||||
<th className='sortable' onClick={sortShips('maxJumpRange')}>{translate('jump')}</th>
|
||||
<th className='sortable' onClick={sortShips('maxCargo')}>{translate('cargo')}</th>
|
||||
|
||||
<th className='sortable lft' onMouseEnter={termtip.bind(null, 'power plant')} onMouseLeave={hide} onClick={sortShips('standard', 0)}>{'pp'}</th>
|
||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'thrusters')} onMouseLeave={hide} onClick={sortShips('standard', 1)}>{'th'}</th>
|
||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'frame shift drive')} onMouseLeave={hide} onClick={sortShips('standard', 2)}>{'fsd'}</th>
|
||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'life support')} onMouseLeave={hide} onClick={sortShips('standard', 3)}>{'ls'}</th>
|
||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'power distriubtor')} onMouseLeave={hide} onClick={sortShips('standard', 4)}>{'pd'}</th>
|
||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'sensors')} onMouseLeave={hide} onClick={sortShips('standard', 5)}>{'s'}</th>
|
||||
|
||||
<th className='sortable lft' onClick={sortShips('hp',1)}>{translate('S')}</th>
|
||||
<th className='sortable' onClick={sortShips('hp', 2)}>{translate('M')}</th>
|
||||
<th className='sortable' onClick={sortShips('hp', 3)}>{translate('L')}</th>
|
||||
|
||||
@@ -67,32 +67,109 @@ export function shieldStrength(mass, baseShield, sg, multiplier) {
|
||||
/**
|
||||
* Calculate the a ships speed based on mass, and thrusters.
|
||||
*
|
||||
* @param {number} mass Current mass of the ship
|
||||
* @param {number} baseSpeed Base speed m/s for ship
|
||||
* @param {number} baseBoost Base boost speed m/s for ship
|
||||
* @param {object} thrusters The Thrusters used
|
||||
* @param {number} pipSpeed Speed pip multiplier
|
||||
* @return {object} Approximate speed by pips
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {number} baseSpeed base speed m/s for ship
|
||||
* @param {object} thrusters The ship's thrusters
|
||||
* @param {number} engpip the multiplier per pip to engines
|
||||
* @return {array} Speed by pips
|
||||
*/
|
||||
export function speed(mass, baseSpeed, baseBoost, thrusters, pipSpeed) {
|
||||
export function speed(mass, baseSpeed, thrusters, engpip) {
|
||||
// thrusters might be a module or a template; handle either here
|
||||
const minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
||||
const optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
||||
const maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
||||
const minMul = thrusters instanceof Module ? thrusters.getMinMul('speed') : (thrusters.minmulspeed ? thrusters.minmulspeed : thrusters.minmul);
|
||||
const optMul = thrusters instanceof Module ? thrusters.getOptMul('speed') : (thrusters.optmulspeed ? thrusters.minmulspeed : thrusters.minmul);
|
||||
const maxMul = thrusters instanceof Module ? thrusters.getMaxMul('speed') : (thrusters.maxmulspeed ? thrusters.minmulspeed : thrusters.minmul);
|
||||
|
||||
let results = normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseSpeed, engpip);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate pitch of a ship based on mass and thrusters
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {number} basePitch base pitch of the ship
|
||||
* @param {object} thrusters the ship's thrusters
|
||||
* @param {number} engpip the multiplier per pip to engines
|
||||
* @return {array} Pitch by pips
|
||||
*/
|
||||
export function pitch(mass, basePitch, thrusters, engpip) {
|
||||
// thrusters might be a module or a template; handle either here
|
||||
let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
||||
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
||||
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
||||
let minMul = thrusters instanceof Module ? thrusters.getMinMul() : thrusters.minmul;
|
||||
let optMul = thrusters instanceof Module ? thrusters.getOptMul() : thrusters.optmul;
|
||||
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul() : thrusters.maxmul;
|
||||
let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
|
||||
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
|
||||
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
|
||||
|
||||
let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
|
||||
let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
|
||||
let ynorm = Math.pow(xnorm, exponent);
|
||||
let mul = minMul + ynorm * (maxMul - minMul);
|
||||
let speed = baseSpeed * mul;
|
||||
|
||||
return {
|
||||
'0 Pips': speed * (1 - (pipSpeed * 4)),
|
||||
'2 Pips': speed * (1 - (pipSpeed * 2)),
|
||||
'4 Pips': speed,
|
||||
'boost': baseBoost * mul
|
||||
};
|
||||
return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, basePitch, engpip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate yaw of a ship based on mass and thrusters
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {number} baseYaw base yaw of the ship
|
||||
* @param {object} thrusters the ship's thrusters
|
||||
* @param {number} engpip the multiplier per pip to engines
|
||||
* @return {array} Yaw by pips
|
||||
*/
|
||||
export function yaw(mass, baseYaw, thrusters, engpip) {
|
||||
// thrusters might be a module or a template; handle either here
|
||||
let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
||||
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
||||
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
||||
let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
|
||||
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
|
||||
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
|
||||
|
||||
return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseYaw, engpip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate roll of a ship based on mass and thrusters
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {number} baseRoll base roll of the ship
|
||||
* @param {object} thrusters the ship's thrusters
|
||||
* @param {number} engpip the multiplier per pip to engines
|
||||
* @return {array} Roll by pips
|
||||
*/
|
||||
export function roll(mass, baseRoll, thrusters, engpip) {
|
||||
// thrusters might be a module or a template; handle either here
|
||||
let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
||||
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
||||
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
||||
let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
|
||||
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
|
||||
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
|
||||
|
||||
return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseRoll, engpip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise according to FD's calculations and return suitable values
|
||||
* @param {number} minMass the minimum mass of the thrusters
|
||||
* @param {number} optMass the optimum mass of the thrusters
|
||||
* @param {number} maxMass the maximum mass of the thrusters
|
||||
* @param {number} minMul the minimum multiplier of the thrusters
|
||||
* @param {number} optMul the optimum multiplier of the thrusters
|
||||
* @param {number} maxMul the maximum multiplier of the thrusters
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {base} base the base value from which to calculate
|
||||
* @param {number} engpip the multiplier per pip to engines
|
||||
* @return {array} values by pips
|
||||
*/
|
||||
function normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base, engpip) {
|
||||
const xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
|
||||
const exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
|
||||
const ynorm = Math.pow(xnorm, exponent);
|
||||
const mul = minMul + ynorm * (maxMul - minMul);
|
||||
const res = base * mul;
|
||||
|
||||
return [res * (1 - (engpip * 4)),
|
||||
res * (1 - (engpip * 3)),
|
||||
res * (1 - (engpip * 2)),
|
||||
res * (1 - (engpip * 1)),
|
||||
res];
|
||||
}
|
||||
|
||||
@@ -105,8 +105,9 @@ export const BulkheadNames = [
|
||||
export const ShipFacets = [
|
||||
{ // 0
|
||||
title: 'agility',
|
||||
props: ['agility'],
|
||||
fmt: 'int',
|
||||
props: ['topPitch', 'topRoll', 'topYaw'],
|
||||
lbls: ['pitch', 'roll', 'yaw'],
|
||||
fmt: 'f1',
|
||||
i: 0
|
||||
},
|
||||
{ // 1
|
||||
@@ -185,11 +186,18 @@ export const ShipFacets = [
|
||||
},
|
||||
{ // 11
|
||||
title: 'DPS',
|
||||
props: ['totalDps'],
|
||||
lbls: ['DPS'],
|
||||
props: ['totalDps', 'totalExplDps', 'totalKinDps', 'totalThermDps'],
|
||||
lbls: ['total', 'explosive', 'kinetic', 'thermal'],
|
||||
fmt: 'round',
|
||||
i: 11
|
||||
},
|
||||
{ // 14
|
||||
title: 'Sustained DPS',
|
||||
props: ['totalSDps', 'totalExplSDps', 'totalKinSDps', 'totalThermSDps'],
|
||||
lbls: ['total', 'explosive', 'kinetic', 'thermal'],
|
||||
fmt: 'round',
|
||||
i: 14
|
||||
},
|
||||
{ // 12
|
||||
title: 'EPS',
|
||||
props: ['totalEps'],
|
||||
|
||||
@@ -12,20 +12,28 @@ export default class Module {
|
||||
* @param {Object} params Module parameters. Either grp/id or template
|
||||
*/
|
||||
constructor(params) {
|
||||
let properties = Object.assign({ grp: null, id: null, template: null, }, params);
|
||||
let properties = Object.assign({ grp: null, id: null, template: null }, params);
|
||||
|
||||
let template;
|
||||
if (properties.template == undefined) {
|
||||
return ModuleUtils.findModule(properties.grp, properties.id);
|
||||
if (properties.class != undefined) {
|
||||
// We already have a fully-formed module; copy the data over
|
||||
for (let p in properties) { this[p] = properties[p]; }
|
||||
} else if (properties.template != undefined) {
|
||||
// We have a template from coriolis-data; copy the data over
|
||||
for (let p in properties.template) { this[p] = properties.template[p]; }
|
||||
} else {
|
||||
template = properties.template;
|
||||
if (template) {
|
||||
// Copy all properties from coriolis-data template
|
||||
for (let p in template) { this[p] = template[p]; }
|
||||
}
|
||||
// We don't have a template; find it given the group and ID
|
||||
return ModuleUtils.findModule(properties.grp, properties.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone an existing module
|
||||
* @return {Object} A clone of the existing module
|
||||
*/
|
||||
clone() {
|
||||
return new Module(JSON.parse(JSON.stringify(this)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value for a given modification
|
||||
* @param {Number} name The name of the modification
|
||||
@@ -302,6 +310,14 @@ export default class Module {
|
||||
return this._getModifiedValue('hullreinforcement');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the protection for this module, taking in to account modifications
|
||||
* @return {Number} the protection of this module
|
||||
*/
|
||||
getProtection() {
|
||||
return this._getModifiedValue('protection');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the delay for this module, taking in to account modifications
|
||||
* @return {Number} the delay of this module
|
||||
@@ -370,42 +386,60 @@ export default class Module {
|
||||
|
||||
/**
|
||||
* Get the minimum multiplier for this module, taking in to account modifications
|
||||
* @param {string} type the type for which we are obtaining the multiplier. Can be 'speed', 'rotation', 'acceleration', or null
|
||||
* @return {Number} the minimum multiplier of this module
|
||||
*/
|
||||
getMinMul() {
|
||||
getMinMul(type = null) {
|
||||
// Modifier is optmul
|
||||
let result = 0;
|
||||
if (this['minmul']) {
|
||||
if (this['minmul' + type]) {
|
||||
result = this['minmul' + type];
|
||||
} else if (this['minmul']) {
|
||||
result = this['minmul'];
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmul') / 10000;
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
}
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmul') / 10000;
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the optimum multiplier for this module, taking in to account modifications
|
||||
* @param {string} type the type for which we are obtaining the multiplier. Can be 'speed', 'rotation', 'acceleration', or null
|
||||
* @return {Number} the optimum multiplier of this module
|
||||
*/
|
||||
getOptMul() {
|
||||
return this._getModifiedValue('optmul');
|
||||
getOptMul(type = null) {
|
||||
// Modifier is optmul
|
||||
let result = 0;
|
||||
if (this['optmul' + type]) {
|
||||
result = this['optmul' + type];
|
||||
} else if (this['optmul']) {
|
||||
result = this['optmul'];
|
||||
}
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmul') / 10000;
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum multiplier for this module, taking in to account modifications
|
||||
* @param {string} type the type for which we are obtaining the multiplier. Can be 'speed', 'rotation', 'acceleration', or null
|
||||
* @return {Number} the maximum multiplier of this module
|
||||
*/
|
||||
getMaxMul() {
|
||||
getMaxMul(type = null) {
|
||||
// Modifier is optmul
|
||||
let result = 0;
|
||||
if (this['maxmul']) {
|
||||
if (this['maxmul' + type]) {
|
||||
result = this['maxmul' + type];
|
||||
} else if (this['maxmul']) {
|
||||
result = this['maxmul'];
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmul') / 10000;
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
}
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmul') / 10000;
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -561,6 +595,14 @@ export default class Module {
|
||||
return this._getModifiedValue('shieldreinforcement');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the piercing for this module, taking in to account modifications
|
||||
* @return {Number} the piercing for this module
|
||||
*/
|
||||
getPiercing() {
|
||||
return this._getModifiedValue('piercing');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bays for this module, taking in to account modifications
|
||||
* @return {Number} the bays for this module
|
||||
|
||||
@@ -134,67 +134,21 @@ export function toDetailedBuild(buildName, ship) {
|
||||
*/
|
||||
export function fromDetailedBuild(detailedBuild) {
|
||||
let shipId = Object.keys(Ships).find((shipId) => Ships[shipId].properties.name.toLowerCase() == detailedBuild.ship.toLowerCase());
|
||||
|
||||
if (!shipId) {
|
||||
throw 'No such ship: ' + detailedBuild.ship;
|
||||
}
|
||||
|
||||
let comps = detailedBuild.components;
|
||||
let stn = comps.standard;
|
||||
let priorities = [stn.cargoHatch && stn.cargoHatch.priority !== undefined ? stn.cargoHatch.priority - 1 : 0];
|
||||
let enabled = [stn.cargoHatch && stn.cargoHatch.enabled !== undefined ? stn.cargoHatch.enabled : true];
|
||||
let shipData = Ships[shipId];
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
let bulkheads = ModuleUtils.bulkheadIndex(stn.bulkheads);
|
||||
let modifications = new Array(stn.bulkheads.modifications);
|
||||
let blueprints = new Array(stn.bulkheads.blueprint);
|
||||
|
||||
if (bulkheads < 0) {
|
||||
throw 'Invalid bulkheads: ' + stn.bulkheads;
|
||||
if (!detailedBuild.references[0] || !detailedBuild.references[0].code) {
|
||||
throw 'Missing reference code';
|
||||
}
|
||||
|
||||
let standard = STANDARD.map((c) => {
|
||||
if (!stn[c].class || !stn[c].rating) {
|
||||
throw 'Invalid value for ' + c;
|
||||
}
|
||||
priorities.push(stn[c].priority === undefined ? 0 : stn[c].priority - 1);
|
||||
enabled.push(stn[c].enabled === undefined ? true : stn[c].enabled);
|
||||
modifications.push(stn[c].modifications);
|
||||
blueprints.push(stn[c].blueprint);
|
||||
return ModuleUtils.findStandardId(STANDARD_GROUPS[c], stn[c].class, stn[c].rating, stn[c].name);
|
||||
});
|
||||
|
||||
let internal = comps.internal.map(c => c ? ModuleUtils.findInternalId(c.group, c.class, c.rating, c.name) : 0);
|
||||
|
||||
let hardpoints = comps.hardpoints.map(c => c ? ModuleUtils.findHardpointId(c.group, c.class, c.rating, c.name, MountMap[c.mount], c.missile) : 0)
|
||||
.concat(comps.utility.map(c => c ? ModuleUtils.findHardpointId(c.group, c.class, c.rating, c.name, MountMap[c.mount]) : 0));
|
||||
|
||||
// The ordering of these arrays must match the order in which they are read in Ship.buildWith
|
||||
priorities = priorities.concat(
|
||||
comps.hardpoints.map(c => (!c || c.priority === undefined) ? 0 : c.priority - 1),
|
||||
comps.utility.map(c => (!c || c.priority === undefined) ? 0 : c.priority - 1),
|
||||
comps.internal.map(c => (!c || c.priority === undefined) ? 0 : c.priority - 1)
|
||||
);
|
||||
enabled = enabled.concat(
|
||||
comps.hardpoints.map(c => (!c || c.enabled === undefined) ? true : c.enabled * 1),
|
||||
comps.utility.map(c => (!c || c.enabled === undefined) ? true : c.enabled * 1),
|
||||
comps.internal.map(c => (!c || c.enabled === undefined) ? true : c.enabled * 1)
|
||||
);
|
||||
modifications = modifications.concat(
|
||||
comps.hardpoints.map(c => (c ? c.modifications : null)),
|
||||
comps.utility.map(c => (c ? c.modifications : null)),
|
||||
comps.internal.map(c => (c ? c.modifications : null))
|
||||
);
|
||||
blueprints = blueprints.concat(
|
||||
comps.hardpoints.map(c => (c ? c.blueprint : null)),
|
||||
comps.utility.map(c => (c ? c.blueprint : null)),
|
||||
comps.internal.map(c => (c ? c.blueprint : null))
|
||||
);
|
||||
|
||||
ship.buildWith({ bulkheads, standard, hardpoints, internal }, priorities, enabled, modifications, blueprints);
|
||||
ship.buildFrom(detailedBuild.references[0].code);
|
||||
|
||||
return ship;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of ship-loadout JSON Schema object for export
|
||||
|
||||
@@ -5,7 +5,7 @@ import Module from './Module';
|
||||
import LZString from 'lz-string';
|
||||
import * as _ from 'lodash';
|
||||
import isEqual from 'lodash/lang';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import { Ships, Modifications } from 'coriolis-data/dist';
|
||||
const zlib = require('zlib');
|
||||
|
||||
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh'];
|
||||
@@ -188,10 +188,10 @@ export default class Ship {
|
||||
* Calculate the hypothetical top speeds at cargo and fuel tonnage
|
||||
* @param {Number} fuel Fuel available in tons
|
||||
* @param {Number} cargo Cargo in tons
|
||||
* @return {Object} Speed at pip settings and boost
|
||||
* @return {array} Speed at pip settings
|
||||
*/
|
||||
calcSpeedsWith(fuel, cargo) {
|
||||
return Calc.speed(this.unladenMass + fuel + cargo, this.speed, this.boost, this.standard[1].m, this.pipSpeed);
|
||||
return Calc.speed(this.unladenMass + fuel + cargo, this.speed, this.standard[1].m, this.pipSpeed);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,13 +201,11 @@ export default class Ship {
|
||||
* @return {Number} Recovery time in seconds
|
||||
*/
|
||||
calcShieldRecovery() {
|
||||
if (this.shield > 0) {
|
||||
const sgSlot = this.findInternalByGroup('sg');
|
||||
if (sgSlot != null) {
|
||||
let brokenRegenRate = 1 + sgSlot.m.getModValue('brokenregen') / 10000;
|
||||
// 50% of shield strength / recovery recharge rate + 15 second delay before recharge starts
|
||||
return ((this.shield / 2) / (sgSlot.m.recover * brokenRegenRate)) + 15;
|
||||
}
|
||||
const shieldGenerator = this.findShieldGenerator();
|
||||
if (shieldGenerator) {
|
||||
const brokenRegenRate = shieldGenerator.getBrokenRegenerationRate();
|
||||
// 50% of shield strength / broken recharge rate + 15 second delay before recharge starts
|
||||
return ((this.shield / 2) / brokenRegenRate) + 15;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -219,13 +217,12 @@ export default class Ship {
|
||||
* @return {Number} 50 - 100% Recharge time in seconds
|
||||
*/
|
||||
calcShieldRecharge() {
|
||||
if (this.shield > 0) {
|
||||
const sgSlot = this.findInternalByGroup('sg');
|
||||
if (sgSlot != null) {
|
||||
let regenRate = 1 + sgSlot.m.getModValue('regen') / 10000;
|
||||
// 50% -> 100% recharge time, Bi-Weave shields charge at 1.8 MJ/s
|
||||
return (this.shield / 2) / ((sgSlot.m.grp == 'bsg' ? 1.8 : 1) * regenRate);
|
||||
}
|
||||
const shieldGenerator = this.findShieldGenerator();
|
||||
if (shieldGenerator) {
|
||||
const regenRate = shieldGenerator.getRegenerationRate();
|
||||
|
||||
// 50% of shield strength / recharge rate
|
||||
return (this.shield / 2) / regenRate;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -295,12 +292,22 @@ export default class Ship {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the shield generator for this ship
|
||||
* @return {object} The shield generator module for this ship
|
||||
*/
|
||||
findShieldGenerator() {
|
||||
const slot = this.internal.find(slot => slot.m && ModuleUtils.isShieldGenerator(slot.m.grp));
|
||||
return slot ? slot.m : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the ship to a string
|
||||
* @return {String} Serialized ship 'code'
|
||||
*/
|
||||
toString() {
|
||||
return [
|
||||
'A',
|
||||
this.getStandardString(),
|
||||
this.getHardpointsString(),
|
||||
this.getInternalString(),
|
||||
@@ -432,7 +439,7 @@ export default class Ship {
|
||||
let newMass = m.getMass();
|
||||
this.unladenMass = this.unladenMass - oldMass + newMass;
|
||||
this.ladenMass = this.ladenMass - oldMass + newMass;
|
||||
this.updateTopSpeed();
|
||||
this.updateMovement();
|
||||
this.updateJumpStats();
|
||||
} else if (name === 'maxfuel') {
|
||||
m.setModValue(name, value);
|
||||
@@ -440,19 +447,19 @@ export default class Ship {
|
||||
} else if (name === 'optmass') {
|
||||
m.setModValue(name, value);
|
||||
// Could be for any of thrusters, FSD or shield
|
||||
this.updateTopSpeed();
|
||||
this.updateMovement();
|
||||
this.updateJumpStats();
|
||||
this.recalculateShield();
|
||||
} else if (name === 'optmul') {
|
||||
m.setModValue(name, value);
|
||||
// Could be for any of thrusters, FSD or shield
|
||||
this.updateTopSpeed();
|
||||
this.updateMovement();
|
||||
this.updateJumpStats();
|
||||
this.recalculateShield();
|
||||
} else if (name === 'shieldboost') {
|
||||
m.setModValue(name, value);
|
||||
this.recalculateShield();
|
||||
} else if (name === 'hullboost' || name === 'hullreinforcement') {
|
||||
} else if (name === 'hullboost' || name === 'hullreinforcement' || name === 'modulereinforcement') {
|
||||
m.setModValue(name, value);
|
||||
this.recalculateArmour();
|
||||
} else if (name === 'shieldreinforcement') {
|
||||
@@ -597,7 +604,7 @@ export default class Ship {
|
||||
.recalculateDps()
|
||||
.recalculateEps()
|
||||
.recalculateHps()
|
||||
.updateTopSpeed();
|
||||
.updateMovement();
|
||||
}
|
||||
|
||||
return this.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
|
||||
@@ -621,6 +628,19 @@ export default class Ship {
|
||||
enabled = null,
|
||||
code = parts[0];
|
||||
|
||||
// Code has a version ID embedded as the first character (if it is alphabetic)
|
||||
let version;
|
||||
if (code && code.match(/^[0-4]/)) {
|
||||
// Starting with bulkhead number is version 1
|
||||
version = 1;
|
||||
} else {
|
||||
// Version 2 (current version)
|
||||
version = 2;
|
||||
if (code) {
|
||||
code = code.substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (parts[1]) {
|
||||
enabled = LZString.decompressFromBase64(Utils.fromUrlSafe(parts[1])).split('');
|
||||
}
|
||||
@@ -644,6 +664,11 @@ export default class Ship {
|
||||
|
||||
decodeToArray(code, internal, decodeToArray(code, hardpoints, decodeToArray(code, standard, 1)));
|
||||
|
||||
if (version != 2) {
|
||||
// Alter as required due to changes in the (build) code from one version to the next
|
||||
this.upgradeInternals(internal, 1 + this.standard.length + this.hardpoints.length, priorities, enabled, modifications, blueprints, version);
|
||||
}
|
||||
|
||||
return this.buildWith(
|
||||
{
|
||||
bulkheads: code.charAt(0) * 1,
|
||||
@@ -801,7 +826,7 @@ export default class Ship {
|
||||
let epsChanged = n && n.getEps() || old && old.getEps();
|
||||
let hpsChanged = n && n.getHps() || old && old.getHps();
|
||||
|
||||
let armourChange = (slot === this.bulkheads) || (n && n.grp === 'hr') || (old && old.grp === 'hr');
|
||||
let armourChange = (slot === this.bulkheads) || (n && n.grp === 'hr') || (old && old.grp === 'hr') || (n && n.grp === 'mrp') || (old && old.grp === 'mrp');
|
||||
|
||||
let shieldChange = (n && n.grp === 'bsg') || (old && old.grp === 'bsg') || (n && n.grp === 'psg') || (old && old.grp === 'psg') || (n && n.grp === 'sg') || (old && old.grp === 'sg') || (n && n.grp === 'sb') || (old && old.grp === 'sb');
|
||||
|
||||
@@ -876,7 +901,7 @@ export default class Ship {
|
||||
if (shieldCellsChange) {
|
||||
this.recalculateShieldCells();
|
||||
}
|
||||
this.updateTopSpeed();
|
||||
this.updateMovement();
|
||||
this.updateJumpStats();
|
||||
}
|
||||
return this;
|
||||
@@ -1088,13 +1113,23 @@ export default class Ship {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update top speed and boost
|
||||
* Update movement values
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
updateTopSpeed() {
|
||||
let speeds = Calc.speed(this.unladenMass + this.fuelCapacity, this.speed, this.boost, this.standard[1].m, this.pipSpeed);
|
||||
this.topSpeed = speeds['4 Pips'];
|
||||
this.topBoost = this.canBoost() ? speeds.boost : 0;
|
||||
updateMovement() {
|
||||
this.speeds = Calc.speed(this.unladenMass + this.fuelCapacity, this.speed, this.standard[1].m, this.pipSpeed);
|
||||
this.topSpeed = this.speeds[4];
|
||||
this.topBoost = this.canBoost() ? this.speeds[4] * this.boost / this.speed : 0;
|
||||
|
||||
this.pitches = Calc.pitch(this.unladenMass + this.fuelCapacity, this.pitch, this.standard[1].m, this.pipSpeed);
|
||||
this.topPitch = this.pitches[4];
|
||||
|
||||
this.rolls = Calc.roll(this.unladenMass + this.fuelCapacity, this.roll, this.standard[1].m, this.pipSpeed);
|
||||
this.topRoll = this.rolls[4];
|
||||
|
||||
this.yaws = Calc.yaw(this.unladenMass + this.fuelCapacity, this.yaw, this.standard[1].m, this.pipSpeed);
|
||||
this.topYaw = this.yaws[4];
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1104,6 +1139,7 @@ export default class Ship {
|
||||
*/
|
||||
recalculateShield() {
|
||||
let shield = 0;
|
||||
let shieldBoost = 1;
|
||||
let shieldExplRes = null;
|
||||
let shieldKinRes = null;
|
||||
let shieldThermRes = null;
|
||||
@@ -1111,16 +1147,15 @@ export default class Ship {
|
||||
const sgSlot = this.findInternalByGroup('sg');
|
||||
if (sgSlot && sgSlot.enabled) {
|
||||
// Shield from generator
|
||||
const baseShield = Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.m, 1);
|
||||
shield = baseShield;
|
||||
shield = Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.m, 1);
|
||||
shieldExplRes = 1 - sgSlot.m.getExplosiveResistance();
|
||||
shieldKinRes = 1 - sgSlot.m.getKineticResistance();
|
||||
shieldThermRes = 1 - sgSlot.m.getThermalResistance();
|
||||
|
||||
// Shield from boosters
|
||||
for (let slot of this.hardpoints) {
|
||||
if (slot.m && slot.m.grp == 'sb') {
|
||||
shield += baseShield * slot.m.getShieldBoost();
|
||||
if (slot.enabled && slot.m && slot.m.grp == 'sb') {
|
||||
shieldBoost += slot.m.getShieldBoost();
|
||||
shieldExplRes *= (1 - slot.m.getExplosiveResistance());
|
||||
shieldKinRes *= (1 - slot.m.getKineticResistance());
|
||||
shieldThermRes *= (1 - slot.m.getThermalResistance());
|
||||
@@ -1128,6 +1163,12 @@ export default class Ship {
|
||||
}
|
||||
}
|
||||
|
||||
// We apply diminishing returns to the boosted value
|
||||
// (no we don't; FD pulled back on this idea. But leave this here in case they reinstate it)
|
||||
// shieldBoost = Math.min(shieldBoost, (1 - Math.pow(Math.E, -0.7 * shieldBoost)) * 2.5);
|
||||
|
||||
shield = shield * shieldBoost;
|
||||
|
||||
this.shield = shield;
|
||||
this.shieldExplRes = shieldExplRes ? 1 - this.diminishingReturns(1 - shieldExplRes, 0.5, 0.75) : null;
|
||||
this.shieldKinRes = shieldKinRes ? 1 - this.diminishingReturns(1 - shieldKinRes, 0.5, 0.75) : null;
|
||||
@@ -1162,11 +1203,13 @@ export default class Ship {
|
||||
// Armour from bulkheads
|
||||
let bulkhead = this.bulkheads.m;
|
||||
let armour = this.baseArmour + (this.baseArmour * bulkhead.getHullBoost());
|
||||
let modulearmour = 0;
|
||||
let moduleprotection = 1;
|
||||
let hullExplRes = 1 - bulkhead.getExplosiveResistance();
|
||||
let hullKinRes = 1 - bulkhead.getKineticResistance();
|
||||
let hullThermRes = 1 - bulkhead.getThermalResistance();
|
||||
|
||||
// Armour from HRPs
|
||||
// Armour from HRPs and module armour from MRPs
|
||||
for (let slot of this.internal) {
|
||||
if (slot.m && slot.m.grp == 'hr') {
|
||||
armour += slot.m.getHullReinforcement();
|
||||
@@ -1177,9 +1220,16 @@ export default class Ship {
|
||||
hullKinRes *= (1 - slot.m.getKineticResistance());
|
||||
hullThermRes *= (1 - slot.m.getThermalResistance());
|
||||
}
|
||||
if (slot.m && slot.m.grp == 'mrp') {
|
||||
modulearmour += slot.m.getIntegrity();
|
||||
moduleprotection = moduleprotection * (1 - slot.m.getProtection());
|
||||
}
|
||||
}
|
||||
moduleprotection = 1 - moduleprotection;
|
||||
|
||||
this.armour = armour;
|
||||
this.modulearmour = modulearmour;
|
||||
this.moduleprotection = moduleprotection;
|
||||
this.hullExplRes = 1 - this.diminishingReturns(1 - hullExplRes, 0.5, 0.75);
|
||||
this.hullKinRes = 1 - this.diminishingReturns(1 - hullKinRes, 0.5, 0.75);
|
||||
this.hullThermRes = 1 - this.diminishingReturns(1 - hullThermRes, 0.5, 0.75);
|
||||
@@ -1369,7 +1419,7 @@ export default class Ship {
|
||||
for (let slot of slots) {
|
||||
if (slot.length > 0) {
|
||||
buffer.writeInt8(i, curpos++);
|
||||
if (blueprints[i]) {
|
||||
if (blueprints[i] && blueprints[i].id) {
|
||||
buffer.writeInt8(MODIFICATION_ID_BLUEPRINT, curpos++);
|
||||
buffer.writeInt32LE(blueprints[i].id, curpos);
|
||||
curpos += 4;
|
||||
@@ -1434,9 +1484,13 @@ export default class Ship {
|
||||
curpos += 4;
|
||||
// There are a number of 'special' modification IDs, check for them here
|
||||
if (modificationId === MODIFICATION_ID_BLUEPRINT) {
|
||||
blueprint = Object.assign(blueprint, _.find(Modifications.blueprints, function(o) { return o.id === modificationValue; }));
|
||||
if (modificationValue !== 0) {
|
||||
blueprint = Object.assign(blueprint, _.find(Modifications.blueprints, function(o) { return o.id === modificationValue; }));
|
||||
}
|
||||
} else if (modificationId === MODIFICATION_ID_GRADE) {
|
||||
blueprint.grade = modificationValue;
|
||||
if (modificationValue !== 0) {
|
||||
blueprint.grade = modificationValue;
|
||||
}
|
||||
} else if (modificationId === MODIFICATION_ID_SPECIAL) {
|
||||
blueprint.special = _.find(Modifications.specials, function(o) { return o.id === modificationValue; });
|
||||
} else {
|
||||
@@ -1447,7 +1501,9 @@ export default class Ship {
|
||||
modificationId = buffer.readInt8(curpos++);
|
||||
}
|
||||
modArr[slot] = modifications;
|
||||
blueprintArr[slot] = blueprint;
|
||||
if (blueprint.id) {
|
||||
blueprintArr[slot] = blueprint;
|
||||
}
|
||||
slot = buffer.readInt8(curpos++);
|
||||
}
|
||||
}
|
||||
@@ -1615,4 +1671,37 @@ export default class Ship {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade information about internals with version changes
|
||||
* @param {array} internals the internals from the ship code
|
||||
* @param {int} offset the offset of the internals information in the priorities etc. arrays
|
||||
* @param {array} priorities the existing priorities arrray
|
||||
* @param {array} enableds the existing enableds arrray
|
||||
* @param {array} modifications the existing modifications arrray
|
||||
* @param {array} blueprints the existing blueprints arrray
|
||||
* @param {int} version the version of the information
|
||||
*/
|
||||
upgradeInternals(internals, offset, priorities, enableds, modifications, blueprints, version) {
|
||||
if (version == 1) {
|
||||
// Version 2 reflects the addition of military slots. this means that we need to juggle the internals and their
|
||||
// associated information around to make holes in the appropriate places
|
||||
for (let slotId = 0; slotId < this.internal.length; slotId++) {
|
||||
if (this.internal[slotId].eligible && this.internal[slotId].eligible.mrp) {
|
||||
// Found a restricted military slot - push all of the existing items down one to compensate for the fact that they didn't exist before now
|
||||
internals.push.apply(internals, [0].concat(internals.splice(slotId).slice(0, -1)));
|
||||
|
||||
const offsetSlotId = offset + slotId;
|
||||
|
||||
// Same for priorities etc.
|
||||
if (priorities) { priorities.push.apply(priorities, [0].concat(priorities.splice(offsetSlotId))); }
|
||||
if (enableds) { enableds.push.apply(enableds, [1].concat(enableds.splice(offsetSlotId))); }
|
||||
if (modifications) { modifications.push.apply(modifications, [null].concat(modifications.splice(offsetSlotId).slice(0, -1))); }
|
||||
if (blueprints) { blueprints.push.apply(blueprints, [null].concat(blueprints.splice(offsetSlotId).slice(0, -1))); }
|
||||
}
|
||||
}
|
||||
// Ensure that all items are the correct length
|
||||
internals.splice(Ships[this.id].slots.internal.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
|
||||
'Type7': 'type_7_transport',
|
||||
'Type9': 'type_9_heavy',
|
||||
'Viper': 'viper',
|
||||
'Viper_MKIV': 'viper_mk_iv',
|
||||
'Viper_MkIV': 'viper_mk_iv',
|
||||
'Vulture': 'vulture'
|
||||
};
|
||||
|
||||
@@ -224,7 +224,9 @@ export function shipFromJson(json) {
|
||||
// Now that we know what we're looking for, find it
|
||||
const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum;
|
||||
const hardpointSlot = json.modules[hardpointName];
|
||||
if (!hardpointSlot.module) {
|
||||
if (!hardpointSlot) {
|
||||
// This can happen with old imports that don't contain new hardpoints
|
||||
} else if (!hardpointSlot.module) {
|
||||
// No module
|
||||
} else {
|
||||
const hardpointJson = hardpointSlot.module;
|
||||
@@ -239,19 +241,31 @@ export function shipFromJson(json) {
|
||||
|
||||
// Add internal compartments
|
||||
let internalSlotNum = 1;
|
||||
let militarySlotNum = 1;
|
||||
for (let i in shipTemplate.slots.internal) {
|
||||
const internalClassNum = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].class : shipTemplate.slots.internal[i];
|
||||
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false;
|
||||
|
||||
// The internal slot might be a standard or a military slot. Military slots have a different naming system
|
||||
let internalSlot = null;
|
||||
while (internalSlot === null && internalSlotNum < 99) {
|
||||
// Slot numbers are not contiguous so handle skips
|
||||
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + internalClassNum;
|
||||
if (json.modules[internalName]) {
|
||||
internalSlot = json.modules[internalName];
|
||||
if (isMilitary) {
|
||||
const internalName = 'Military0' + militarySlotNum;
|
||||
internalSlot = json.modules[internalName];
|
||||
militarySlotNum++;
|
||||
} else {
|
||||
while (internalSlot === null && internalSlotNum < 99) {
|
||||
// Slot numbers are not contiguous so handle skips
|
||||
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + internalClassNum;
|
||||
if (json.modules[internalName]) {
|
||||
internalSlot = json.modules[internalName];
|
||||
}
|
||||
internalSlotNum++;
|
||||
}
|
||||
internalSlotNum++;
|
||||
}
|
||||
if (!internalSlot.module) {
|
||||
|
||||
if (!internalSlot) {
|
||||
// This can happen with old imports that don't contain new slots
|
||||
} else if (!internalSlot.module) {
|
||||
// No module
|
||||
} else {
|
||||
const internalJson = internalSlot.module;
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import request from 'superagent';
|
||||
|
||||
const SHORTEN_API = 'https://www.googleapis.com/urlshortener/v1/url?key=';
|
||||
|
||||
export default function shorternUrl(url, success, error) {
|
||||
shortenUrlEddp(url, success, error);
|
||||
}
|
||||
|
||||
const SHORTEN_API_GOOGLE = 'https://www.googleapis.com/urlshortener/v1/url?key=';
|
||||
/**
|
||||
* Shorten a URL using Google's URL shortener API
|
||||
* @param {string} url The URL to shorten
|
||||
* @param {function} success Success callback
|
||||
* @param {function} error Failure/Error callback
|
||||
*/
|
||||
export default function shortenUrl(url, success, error) {
|
||||
function shortenUrlGoogle(url, success, error) {
|
||||
if (window.navigator.onLine) {
|
||||
try {
|
||||
request.post(SHORTEN_API + window.CORIOLIS_GAPI_KEY)
|
||||
request.post(SHORTEN_API_GOOGLE + window.CORIOLIS_GAPI_KEY)
|
||||
.send({ longUrl: url })
|
||||
.end(function(err, response) {
|
||||
if (err) {
|
||||
@@ -27,3 +31,30 @@ export default function shortenUrl(url, success, error) {
|
||||
error('Not Online');
|
||||
}
|
||||
}
|
||||
|
||||
const SHORTEN_API_EDDP = 'http://eddp.co/u';
|
||||
/**
|
||||
* Shorten a URL using EDDP's URL shortener API
|
||||
* @param {string} url The URL to shorten
|
||||
* @param {function} success Success callback
|
||||
* @param {function} error Failure/Error callback
|
||||
*/
|
||||
function shortenUrlEddp(url, success, error) {
|
||||
if (window.navigator.onLine) {
|
||||
try {
|
||||
request.post(SHORTEN_API_EDDP)
|
||||
.send(url)
|
||||
.end(function(err, response) {
|
||||
if (err) {
|
||||
error('Bad Request');
|
||||
} else {
|
||||
success(response.header['location']);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
error(e.message ? e.message : e);
|
||||
}
|
||||
} else {
|
||||
error('Not Online');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
@import 'tooltip';
|
||||
@import 'buttons';
|
||||
@import 'error';
|
||||
@import 'shipselector';
|
||||
@import 'sortable';
|
||||
@import 'loader';
|
||||
|
||||
@@ -41,8 +42,10 @@ div, a, li {
|
||||
#coriolis {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-top: 48px;
|
||||
overflow-y: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.page {
|
||||
|
||||
@@ -20,8 +20,12 @@ header {
|
||||
line-height: 3em;
|
||||
font-family: @fTitle;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
box-sizing: border-box;
|
||||
.user-select-none();
|
||||
|
||||
.menu {
|
||||
@@ -186,6 +190,7 @@ header {
|
||||
}
|
||||
|
||||
.no-wrap {
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -200,4 +205,4 @@ header {
|
||||
margin:0px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,10 @@
|
||||
width: 1.1em;
|
||||
height: 1em;
|
||||
stoke: @fg;
|
||||
stroke-width: 20;
|
||||
fill: transparent;
|
||||
|
||||
|
||||
&.sm {
|
||||
width: 0.8em;
|
||||
height: 0.75em;
|
||||
|
||||
199
src/less/shipselector.less
Executable file
199
src/less/shipselector.less
Executable file
@@ -0,0 +1,199 @@
|
||||
.shipselector {
|
||||
background-color: @bgBlack;
|
||||
margin: 0;
|
||||
padding: 0 0 0 1em;
|
||||
height: 3em;
|
||||
line-height: 3em;
|
||||
font-family: @fTitle;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
|
||||
.user-select-none();
|
||||
|
||||
.menu {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
cursor: default;
|
||||
|
||||
&.r {
|
||||
.menu-list {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.smallTablet({
|
||||
position: static;
|
||||
position: initial;
|
||||
});
|
||||
}
|
||||
|
||||
.menu-header {
|
||||
padding : 0 1em;
|
||||
cursor: pointer;
|
||||
color: @warning;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
font-family: @fStandard;
|
||||
position: absolute;
|
||||
padding: 0.5em 1em;
|
||||
box-sizing: border-box;
|
||||
min-width: 100%;
|
||||
overflow-x: hidden;
|
||||
background-color: @bgBlack;
|
||||
font-size: 0.9em;
|
||||
overflow-y: auto;
|
||||
z-index: 0;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
max-height: 500px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.5em;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: @warning-disabled;
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
text-align: right;
|
||||
font-size: 1em;
|
||||
font-family: @fStandard;
|
||||
}
|
||||
|
||||
.smallTablet({
|
||||
max-height: 400px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-bottom: 1px solid @bg;
|
||||
});
|
||||
|
||||
|
||||
.tablet({
|
||||
li, a {
|
||||
padding: 0.3em 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
.dbl {
|
||||
-webkit-column-count: 2; /* Chrome, Safari, Opera */
|
||||
-moz-column-count: 2; /* Firefox */
|
||||
column-count: 2;
|
||||
ul {
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
.smallTablet({
|
||||
-webkit-column-count: 3; /* Chrome, Safari, Opera */
|
||||
-moz-column-count: 3; /* Firefox */
|
||||
column-count: 3;
|
||||
|
||||
ul {
|
||||
min-width: 20em;
|
||||
}
|
||||
});
|
||||
|
||||
.largePhone({
|
||||
-webkit-column-count: 2; /* Chrome, Safari, Opera */
|
||||
-moz-column-count: 2; /* Firefox */
|
||||
column-count: 2;
|
||||
});
|
||||
|
||||
.smallPhone({
|
||||
-webkit-column-count: 1; /* Chrome, Safari, Opera */
|
||||
-moz-column-count: 1; /* Firefox */
|
||||
column-count: 1;
|
||||
});
|
||||
}
|
||||
|
||||
.quad {
|
||||
-webkit-column-count: 4; /* Chrome, Safari, Opera */
|
||||
-moz-column-count: 4; /* Firefox */
|
||||
column-count: 4;
|
||||
ul {
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
.smallTablet({
|
||||
-webkit-column-count: 3; /* Chrome, Safari, Opera */
|
||||
-moz-column-count: 3; /* Firefox */
|
||||
column-count: 3;
|
||||
|
||||
ul {
|
||||
min-width: 20em;
|
||||
}
|
||||
});
|
||||
|
||||
.largePhone({
|
||||
-webkit-column-count: 2; /* Chrome, Safari, Opera */
|
||||
-moz-column-count: 2; /* Firefox */
|
||||
column-count: 2;
|
||||
});
|
||||
|
||||
.smallPhone({
|
||||
-webkit-column-count: 1; /* Chrome, Safari, Opera */
|
||||
-moz-column-count: 1; /* Firefox */
|
||||
column-count: 1;
|
||||
});
|
||||
}
|
||||
|
||||
ul {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
margin: 0 0 0.5em;
|
||||
padding: 0;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
li {
|
||||
white-space: normal;
|
||||
list-style: none;
|
||||
margin-left: 1em;
|
||||
line-height: 1.1em;
|
||||
}
|
||||
|
||||
a {
|
||||
vertical-align: middle;
|
||||
color: @warning;
|
||||
text-decoration: none;
|
||||
|
||||
&:visited {
|
||||
color: @warning;
|
||||
}
|
||||
.no-touch &:hover {
|
||||
color: teal;
|
||||
}
|
||||
&.active {
|
||||
color: @primary;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid @disabled;
|
||||
}
|
||||
|
||||
.no-wrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.3em;
|
||||
display: inline-block;
|
||||
margin:0px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user