chore!: create a more typed EsfFit (#130)
This commit is contained in:
@@ -36,99 +36,30 @@ Hammerhead II x1
|
||||
};
|
||||
|
||||
export const hashFits = {
|
||||
Loki: "fit:v2:H4sIAAAAAAAACmWQMZJDMQhD+38WChBgwx32Ijtb5P5d4ONkdpLOTwhbFjKT6efx90uXk+pmJqE+I9YKEueblC0WyXpDvABrZy2OTTR8UTIPwtp4UIQApNz3C/5DkiRYbwA3RA70TowLINl+8qHMJoYBIxPgTLwnHAOb4FtKOlkuxJeSn0p95lORLwVVQ+i6n9FKI77nTbXqbntPpqgrKoWVEDXN2pNtY01tWBM8rcAjTj9OtaJ6aBV5+qHdM345owlT0hPutE6T0AEAAA==",
|
||||
"Tornado v1":
|
||||
"fit:v1:H4sIAAAAAAAACj3OsRVDIQwEwZwqXMAFnE7wUWw34gIM/Wd+ClA2m62LHZ9zfq/3d++z0UgMBthoF7rwiwBjGNisl1gyMJ6VUulBeDC1SnGlXmLJSir51YBxhYOSRdaSDJruLRyizMEWlpw9KYhz5d8f4N5qF/QAAAA=",
|
||||
"Loki v2":
|
||||
"fit:v2:H4sIAAAAAAAACmWQMZJDMQhD+38WChBgwx32Ijtb5P5d4ONkdpLOTwhbFjKT6efx90uXk+pmJqE+I9YKEueblC0WyXpDvABrZy2OTTR8UTIPwtp4UIQApNz3C/5DkiRYbwA3RA70TowLINl+8qHMJoYBIxPgTLwnHAOb4FtKOlkuxJeSn0p95lORLwVVQ+i6n9FKI77nTbXqbntPpqgrKoWVEDXN2pNtY01tWBM8rcAjTj9OtaJ6aBV5+qHdM345owlT0hPutE6T0AEAAA==",
|
||||
"Loki v3":
|
||||
"fit:v3:H4sIAAAAAAAACoWSwVLDMAxE7/0WHSzJsuUjNw5woV8ATSb1QAnTNjD8PTIQ0pJ4cpTf7s6srNM+vwGllBzc9c8ZNoe+GV5aGz4AgYgSws3unN/bK0TL6L5t8nAwIyZyXKFUqKYKZUvWWrAHjOJchQqgR08VGsAjUc0bi9fpf3qbu33Zg0SUkZH3qlcCWhPwmsCvCWRNEICdclgsYOVQ4nI5BdIofz52F8EPubPyjNHPvAVRQWG20YLYkOjMtR2etp+nc1tOxEvg2YlMAjKBJKkLuCTMf2wS+CKg6ZSaY/9aNmdlBXizezx2PTBHuyf8nUhDUEBxI7VtBMBwMeo0UojJAkYxslp0cm58+P6kn4cvMOSO9GcDAAA=",
|
||||
"Killmail (structure)": "fit:killmail:117621358/efe9a3e74e6e0ef983846a82a234211090b94fd5",
|
||||
"Killmail (ship)": "fit:killmail:117923593/4863ca35a23b480dc9feead5e87f2a9fbf1b1102",
|
||||
"Buzzard (eft)":
|
||||
"fit:eft:H4sIAAAAAAAA/4VSy3IaMRC86yvmA5Iq8C42Pq7XdkIVYAI4l1QOg1asp7wrbQaJ19dnJCBUcclJU+rumZ7Hr6dwPCJXX6BokRlKZz27BpZuZ/i3mpBmB0XYU0PIB5jFbyGxgZGaonVrWsnHyHrDFhtYeA7ahwj/H1eDyRR+BKxgTK8wNxvPSNZUkKrukLuKaWvU3DSkoRD94RiTjdTy0JmvL/Biq8BkayiRawcLjdYaVvGFQv8JtCFPTmJmPERdQuZoa7MmW0XlP0ilpmbsVgbGGKz+OJUq3dawh7duA2Xj8DOKns2WtDmhbYfanxqsSKdy09A0tCZxohYtNg18Y9ySP4hNIZN3DO9dzVilFCfKd+mINx16kin9NI3TUfDWeWrp3LQquBXprEFvNrDvq6fAcVpjV8t4SmIdyMP+rq9kHCvxIZTyQ5Z6meiVk+fqGVusBSnYi1cdy05N4PT4neNPkWfqlUk4qbs1SsvXKpm6qZupifFxxZqxi/76A3VTNosnQV7SyNhcsFW0eD6pK+dyY7Lc5FUtzmMpvA/WtMZ6eLcR6qul7LIRh7eanloydd0VuaR/UMNer5dHrQT9ewlyiYYPmUSP6U/A/D5Gd4PYZ1L0H8XZMEVCzPKUJUrUXxFh1SJBAwAA",
|
||||
};
|
||||
|
||||
export const fullFits = [
|
||||
null,
|
||||
{
|
||||
name: "Tengu",
|
||||
ship_type_id: 29984,
|
||||
description: "",
|
||||
items: [
|
||||
{ flag: 5, quantity: 1, type_id: 35794 },
|
||||
{ flag: 5, quantity: 1, type_id: 35794 },
|
||||
{ flag: 5, quantity: 1, type_id: 35795 },
|
||||
{ flag: 5, quantity: 3720, type_id: 24492 },
|
||||
{ flag: 5, quantity: 396, type_id: 24492 },
|
||||
{ flag: 5, quantity: 5472, type_id: 2679 },
|
||||
{ flag: 5, quantity: 8, type_id: 30486 },
|
||||
{ flag: 11, quantity: 1, type_id: 22291 },
|
||||
{ flag: 12, quantity: 1, type_id: 22291 },
|
||||
{ flag: 13, quantity: 1, type_id: 22291 },
|
||||
{ flag: 19, quantity: 1, type_id: 41218 },
|
||||
{ flag: 20, quantity: 1, type_id: 35790 },
|
||||
{ flag: 21, quantity: 1, type_id: 2281 },
|
||||
{ flag: 22, quantity: 1, type_id: 15766 },
|
||||
{ flag: 23, quantity: 1, type_id: 19187 },
|
||||
{ flag: 24, quantity: 1, type_id: 19187 },
|
||||
{ flag: 25, quantity: 1, type_id: 35790 },
|
||||
{ flag: 27, quantity: 1, type_id: 25715, charge: { type_id: 20308 } },
|
||||
{ flag: 28, quantity: 1, type_id: 25715, charge: { type_id: 20308 } },
|
||||
{ flag: 29, quantity: 1, type_id: 25715, charge: { type_id: 20308 } },
|
||||
{ flag: 30, quantity: 1, type_id: 25715, charge: { type_id: 20308 } },
|
||||
{ flag: 31, quantity: 1, type_id: 25715, charge: { type_id: 20308 } },
|
||||
{ flag: 32, quantity: 1, type_id: 25715, charge: { type_id: 20308 } },
|
||||
{ flag: 33, quantity: 1, type_id: 28756 },
|
||||
{ flag: 92, quantity: 1, type_id: 31724 },
|
||||
{ flag: 93, quantity: 1, type_id: 31824 },
|
||||
{ flag: 94, quantity: 1, type_id: 31378 },
|
||||
{ flag: 125, quantity: 1, type_id: 45626 },
|
||||
{ flag: 126, quantity: 1, type_id: 45591 },
|
||||
{ flag: 127, quantity: 1, type_id: 45601 },
|
||||
{ flag: 128, quantity: 1, type_id: 45615 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Legion",
|
||||
ship_type_id: 29986,
|
||||
description: "",
|
||||
items: [
|
||||
{ flag: 5, quantity: 1, type_id: 32014 },
|
||||
{ flag: 5, quantity: 1, type_id: 33474 },
|
||||
{ flag: 5, quantity: 2, type_id: 30832 },
|
||||
{ flag: 5, quantity: 2, type_id: 30834 },
|
||||
{ flag: 5, quantity: 6, type_id: 12826 },
|
||||
{ flag: 11, quantity: 1, type_id: 3530 },
|
||||
{ flag: 12, quantity: 1, type_id: 14072 },
|
||||
{ flag: 13, quantity: 1, type_id: 14072 },
|
||||
{ flag: 14, quantity: 1, type_id: 5839 },
|
||||
{ flag: 15, quantity: 1, type_id: 2364 },
|
||||
{ flag: 19, quantity: 1, type_id: 2024 },
|
||||
{ flag: 20, quantity: 1, type_id: 3244 },
|
||||
{ flag: 21, quantity: 1, type_id: 527 },
|
||||
{ flag: 22, quantity: 1, type_id: 35656 },
|
||||
{ flag: 27, quantity: 1, type_id: 3025, charge: { type_id: 253 } },
|
||||
{ flag: 28, quantity: 1, type_id: 3025, charge: { type_id: 253 } },
|
||||
{ flag: 29, quantity: 1, type_id: 3025, charge: { type_id: 253 } },
|
||||
{ flag: 30, quantity: 1, type_id: 3025, charge: { type_id: 253 } },
|
||||
{ flag: 31, quantity: 1, type_id: 3025, charge: { type_id: 253 } },
|
||||
{ flag: 32, quantity: 1, type_id: 3025, charge: { type_id: 253 } },
|
||||
{ flag: 33, quantity: 1, type_id: 28756 },
|
||||
{ flag: 34, quantity: 1, type_id: 11578 },
|
||||
{ flag: 92, quantity: 1, type_id: 31055 },
|
||||
{ flag: 93, quantity: 1, type_id: 31215 },
|
||||
{ flag: 94, quantity: 1, type_id: 31071 },
|
||||
{ flag: 125, quantity: 1, type_id: 45612 },
|
||||
{ flag: 126, quantity: 1, type_id: 45586 },
|
||||
{ flag: 127, quantity: 1, type_id: 45622 },
|
||||
{ flag: 128, quantity: 1, type_id: 45598 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Loki",
|
||||
export const esiFits = {
|
||||
Loki: {
|
||||
ship_type_id: 29990,
|
||||
name: "C3 HAM",
|
||||
description: "",
|
||||
items: [
|
||||
{ flag: 5, quantity: 1, type_id: 33700 },
|
||||
{ flag: 5, quantity: 150, type_id: 28668 },
|
||||
{ flag: 5, quantity: 16, type_id: 30486 },
|
||||
{ flag: 5, quantity: 16, type_id: 30488 },
|
||||
{ flag: 5, quantity: 330, type_id: 2679 },
|
||||
{ flag: 5, quantity: 9000, type_id: 13856 },
|
||||
{ flag: 5, quantity: 9000, type_id: 24488 },
|
||||
{ flag: 11, quantity: 1, type_id: 22291 },
|
||||
{ flag: 12, quantity: 1, type_id: 22291 },
|
||||
{ flag: 125, quantity: 1, type_id: 45633 },
|
||||
{ flag: 126, quantity: 1, type_id: 45595 },
|
||||
{ flag: 127, quantity: 1, type_id: 45608 },
|
||||
{ flag: 128, quantity: 1, type_id: 45621 },
|
||||
{ flag: 19, quantity: 1, type_id: 19203 },
|
||||
{ flag: 20, quantity: 1, type_id: 19289 },
|
||||
{ flag: 21, quantity: 1, type_id: 2281 },
|
||||
@@ -136,67 +67,185 @@ export const fullFits = [
|
||||
{ flag: 23, quantity: 1, type_id: 14142 },
|
||||
{ flag: 24, quantity: 1, type_id: 41220 },
|
||||
{ flag: 25, quantity: 1, type_id: 14108 },
|
||||
{ flag: 27, quantity: 1, type_id: 25715, charge: { type_id: 24488 } },
|
||||
{ flag: 28, quantity: 1, type_id: 25715, charge: { type_id: 24488 } },
|
||||
{ flag: 29, quantity: 1, type_id: 25715, charge: { type_id: 24488 } },
|
||||
{ flag: 30, quantity: 1, type_id: 25715, charge: { type_id: 24488 } },
|
||||
{ flag: 31, quantity: 1, type_id: 25715, charge: { type_id: 24488 } },
|
||||
{ flag: 27, quantity: 1, type_id: 25715 },
|
||||
{ flag: 28, quantity: 1, type_id: 25715 },
|
||||
{ flag: 29, quantity: 1, type_id: 25715 },
|
||||
{ flag: 30, quantity: 1, type_id: 25715 },
|
||||
{ flag: 31, quantity: 1, type_id: 25715 },
|
||||
{ flag: 32, quantity: 1, type_id: 30836 },
|
||||
{ flag: 33, quantity: 1, type_id: 11578 },
|
||||
{ flag: 34, quantity: 1, type_id: 28756, charge: { type_id: 30488 } },
|
||||
{ flag: 34, quantity: 1, type_id: 28756 },
|
||||
{ flag: 5, quantity: 1, type_id: 33700 },
|
||||
{ flag: 5, quantity: 150, type_id: 28668 },
|
||||
{ flag: 5, quantity: 16, type_id: 30486 },
|
||||
{ flag: 5, quantity: 16, type_id: 30488 },
|
||||
{ flag: 5, quantity: 330, type_id: 2679 },
|
||||
{ flag: 5, quantity: 9000, type_id: 13856 },
|
||||
{ flag: 5, quantity: 9000, type_id: 24488 },
|
||||
{ flag: 87, quantity: 8, type_id: 2456 },
|
||||
{ flag: 92, quantity: 1, type_id: 31748 },
|
||||
{ flag: 93, quantity: 1, type_id: 31760 },
|
||||
{ flag: 94, quantity: 1, type_id: 31588 },
|
||||
{ flag: 125, quantity: 1, type_id: 45633 },
|
||||
{ flag: 126, quantity: 1, type_id: 45595 },
|
||||
{ flag: 127, quantity: 1, type_id: 45608 },
|
||||
{ flag: 128, quantity: 1, type_id: 45621 },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const fullFits: EsfFit[] = [
|
||||
null,
|
||||
{
|
||||
name: "Tengu",
|
||||
shipTypeId: 29984,
|
||||
description: "",
|
||||
cargo: [
|
||||
{ quantity: 1, typeId: 35794 },
|
||||
{ quantity: 1, typeId: 35794 },
|
||||
{ quantity: 1, typeId: 35795 },
|
||||
{ quantity: 3720, typeId: 24492 },
|
||||
{ quantity: 396, typeId: 24492 },
|
||||
{ quantity: 5472, typeId: 2679 },
|
||||
{ quantity: 8, typeId: 30486 },
|
||||
],
|
||||
modules: [
|
||||
{ slot: { type: "Low", index: 1 }, typeId: 22291, state: "Active" },
|
||||
{ slot: { type: "Low", index: 2 }, typeId: 22291, state: "Active" },
|
||||
{ slot: { type: "Low", index: 3 }, typeId: 22291, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 1 }, typeId: 41218, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 2 }, typeId: 35790, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 3 }, typeId: 2281, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 4 }, typeId: 15766, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 5 }, typeId: 19187, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 6 }, typeId: 19187, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 7 }, typeId: 35790, state: "Active" },
|
||||
{ slot: { type: "High", index: 1 }, typeId: 25715, state: "Active", charge: { typeId: 20308 } },
|
||||
{ slot: { type: "High", index: 2 }, typeId: 25715, state: "Active", charge: { typeId: 20308 } },
|
||||
{ slot: { type: "High", index: 3 }, typeId: 25715, state: "Active", charge: { typeId: 20308 } },
|
||||
{ slot: { type: "High", index: 4 }, typeId: 25715, state: "Active", charge: { typeId: 20308 } },
|
||||
{ slot: { type: "High", index: 5 }, typeId: 25715, state: "Active", charge: { typeId: 20308 } },
|
||||
{ slot: { type: "High", index: 6 }, typeId: 25715, state: "Active", charge: { typeId: 20308 } },
|
||||
{ slot: { type: "High", index: 7 }, typeId: 28756, state: "Active" },
|
||||
{ slot: { type: "Rig", index: 1 }, typeId: 31724, state: "Active" },
|
||||
{ slot: { type: "Rig", index: 2 }, typeId: 31824, state: "Active" },
|
||||
{ slot: { type: "Rig", index: 3 }, typeId: 31378, state: "Active" },
|
||||
{ slot: { type: "SubSystem", index: 1 }, typeId: 45626, state: "Active" },
|
||||
{ slot: { type: "SubSystem", index: 2 }, typeId: 45591, state: "Active" },
|
||||
{ slot: { type: "SubSystem", index: 3 }, typeId: 45601, state: "Active" },
|
||||
{ slot: { type: "SubSystem", index: 4 }, typeId: 45615, state: "Active" },
|
||||
],
|
||||
drones: [],
|
||||
},
|
||||
{
|
||||
name: "Legion",
|
||||
shipTypeId: 29986,
|
||||
description: "",
|
||||
modules: [
|
||||
{ slot: { type: "Low", index: 1 }, typeId: 3530, state: "Active" },
|
||||
{ slot: { type: "Low", index: 2 }, typeId: 14072, state: "Active" },
|
||||
{ slot: { type: "Low", index: 3 }, typeId: 14072, state: "Active" },
|
||||
{ slot: { type: "Low", index: 4 }, typeId: 5839, state: "Active" },
|
||||
{ slot: { type: "Low", index: 5 }, typeId: 2364, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 1 }, typeId: 2024, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 2 }, typeId: 3244, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 3 }, typeId: 527, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 4 }, typeId: 35656, state: "Active" },
|
||||
{ slot: { type: "High", index: 1 }, typeId: 3025, state: "Active", charge: { typeId: 253 } },
|
||||
{ slot: { type: "High", index: 2 }, typeId: 3025, state: "Active", charge: { typeId: 253 } },
|
||||
{ slot: { type: "High", index: 3 }, typeId: 3025, state: "Active", charge: { typeId: 253 } },
|
||||
{ slot: { type: "High", index: 4 }, typeId: 3025, state: "Active", charge: { typeId: 253 } },
|
||||
{ slot: { type: "High", index: 5 }, typeId: 3025, state: "Active", charge: { typeId: 253 } },
|
||||
{ slot: { type: "High", index: 6 }, typeId: 3025, state: "Active", charge: { typeId: 253 } },
|
||||
{ slot: { type: "High", index: 7 }, typeId: 28756, state: "Active" },
|
||||
{ slot: { type: "High", index: 8 }, typeId: 11578, state: "Active" },
|
||||
{ slot: { type: "Rig", index: 1 }, typeId: 31055, state: "Active" },
|
||||
{ slot: { type: "Rig", index: 2 }, typeId: 31215, state: "Active" },
|
||||
{ slot: { type: "Rig", index: 3 }, typeId: 31071, state: "Active" },
|
||||
{ slot: { type: "SubSystem", index: 1 }, typeId: 45612, state: "Active" },
|
||||
{ slot: { type: "SubSystem", index: 2 }, typeId: 45586, state: "Active" },
|
||||
{ slot: { type: "SubSystem", index: 3 }, typeId: 45622, state: "Active" },
|
||||
{ slot: { type: "SubSystem", index: 4 }, typeId: 45598, state: "Active" },
|
||||
],
|
||||
drones: [],
|
||||
cargo: [
|
||||
{ quantity: 1, typeId: 32014 },
|
||||
{ quantity: 1, typeId: 33474 },
|
||||
{ quantity: 2, typeId: 30832 },
|
||||
{ quantity: 2, typeId: 30834 },
|
||||
{ quantity: 6, typeId: 12826 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Killmail 117621358",
|
||||
ship_type_id: 35833,
|
||||
name: "Loki",
|
||||
description: "",
|
||||
items: [
|
||||
{ flag: 5, type_id: 37821, quantity: 6 },
|
||||
{ flag: 5, type_id: 37822, quantity: 7 },
|
||||
{ flag: 5, type_id: 37823, quantity: 7 },
|
||||
{ flag: 5, type_id: 37824, quantity: 7 },
|
||||
{ flag: 5, type_id: 37843, quantity: 6102 },
|
||||
{ flag: 5, type_id: 37844, quantity: 4249 },
|
||||
{ flag: 5, type_id: 63195, quantity: 17200 },
|
||||
{ flag: 11, type_id: 47362, quantity: 1 },
|
||||
{ flag: 12, type_id: 47342, quantity: 1 },
|
||||
{ flag: 13, type_id: 47362, quantity: 1 },
|
||||
{ flag: 14, type_id: 47362, quantity: 1 },
|
||||
{ flag: 19, type_id: 35944, quantity: 1 },
|
||||
{ flag: 20, type_id: 47334, quantity: 1 },
|
||||
{ flag: 21, type_id: 47366, quantity: 1 },
|
||||
{ flag: 22, type_id: 47338, quantity: 1 },
|
||||
{ flag: 23, type_id: 47338, quantity: 1 },
|
||||
{ flag: 27, type_id: 47327, quantity: 1 },
|
||||
{ flag: 28, type_id: 47323, quantity: 1 },
|
||||
{ flag: 29, type_id: 47323, quantity: 1 },
|
||||
{ flag: 30, type_id: 47323, quantity: 1 },
|
||||
{ flag: 31, type_id: 47330, quantity: 1 },
|
||||
{ flag: 32, type_id: 47330, quantity: 1 },
|
||||
{ flag: 92, type_id: 37254, quantity: 1 },
|
||||
{ flag: 93, type_id: 37258, quantity: 1 },
|
||||
{ flag: 94, type_id: 37260, quantity: 1 },
|
||||
{ flag: 158, type_id: 47035, quantity: 10 },
|
||||
{ flag: 158, type_id: 47037, quantity: 10 },
|
||||
{ flag: 158, type_id: 47119, quantity: 12 },
|
||||
{ flag: 158, type_id: 47123, quantity: 6 },
|
||||
{ flag: 158, type_id: 47127, quantity: 6 },
|
||||
{ flag: 158, type_id: 47127, quantity: 6 },
|
||||
{ flag: 158, type_id: 47136, quantity: 3 },
|
||||
{ flag: 158, type_id: 47141, quantity: 2 },
|
||||
{ flag: 158, type_id: 47141, quantity: 6 },
|
||||
{ flag: 158, type_id: 47143, quantity: 9 },
|
||||
{ flag: 164, type_id: 35894, quantity: 1 },
|
||||
{ flag: 172, type_id: 4246, quantity: 14030 },
|
||||
{ flag: 180, type_id: 56204, quantity: 1 },
|
||||
shipTypeId: 29990,
|
||||
modules: [
|
||||
{ slot: { type: "Low", index: 1 }, typeId: 22291, state: "Active" },
|
||||
{ slot: { type: "Low", index: 2 }, typeId: 22291, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 1 }, typeId: 19203, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 2 }, typeId: 19289, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 3 }, typeId: 2281, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 4 }, typeId: 17500, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 5 }, typeId: 14142, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 6 }, typeId: 41220, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 7 }, typeId: 14108, state: "Active" },
|
||||
{ slot: { type: "High", index: 1 }, typeId: 25715, state: "Active", charge: { typeId: 24488 } },
|
||||
{ slot: { type: "High", index: 2 }, typeId: 25715, state: "Active", charge: { typeId: 24488 } },
|
||||
{ slot: { type: "High", index: 3 }, typeId: 25715, state: "Active", charge: { typeId: 24488 } },
|
||||
{ slot: { type: "High", index: 4 }, typeId: 25715, state: "Active", charge: { typeId: 24488 } },
|
||||
{ slot: { type: "High", index: 5 }, typeId: 25715, state: "Active", charge: { typeId: 24488 } },
|
||||
{ slot: { type: "High", index: 6 }, typeId: 30836, state: "Active" },
|
||||
{ slot: { type: "High", index: 7 }, typeId: 11578, state: "Active" },
|
||||
{ slot: { type: "High", index: 8 }, typeId: 28756, state: "Active", charge: { typeId: 30488 } },
|
||||
{ slot: { type: "Rig", index: 1 }, typeId: 31748, state: "Active" },
|
||||
{ slot: { type: "Rig", index: 2 }, typeId: 31760, state: "Active" },
|
||||
{ slot: { type: "Rig", index: 3 }, typeId: 31588, state: "Active" },
|
||||
{ slot: { type: "SubSystem", index: 1 }, typeId: 45633, state: "Active" },
|
||||
{ slot: { type: "SubSystem", index: 2 }, typeId: 45595, state: "Active" },
|
||||
{ slot: { type: "SubSystem", index: 3 }, typeId: 45608, state: "Active" },
|
||||
{ slot: { type: "SubSystem", index: 4 }, typeId: 45621, state: "Active" },
|
||||
],
|
||||
drones: [{ typeId: 2456, states: { Active: 5, Passive: 3 } }],
|
||||
cargo: [
|
||||
{ quantity: 1, typeId: 33700 },
|
||||
{ quantity: 150, typeId: 28668 },
|
||||
{ quantity: 16, typeId: 30486 },
|
||||
{ quantity: 16, typeId: 30488 },
|
||||
{ quantity: 330, typeId: 2679 },
|
||||
{ quantity: 9000, typeId: 13856 },
|
||||
{ quantity: 9000, typeId: 24488 },
|
||||
],
|
||||
},
|
||||
{
|
||||
shipTypeId: 35833,
|
||||
name: "Killmail 117621358",
|
||||
description: "",
|
||||
modules: [
|
||||
{ slot: { type: "Low", index: 1 }, typeId: 47362, state: "Active" },
|
||||
{ slot: { type: "Low", index: 2 }, typeId: 47342, state: "Active" },
|
||||
{ slot: { type: "Low", index: 3 }, typeId: 47362, state: "Active" },
|
||||
{ slot: { type: "Low", index: 4 }, typeId: 47362, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 1 }, typeId: 35944, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 2 }, typeId: 47334, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 3 }, typeId: 47366, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 4 }, typeId: 47338, state: "Active" },
|
||||
{ slot: { type: "Medium", index: 5 }, typeId: 47338, state: "Active" },
|
||||
{ slot: { type: "High", index: 1 }, typeId: 47327, state: "Active" },
|
||||
{ slot: { type: "High", index: 2 }, typeId: 47323, state: "Active" },
|
||||
{ slot: { type: "High", index: 3 }, typeId: 47323, state: "Active" },
|
||||
{ slot: { type: "High", index: 4 }, typeId: 47323, state: "Active" },
|
||||
{ slot: { type: "High", index: 5 }, typeId: 47330, state: "Active" },
|
||||
{ slot: { type: "High", index: 6 }, typeId: 47330, state: "Active" },
|
||||
{ slot: { type: "Rig", index: 1 }, typeId: 37254, state: "Active" },
|
||||
{ slot: { type: "Rig", index: 2 }, typeId: 37258, state: "Active" },
|
||||
{ slot: { type: "Rig", index: 3 }, typeId: 37260, state: "Active" },
|
||||
],
|
||||
drones: [],
|
||||
cargo: [
|
||||
{ typeId: 37821, quantity: 6 },
|
||||
{ typeId: 37844, quantity: 4249 },
|
||||
{ typeId: 37822, quantity: 7 },
|
||||
{ typeId: 37824, quantity: 7 },
|
||||
{ typeId: 37843, quantity: 6102 },
|
||||
{ typeId: 37823, quantity: 7 },
|
||||
{ typeId: 63195, quantity: 17200 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -36,9 +36,11 @@ export const withDecoratorFull = (Story: StoryFn) => (
|
||||
|
||||
export const useFitSelection = (fit: EsfFit | null) => {
|
||||
const currentFit = useCurrentFit();
|
||||
const setFit = currentFit.setFit;
|
||||
|
||||
const setFitRef = React.useRef(currentFit.setFit);
|
||||
setFitRef.current = currentFit.setFit;
|
||||
|
||||
React.useEffect(() => {
|
||||
setFit(fit);
|
||||
}, [setFit, fit]);
|
||||
setFitRef.current(fit);
|
||||
}, [fit]);
|
||||
};
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@eveshipfit/data": "^9",
|
||||
"@eveshipfit/dogma-engine": "^4",
|
||||
"@eveshipfit/dogma-engine": "^5",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
|
||||
@@ -3,7 +3,8 @@ import React from "react";
|
||||
|
||||
import { Icon } from "@/components/Icon";
|
||||
import { useEveData } from "@/providers/EveDataProvider";
|
||||
import { StatisticsItemAttribute, StatisticsItemAttributeEffect, useStatistics } from "@/providers/StatisticsProvider";
|
||||
import { useStatistics } from "@/providers/StatisticsProvider";
|
||||
import { CalculationItemAttribute, CalculationItemAttributeEffect } from "@/providers/DogmaEngineProvider";
|
||||
|
||||
import styles from "./CalculationDetail.module.css";
|
||||
|
||||
@@ -42,7 +43,7 @@ function stateToInteger(state: string): number {
|
||||
}
|
||||
}
|
||||
|
||||
const Effect = (props: { effect: StatisticsItemAttributeEffect }) => {
|
||||
const Effect = (props: { effect: CalculationItemAttributeEffect }) => {
|
||||
const eveData = useEveData();
|
||||
const statistics = useStatistics();
|
||||
|
||||
@@ -123,7 +124,7 @@ const Effect = (props: { effect: StatisticsItemAttributeEffect }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const CalculationDetailMeta = (props: { attributeId: number; attribute: StatisticsItemAttribute }) => {
|
||||
const CalculationDetailMeta = (props: { attributeId: number; attribute: CalculationItemAttribute }) => {
|
||||
const [expanded, setExpanded] = React.useState(false);
|
||||
const eveData = useEveData();
|
||||
|
||||
@@ -175,7 +176,7 @@ export const CalculationDetail = (props: {
|
||||
const statistics = useStatistics();
|
||||
if (statistics === null) return <></>;
|
||||
|
||||
let attributes: [number, StatisticsItemAttribute][] = [];
|
||||
let attributes: [number, CalculationItemAttribute][] = [];
|
||||
|
||||
if (props.source === "Ship") {
|
||||
attributes = [...(statistics.hull.attributes.entries() ?? [])];
|
||||
|
||||
@@ -4,11 +4,20 @@ import React from "react";
|
||||
import { CharAttribute, ShipAttribute } from "@/components/ShipAttribute";
|
||||
import { useFitManager } from "@/providers/FitManagerProvider";
|
||||
import { useEveData } from "@/providers/EveDataProvider";
|
||||
import { StatisticsItem, useStatistics } from "@/providers/StatisticsProvider";
|
||||
import { useStatistics } from "@/providers/StatisticsProvider";
|
||||
import { CalculationItem } from "@/providers/DogmaEngineProvider";
|
||||
|
||||
import styles from "./DroneBay.module.css";
|
||||
|
||||
const DroneBayEntrySelected = ({ drone, index, isOpen }: { drone: StatisticsItem; index: number; isOpen: boolean }) => {
|
||||
const DroneBayEntrySelected = ({
|
||||
drone,
|
||||
index,
|
||||
isOpen,
|
||||
}: {
|
||||
drone: CalculationItem;
|
||||
index: number;
|
||||
isOpen: boolean;
|
||||
}) => {
|
||||
const fitManager = useFitManager();
|
||||
|
||||
const onClick = React.useCallback(() => {
|
||||
@@ -29,7 +38,7 @@ const DroneBayEntrySelected = ({ drone, index, isOpen }: { drone: StatisticsItem
|
||||
);
|
||||
};
|
||||
|
||||
const DroneBayEntry = ({ name, drones }: { name: string; drones: StatisticsItem[] }) => {
|
||||
const DroneBayEntry = ({ name, drones }: { name: string; drones: CalculationItem[] }) => {
|
||||
const eveData = useEveData();
|
||||
const statistics = useStatistics();
|
||||
const fitManager = useFitManager();
|
||||
@@ -93,8 +102,8 @@ export const DroneBay = () => {
|
||||
if (eveData === null || statistics === null) return <></>;
|
||||
|
||||
/* Group drones by type_id */
|
||||
const dronesGrouped: Record<string, StatisticsItem[]> = {};
|
||||
for (const drone of statistics.items.filter((item) => item.flag == 87)) {
|
||||
const dronesGrouped: Record<string, CalculationItem[]> = {};
|
||||
for (const drone of statistics.items.filter((item) => item.slot.type == "DroneBay")) {
|
||||
const name = eveData.typeIDs?.[drone.type_id].name ?? "";
|
||||
|
||||
if (dronesGrouped[name] === undefined) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ModalDialog } from "@/components/ModalDialog";
|
||||
import { useClipboard } from "@/hooks/Clipboard";
|
||||
import { useExportEft } from "@/hooks/ExportEft";
|
||||
import { useImportEft } from "@/hooks/ImportEft";
|
||||
import { useImportEsiFitting } from "@/hooks/ImportEsiFitting";
|
||||
import { EsfFit } from "@/providers/CurrentFitProvider";
|
||||
import { useFitManager } from "@/providers/FitManagerProvider";
|
||||
|
||||
@@ -14,6 +15,7 @@ export const ClipboardButton = () => {
|
||||
const fitManager = useFitManager();
|
||||
const exportEft = useExportEft();
|
||||
const importEft = useImportEft();
|
||||
const importEsiFitting = useImportEsiFitting();
|
||||
const { copy, copied } = useClipboard();
|
||||
|
||||
const [isPopupOpen, setIsPopupOpen] = React.useState(false);
|
||||
@@ -42,7 +44,7 @@ export const ClipboardButton = () => {
|
||||
|
||||
let fit: EsfFit | undefined | null;
|
||||
if (fitString.startsWith("{")) {
|
||||
fit = JSON.parse(fitString);
|
||||
fit = importEsiFitting(JSON.parse(fitString));
|
||||
} else {
|
||||
try {
|
||||
fit = importEft(fitString);
|
||||
@@ -66,7 +68,7 @@ export const ClipboardButton = () => {
|
||||
|
||||
setIsPasteOpen(false);
|
||||
setIsPopupOpen(false);
|
||||
}, [fitManager, importEft]);
|
||||
}, [fitManager, importEft, importEsiFitting]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -4,9 +4,10 @@ import React from "react";
|
||||
import { defaultDataUrl } from "@/settings";
|
||||
import { Icon } from "@/components/Icon";
|
||||
import { TreeListing, TreeHeader, TreeLeaf } from "@/components/TreeListing";
|
||||
import { StatisticsSlotType, useStatistics } from "@/providers/StatisticsProvider";
|
||||
import { useStatistics } from "@/providers/StatisticsProvider";
|
||||
import { useFitManager } from "@/providers/FitManagerProvider";
|
||||
import { useEveData } from "@/providers/EveDataProvider";
|
||||
import { CalculationSlotType } from "@/providers/DogmaEngineProvider";
|
||||
|
||||
import styles from "./HardwareListing.module.css";
|
||||
|
||||
@@ -21,7 +22,7 @@ interface ListingItem {
|
||||
name: string;
|
||||
meta: number;
|
||||
typeId: number;
|
||||
slotType: StatisticsSlotType | "droneBay" | "charge";
|
||||
slotType: CalculationSlotType;
|
||||
}
|
||||
|
||||
interface ListingGroup {
|
||||
@@ -43,14 +44,14 @@ interface Filter {
|
||||
|
||||
const OnItemDragStart = (
|
||||
typeId: number,
|
||||
slotType: StatisticsSlotType | "droneBay" | "charge",
|
||||
slotType: CalculationSlotType,
|
||||
): ((e: React.DragEvent<HTMLDivElement>) => void) => {
|
||||
return (e: React.DragEvent<HTMLDivElement>) => {
|
||||
const img = new Image();
|
||||
img.src = `https://images.evetech.net/types/${typeId}/icon?size=64`;
|
||||
e.dataTransfer.setDragImage(img, 32, 32);
|
||||
e.dataTransfer.setData("application/type_id", typeId.toString());
|
||||
e.dataTransfer.setData("application/slot_type", slotType);
|
||||
e.dataTransfer.setData("application/esf-type-id", typeId.toString());
|
||||
e.dataTransfer.setData("application/esf-slot-type", slotType);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -193,35 +194,35 @@ export const HardwareListing = () => {
|
||||
if (module.marketGroupID === undefined) continue;
|
||||
if (!module.published) continue;
|
||||
|
||||
let slotType: StatisticsSlotType | "droneBay" | "charge" | undefined;
|
||||
let slotType: CalculationSlotType | undefined;
|
||||
if (module.categoryID !== 8) {
|
||||
slotType = eveData.typeDogma[typeId]?.dogmaEffects
|
||||
.map((effect) => {
|
||||
switch (effect.effectID) {
|
||||
case eveData.effectMapping.loPower:
|
||||
return "lowslot";
|
||||
return "Low";
|
||||
case eveData.effectMapping.medPower:
|
||||
return "medslot";
|
||||
return "Medium";
|
||||
case eveData.effectMapping.hiPower:
|
||||
return "hislot";
|
||||
return "High";
|
||||
case eveData.effectMapping.rigSlot:
|
||||
return "rig";
|
||||
return "Rig";
|
||||
case eveData.effectMapping.subSystem:
|
||||
return "subsystem";
|
||||
return "SubSystem";
|
||||
}
|
||||
})
|
||||
.filter((slot) => slot !== undefined)[0];
|
||||
if (module.categoryID === 18) {
|
||||
slotType = "droneBay";
|
||||
slotType = "DroneBay";
|
||||
}
|
||||
|
||||
if (slotType === undefined) continue;
|
||||
|
||||
if (filter.lowslot || filter.medslot || filter.hislot || filter.rig_subsystem || filter.drone) {
|
||||
if (slotType === "lowslot" && !filter.lowslot) continue;
|
||||
if (slotType === "medslot" && !filter.medslot) continue;
|
||||
if (slotType === "hislot" && !filter.hislot) continue;
|
||||
if ((slotType === "rig" || slotType === "subsystem") && !filter.rig_subsystem) continue;
|
||||
if (slotType === "Low" && !filter.lowslot) continue;
|
||||
if (slotType === "Medium" && !filter.medslot) continue;
|
||||
if (slotType === "High" && !filter.hislot) continue;
|
||||
if ((slotType === "Rig" || slotType === "SubSystem") && !filter.rig_subsystem) continue;
|
||||
if (module.categoryID === 18 && !filter.drone) continue;
|
||||
}
|
||||
} else {
|
||||
@@ -236,13 +237,13 @@ export const HardwareListing = () => {
|
||||
for (const chargeGroupID of filter.moduleWithCharge.chargeGroupIDs) {
|
||||
if (module.groupID !== chargeGroupID) continue;
|
||||
|
||||
slotType = "charge";
|
||||
slotType = "Charge";
|
||||
break;
|
||||
}
|
||||
|
||||
if (slotType === undefined) continue;
|
||||
} else {
|
||||
slotType = "charge";
|
||||
slotType = "Charge";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ const Hull = (props: { typeId: number; entry: ListingHull }) => {
|
||||
|
||||
return (
|
||||
<TreeLeaf
|
||||
key={`${fit.fit.ship_type_id}-${index}`}
|
||||
key={`${fit.fit.shipTypeId}-${index}`}
|
||||
level={4}
|
||||
content={fit.fit.name}
|
||||
onClick={() => fitManager.setFit(fit.fit)}
|
||||
@@ -166,13 +166,13 @@ export const HullListing = () => {
|
||||
const localFitsGrouped = React.useMemo(() => {
|
||||
const grouped: Record<string, ListingFit[]> = {};
|
||||
for (const fit of localFits.fittings) {
|
||||
if (fit.ship_type_id === undefined) continue;
|
||||
if (fit.shipTypeId === undefined) continue;
|
||||
|
||||
if (grouped[fit.ship_type_id] === undefined) {
|
||||
grouped[fit.ship_type_id] = [];
|
||||
if (grouped[fit.shipTypeId] === undefined) {
|
||||
grouped[fit.shipTypeId] = [];
|
||||
}
|
||||
|
||||
grouped[fit.ship_type_id].push({
|
||||
grouped[fit.shipTypeId].push({
|
||||
origin: "local",
|
||||
fit,
|
||||
});
|
||||
@@ -186,13 +186,13 @@ export const HullListing = () => {
|
||||
|
||||
const grouped: Record<string, ListingFit[]> = {};
|
||||
for (const fit of characterFittings) {
|
||||
if (fit.ship_type_id === undefined) continue;
|
||||
if (fit.shipTypeId === undefined) continue;
|
||||
|
||||
if (grouped[fit.ship_type_id] === undefined) {
|
||||
grouped[fit.ship_type_id] = [];
|
||||
if (grouped[fit.shipTypeId] === undefined) {
|
||||
grouped[fit.shipTypeId] = [];
|
||||
}
|
||||
|
||||
grouped[fit.ship_type_id].push({
|
||||
grouped[fit.shipTypeId].push({
|
||||
origin: "character",
|
||||
fit,
|
||||
});
|
||||
@@ -213,7 +213,7 @@ export const HullListing = () => {
|
||||
if (hull.marketGroupID === undefined) continue;
|
||||
if (!hull.published) continue;
|
||||
|
||||
if (filter.currentHull && currentFit.fit?.ship_type_id !== parseInt(typeId)) continue;
|
||||
if (filter.currentHull && currentFit.fit?.shipTypeId !== parseInt(typeId)) continue;
|
||||
|
||||
const fits: ListingFit[] = [];
|
||||
if (anyFilter) {
|
||||
@@ -221,7 +221,7 @@ export const HullListing = () => {
|
||||
if (filter.characterFits && Object.keys(characterFitsGrouped).includes(typeId))
|
||||
fits.push(...characterFitsGrouped[typeId]);
|
||||
if (fits.length == 0) {
|
||||
if (!filter.currentHull || currentFit.fit?.ship_type_id !== parseInt(typeId)) continue;
|
||||
if (!filter.currentHull || currentFit.fit?.shipTypeId !== parseInt(typeId)) continue;
|
||||
}
|
||||
} else {
|
||||
if (Object.keys(localFitsGrouped).includes(typeId)) fits.push(...localFitsGrouped[typeId]);
|
||||
|
||||
@@ -14,7 +14,7 @@ export const Hull = () => {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const shipTypeId = currentFit.fit.ship_type_id;
|
||||
const shipTypeId = currentFit.fit.shipTypeId;
|
||||
if (shipTypeId === undefined) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
import { useFitManager } from "@/providers/FitManagerProvider";
|
||||
import { StatisticsSlotType } from "@/providers/StatisticsProvider";
|
||||
import { CalculationSlotType } from "@/providers/DogmaEngineProvider";
|
||||
|
||||
import styles from "./ShipFit.module.css";
|
||||
|
||||
@@ -21,11 +21,11 @@ export const HullDraggable = () => {
|
||||
return Number.isInteger(num) ? num : undefined;
|
||||
};
|
||||
|
||||
const draggedTypeId: number | undefined = parseNumber(e.dataTransfer.getData("application/type_id"));
|
||||
const draggedSlotId: number | undefined = parseNumber(e.dataTransfer.getData("application/slot_id"));
|
||||
const draggedSlotType: StatisticsSlotType | "droneBay" | "charge" = e.dataTransfer.getData(
|
||||
"application/slot_type",
|
||||
) as StatisticsSlotType | "droneBay" | "charge";
|
||||
const draggedTypeId: number | undefined = parseNumber(e.dataTransfer.getData("application/esf-type-id"));
|
||||
const draggedSlotId: number | undefined = parseNumber(e.dataTransfer.getData("application/esf-slot-index"));
|
||||
const draggedSlotType: CalculationSlotType = e.dataTransfer.getData(
|
||||
"application/esf-slot-type",
|
||||
) as CalculationSlotType;
|
||||
|
||||
if (draggedTypeId === undefined) {
|
||||
return;
|
||||
|
||||
@@ -3,19 +3,19 @@ import React from "react";
|
||||
import styles from "./ShipFit.module.css";
|
||||
|
||||
const highlightSettings = {
|
||||
lowslot: {
|
||||
Low: {
|
||||
width: 12,
|
||||
height: 3,
|
||||
x: 0,
|
||||
y: 9,
|
||||
},
|
||||
medslot: {
|
||||
Medium: {
|
||||
width: 3,
|
||||
height: 12,
|
||||
x: 9,
|
||||
y: 0,
|
||||
},
|
||||
hislot: {
|
||||
High: {
|
||||
width: 12,
|
||||
height: 3,
|
||||
x: 0,
|
||||
@@ -23,7 +23,7 @@ const highlightSettings = {
|
||||
},
|
||||
};
|
||||
|
||||
export const RadialMenu = (props: { type: "lowslot" | "medslot" | "hislot" }) => {
|
||||
export const RadialMenu = (props: { type: "Low" | "Medium" | "High" }) => {
|
||||
const highlight = highlightSettings[props.type];
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,7 @@ import clsx from "clsx";
|
||||
|
||||
import { Icon } from "@/components/Icon";
|
||||
import { useEveData } from "@/providers/EveDataProvider";
|
||||
import { useStatistics } from "@/providers/StatisticsProvider";
|
||||
import { StatisticsSlots, useStatistics } from "@/providers/StatisticsProvider";
|
||||
|
||||
import { FitLink } from "./FitLink";
|
||||
import { Hull } from "./Hull";
|
||||
@@ -26,14 +26,14 @@ export const ShipFit = (props: { withStats?: boolean }) => {
|
||||
|
||||
if (eveData === null) return <></>;
|
||||
|
||||
const slots = statistics?.slots ?? {
|
||||
hislot: 0,
|
||||
medslot: 0,
|
||||
lowslot: 0,
|
||||
rig: 0,
|
||||
subsystem: 0,
|
||||
turret: 0,
|
||||
launcher: 0,
|
||||
const slots: StatisticsSlots = statistics?.slots ?? {
|
||||
High: 0,
|
||||
Medium: 0,
|
||||
Low: 0,
|
||||
Rig: 0,
|
||||
SubSystem: 0,
|
||||
Turret: 0,
|
||||
Launcher: 0,
|
||||
};
|
||||
|
||||
let launcherSlotsUsed =
|
||||
@@ -65,7 +65,7 @@ export const ShipFit = (props: { withStats?: boolean }) => {
|
||||
<Icon name="hardpoint-turret" size={16} />
|
||||
</div>
|
||||
</RingTopItem>
|
||||
{Array.from({ length: slots.turret }, (_, i) => {
|
||||
{Array.from({ length: slots.Turret }, (_, i) => {
|
||||
turretSlotsUsed--;
|
||||
return (
|
||||
<RingTopItem key={i} rotation={-40 + i * 3} background>
|
||||
@@ -85,7 +85,7 @@ export const ShipFit = (props: { withStats?: boolean }) => {
|
||||
<Icon name="hardpoint-launcher" size={16} />
|
||||
</div>
|
||||
</RingTopItem>
|
||||
{Array.from({ length: slots.launcher }, (_, i) => {
|
||||
{Array.from({ length: slots.Launcher }, (_, i) => {
|
||||
launcherSlotsUsed--;
|
||||
return (
|
||||
<RingTopItem key={i} rotation={39 - i * 3} background>
|
||||
@@ -119,113 +119,113 @@ export const ShipFit = (props: { withStats?: boolean }) => {
|
||||
)}
|
||||
|
||||
<RingTopItem rotation={-45} background>
|
||||
<RadialMenu type="hislot" />
|
||||
<RadialMenu type="High" />
|
||||
</RingTopItem>
|
||||
|
||||
<RingTopItem rotation={-36.5 + (71 / 7) * 0}>
|
||||
<Slot type="hislot" index={1} fittable={slots.hislot >= 1} main />
|
||||
<Slot type="High" index={1} fittable={slots.High >= 1} main />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={-36.5 + (71 / 7) * 1}>
|
||||
<Slot type="hislot" index={2} fittable={slots.hislot >= 2} />
|
||||
<Slot type="High" index={2} fittable={slots.High >= 2} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={-36.5 + (71 / 7) * 2}>
|
||||
<Slot type="hislot" index={3} fittable={slots.hislot >= 3} />
|
||||
<Slot type="High" index={3} fittable={slots.High >= 3} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={-36.5 + (71 / 7) * 3}>
|
||||
<Slot type="hislot" index={4} fittable={slots.hislot >= 4} />
|
||||
<Slot type="High" index={4} fittable={slots.High >= 4} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={-36.5 + (71 / 7) * 4}>
|
||||
<Slot type="hislot" index={5} fittable={slots.hislot >= 5} />
|
||||
<Slot type="High" index={5} fittable={slots.High >= 5} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={-36.5 + (71 / 7) * 5}>
|
||||
<Slot type="hislot" index={6} fittable={slots.hislot >= 6} />
|
||||
<Slot type="High" index={6} fittable={slots.High >= 6} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={-36.5 + (71 / 7) * 6}>
|
||||
<Slot type="hislot" index={7} fittable={slots.hislot >= 7} />
|
||||
<Slot type="High" index={7} fittable={slots.High >= 7} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={-36.5 + (71 / 7) * 7}>
|
||||
<Slot type="hislot" index={8} fittable={slots.hislot >= 8} />
|
||||
<Slot type="High" index={8} fittable={slots.High >= 8} />
|
||||
</RingTopItem>
|
||||
|
||||
<RingTopItem rotation={43} background>
|
||||
<RadialMenu type="medslot" />
|
||||
<RadialMenu type="Medium" />
|
||||
</RingTopItem>
|
||||
|
||||
<RingTopItem rotation={53 + (72 / 7) * 0}>
|
||||
<Slot type="medslot" index={1} fittable={slots.medslot >= 1} />
|
||||
<Slot type="Medium" index={1} fittable={slots.Medium >= 1} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={53 + (72 / 7) * 1}>
|
||||
<Slot type="medslot" index={2} fittable={slots.medslot >= 2} />
|
||||
<Slot type="Medium" index={2} fittable={slots.Medium >= 2} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={53 + (72 / 7) * 2}>
|
||||
<Slot type="medslot" index={3} fittable={slots.medslot >= 3} />
|
||||
<Slot type="Medium" index={3} fittable={slots.Medium >= 3} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={53 + (72 / 7) * 3}>
|
||||
<Slot type="medslot" index={4} fittable={slots.medslot >= 4} />
|
||||
<Slot type="Medium" index={4} fittable={slots.Medium >= 4} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={53 + (72 / 7) * 4}>
|
||||
<Slot type="medslot" index={5} fittable={slots.medslot >= 5} />
|
||||
<Slot type="Medium" index={5} fittable={slots.Medium >= 5} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={53 + (72 / 7) * 5}>
|
||||
<Slot type="medslot" index={6} fittable={slots.medslot >= 6} />
|
||||
<Slot type="Medium" index={6} fittable={slots.Medium >= 6} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={53 + (72 / 7) * 6}>
|
||||
<Slot type="medslot" index={7} fittable={slots.medslot >= 7} />
|
||||
<Slot type="Medium" index={7} fittable={slots.Medium >= 7} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={53 + (72 / 7) * 7}>
|
||||
<Slot type="medslot" index={8} fittable={slots.medslot >= 8} />
|
||||
<Slot type="Medium" index={8} fittable={slots.Medium >= 8} />
|
||||
</RingTopItem>
|
||||
|
||||
<RingTopItem rotation={133} background>
|
||||
<RadialMenu type="lowslot" />
|
||||
<RadialMenu type="Low" />
|
||||
</RingTopItem>
|
||||
|
||||
<RingTopItem rotation={142 + (72 / 7) * 0}>
|
||||
<Slot type="lowslot" index={1} fittable={slots.lowslot >= 1} />
|
||||
<Slot type="Low" index={1} fittable={slots.Low >= 1} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={142 + (72 / 7) * 1}>
|
||||
<Slot type="lowslot" index={2} fittable={slots.lowslot >= 2} />
|
||||
<Slot type="Low" index={2} fittable={slots.Low >= 2} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={142 + (72 / 7) * 2}>
|
||||
<Slot type="lowslot" index={3} fittable={slots.lowslot >= 3} />
|
||||
<Slot type="Low" index={3} fittable={slots.Low >= 3} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={142 + (72 / 7) * 3}>
|
||||
<Slot type="lowslot" index={4} fittable={slots.lowslot >= 4} />
|
||||
<Slot type="Low" index={4} fittable={slots.Low >= 4} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={142 + (72 / 7) * 4}>
|
||||
<Slot type="lowslot" index={5} fittable={slots.lowslot >= 5} />
|
||||
<Slot type="Low" index={5} fittable={slots.Low >= 5} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={142 + (72 / 7) * 5}>
|
||||
<Slot type="lowslot" index={6} fittable={slots.lowslot >= 6} />
|
||||
<Slot type="Low" index={6} fittable={slots.Low >= 6} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={142 + (72 / 7) * 6}>
|
||||
<Slot type="lowslot" index={7} fittable={slots.lowslot >= 7} />
|
||||
<Slot type="Low" index={7} fittable={slots.Low >= 7} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={142 + (72 / 7) * 7}>
|
||||
<Slot type="lowslot" index={8} fittable={slots.lowslot >= 8} />
|
||||
<Slot type="Low" index={8} fittable={slots.Low >= 8} />
|
||||
</RingTopItem>
|
||||
|
||||
<RingTopItem rotation={-74 + (21 / 2) * 0}>
|
||||
<Slot type="rig" index={1} fittable={slots.rig >= 1} />
|
||||
<Slot type="Rig" index={1} fittable={slots.Rig >= 1} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={-74 + (21 / 2) * 1}>
|
||||
<Slot type="rig" index={2} fittable={slots.rig >= 2} />
|
||||
<Slot type="Rig" index={2} fittable={slots.Rig >= 2} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={-74 + (21 / 2) * 2}>
|
||||
<Slot type="rig" index={3} fittable={slots.rig >= 3} />
|
||||
<Slot type="Rig" index={3} fittable={slots.Rig >= 3} />
|
||||
</RingTopItem>
|
||||
|
||||
<RingTopItem rotation={-128 + (38 / 3) * 0}>
|
||||
<Slot type="subsystem" index={1} fittable={slots.subsystem >= 1} />
|
||||
<Slot type="SubSystem" index={1} fittable={slots.SubSystem >= 1} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={-128 + (38 / 3) * 1}>
|
||||
<Slot type="subsystem" index={2} fittable={slots.subsystem >= 2} />
|
||||
<Slot type="SubSystem" index={2} fittable={slots.SubSystem >= 2} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={-128 + (38 / 3) * 2}>
|
||||
<Slot type="subsystem" index={3} fittable={slots.subsystem >= 3} />
|
||||
<Slot type="SubSystem" index={3} fittable={slots.SubSystem >= 3} />
|
||||
</RingTopItem>
|
||||
<RingTopItem rotation={-128 + (38 / 3) * 3}>
|
||||
<Slot type="subsystem" index={4} fittable={slots.subsystem >= 4} />
|
||||
<Slot type="SubSystem" index={4} fittable={slots.SubSystem >= 4} />
|
||||
</RingTopItem>
|
||||
</RingTop>
|
||||
|
||||
|
||||
@@ -4,57 +4,47 @@ import { Icon, IconName } from "@/components/Icon";
|
||||
import { useEveData } from "@/providers/EveDataProvider";
|
||||
import { useStatistics } from "@/providers/StatisticsProvider";
|
||||
import { useFitManager } from "@/providers/FitManagerProvider";
|
||||
import { State } from "@/providers/CurrentFitProvider";
|
||||
import { CalculationSlot } from "@/providers/DogmaEngineProvider";
|
||||
import { EsfSlot, EsfSlotType, EsfState } from "@/providers/CurrentFitProvider";
|
||||
|
||||
import styles from "./ShipFit.module.css";
|
||||
|
||||
const esiFlagMapping: Record<string, number[]> = {
|
||||
lowslot: [11, 12, 13, 14, 15, 16, 17, 18],
|
||||
medslot: [19, 20, 21, 22, 23, 24, 25, 26],
|
||||
hislot: [27, 28, 29, 30, 31, 32, 33, 34],
|
||||
rig: [92, 93, 94],
|
||||
subsystem: [125, 126, 127, 128],
|
||||
};
|
||||
|
||||
const stateRotation: Record<string, State[]> = {
|
||||
const stateRotation: Record<string, EsfState[]> = {
|
||||
Passive: ["Passive"],
|
||||
Online: ["Passive", "Online"],
|
||||
Active: ["Passive", "Online", "Active"],
|
||||
Overload: ["Passive", "Online", "Active", "Overload"],
|
||||
};
|
||||
|
||||
export const Slot = (props: { type: string; index: number; fittable: boolean; main?: boolean }) => {
|
||||
export const Slot = (props: { type: EsfSlotType; index: number; fittable: boolean; main?: boolean }) => {
|
||||
const eveData = useEveData();
|
||||
const statistics = useStatistics();
|
||||
const fitManager = useFitManager();
|
||||
|
||||
const esiFlagType = props.type;
|
||||
const esiFlag = esiFlagMapping[esiFlagType][props.index - 1];
|
||||
|
||||
const esiItem = statistics?.items.find((item) => item.flag == esiFlag);
|
||||
const active = esiItem?.max_state !== "Passive" && esiItem?.max_state !== "Online";
|
||||
const module = statistics?.items.find((item) => item.slot.type === props.type && item.slot.index === props.index);
|
||||
const active = module?.max_state !== "Passive" && module?.max_state !== "Online";
|
||||
|
||||
const offlineState = React.useCallback(
|
||||
(e: React.MouseEvent<SVGSVGElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (esiItem === undefined) return;
|
||||
if (module === undefined) return;
|
||||
|
||||
if (esiItem.state === "Passive") {
|
||||
fitManager.setModuleState(esiItem.flag, "Online");
|
||||
if (module.state === "Passive") {
|
||||
fitManager.setModuleState(module.slot as EsfSlot, "Online");
|
||||
} else {
|
||||
fitManager.setModuleState(esiItem.flag, "Passive");
|
||||
fitManager.setModuleState(module.slot as EsfSlot, "Passive");
|
||||
}
|
||||
},
|
||||
[fitManager, esiItem],
|
||||
[fitManager, module],
|
||||
);
|
||||
|
||||
const cycleState = React.useCallback(
|
||||
(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
if (esiItem === undefined) return;
|
||||
if (module === undefined) return;
|
||||
|
||||
const states = stateRotation[esiItem.max_state];
|
||||
const stateIndex = states.indexOf(esiItem.state);
|
||||
const states = stateRotation[module.max_state];
|
||||
const stateIndex = states.indexOf(module.state as EsfState);
|
||||
|
||||
let newState;
|
||||
if (e.shiftKey) {
|
||||
@@ -63,41 +53,41 @@ export const Slot = (props: { type: string; index: number; fittable: boolean; ma
|
||||
newState = states[(stateIndex + 1) % states.length];
|
||||
}
|
||||
|
||||
fitManager.setModuleState(esiItem.flag, newState);
|
||||
fitManager.setModuleState(module.slot as EsfSlot, newState);
|
||||
},
|
||||
[fitManager, esiItem],
|
||||
[fitManager, module],
|
||||
);
|
||||
|
||||
const unfitModule = React.useCallback(
|
||||
(e: React.MouseEvent<SVGSVGElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (esiItem === undefined) return;
|
||||
if (module === undefined) return;
|
||||
|
||||
fitManager.removeModule(esiItem.flag);
|
||||
fitManager.removeModule(module.slot as EsfSlot);
|
||||
},
|
||||
[fitManager, esiItem],
|
||||
[fitManager, module],
|
||||
);
|
||||
|
||||
const unfitCharge = React.useCallback(
|
||||
(e: React.MouseEvent<SVGSVGElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
if (esiItem === undefined) return;
|
||||
if (module === undefined) return;
|
||||
|
||||
fitManager.removeCharge(esiItem.flag);
|
||||
fitManager.removeCharge(module.slot as EsfSlot);
|
||||
},
|
||||
[fitManager, esiItem],
|
||||
[fitManager, module],
|
||||
);
|
||||
|
||||
const onDragStart = React.useCallback(
|
||||
(e: React.DragEvent<HTMLDivElement>) => {
|
||||
if (esiItem === undefined) return;
|
||||
if (module === undefined) return;
|
||||
|
||||
e.dataTransfer.setData("application/type_id", esiItem.type_id.toString());
|
||||
e.dataTransfer.setData("application/slot_id", esiFlag.toString());
|
||||
e.dataTransfer.setData("application/slot_type", esiFlagType);
|
||||
e.dataTransfer.setData("application/esf-type-id", module.type_id.toString());
|
||||
e.dataTransfer.setData("application/esf-slot-type", module.slot.type);
|
||||
e.dataTransfer.setData("application/esf-slot-index", module.slot.index?.toString() ?? "");
|
||||
},
|
||||
[esiItem, esiFlag, esiFlagType],
|
||||
[module],
|
||||
);
|
||||
|
||||
const onDragOver = React.useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||
@@ -113,32 +103,39 @@ export const Slot = (props: { type: string; index: number; fittable: boolean; ma
|
||||
return Number.isInteger(num) ? num : undefined;
|
||||
};
|
||||
|
||||
const draggedTypeId: number | undefined = parseNumber(e.dataTransfer.getData("application/type_id"));
|
||||
const draggedSlotId: number | undefined = parseNumber(e.dataTransfer.getData("application/slot_id"));
|
||||
const draggedSlotType: string = e.dataTransfer.getData("application/slot_type");
|
||||
const draggedTypeId: number | undefined = parseNumber(e.dataTransfer.getData("application/esf-type-id"));
|
||||
const draggedSlotIndex: CalculationSlot["index"] = parseNumber(
|
||||
e.dataTransfer.getData("application/esf-slot-index"),
|
||||
);
|
||||
const draggedSlotType: CalculationSlot["type"] = e.dataTransfer.getData(
|
||||
"application/esf-slot-type",
|
||||
) as CalculationSlot["type"];
|
||||
|
||||
if (draggedTypeId === undefined) {
|
||||
if (draggedTypeId === undefined || draggedSlotType === "DroneBay") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (draggedSlotType === "charge") {
|
||||
fitManager.setCharge(esiFlag, draggedTypeId);
|
||||
if (draggedSlotType === "Charge") {
|
||||
fitManager.setCharge({ type: props.type, index: props.index }, draggedTypeId);
|
||||
return;
|
||||
}
|
||||
|
||||
const isValidSlotGroup = draggedSlotType === esiFlagType;
|
||||
const isValidSlotGroup = draggedSlotType === props.type;
|
||||
if (!isValidSlotGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isDraggedFromAnotherSlot = draggedSlotId !== undefined;
|
||||
const isDraggedFromAnotherSlot = draggedSlotIndex !== undefined;
|
||||
if (isDraggedFromAnotherSlot) {
|
||||
fitManager.swapModule(esiFlag, draggedSlotId);
|
||||
fitManager.swapModule(
|
||||
{ type: props.type, index: props.index },
|
||||
{ type: draggedSlotType, index: draggedSlotIndex },
|
||||
);
|
||||
} else {
|
||||
fitManager.setModule(esiFlag, draggedTypeId);
|
||||
fitManager.setModule({ type: props.type, index: props.index }, draggedTypeId);
|
||||
}
|
||||
},
|
||||
[fitManager, esiFlag, esiFlagType],
|
||||
[fitManager, props],
|
||||
);
|
||||
|
||||
if (eveData === null || statistics === null) return <></>;
|
||||
@@ -202,14 +199,14 @@ export const Slot = (props: { type: string; index: number; fittable: boolean; ma
|
||||
preserveAspectRatio="xMidYMin slice"
|
||||
>
|
||||
<use href="#slot" />
|
||||
{props.fittable && esiItem && active && <use href="#slot-active" />}
|
||||
{props.fittable && esiItem && !active && <use href="#slot-passive" />}
|
||||
{props.fittable && module !== undefined && active && <use href="#slot-active" />}
|
||||
{props.fittable && module !== undefined && !active && <use href="#slot-passive" />}
|
||||
</svg>
|
||||
</>
|
||||
);
|
||||
|
||||
/* Not fittable and nothing fitted; no need to render the slot. */
|
||||
if (esiItem === undefined && !props.fittable) {
|
||||
if (module === undefined && !props.fittable) {
|
||||
return (
|
||||
<div className={styles.slotOuter} data-hasitem={false}>
|
||||
<div className={styles.slot} data-state="Unavailable">
|
||||
@@ -219,12 +216,12 @@ export const Slot = (props: { type: string; index: number; fittable: boolean; ma
|
||||
);
|
||||
}
|
||||
|
||||
if (esiItem !== undefined && eveData !== null) {
|
||||
if (esiItem.charge !== undefined) {
|
||||
if (module !== undefined && eveData !== null) {
|
||||
if (module.charge !== undefined) {
|
||||
item = (
|
||||
<img
|
||||
src={`https://images.evetech.net/types/${esiItem.charge.type_id}/icon?size=64`}
|
||||
title={`${eveData.typeIDs[esiItem.type_id].name}\n${eveData.typeIDs[esiItem.charge.type_id].name}`}
|
||||
src={`https://images.evetech.net/types/${module.charge.type_id}/icon?size=64`}
|
||||
title={`${eveData.typeIDs[module.type_id].name}\n${eveData.typeIDs[module.charge.type_id].name}`}
|
||||
draggable={true}
|
||||
onDragStart={onDragStart}
|
||||
/>
|
||||
@@ -232,8 +229,8 @@ export const Slot = (props: { type: string; index: number; fittable: boolean; ma
|
||||
} else {
|
||||
item = (
|
||||
<img
|
||||
src={`https://images.evetech.net/types/${esiItem.type_id}/icon?size=64`}
|
||||
title={eveData.typeIDs[esiItem.type_id].name}
|
||||
src={`https://images.evetech.net/types/${module.type_id}/icon?size=64`}
|
||||
title={eveData.typeIDs[module.type_id].name}
|
||||
draggable={true}
|
||||
onDragStart={onDragStart}
|
||||
/>
|
||||
@@ -244,19 +241,19 @@ export const Slot = (props: { type: string; index: number; fittable: boolean; ma
|
||||
|
||||
let icon: IconName | undefined;
|
||||
switch (props.type) {
|
||||
case "lowslot":
|
||||
case "Low":
|
||||
icon = "fitting-lowslot";
|
||||
break;
|
||||
|
||||
case "medslot":
|
||||
case "Medium":
|
||||
icon = "fitting-medslot";
|
||||
break;
|
||||
|
||||
case "hislot":
|
||||
case "High":
|
||||
icon = "fitting-hislot";
|
||||
break;
|
||||
|
||||
case "rig":
|
||||
case "Rig":
|
||||
icon = "fitting-rig-subsystem";
|
||||
break;
|
||||
}
|
||||
@@ -266,16 +263,16 @@ export const Slot = (props: { type: string; index: number; fittable: boolean; ma
|
||||
}
|
||||
}
|
||||
|
||||
const state = esiItem?.state === "Passive" && esiItem?.max_state !== "Passive" ? "Offline" : esiItem?.state;
|
||||
const state = module?.state === "Passive" && module?.max_state !== "Passive" ? "Offline" : module?.state;
|
||||
|
||||
return (
|
||||
<div className={styles.slotOuter} data-hasitem={esiItem !== undefined}>
|
||||
<div className={styles.slotOuter} data-hasitem={module !== undefined}>
|
||||
<div className={styles.slot} onClick={cycleState} data-state={state} onDrop={onDragEnd} onDragOver={onDragOver}>
|
||||
{svg}
|
||||
<div className={imageStyle}>{item}</div>
|
||||
</div>
|
||||
<div className={styles.slotOptions}>
|
||||
{esiItem?.charge !== undefined && (
|
||||
{module?.charge !== undefined && (
|
||||
<svg viewBox="0 0 20 20" width={20} xmlns="http://www.w3.org/2000/svg" onClick={unfitCharge}>
|
||||
<title>Remove Charge</title>
|
||||
<use href="#uncharge" />
|
||||
@@ -285,7 +282,7 @@ export const Slot = (props: { type: string; index: number; fittable: boolean; ma
|
||||
<title>Unfit Module</title>
|
||||
<use href="#unfit" />
|
||||
</svg>
|
||||
{esiItem?.max_state !== "Passive" && (
|
||||
{module?.max_state !== "Passive" && (
|
||||
<svg viewBox="0 0 20 20" width={20} xmlns="http://www.w3.org/2000/svg" onClick={offlineState}>
|
||||
<title>Put Offline</title>
|
||||
<use href="#offline" />
|
||||
|
||||
@@ -35,7 +35,7 @@ const ShipDroneBay = () => {
|
||||
|
||||
if (eveData === null) return <></>;
|
||||
|
||||
const isStructure = eveData.typeIDs[currentFit.fit?.ship_type_id ?? 0]?.categoryID === 65;
|
||||
const isStructure = eveData.typeIDs[currentFit.fit?.shipTypeId ?? 0]?.categoryID === 65;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -22,7 +22,7 @@ export const ShipStatistics = () => {
|
||||
const statistics = useStatistics();
|
||||
|
||||
let capacitorState = "Stable";
|
||||
const isStructure = eveData?.typeIDs[currentFit.fit?.ship_type_id ?? 0]?.categoryID === 65;
|
||||
const isStructure = eveData?.typeIDs[currentFit.fit?.shipTypeId ?? 0]?.categoryID === 65;
|
||||
|
||||
const attributeId = eveData?.attributeMapping.capacitorDepletesIn ?? 0;
|
||||
const capacitorDepletesIn = statistics?.hull.attributes.get(attributeId)?.value;
|
||||
|
||||
@@ -1,27 +1,16 @@
|
||||
import React from "react";
|
||||
|
||||
import { useCurrentFit } from "@/providers/CurrentFitProvider";
|
||||
import { EsfSlotType, useCurrentFit } from "@/providers/CurrentFitProvider";
|
||||
import { useEveData } from "@/providers/EveDataProvider";
|
||||
import { useStatistics } from "@/providers/StatisticsProvider";
|
||||
|
||||
/** Mapping between slot types and ESI flags (for first slot in the type). */
|
||||
const esiFlagMapping: Record<"hislot" | "medslot" | "lowslot" | "subsystem" | "rig" | "droneBay", number[]> = {
|
||||
lowslot: [11, 12, 13, 14, 15, 16, 17, 18],
|
||||
medslot: [19, 20, 21, 22, 23, 24, 25, 26],
|
||||
hislot: [27, 28, 29, 30, 31, 32, 33, 34],
|
||||
rig: [92, 93, 94],
|
||||
subsystem: [125, 126, 127, 128],
|
||||
droneBay: [87],
|
||||
};
|
||||
|
||||
/** Mapping between slot-type and the EFT string name. */
|
||||
const slotToEft: Record<"hislot" | "medslot" | "lowslot" | "subsystem" | "rig" | "droneBay", string> = {
|
||||
lowslot: "Low Slot",
|
||||
medslot: "Mid Slot",
|
||||
hislot: "High Slot",
|
||||
rig: "Rig Slot",
|
||||
subsystem: "Subsystem Slot",
|
||||
droneBay: "Drone Bay",
|
||||
const slotToEft: Record<EsfSlotType, string> = {
|
||||
Low: "Low Slot",
|
||||
Medium: "Med Slot",
|
||||
High: "High Slot",
|
||||
Rig: "Rig Slot",
|
||||
SubSystem: "Subsystem Slot",
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -39,55 +28,49 @@ export function useExportEft() {
|
||||
|
||||
let eft = "";
|
||||
|
||||
const shipType = eveData.typeIDs[fit.ship_type_id];
|
||||
const shipType = eveData.typeIDs[fit.shipTypeId];
|
||||
if (shipType === undefined) return null;
|
||||
|
||||
eft += `[${shipType.name}, ${fit.name}]\n`;
|
||||
|
||||
for (const slotType of Object.keys(esiFlagMapping) as (
|
||||
| "hislot"
|
||||
| "medslot"
|
||||
| "lowslot"
|
||||
| "subsystem"
|
||||
| "rig"
|
||||
| "droneBay"
|
||||
)[]) {
|
||||
let index = 1;
|
||||
|
||||
for (const flag of esiFlagMapping[slotType]) {
|
||||
if (slotType !== "droneBay" && index > statistics.slots[slotType]) break;
|
||||
index += 1;
|
||||
|
||||
const modules = fit.items.filter((item) => item.flag === flag);
|
||||
if (modules === undefined || modules.length === 0) {
|
||||
for (const slotType of ["High", "Medium", "Low", "Rig", "SubSystem"] as EsfSlotType[]) {
|
||||
for (let i = 1; i <= statistics.slots[slotType]; i++) {
|
||||
const module = fit.modules.find((item) => item.slot.type === slotType && item.slot.index === i);
|
||||
if (module === undefined) {
|
||||
eft += "[Empty " + slotToEft[slotType] + "]\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const module of modules) {
|
||||
const moduleType = eveData.typeIDs[module.type_id];
|
||||
if (moduleType === undefined) {
|
||||
eft += "[Empty " + slotToEft[slotType] + "]\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
eft += moduleType.name;
|
||||
if (module.quantity > 1) {
|
||||
eft += ` x${module.quantity}`;
|
||||
}
|
||||
if (module.charge !== undefined) {
|
||||
const chargeType = eveData.typeIDs[module.charge.type_id];
|
||||
if (chargeType !== undefined) {
|
||||
eft += `, ${chargeType.name}`;
|
||||
}
|
||||
}
|
||||
eft += "\n";
|
||||
const moduleType = eveData.typeIDs[module.typeId];
|
||||
if (moduleType === undefined) {
|
||||
eft += "[Empty " + slotToEft[slotType] + "]\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
eft += moduleType.name;
|
||||
if (module.charge !== undefined) {
|
||||
const chargeType = eveData.typeIDs[module.charge.typeId];
|
||||
if (chargeType !== undefined) {
|
||||
eft += `, ${chargeType.name}`;
|
||||
}
|
||||
}
|
||||
eft += "\n";
|
||||
}
|
||||
|
||||
eft += "\n";
|
||||
}
|
||||
|
||||
for (const drone of fit.drones) {
|
||||
const droneType = eveData.typeIDs[drone.typeId];
|
||||
if (droneType === undefined) continue;
|
||||
|
||||
eft += droneType.name;
|
||||
if (drone.states.Active > 1) {
|
||||
eft += ` x${drone.states.Active + drone.states.Passive}`;
|
||||
}
|
||||
eft += "\n";
|
||||
}
|
||||
|
||||
return eft;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,13 +19,19 @@ async function compress(str: string): Promise<string> {
|
||||
}
|
||||
|
||||
async function encodeFit(fit: EsfFit): Promise<string> {
|
||||
let result = `${fit.ship_type_id},${fit.name},${fit.description}\n`;
|
||||
let result = `ship,${fit.shipTypeId},${fit.name},${fit.description}\n`;
|
||||
|
||||
for (const item of fit.items) {
|
||||
result += `${item.flag},${item.type_id},${item.quantity},${item.charge?.type_id ?? ""},${item.state ?? ""}\n`;
|
||||
for (const module of fit.modules) {
|
||||
result += `module,${module.slot.type},${module.slot.index},${module.typeId},${module.state},${module.charge?.typeId ?? ""}\n`;
|
||||
}
|
||||
for (const drone of fit.drones) {
|
||||
result += `drone,${drone.typeId},${drone.states.Active},${drone.states.Passive}\n`;
|
||||
}
|
||||
for (const cargo of fit.cargo) {
|
||||
result += `cargo,${cargo.typeId},${cargo.quantity}\n`;
|
||||
}
|
||||
|
||||
return "v2:" + (await compress(result));
|
||||
return "v3:" + (await compress(result));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
import React from "react";
|
||||
|
||||
import { EsfFit } from "@/providers/CurrentFitProvider";
|
||||
import { EsfFit, EsfSlotType } from "@/providers/CurrentFitProvider";
|
||||
import { useEveData } from "@/providers/EveDataProvider";
|
||||
|
||||
/** Mapping between slot types and ESI flags (for first slot in the type). */
|
||||
const esiFlagMapping: Record<string, number[]> = {
|
||||
lowslot: [11, 12, 13, 14, 15, 16, 17, 18],
|
||||
medslot: [19, 20, 21, 22, 23, 24, 25, 26],
|
||||
hislot: [27, 28, 29, 30, 31, 32, 33, 34],
|
||||
rig: [92, 93, 94],
|
||||
subsystem: [125, 126, 127, 128],
|
||||
};
|
||||
|
||||
/** Mapping between dogma effect IDs and slot types. */
|
||||
const effectIdMapping: Record<number, string> = {
|
||||
11: "lowslot",
|
||||
13: "medslot",
|
||||
12: "hislot",
|
||||
2663: "rig",
|
||||
3772: "subsystem",
|
||||
const effectIdMapping: Record<number, EsfSlotType | "DroneBay"> = {
|
||||
11: "Low",
|
||||
13: "Medium",
|
||||
12: "High",
|
||||
2663: "Rig",
|
||||
3772: "SubSystem",
|
||||
};
|
||||
const attributeIdMapping: Record<number, string> = {
|
||||
1272: "droneBay",
|
||||
const attributeIdMapping: Record<number, EsfSlotType | "DroneBay"> = {
|
||||
1272: "DroneBay",
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -34,9 +25,7 @@ export function useImportEft() {
|
||||
if (eveData === null) return null;
|
||||
|
||||
function lookupTypeByName(name: string): number | undefined {
|
||||
if (eveData === null) return undefined;
|
||||
|
||||
for (const typeId in eveData.typeIDs) {
|
||||
for (const typeId in eveData?.typeIDs) {
|
||||
const type = eveData.typeIDs[typeId];
|
||||
|
||||
if (type.name === name) {
|
||||
@@ -48,10 +37,12 @@ export function useImportEft() {
|
||||
}
|
||||
|
||||
const fit: EsfFit = {
|
||||
shipTypeId: 0,
|
||||
name: "EFT Import",
|
||||
description: "",
|
||||
ship_type_id: 0,
|
||||
items: [],
|
||||
modules: [],
|
||||
drones: [],
|
||||
cargo: [],
|
||||
};
|
||||
|
||||
const lines = eft.trim().split("\n");
|
||||
@@ -63,19 +54,18 @@ export function useImportEft() {
|
||||
const shipTypeId = lookupTypeByName(shipType);
|
||||
if (shipTypeId === undefined) throw new Error(`Unknown ship '${shipType}'.`);
|
||||
|
||||
fit.ship_type_id = shipTypeId;
|
||||
fit.shipTypeId = shipTypeId;
|
||||
fit.name = lines[0].split(",")[1].slice(0, -1).trim();
|
||||
|
||||
const slotIndex: Record<string, number> = {
|
||||
lowslot: 0,
|
||||
medslot: 0,
|
||||
hislot: 0,
|
||||
rig: 0,
|
||||
subsystem: 0,
|
||||
droneBay: 0,
|
||||
const slotIndex: Record<EsfSlotType, number> = {
|
||||
Low: 1,
|
||||
Medium: 1,
|
||||
High: 1,
|
||||
Rig: 1,
|
||||
SubSystem: 1,
|
||||
};
|
||||
|
||||
let lastSlotType = "";
|
||||
let lastSlotType: EsfSlotType | "DroneBay" | undefined = undefined;
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (line.trim() === "") continue;
|
||||
@@ -90,7 +80,7 @@ export function useImportEft() {
|
||||
*/
|
||||
|
||||
if (line.startsWith("[") || line.startsWith(" ")) {
|
||||
if (lastSlotType != "") {
|
||||
if (lastSlotType !== undefined && lastSlotType !== "DroneBay") {
|
||||
slotIndex[lastSlotType]++;
|
||||
}
|
||||
continue;
|
||||
@@ -110,7 +100,7 @@ export function useImportEft() {
|
||||
const attributes = eveData.typeDogma[itemTypeId]?.dogmaAttributes;
|
||||
|
||||
/* Find what type of slot this item goes into. */
|
||||
let slotType = undefined;
|
||||
let slotType: EsfSlotType | "DroneBay" | undefined = undefined;
|
||||
if (slotType === undefined && effects !== undefined) {
|
||||
for (const effectId in effects) {
|
||||
slotType = effectIdMapping[effects[effectId].effectID];
|
||||
@@ -128,18 +118,31 @@ export function useImportEft() {
|
||||
if (slotType === undefined) continue;
|
||||
lastSlotType = slotType;
|
||||
|
||||
const flag = slotType === "droneBay" ? 87 : esiFlagMapping[slotType][slotIndex[slotType]];
|
||||
let charge = undefined;
|
||||
if (chargeTypeId !== undefined) {
|
||||
charge = {
|
||||
type_id: chargeTypeId,
|
||||
typeId: chargeTypeId,
|
||||
};
|
||||
}
|
||||
|
||||
fit.items.push({
|
||||
flag,
|
||||
quantity: itemCount,
|
||||
type_id: itemTypeId,
|
||||
if (slotType === "DroneBay") {
|
||||
fit.drones.push({
|
||||
typeId: itemTypeId,
|
||||
states: {
|
||||
Active: itemCount,
|
||||
Passive: 0,
|
||||
},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
fit.modules.push({
|
||||
slot: {
|
||||
type: slotType,
|
||||
index: slotIndex[slotType],
|
||||
},
|
||||
typeId: itemTypeId,
|
||||
state: "Active",
|
||||
charge,
|
||||
});
|
||||
slotIndex[slotType]++;
|
||||
|
||||
34
src/hooks/ImportEsiFitting/ImportEsiFitting.stories.tsx
Normal file
34
src/hooks/ImportEsiFitting/ImportEsiFitting.stories.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import { esiFits } from "../../../.storybook/fits";
|
||||
|
||||
import { EveDataProvider } from "@/providers/EveDataProvider";
|
||||
|
||||
import { ImportEsiFitting } from "./ImportEsiFitting";
|
||||
|
||||
const meta: Meta<typeof ImportEsiFitting> = {
|
||||
component: ImportEsiFitting,
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ImportEsiFitting>;
|
||||
|
||||
export const Default: Story = {
|
||||
argTypes: {
|
||||
esiFit: {
|
||||
control: "select",
|
||||
options: Object.keys(esiFits),
|
||||
mapping: esiFits,
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<EveDataProvider>
|
||||
<Story />
|
||||
</EveDataProvider>
|
||||
),
|
||||
],
|
||||
render: (args) => <ImportEsiFitting {...args} />,
|
||||
};
|
||||
98
src/hooks/ImportEsiFitting/ImportEsiFitting.tsx
Normal file
98
src/hooks/ImportEsiFitting/ImportEsiFitting.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import React from "react";
|
||||
|
||||
import { EsfCargo, EsfDrone, EsfFit, EsfModule } from "@/providers/CurrentFitProvider";
|
||||
import { useEveData } from "@/providers/EveDataProvider";
|
||||
import { esiFlagToEsfSlot } from "../ImportEveShipFitHash";
|
||||
|
||||
export interface EsiFit {
|
||||
name: string;
|
||||
description: string;
|
||||
ship_type_id: number;
|
||||
items: {
|
||||
item_id: number;
|
||||
type_id: number;
|
||||
flag: number;
|
||||
quantity: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an ESI Fitting JSON to an ESF Fit.
|
||||
*/
|
||||
export function useImportEsiFitting() {
|
||||
const eveData = useEveData();
|
||||
|
||||
return (esiFit: EsiFit): EsfFit | null => {
|
||||
if (eveData === null) return null;
|
||||
|
||||
const modules = esiFit.items
|
||||
.map((item): EsfModule | undefined => {
|
||||
const slot = esiFlagToEsfSlot[item.flag];
|
||||
if (slot === undefined) return undefined;
|
||||
|
||||
return {
|
||||
typeId: item.type_id,
|
||||
slot,
|
||||
state: "Active",
|
||||
};
|
||||
})
|
||||
.filter((item): item is EsfModule => item !== undefined);
|
||||
|
||||
const drones = esiFit.items
|
||||
.map((item): EsfDrone | undefined => {
|
||||
if (item.flag !== 87) return undefined;
|
||||
|
||||
return {
|
||||
typeId: item.type_id,
|
||||
states: {
|
||||
Active: item.quantity,
|
||||
Passive: 0,
|
||||
},
|
||||
};
|
||||
})
|
||||
.filter((item): item is EsfDrone => item !== undefined);
|
||||
|
||||
const cargo = esiFit.items
|
||||
.map((item): EsfCargo | undefined => {
|
||||
if (item.flag !== 5) return undefined;
|
||||
|
||||
return {
|
||||
typeId: item.type_id,
|
||||
quantity: item.quantity,
|
||||
};
|
||||
})
|
||||
.filter((item): item is EsfCargo => item !== undefined);
|
||||
|
||||
const fit: EsfFit = {
|
||||
name: esiFit.name,
|
||||
description: esiFit.description,
|
||||
shipTypeId: esiFit.ship_type_id,
|
||||
modules,
|
||||
drones,
|
||||
cargo,
|
||||
};
|
||||
|
||||
return fit;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FormatEftToEsiProps {
|
||||
/** The ESI Fitting JSON. */
|
||||
esiFit: EsiFit;
|
||||
}
|
||||
|
||||
/**
|
||||
* `useImportEsiFitting` converts an ESI Fitting JSON to an ESF fit.
|
||||
*
|
||||
* Note: do not use this React component itself, but the `useImportEsiFitting` React hook instead.
|
||||
*/
|
||||
export const ImportEsiFitting = (props: FormatEftToEsiProps) => {
|
||||
const importEsiFitting = useImportEsiFitting();
|
||||
|
||||
if (props.esiFit === undefined) {
|
||||
return <div>No fit selected.</div>;
|
||||
}
|
||||
|
||||
const fit = importEsiFitting(props.esiFit);
|
||||
return <pre>{JSON.stringify(fit, null, 2)}</pre>;
|
||||
};
|
||||
2
src/hooks/ImportEsiFitting/index.ts
Normal file
2
src/hooks/ImportEsiFitting/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { useImportEsiFitting } from "./ImportEsiFitting";
|
||||
export type { EsiFit } from "./ImportEsiFitting";
|
||||
13
src/hooks/ImportEveShipFitHash/DecodeEft.tsx
Normal file
13
src/hooks/ImportEveShipFitHash/DecodeEft.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useImportEft } from "@/hooks/ImportEft";
|
||||
import { EsfFit } from "@/providers/CurrentFitProvider";
|
||||
|
||||
import { decompress } from "./Decompress";
|
||||
|
||||
export function useDecodeEft() {
|
||||
const importEft = useImportEft();
|
||||
|
||||
return async (eftCompressed: string): Promise<EsfFit | null> => {
|
||||
const eft = await decompress(eftCompressed);
|
||||
return importEft(eft);
|
||||
};
|
||||
}
|
||||
29
src/hooks/ImportEveShipFitHash/DecodeEsfFitV1.tsx
Normal file
29
src/hooks/ImportEveShipFitHash/DecodeEsfFitV1.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { EsfFit, EsfModule } from "@/providers/CurrentFitProvider";
|
||||
|
||||
import { decompress } from "./Decompress";
|
||||
import { esiFlagToEsfSlot } from "./EsiFlags";
|
||||
|
||||
export async function decodeEsfFitV1(fitCompressed: string): Promise<EsfFit | null> {
|
||||
const fitEncoded = await decompress(fitCompressed);
|
||||
|
||||
const fitLines = fitEncoded.trim().split("\n");
|
||||
const fitHeader = fitLines[0].split(",");
|
||||
|
||||
const modules = fitLines.slice(1).map((line): EsfModule => {
|
||||
const item = line.split(",");
|
||||
return {
|
||||
slot: esiFlagToEsfSlot[parseInt(item[0])],
|
||||
typeId: parseInt(item[1]),
|
||||
state: "Active",
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
shipTypeId: parseInt(fitHeader[0]),
|
||||
name: fitHeader[1],
|
||||
description: fitHeader[2],
|
||||
modules,
|
||||
drones: [], // v1 didn't store drones.
|
||||
cargo: [], // v2 didn't store cargo.
|
||||
};
|
||||
}
|
||||
86
src/hooks/ImportEveShipFitHash/DecodeEsfFitV2.tsx
Normal file
86
src/hooks/ImportEveShipFitHash/DecodeEsfFitV2.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import { EsfCargo, EsfDrone, EsfFit, EsfModule, EsfState } from "@/providers/CurrentFitProvider";
|
||||
|
||||
import { decompress } from "./Decompress";
|
||||
import { esiFlagToEsfSlot } from "./EsiFlags";
|
||||
|
||||
export async function decodeEsfFitV2(fitCompressed: string): Promise<EsfFit | null> {
|
||||
const fitEncoded = await decompress(fitCompressed);
|
||||
|
||||
const fitLines = fitEncoded.trim().split("\n");
|
||||
const fitHeader = fitLines[0].split(",");
|
||||
|
||||
const modules = fitLines
|
||||
.slice(1)
|
||||
.map((line): EsfModule | undefined => {
|
||||
const item = line.split(",");
|
||||
const flag = parseInt(item[0]);
|
||||
if (esiFlagToEsfSlot[flag] === undefined) return undefined; // Skip anything not modules.
|
||||
|
||||
let charge = undefined;
|
||||
if (item[3]) {
|
||||
charge = {
|
||||
typeId: parseInt(item[3]),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
slot: esiFlagToEsfSlot[flag],
|
||||
typeId: parseInt(item[1]),
|
||||
charge,
|
||||
state: (item[4] as EsfState) || "Active",
|
||||
};
|
||||
})
|
||||
.filter((item): item is EsfModule => item !== undefined);
|
||||
|
||||
const drones = fitLines
|
||||
.slice(1)
|
||||
.map((line): EsfDrone | undefined => {
|
||||
const item = line.split(",");
|
||||
const flag = parseInt(item[0]);
|
||||
if (flag != 87) return undefined; // Skip anything not drones.
|
||||
|
||||
const quantity = parseInt(item[2]);
|
||||
|
||||
return {
|
||||
typeId: parseInt(item[1]),
|
||||
states: {
|
||||
Active: item[4] !== "Passive" ? quantity : 0,
|
||||
Passive: item[4] === "Passive" ? quantity : 0,
|
||||
},
|
||||
};
|
||||
})
|
||||
.filter((item): item is EsfDrone => item !== undefined);
|
||||
/* Drones can now be in the list twice, once for active and once for passive. Deduplicate. */
|
||||
const droneMap = new Map<number, EsfDrone>();
|
||||
drones.forEach((drone) => {
|
||||
if (droneMap.has(drone.typeId)) {
|
||||
droneMap.get(drone.typeId)!.states.Active += drone.states.Active;
|
||||
droneMap.get(drone.typeId)!.states.Passive += drone.states.Passive;
|
||||
} else {
|
||||
droneMap.set(drone.typeId, drone);
|
||||
}
|
||||
});
|
||||
|
||||
const cargo = fitLines
|
||||
.slice(1)
|
||||
.map((line): EsfCargo | undefined => {
|
||||
const item = line.split(",");
|
||||
const flag = parseInt(item[0]);
|
||||
if (flag != 5) return undefined; // Skip anything not cargo.
|
||||
|
||||
return {
|
||||
typeId: parseInt(item[1]),
|
||||
quantity: parseInt(item[2]),
|
||||
};
|
||||
})
|
||||
.filter((item): item is EsfCargo => item !== undefined);
|
||||
|
||||
return {
|
||||
shipTypeId: parseInt(fitHeader[0]),
|
||||
name: fitHeader[1],
|
||||
description: fitHeader[2],
|
||||
modules,
|
||||
drones: Array.from(droneMap.values()),
|
||||
cargo,
|
||||
};
|
||||
}
|
||||
67
src/hooks/ImportEveShipFitHash/DecodeEsfFitV3.tsx
Normal file
67
src/hooks/ImportEveShipFitHash/DecodeEsfFitV3.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { EsfCargo, EsfDrone, EsfFit, EsfModule, EsfSlotType, EsfState } from "@/providers/CurrentFitProvider";
|
||||
|
||||
import { decompress } from "./Decompress";
|
||||
|
||||
export async function decodeEsfFitV3(fitCompressed: string): Promise<EsfFit | null> {
|
||||
const fitEncoded = await decompress(fitCompressed);
|
||||
|
||||
const fitLines = fitEncoded.trim().split("\n");
|
||||
const fitHeader = fitLines[0].split(",");
|
||||
|
||||
const modules = fitLines
|
||||
.slice(1)
|
||||
.map((line): EsfModule | undefined => {
|
||||
const item = line.split(",");
|
||||
const type = item[0];
|
||||
if (type !== "module") return undefined;
|
||||
|
||||
return {
|
||||
slot: {
|
||||
type: item[1] as EsfSlotType,
|
||||
index: parseInt(item[2]),
|
||||
},
|
||||
typeId: parseInt(item[3]),
|
||||
state: item[4] as EsfState,
|
||||
charge: item[5] ? { typeId: parseInt(item[5]) } : undefined,
|
||||
};
|
||||
})
|
||||
.filter((item): item is EsfModule => item !== undefined);
|
||||
const drones = fitLines
|
||||
.slice(1)
|
||||
.map((line): EsfDrone | undefined => {
|
||||
const item = line.split(",");
|
||||
const type = item[0];
|
||||
if (type !== "drone") return undefined;
|
||||
|
||||
return {
|
||||
typeId: parseInt(item[1]),
|
||||
states: {
|
||||
Active: parseInt(item[2]),
|
||||
Passive: parseInt(item[3]),
|
||||
},
|
||||
};
|
||||
})
|
||||
.filter((item): item is EsfDrone => item !== undefined);
|
||||
const cargo = fitLines
|
||||
.slice(1)
|
||||
.map((line): EsfCargo | undefined => {
|
||||
const item = line.split(",");
|
||||
const type = item[0];
|
||||
if (type !== "cargo") return undefined;
|
||||
|
||||
return {
|
||||
typeId: parseInt(item[1]),
|
||||
quantity: parseInt(item[2]),
|
||||
};
|
||||
})
|
||||
.filter((item): item is EsfCargo => item !== undefined);
|
||||
|
||||
return {
|
||||
shipTypeId: parseInt(fitHeader[1]),
|
||||
name: fitHeader[2],
|
||||
description: fitHeader[3],
|
||||
modules,
|
||||
drones,
|
||||
cargo,
|
||||
};
|
||||
}
|
||||
110
src/hooks/ImportEveShipFitHash/DecodeKillMail.tsx
Normal file
110
src/hooks/ImportEveShipFitHash/DecodeKillMail.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { EsfCargo, EsfDrone, EsfFit, EsfModule } from "@/providers/CurrentFitProvider";
|
||||
import { useEveData } from "@/providers/EveDataProvider";
|
||||
|
||||
import { esiFlagToEsfSlot } from "./EsiFlags";
|
||||
|
||||
export function useFetchKillMail() {
|
||||
const eveData = useEveData();
|
||||
|
||||
return async (killMailHash: string): Promise<EsfFit | null> => {
|
||||
if (eveData === null) return null;
|
||||
|
||||
/* The hash is in the format "id/hash". */
|
||||
const [killmailId, killmailHash] = killMailHash.split("/", 2);
|
||||
|
||||
/* Fetch the killmail from ESI. */
|
||||
const response = await fetch(`https://esi.evetech.net/v1/killmails/${killmailId}/${killmailHash}/`);
|
||||
if (response.status !== 200) return null;
|
||||
|
||||
const killMail = await response.json();
|
||||
|
||||
/* Convert the killmail items to a flatter list (dropped vs destroyed). */
|
||||
type KillMailItem = {
|
||||
flag: number;
|
||||
type_id: number;
|
||||
quantity: number;
|
||||
};
|
||||
const items: KillMailItem[] = killMail.victim.items.map(
|
||||
(item: { flag: number; item_type_id: number; quantity_destroyed?: number; quantity_dropped?: number }) => {
|
||||
return {
|
||||
flag: item.flag,
|
||||
type_id: item.item_type_id,
|
||||
quantity: (item.quantity_dropped ?? 0) + (item.quantity_destroyed ?? 0),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
/* Find the modules from the item-list. */
|
||||
let modules = items
|
||||
.map((item): EsfModule | undefined => {
|
||||
if (esiFlagToEsfSlot[item.flag] === undefined) return undefined; // Skip anything not modules.
|
||||
|
||||
return {
|
||||
slot: esiFlagToEsfSlot[item.flag],
|
||||
typeId: item.type_id,
|
||||
charge: undefined,
|
||||
state: "Active",
|
||||
};
|
||||
})
|
||||
.filter((item): item is EsfModule => item !== undefined);
|
||||
|
||||
/* Find the drones from the item-list. */
|
||||
const drones = items
|
||||
.map((item): EsfDrone | undefined => {
|
||||
if (item.flag !== 87) return undefined; // Skip anything not drones.
|
||||
|
||||
return {
|
||||
typeId: item.type_id,
|
||||
states: {
|
||||
Active: item.quantity,
|
||||
Passive: 0,
|
||||
},
|
||||
};
|
||||
})
|
||||
.filter((item): item is EsfDrone => item !== undefined);
|
||||
|
||||
/* Find the cargo from the item-list. */
|
||||
const cargo = items
|
||||
.map((item): EsfCargo | undefined => {
|
||||
if (item.flag !== 5) return undefined; // Skip anything not cargo.
|
||||
|
||||
return {
|
||||
typeId: item.type_id,
|
||||
quantity: item.quantity,
|
||||
};
|
||||
})
|
||||
.filter((item): item is EsfCargo => item !== undefined);
|
||||
|
||||
/* When importing fits, it can be that the ammo is on the same slot as the module, instead as charge. Fix that. */
|
||||
modules = modules
|
||||
.map((moduleOrCharge) => {
|
||||
/* Looks for items that are charges. */
|
||||
if (eveData.typeIDs[moduleOrCharge.typeId]?.categoryID !== 8) return moduleOrCharge;
|
||||
|
||||
/* Find the module on the same slot. */
|
||||
const module = modules.find(
|
||||
(itemModule) => itemModule.slot === moduleOrCharge.slot && itemModule.typeId !== moduleOrCharge.typeId,
|
||||
);
|
||||
|
||||
if (module !== undefined) {
|
||||
/* Assign the charge to the module. */
|
||||
module.charge = {
|
||||
typeId: moduleOrCharge.typeId,
|
||||
};
|
||||
}
|
||||
|
||||
/* Remove the charge from the slot. */
|
||||
return undefined;
|
||||
})
|
||||
.filter((item): item is EsfModule => item !== undefined);
|
||||
|
||||
return {
|
||||
shipTypeId: killMail.victim.ship_type_id,
|
||||
name: `Killmail ${killmailId}`,
|
||||
description: "",
|
||||
modules,
|
||||
drones,
|
||||
cargo,
|
||||
};
|
||||
};
|
||||
}
|
||||
15
src/hooks/ImportEveShipFitHash/Decompress.tsx
Normal file
15
src/hooks/ImportEveShipFitHash/Decompress.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export async function decompress(base64compressedBytes: string): Promise<string> {
|
||||
const stream = new Blob([Uint8Array.from(atob(base64compressedBytes), (c) => c.charCodeAt(0))]).stream();
|
||||
const decompressedStream = stream.pipeThrough(new DecompressionStream("gzip"));
|
||||
const reader = decompressedStream.getReader();
|
||||
|
||||
let result = "";
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
result += String.fromCharCode.apply(null, value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
35
src/hooks/ImportEveShipFitHash/EsiFlags.tsx
Normal file
35
src/hooks/ImportEveShipFitHash/EsiFlags.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { EsfSlot } from "@/providers/CurrentFitProvider";
|
||||
|
||||
export const esiFlagToEsfSlot: Record<number, EsfSlot> = {
|
||||
11: { type: "Low", index: 1 },
|
||||
12: { type: "Low", index: 2 },
|
||||
13: { type: "Low", index: 3 },
|
||||
14: { type: "Low", index: 4 },
|
||||
15: { type: "Low", index: 5 },
|
||||
16: { type: "Low", index: 6 },
|
||||
17: { type: "Low", index: 7 },
|
||||
18: { type: "Low", index: 8 },
|
||||
19: { type: "Medium", index: 1 },
|
||||
20: { type: "Medium", index: 2 },
|
||||
21: { type: "Medium", index: 3 },
|
||||
22: { type: "Medium", index: 4 },
|
||||
23: { type: "Medium", index: 5 },
|
||||
24: { type: "Medium", index: 6 },
|
||||
25: { type: "Medium", index: 7 },
|
||||
26: { type: "Medium", index: 8 },
|
||||
27: { type: "High", index: 1 },
|
||||
28: { type: "High", index: 2 },
|
||||
29: { type: "High", index: 3 },
|
||||
30: { type: "High", index: 4 },
|
||||
31: { type: "High", index: 5 },
|
||||
32: { type: "High", index: 6 },
|
||||
33: { type: "High", index: 7 },
|
||||
34: { type: "High", index: 8 },
|
||||
92: { type: "Rig", index: 1 },
|
||||
93: { type: "Rig", index: 2 },
|
||||
94: { type: "Rig", index: 3 },
|
||||
125: { type: "SubSystem", index: 1 },
|
||||
126: { type: "SubSystem", index: 2 },
|
||||
127: { type: "SubSystem", index: 3 },
|
||||
128: { type: "SubSystem", index: 4 },
|
||||
};
|
||||
@@ -1,150 +1,12 @@
|
||||
import React from "react";
|
||||
|
||||
import { EsfFit } from "@/providers/CurrentFitProvider";
|
||||
import { useEveData } from "@/providers/EveDataProvider";
|
||||
import { useImportEft } from "../ImportEft";
|
||||
|
||||
async function decompress(base64compressedBytes: string): Promise<string> {
|
||||
const stream = new Blob([Uint8Array.from(atob(base64compressedBytes), (c) => c.charCodeAt(0))]).stream();
|
||||
const decompressedStream = stream.pipeThrough(new DecompressionStream("gzip"));
|
||||
const reader = decompressedStream.getReader();
|
||||
|
||||
let result = "";
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
result += String.fromCharCode.apply(null, value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function decodeEsfFitV1(fitCompressed: string): Promise<EsfFit | null> {
|
||||
const fitEncoded = await decompress(fitCompressed);
|
||||
|
||||
const fitLines = fitEncoded.trim().split("\n");
|
||||
const fitHeader = fitLines[0].split(",");
|
||||
|
||||
const fitItems = fitLines.slice(1).map((line) => {
|
||||
const item = line.split(",");
|
||||
return {
|
||||
flag: parseInt(item[0]),
|
||||
type_id: parseInt(item[1]),
|
||||
quantity: parseInt(item[2]),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
ship_type_id: parseInt(fitHeader[0]),
|
||||
name: fitHeader[1],
|
||||
description: fitHeader[2],
|
||||
items: fitItems,
|
||||
};
|
||||
}
|
||||
|
||||
async function decodeEsfFitV2(fitCompressed: string): Promise<EsfFit | null> {
|
||||
const fitEncoded = await decompress(fitCompressed);
|
||||
|
||||
const fitLines = fitEncoded.trim().split("\n");
|
||||
const fitHeader = fitLines[0].split(",");
|
||||
|
||||
const fitItems = fitLines.slice(1).map((line) => {
|
||||
const item = line.split(",");
|
||||
|
||||
let charge = undefined;
|
||||
if (item[3]) {
|
||||
charge = {
|
||||
type_id: parseInt(item[3]),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
flag: parseInt(item[0]),
|
||||
type_id: parseInt(item[1]),
|
||||
quantity: parseInt(item[2]),
|
||||
charge,
|
||||
state: item[4] || undefined,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
ship_type_id: parseInt(fitHeader[0]),
|
||||
name: fitHeader[1],
|
||||
description: fitHeader[2],
|
||||
items: fitItems,
|
||||
};
|
||||
}
|
||||
|
||||
function useFetchKillMail() {
|
||||
const eveData = useEveData();
|
||||
|
||||
return async (killMailHash: string): Promise<EsfFit | null> => {
|
||||
if (eveData === null) return null;
|
||||
|
||||
/* The hash is in the format "id/hash". */
|
||||
const [killmailId, killmailHash] = killMailHash.split("/", 2);
|
||||
|
||||
/* Fetch the killmail from ESI. */
|
||||
const response = await fetch(`https://esi.evetech.net/v1/killmails/${killmailId}/${killmailHash}/`);
|
||||
if (response.status !== 200) return null;
|
||||
|
||||
const killMail = await response.json();
|
||||
|
||||
/* Convert the killmail to a fit; be mindful that ammo and a module can be on the same slot. */
|
||||
let fitItems: EsfFit["items"] = killMail.victim.items.map(
|
||||
(item: { flag: number; item_type_id: number; quantity_destroyed?: number; quantity_dropped?: number }) => {
|
||||
return {
|
||||
flag: item.flag,
|
||||
type_id: item.item_type_id,
|
||||
quantity: (item.quantity_dropped ?? 0) + (item.quantity_destroyed ?? 0),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
fitItems = fitItems
|
||||
.map((item) => {
|
||||
/* When importing fits, it can be that the ammo is on the same slot as the module, instead as charge. Fix that. */
|
||||
|
||||
/* Ignore cargobay. */
|
||||
if (item.flag === 5) return item;
|
||||
/* Looks for items that are charges. */
|
||||
if (eveData.typeIDs[item.type_id]?.categoryID !== 8) return item;
|
||||
|
||||
/* Find the module on the same slot. */
|
||||
const module = fitItems.find(
|
||||
(itemModule) => itemModule.flag === item.flag && itemModule.type_id !== item.type_id,
|
||||
);
|
||||
|
||||
if (module !== undefined) {
|
||||
/* Assign the charge to the module. */
|
||||
module.charge = {
|
||||
type_id: item.type_id,
|
||||
};
|
||||
}
|
||||
|
||||
/* Remove the charge from the slot. */
|
||||
return undefined;
|
||||
})
|
||||
.filter((item): item is EsfFit["items"][number] => item !== undefined);
|
||||
|
||||
return {
|
||||
ship_type_id: killMail.victim.ship_type_id,
|
||||
name: `Killmail ${killmailId}`,
|
||||
description: "",
|
||||
items: fitItems,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function useDecodeEft() {
|
||||
const importEft = useImportEft();
|
||||
|
||||
return async (eftCompressed: string): Promise<EsfFit | null> => {
|
||||
const eft = await decompress(eftCompressed);
|
||||
return importEft(eft);
|
||||
};
|
||||
}
|
||||
import { decodeEsfFitV1 } from "./DecodeEsfFitV1";
|
||||
import { decodeEsfFitV2 } from "./DecodeEsfFitV2";
|
||||
import { decodeEsfFitV3 } from "./DecodeEsfFitV3";
|
||||
import { useDecodeEft } from "./DecodeEft";
|
||||
import { useFetchKillMail } from "./DecodeKillMail";
|
||||
|
||||
/**
|
||||
* Convert a hash from window.location.hash to an ESI fit.
|
||||
@@ -168,6 +30,9 @@ export function useImportEveShipFitHash() {
|
||||
case "v2":
|
||||
fit = await decodeEsfFitV2(fitEncoded);
|
||||
break;
|
||||
case "v3":
|
||||
fit = await decodeEsfFitV3(fitEncoded);
|
||||
break;
|
||||
case "killmail":
|
||||
fit = await fetchKillMail(fitEncoded);
|
||||
break;
|
||||
@@ -193,13 +58,20 @@ export const ImportEveShipFitHash = (props: ImportEveShipFitHashProps) => {
|
||||
const importEveShipFitHash = useImportEveShipFitHash();
|
||||
const [fit, setFit] = React.useState<EsfFit | null | undefined>(undefined);
|
||||
|
||||
const importEveShipFitHashRef = React.useRef(importEveShipFitHash);
|
||||
importEveShipFitHashRef.current = importEveShipFitHash;
|
||||
|
||||
React.useEffect(() => {
|
||||
async function getFit(fitHash: string) {
|
||||
setFit(await importEveShipFitHash(fitHash));
|
||||
setFit(await importEveShipFitHashRef.current(fitHash));
|
||||
}
|
||||
|
||||
getFit(props.fitHash);
|
||||
}, [props.fitHash, importEveShipFitHash]);
|
||||
}, [props.fitHash]);
|
||||
|
||||
if (props.fitHash === undefined) {
|
||||
return <div>Select a fit hash.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export { useImportEveShipFitHash } from "./ImportEveShipFitHash";
|
||||
export { esiFlagToEsfSlot } from "./EsiFlags";
|
||||
|
||||
@@ -15,6 +15,7 @@ export * from "./hooks/Clipboard";
|
||||
export * from "./hooks/ExportEft";
|
||||
export * from "./hooks/ExportEveShipFitHash";
|
||||
export * from "./hooks/ImportEft";
|
||||
export * from "./hooks/ImportEsiFitting";
|
||||
export * from "./hooks/ImportEveShipFitHash";
|
||||
export * from "./hooks/LocalStorage";
|
||||
export * from "./providers/Characters";
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React from "react";
|
||||
|
||||
import { Character } from "@/providers/CurrentCharacterProvider";
|
||||
import { EsfFit } from "@/providers/CurrentFitProvider";
|
||||
import { useEveData } from "@/providers/EveDataProvider";
|
||||
import { useImportEsiFitting } from "@/hooks/ImportEsiFitting";
|
||||
import { useLocalStorage } from "@/hooks/LocalStorage";
|
||||
|
||||
import { CharactersContext, useCharactersInternal } from "../CharactersContext";
|
||||
@@ -61,6 +63,7 @@ const createEmptyCharacter = (name: string): Character => {
|
||||
export const EsiCharactersProvider = (props: EsiProps) => {
|
||||
const characters = useCharactersInternal();
|
||||
const eveData = useEveData();
|
||||
const importEsiFitting = useImportEsiFitting();
|
||||
|
||||
const [firstLoad, setFirstLoad] = React.useState(true);
|
||||
|
||||
@@ -134,8 +137,8 @@ export const EsiCharactersProvider = (props: EsiProps) => {
|
||||
|
||||
const skills = await getSkills(characterId, accessToken);
|
||||
if (skills === undefined) return;
|
||||
const fittings = await getCharFittings(characterId, accessToken);
|
||||
if (fittings === undefined) return;
|
||||
const esiFittings = await getCharFittings(characterId, accessToken);
|
||||
if (esiFittings === undefined) return;
|
||||
|
||||
/* Ensure all skills are set; also those not learnt. */
|
||||
for (const typeId in eveData.typeIDs) {
|
||||
@@ -144,6 +147,13 @@ export const EsiCharactersProvider = (props: EsiProps) => {
|
||||
skills[typeId] = 0;
|
||||
}
|
||||
|
||||
/* Convert all fittings to ESF format. */
|
||||
const fittings = esiFittings
|
||||
.map((fitting) => {
|
||||
return importEsiFitting(fitting);
|
||||
})
|
||||
.filter((fitting): fitting is EsfFit => fitting !== null);
|
||||
|
||||
setEsiCharacters((oldEsiCharacters: Record<string, Character>) => {
|
||||
return {
|
||||
...oldEsiCharacters,
|
||||
@@ -155,7 +165,7 @@ export const EsiCharactersProvider = (props: EsiProps) => {
|
||||
};
|
||||
});
|
||||
},
|
||||
[setEsiCharacters, ensureAccessToken, eveData],
|
||||
[setEsiCharacters, importEsiFitting, ensureAccessToken, eveData],
|
||||
);
|
||||
|
||||
if (firstLoad) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EsfFit } from "@/providers/CurrentFitProvider";
|
||||
import { EsiFit } from "@/hooks/ImportEsiFitting";
|
||||
|
||||
export async function getCharFittings(characterId: string, accessToken: string): Promise<EsfFit[] | undefined> {
|
||||
export async function getCharFittings(characterId: string, accessToken: string): Promise<EsiFit[] | undefined> {
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(`https://esi.evetech.net/v1/characters/${characterId}/fittings/`, {
|
||||
|
||||
@@ -1,21 +1,44 @@
|
||||
import React from "react";
|
||||
|
||||
export type State = "Passive" | "Online" | "Active" | "Overload";
|
||||
export type EsfState = "Passive" | "Online" | "Active" | "Overload";
|
||||
export type EsfSlotType = "High" | "Medium" | "Low" | "Rig" | "SubSystem";
|
||||
|
||||
export interface EsfCharge {
|
||||
typeId: number;
|
||||
}
|
||||
|
||||
export interface EsfSlot {
|
||||
type: EsfSlotType;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface EsfModule {
|
||||
typeId: number;
|
||||
slot: EsfSlot;
|
||||
state: EsfState;
|
||||
charge?: EsfCharge;
|
||||
}
|
||||
|
||||
export interface EsfDrone {
|
||||
typeId: number;
|
||||
states: {
|
||||
Passive: number;
|
||||
Active: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface EsfCargo {
|
||||
typeId: number;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface EsfFit {
|
||||
name: string;
|
||||
description: string;
|
||||
ship_type_id: number;
|
||||
items: {
|
||||
type_id: number;
|
||||
quantity: number;
|
||||
flag: number;
|
||||
charge?: {
|
||||
type_id: number;
|
||||
};
|
||||
/* State defaults to "Active" if not set. */
|
||||
state?: State | string;
|
||||
}[];
|
||||
shipTypeId: number;
|
||||
modules: EsfModule[];
|
||||
drones: EsfDrone[];
|
||||
cargo: EsfCargo[];
|
||||
}
|
||||
|
||||
interface CurrentFit {
|
||||
|
||||
@@ -1,2 +1,11 @@
|
||||
export { useCurrentFit, CurrentFitProvider } from "./CurrentFitProvider";
|
||||
export type { EsfFit, State } from "./CurrentFitProvider";
|
||||
export type {
|
||||
EsfCargo,
|
||||
EsfCharge,
|
||||
EsfDrone,
|
||||
EsfFit,
|
||||
EsfModule,
|
||||
EsfSlot,
|
||||
EsfSlotType,
|
||||
EsfState,
|
||||
} from "./CurrentFitProvider";
|
||||
|
||||
41
src/providers/DogmaEngineProvider/DataTypes.tsx
Normal file
41
src/providers/DogmaEngineProvider/DataTypes.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
export interface CalculationItemAttributeEffect {
|
||||
operator: string;
|
||||
penalty: boolean;
|
||||
source: "Ship" | "Char" | "Structure" | "Target" | { Item?: number; Charge?: number; Skill?: number };
|
||||
source_category: string;
|
||||
source_attribute_id: number;
|
||||
}
|
||||
|
||||
export interface CalculationItemAttribute {
|
||||
base_value: number;
|
||||
value: number;
|
||||
effects: CalculationItemAttributeEffect[];
|
||||
}
|
||||
|
||||
export type CalculationSlotType = "High" | "Medium" | "Low" | "Rig" | "SubSystem" | "DroneBay" | "Charge";
|
||||
|
||||
export interface CalculationSlot {
|
||||
type: CalculationSlotType;
|
||||
index: number | undefined;
|
||||
}
|
||||
|
||||
export type CalculationState = "Passive" | "Online" | "Active" | "Overload" | "Target" | "Area" | "Dungeon" | "System";
|
||||
|
||||
export interface CalculationItem {
|
||||
type_id: number;
|
||||
slot: CalculationSlot;
|
||||
charge: CalculationItem | undefined;
|
||||
state: CalculationState;
|
||||
max_state: CalculationState;
|
||||
attributes: Map<number, CalculationItemAttribute>;
|
||||
effects: number[];
|
||||
}
|
||||
|
||||
export interface Calculation {
|
||||
hull: CalculationItem;
|
||||
items: CalculationItem[];
|
||||
skills: CalculationItem[];
|
||||
char: CalculationItem;
|
||||
structure: CalculationItem;
|
||||
target: CalculationItem;
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import React from "react";
|
||||
|
||||
import type { init, calculate } from "@eveshipfit/dogma-engine";
|
||||
|
||||
import {
|
||||
DogmaAttribute,
|
||||
DogmaEffect,
|
||||
@@ -10,10 +8,14 @@ import {
|
||||
TypeID,
|
||||
useEveData,
|
||||
} from "@/providers/EveDataProvider";
|
||||
import { EsfFit } from "@/providers/CurrentFitProvider";
|
||||
import { Skills } from "@/providers/CurrentCharacterProvider";
|
||||
import { calculate } from "@eveshipfit/dogma-engine";
|
||||
|
||||
import { Calculation } from "./DataTypes";
|
||||
|
||||
interface DogmaEngine {
|
||||
init: typeof init;
|
||||
calculate: typeof calculate;
|
||||
calculate: (fit: EsfFit, skills: Skills) => Calculation;
|
||||
}
|
||||
|
||||
const DogmaEngineContext = React.createContext<DogmaEngine | null>(null);
|
||||
@@ -60,7 +62,9 @@ export const DogmaEngineProvider = (props: DogmaEngineProps) => {
|
||||
|
||||
const [firstLoad, setFirstLoad] = React.useState(true);
|
||||
|
||||
const [dogmaEngine, setDogmaEngine] = React.useState<DogmaEngine | null>(null);
|
||||
const [dogmaEngine, setDogmaEngine] = React.useState<{
|
||||
calculate: typeof calculate;
|
||||
} | null>(null);
|
||||
|
||||
if (firstLoad) {
|
||||
setFirstLoad(false);
|
||||
@@ -90,7 +94,44 @@ export const DogmaEngineProvider = (props: DogmaEngineProps) => {
|
||||
}
|
||||
|
||||
const contextValue = React.useMemo(() => {
|
||||
return eveData === null ? null : dogmaEngine;
|
||||
if (eveData === null || dogmaEngine === null) return null;
|
||||
|
||||
return {
|
||||
calculate: (fit: EsfFit, skills: Skills): Calculation => {
|
||||
const dogmaFit = {
|
||||
ship_type_id: fit.shipTypeId,
|
||||
modules: fit.modules.map((module) => ({
|
||||
type_id: module.typeId,
|
||||
slot: module.slot,
|
||||
state: module.state,
|
||||
charge:
|
||||
module.charge === undefined
|
||||
? undefined
|
||||
: {
|
||||
type_id: module.charge.typeId,
|
||||
},
|
||||
})),
|
||||
drones: fit.drones.flatMap((drone) => {
|
||||
const drones = [];
|
||||
for (let i = 0; i < drone.states.Active; i++) {
|
||||
drones.push({
|
||||
type_id: drone.typeId,
|
||||
state: "Active",
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < drone.states.Passive; i++) {
|
||||
drones.push({
|
||||
type_id: drone.typeId,
|
||||
state: "Passive",
|
||||
});
|
||||
}
|
||||
return drones;
|
||||
}),
|
||||
};
|
||||
|
||||
return dogmaEngine.calculate(dogmaFit, skills);
|
||||
},
|
||||
};
|
||||
}, [eveData, dogmaEngine]);
|
||||
|
||||
return <DogmaEngineContext.Provider value={contextValue}>{props.children}</DogmaEngineContext.Provider>;
|
||||
|
||||
@@ -1 +1,10 @@
|
||||
export { DogmaEngineProvider, useDogmaEngine } from "./DogmaEngineProvider";
|
||||
export type {
|
||||
Calculation,
|
||||
CalculationItem,
|
||||
CalculationItemAttribute,
|
||||
CalculationItemAttributeEffect,
|
||||
CalculationSlot,
|
||||
CalculationSlotType,
|
||||
CalculationState,
|
||||
} from "./DataTypes";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
import { EsfFit, State, useCurrentFit } from "@/providers/CurrentFitProvider";
|
||||
import { StatisticsSlotType, useStatistics } from "@/providers/StatisticsProvider";
|
||||
import { EsfFit, EsfSlot, EsfSlotType, EsfState, useCurrentFit } from "@/providers/CurrentFitProvider";
|
||||
import { useStatistics } from "@/providers/StatisticsProvider";
|
||||
import { useEveData } from "@/providers/EveDataProvider";
|
||||
|
||||
interface FitManager {
|
||||
@@ -13,21 +13,21 @@ interface FitManager {
|
||||
setName: (name: string) => void;
|
||||
|
||||
/** Add an item (module, charge, drone) to the fit. */
|
||||
addItem: (typeId: number, slot: StatisticsSlotType | "droneBay" | "charge") => void;
|
||||
addItem: (typeId: number, slot: EsfSlotType | "DroneBay" | "Charge") => void;
|
||||
|
||||
/** Set a module in a slot. */
|
||||
setModule: (flag: number, typeId: number) => void;
|
||||
setModule: (slot: EsfSlot, typeId: number) => void;
|
||||
/** Set the state of a module. */
|
||||
setModuleState: (flag: number, state: State) => void;
|
||||
setModuleState: (slot: EsfSlot, state: EsfState) => void;
|
||||
/** Remove a module from a slot. */
|
||||
removeModule: (flag: number) => void;
|
||||
removeModule: (slot: EsfSlot) => void;
|
||||
/** Swap two modules. */
|
||||
swapModule: (flagA: number, flagB: number) => void;
|
||||
swapModule: (slotA: EsfSlot, slotB: EsfSlot) => void;
|
||||
|
||||
/** Set a charge in a module. */
|
||||
setCharge: (flag: number, typeId: number) => void;
|
||||
setCharge: (slot: EsfSlot, typeId: number) => void;
|
||||
/** Remove a charge from a module. */
|
||||
removeCharge: (flag: number) => void;
|
||||
removeCharge: (slot: EsfSlot) => void;
|
||||
|
||||
/** Activate N drones of a given type. */
|
||||
activateDrones: (typeId: number, amount: number) => void;
|
||||
@@ -35,16 +35,6 @@ interface FitManager {
|
||||
removeDrones: (typeId: number) => void;
|
||||
}
|
||||
|
||||
const slotStart: Record<StatisticsSlotType, number> = {
|
||||
hislot: 27,
|
||||
medslot: 19,
|
||||
lowslot: 11,
|
||||
subsystem: 125,
|
||||
rig: 92,
|
||||
launcher: 0,
|
||||
turret: 0,
|
||||
};
|
||||
|
||||
const FitManagerContext = React.createContext<FitManager>({
|
||||
setFit: () => {},
|
||||
createNewFit: () => {},
|
||||
@@ -112,12 +102,14 @@ export const FitManagerProvider = (props: FitManagerProps) => {
|
||||
setFit({
|
||||
name: "Unnamed Fit",
|
||||
description: "",
|
||||
ship_type_id: typeId,
|
||||
items: [],
|
||||
shipTypeId: typeId,
|
||||
modules: [],
|
||||
drones: [],
|
||||
cargo: [],
|
||||
});
|
||||
},
|
||||
setName: (name: string) => {
|
||||
setFit((oldFit) => {
|
||||
setFit((oldFit: EsfFit | null): EsfFit | null => {
|
||||
if (oldFit === null) return null;
|
||||
|
||||
return {
|
||||
@@ -127,30 +119,30 @@ export const FitManagerProvider = (props: FitManagerProps) => {
|
||||
});
|
||||
},
|
||||
|
||||
addItem: (typeId: number, slot: StatisticsSlotType | "droneBay" | "charge") => {
|
||||
setFit((oldFit) => {
|
||||
addItem: (typeId: number, slot: EsfSlotType | "DroneBay" | "Charge") => {
|
||||
setFit((oldFit: EsfFit | null): EsfFit | null => {
|
||||
if (oldFit === null) return null;
|
||||
|
||||
if (slot === "charge") {
|
||||
if (slot === "Charge") {
|
||||
const chargeSize =
|
||||
eveData.typeDogma[typeId]?.dogmaAttributes.find(
|
||||
(attr) => attr.attributeID === eveData.attributeMapping?.chargeSize,
|
||||
)?.value ?? -1;
|
||||
const groupID = eveData.typeIDs[typeId]?.groupID ?? -1;
|
||||
|
||||
const newItems = [];
|
||||
for (let item of oldFit.items) {
|
||||
const newModules = [];
|
||||
for (let module of oldFit.modules) {
|
||||
/* If the module has size restrictions, ensure the charge matches. */
|
||||
const moduleChargeSize = eveData.typeDogma[item.type_id]?.dogmaAttributes.find(
|
||||
const moduleChargeSize = eveData.typeDogma[module.typeId]?.dogmaAttributes.find(
|
||||
(attr) => attr.attributeID === eveData.attributeMapping.chargeSize,
|
||||
)?.value;
|
||||
if (moduleChargeSize !== undefined && moduleChargeSize !== chargeSize) {
|
||||
newItems.push(item);
|
||||
newModules.push(module);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check if the charge fits in this module; if so, assign it. */
|
||||
for (const attr of eveData.typeDogma[item.type_id]?.dogmaAttributes ?? []) {
|
||||
for (const attr of eveData.typeDogma[module.typeId]?.dogmaAttributes ?? []) {
|
||||
switch (attr.attributeID) {
|
||||
case eveData.attributeMapping.chargeGroup1:
|
||||
case eveData.attributeMapping.chargeGroup2:
|
||||
@@ -158,10 +150,10 @@ export const FitManagerProvider = (props: FitManagerProps) => {
|
||||
case eveData.attributeMapping.chargeGroup4:
|
||||
case eveData.attributeMapping.chargeGroup5:
|
||||
if (attr.value === groupID) {
|
||||
item = {
|
||||
...item,
|
||||
module = {
|
||||
...module,
|
||||
charge: {
|
||||
type_id: typeId,
|
||||
typeId,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -169,73 +161,92 @@ export const FitManagerProvider = (props: FitManagerProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
newItems.push(item);
|
||||
newModules.push(module);
|
||||
}
|
||||
|
||||
return {
|
||||
...oldFit,
|
||||
items: newItems,
|
||||
modules: newModules,
|
||||
};
|
||||
}
|
||||
|
||||
let flag = undefined;
|
||||
if (slot === "DroneBay") {
|
||||
const drone = oldFit.drones.find((item) => item.typeId === typeId);
|
||||
|
||||
if (drone !== undefined) {
|
||||
drone.states.Active++;
|
||||
return oldFit;
|
||||
}
|
||||
|
||||
return {
|
||||
...oldFit,
|
||||
drones: [
|
||||
...oldFit.drones,
|
||||
{
|
||||
typeId: typeId,
|
||||
states: {
|
||||
Active: 1,
|
||||
Passive: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/* Find the first free slot for that slot-type. */
|
||||
if (slot !== "droneBay") {
|
||||
const slotsAvailable = statistics?.slots[slot] ?? 0;
|
||||
for (let i = slotStart[slot]; i < slotStart[slot] + slotsAvailable; i++) {
|
||||
if (oldFit.items.find((item) => item.flag === i) !== undefined) continue;
|
||||
let index = undefined;
|
||||
const slotsAvailable = statistics?.slots[slot] ?? 0;
|
||||
for (let i = 1; i <= slotsAvailable; i++) {
|
||||
if (oldFit.modules.find((item) => item.slot.type === slot && item.slot.index === i) !== undefined) continue;
|
||||
|
||||
flag = i;
|
||||
break;
|
||||
}
|
||||
console.log(flag);
|
||||
} else {
|
||||
flag = 87;
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Couldn't find a free slot. */
|
||||
if (flag === undefined) return oldFit;
|
||||
if (index === undefined) return oldFit;
|
||||
|
||||
return {
|
||||
...oldFit,
|
||||
items: [
|
||||
...oldFit.items,
|
||||
modules: [
|
||||
...oldFit.modules,
|
||||
{
|
||||
flag: flag,
|
||||
type_id: typeId,
|
||||
quantity: 1,
|
||||
slot: {
|
||||
type: slot,
|
||||
index: index,
|
||||
},
|
||||
typeId: typeId,
|
||||
charge: undefined,
|
||||
state: "Active",
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
setModule: (flag: number, typeId: number) => {
|
||||
setFit((oldFit) => {
|
||||
setModule: (slot: EsfSlot, typeId: number) => {
|
||||
setFit((oldFit: EsfFit | null): EsfFit | null => {
|
||||
if (oldFit === null) return null;
|
||||
|
||||
const newItems = oldFit.items
|
||||
.filter((item) => item.flag !== flag)
|
||||
.concat({ flag: flag, type_id: typeId, quantity: 1 });
|
||||
|
||||
return {
|
||||
...oldFit,
|
||||
items: newItems,
|
||||
modules: oldFit.modules
|
||||
.filter((item) => item.slot.type !== slot.type || item.slot.index !== slot.index)
|
||||
.concat({ slot, typeId, state: "Active" }),
|
||||
};
|
||||
});
|
||||
},
|
||||
setModuleState: (flag: number, state: State) => {
|
||||
setFit((oldFit) => {
|
||||
setModuleState: (slot: EsfSlot, state: EsfState) => {
|
||||
setFit((oldFit: EsfFit | null): EsfFit | null => {
|
||||
if (oldFit === null) return null;
|
||||
|
||||
return {
|
||||
...oldFit,
|
||||
items: oldFit?.items.map((item) => {
|
||||
if (item.flag === flag) {
|
||||
modules: oldFit.modules.map((item) => {
|
||||
if (item.slot.type === slot.type && item.slot.index === slot.index) {
|
||||
return {
|
||||
...item,
|
||||
state: state,
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -244,70 +255,68 @@ export const FitManagerProvider = (props: FitManagerProps) => {
|
||||
};
|
||||
});
|
||||
},
|
||||
removeModule: (flag: number) => {
|
||||
setFit((oldFit) => {
|
||||
removeModule: (slot: EsfSlot) => {
|
||||
setFit((oldFit: EsfFit | null): EsfFit | null => {
|
||||
if (oldFit === null) return null;
|
||||
|
||||
return {
|
||||
...oldFit,
|
||||
items: oldFit.items.filter((item) => item.flag !== flag),
|
||||
modules: oldFit.modules.filter((item) => item.slot.type !== slot.type || item.slot.index !== slot.index),
|
||||
};
|
||||
});
|
||||
},
|
||||
swapModule: (flagA: number, flagB: number) => {
|
||||
setFit((oldFit) => {
|
||||
swapModule: (slotA: EsfSlot, slotB: EsfSlot) => {
|
||||
setFit((oldFit: EsfFit | null): EsfFit | null => {
|
||||
if (oldFit === null) return null;
|
||||
|
||||
const newItems = [...oldFit.items];
|
||||
const modules = [...oldFit.modules];
|
||||
|
||||
const fromItemIndex = newItems.findIndex((item) => item.flag === flagA);
|
||||
const fromItem = newItems[fromItemIndex];
|
||||
const moduleA = modules.find((item) => item.slot.type === slotA.type && item.slot.index === slotA.index);
|
||||
const moduleB = modules.find((item) => item.slot.type === slotB.type && item.slot.index === slotB.index);
|
||||
|
||||
const toItemIndex = newItems.findIndex((item) => item.flag === flagB);
|
||||
const toItem = newItems[toItemIndex];
|
||||
if (moduleA !== undefined) {
|
||||
moduleA.slot.index = slotB.index;
|
||||
}
|
||||
|
||||
fromItem.flag = flagB;
|
||||
|
||||
if (toItem !== undefined) {
|
||||
/* Target slot is non-empty, swap items. */
|
||||
toItem.flag = flagA;
|
||||
if (moduleB !== undefined) {
|
||||
moduleB.slot.index = slotA.index;
|
||||
}
|
||||
|
||||
return {
|
||||
...oldFit,
|
||||
items: newItems,
|
||||
modules,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
setCharge: (flag: number, typeId: number) => {
|
||||
setCharge: (slot: EsfSlot, typeId: number) => {
|
||||
const chargeSize =
|
||||
eveData.typeDogma[typeId]?.dogmaAttributes.find(
|
||||
(attr) => attr.attributeID === eveData.attributeMapping?.chargeSize,
|
||||
)?.value ?? -1;
|
||||
const groupID = eveData.typeIDs[typeId]?.groupID ?? -1;
|
||||
|
||||
setFit((oldFit) => {
|
||||
setFit((oldFit: EsfFit | null): EsfFit | null => {
|
||||
if (oldFit === null) return null;
|
||||
|
||||
const newItems = [];
|
||||
const modules = [];
|
||||
|
||||
for (let item of oldFit.items) {
|
||||
for (let module of oldFit.modules) {
|
||||
/* If the module has size restrictions, ensure the charge matches. */
|
||||
const moduleChargeSize = eveData.typeDogma[item.type_id]?.dogmaAttributes.find(
|
||||
const moduleChargeSize = eveData.typeDogma[module.typeId]?.dogmaAttributes.find(
|
||||
(attr) => attr.attributeID === eveData.attributeMapping.chargeSize,
|
||||
)?.value;
|
||||
if (moduleChargeSize !== undefined && moduleChargeSize !== chargeSize) {
|
||||
newItems.push(item);
|
||||
modules.push(module);
|
||||
continue;
|
||||
}
|
||||
if (item.flag !== flag) {
|
||||
newItems.push(item);
|
||||
if (module.slot.type !== slot.type || module.slot.index !== slot.index) {
|
||||
modules.push(module);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check if the charge fits in this module; if so, assign it. */
|
||||
for (const attr of eveData.typeDogma[item.type_id]?.dogmaAttributes ?? []) {
|
||||
for (const attr of eveData.typeDogma[module.typeId]?.dogmaAttributes ?? []) {
|
||||
switch (attr.attributeID) {
|
||||
case eveData.attributeMapping.chargeGroup1:
|
||||
case eveData.attributeMapping.chargeGroup2:
|
||||
@@ -315,10 +324,10 @@ export const FitManagerProvider = (props: FitManagerProps) => {
|
||||
case eveData.attributeMapping.chargeGroup4:
|
||||
case eveData.attributeMapping.chargeGroup5:
|
||||
if (attr.value === groupID) {
|
||||
item = {
|
||||
...item,
|
||||
module = {
|
||||
...module,
|
||||
charge: {
|
||||
type_id: typeId,
|
||||
typeId: typeId,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -326,23 +335,23 @@ export const FitManagerProvider = (props: FitManagerProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
newItems.push(item);
|
||||
modules.push(module);
|
||||
}
|
||||
|
||||
return {
|
||||
...oldFit,
|
||||
items: newItems,
|
||||
modules,
|
||||
};
|
||||
});
|
||||
},
|
||||
removeCharge: (flag: number) => {
|
||||
setFit((oldFit) => {
|
||||
removeCharge: (slot: EsfSlot) => {
|
||||
setFit((oldFit: EsfFit | null): EsfFit | null => {
|
||||
if (oldFit === null) return null;
|
||||
|
||||
return {
|
||||
...oldFit,
|
||||
items: oldFit.items.map((item) => {
|
||||
if (item.flag === flag) {
|
||||
modules: oldFit.modules.map((item) => {
|
||||
if (item.slot.type === slot.type && item.slot.index === slot.index) {
|
||||
return {
|
||||
...item,
|
||||
charge: undefined,
|
||||
@@ -356,19 +365,19 @@ export const FitManagerProvider = (props: FitManagerProps) => {
|
||||
},
|
||||
|
||||
activateDrones: (typeId: number, active: number) => {
|
||||
setFit((oldFit) => {
|
||||
setFit((oldFit: EsfFit | null): EsfFit | null => {
|
||||
if (oldFit === null) return null;
|
||||
|
||||
/* Find the amount of drones in the current fit. */
|
||||
const count = oldFit.items
|
||||
.filter((item) => item.flag === 87 && item.type_id === typeId)
|
||||
.reduce((acc, item) => acc + item.quantity, 0);
|
||||
const count = oldFit.drones
|
||||
.filter((item) => item.typeId === typeId)
|
||||
.reduce((acc, item) => acc + item.states.Active + item.states.Passive, 0);
|
||||
if (count === 0) return oldFit;
|
||||
|
||||
/* If we request the same amount of active than we had, assume we want to deactivate the current. */
|
||||
const currentActive = oldFit.items
|
||||
.filter((item) => item.flag === 87 && item.type_id === typeId && item.state === "Active")
|
||||
.reduce((acc, item) => acc + item.quantity, 0);
|
||||
const currentActive = oldFit.drones
|
||||
.filter((item) => item.typeId === typeId)
|
||||
.reduce((acc, item) => acc + item.states.Active, 0);
|
||||
if (currentActive === active) {
|
||||
active = active - 1;
|
||||
}
|
||||
@@ -377,41 +386,30 @@ export const FitManagerProvider = (props: FitManagerProps) => {
|
||||
active = Math.min(count, active);
|
||||
|
||||
/* Remove all drones of this type. */
|
||||
const newItems = oldFit.items.filter((item) => item.flag !== 87 || item.type_id !== typeId);
|
||||
const drones = oldFit.drones.filter((item) => item.typeId !== typeId);
|
||||
const passive = count - active;
|
||||
|
||||
/* Add the active drones. */
|
||||
if (active > 0) {
|
||||
newItems.push({
|
||||
flag: 87,
|
||||
type_id: typeId,
|
||||
quantity: active,
|
||||
state: "Active",
|
||||
});
|
||||
}
|
||||
|
||||
/* Add the passive drones. */
|
||||
if (active < count) {
|
||||
newItems.push({
|
||||
flag: 87,
|
||||
type_id: typeId,
|
||||
quantity: count - active,
|
||||
state: "Passive",
|
||||
});
|
||||
}
|
||||
drones.push({
|
||||
typeId,
|
||||
states: {
|
||||
Active: active,
|
||||
Passive: passive,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
...oldFit,
|
||||
items: newItems,
|
||||
drones,
|
||||
};
|
||||
});
|
||||
},
|
||||
removeDrones: (typeId: number) => {
|
||||
setFit((oldFit) => {
|
||||
setFit((oldFit: EsfFit | null): EsfFit | null => {
|
||||
if (oldFit === null) return null;
|
||||
|
||||
return {
|
||||
...oldFit,
|
||||
items: oldFit.items.filter((item) => item.flag !== 87 || item.type_id !== typeId),
|
||||
drones: oldFit.drones.filter((item) => item.typeId !== typeId),
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
71
src/providers/LocalFitsProvider/ConvertV2.tsx
Normal file
71
src/providers/LocalFitsProvider/ConvertV2.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { esiFlagToEsfSlot } from "@/hooks/ImportEveShipFitHash";
|
||||
import { EsfCargo, EsfDrone, EsfFit, EsfModule } from "../CurrentFitProvider/CurrentFitProvider";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const ConvertV2 = (fit: any) => {
|
||||
const modules = fit.items
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.map((item: any): EsfModule | undefined => {
|
||||
if (esiFlagToEsfSlot[item.flag] === undefined) return undefined;
|
||||
|
||||
return {
|
||||
typeId: item.type_id,
|
||||
slot: esiFlagToEsfSlot[item.flag],
|
||||
state: item.state ?? "Active",
|
||||
charge:
|
||||
item.charge === undefined
|
||||
? undefined
|
||||
: {
|
||||
typeId: item.charge.type_id,
|
||||
},
|
||||
};
|
||||
})
|
||||
.filter((item: EsfModule | undefined) => item !== undefined) as EsfModule[];
|
||||
|
||||
const drones = fit.items
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.map((item: any): EsfDrone | undefined => {
|
||||
if (item.flag !== 87) return undefined;
|
||||
|
||||
return {
|
||||
typeId: item.type_id,
|
||||
states: {
|
||||
Active: item.state !== "Passive" ? item.quantity : 0,
|
||||
Passive: item.state === "Passive" ? item.quantity : 0,
|
||||
},
|
||||
};
|
||||
})
|
||||
.filter((item: EsfDrone | undefined) => item !== undefined) as EsfDrone[];
|
||||
/* Drones can now be in the list twice, once for active and once for passive. Deduplicate. */
|
||||
const droneMap = new Map<number, EsfDrone>();
|
||||
drones.forEach((drone) => {
|
||||
if (droneMap.has(drone.typeId)) {
|
||||
droneMap.get(drone.typeId)!.states.Active += drone.states.Active;
|
||||
droneMap.get(drone.typeId)!.states.Passive += drone.states.Passive;
|
||||
} else {
|
||||
droneMap.set(drone.typeId, drone);
|
||||
}
|
||||
});
|
||||
|
||||
const cargo = fit.items
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.map((item: any): EsfCargo | undefined => {
|
||||
if (item.flag !== 5) return undefined;
|
||||
|
||||
return {
|
||||
typeId: item.type_id,
|
||||
quantity: item.quantity,
|
||||
};
|
||||
})
|
||||
.filter((item: EsfCargo | undefined) => item !== undefined) as EsfCargo[];
|
||||
|
||||
const newFit: EsfFit = {
|
||||
name: fit.name,
|
||||
shipTypeId: fit.ship_type_id,
|
||||
description: fit.description,
|
||||
modules,
|
||||
drones,
|
||||
cargo,
|
||||
};
|
||||
return newFit;
|
||||
};
|
||||
@@ -19,7 +19,7 @@ const TestStory = () => {
|
||||
{Object.values(localFits.fittings).map((fit) => {
|
||||
return (
|
||||
<div key={fit.name}>
|
||||
{fit.name} - {Object.keys(fit.items).length} items
|
||||
{fit.name} - {Object.keys(fit.modules).length} modules
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -3,6 +3,8 @@ import React from "react";
|
||||
import { useLocalStorage } from "@/hooks/LocalStorage";
|
||||
import { EsfFit } from "@/providers/CurrentFitProvider";
|
||||
|
||||
import { ConvertV2 } from "./ConvertV2";
|
||||
|
||||
interface LocalFits {
|
||||
fittings: EsfFit[];
|
||||
addFit: (fit: EsfFit) => void;
|
||||
@@ -29,6 +31,27 @@ interface LocalFitsProps {
|
||||
*/
|
||||
export const LocalFitsProvider = (props: LocalFitsProps) => {
|
||||
const [localFitsValue, setLocalFitsValue] = useLocalStorage<EsfFit[]>("fits", []);
|
||||
const [firstLoad, setFirstLoad] = React.useState(true);
|
||||
|
||||
if (firstLoad) {
|
||||
setFirstLoad(false);
|
||||
|
||||
let hasOldFits = false;
|
||||
for (const index in localFitsValue) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const oldFit = localFitsValue[index] as any;
|
||||
|
||||
/* If the fit has the field "ship_type_id", it is an old fit. Convert it to the new format. */
|
||||
if (oldFit.ship_type_id !== undefined) {
|
||||
localFitsValue[index] = ConvertV2(oldFit);
|
||||
hasOldFits = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasOldFits) {
|
||||
setLocalFitsValue(localFitsValue);
|
||||
}
|
||||
}
|
||||
|
||||
const addFit = React.useCallback(
|
||||
(fit: EsfFit) => {
|
||||
|
||||
@@ -3,16 +3,11 @@ import React from "react";
|
||||
|
||||
import { fitArgType } from "../../../.storybook/fits";
|
||||
|
||||
import {
|
||||
CurrentCharacterProvider,
|
||||
CurrentFitProvider,
|
||||
DefaultCharactersProvider,
|
||||
DogmaEngineProvider,
|
||||
EsfFit,
|
||||
EsiCharactersProvider,
|
||||
EveDataProvider,
|
||||
useCurrentFit,
|
||||
} from "@/providers";
|
||||
import { CurrentFitProvider, EsfFit, useCurrentFit } from "../CurrentFitProvider";
|
||||
import { EveDataProvider } from "../EveDataProvider";
|
||||
import { DogmaEngineProvider } from "../DogmaEngineProvider";
|
||||
import { DefaultCharactersProvider, EsiCharactersProvider } from "../Characters";
|
||||
import { CurrentCharacterProvider } from "../CurrentCharacterProvider";
|
||||
|
||||
import { StatisticsProvider, useStatistics } from "./";
|
||||
|
||||
|
||||
@@ -1,62 +1,31 @@
|
||||
import React from "react";
|
||||
|
||||
import { EveData, useEveData } from "@/providers/EveDataProvider";
|
||||
import { State, useCurrentFit } from "@/providers/CurrentFitProvider";
|
||||
import { useCurrentFit } from "@/providers/CurrentFitProvider";
|
||||
import { useCurrentCharacter } from "@/providers/CurrentCharacterProvider";
|
||||
import { useDogmaEngine } from "@/providers/DogmaEngineProvider";
|
||||
import { Calculation, useDogmaEngine } from "@/providers/DogmaEngineProvider";
|
||||
|
||||
export interface StatisticsItemAttributeEffect {
|
||||
operator: string;
|
||||
penalty: boolean;
|
||||
source: "Ship" | "Char" | "Structure" | "Target" | { Item?: number; Charge?: number; Skill?: number };
|
||||
source_category: string;
|
||||
source_attribute_id: number;
|
||||
}
|
||||
const StatisticsSlotTypeEntries = ["High", "Medium", "Low", "SubSystem", "Rig", "Launcher", "Turret"] as const;
|
||||
export type StatisticsSlotType = (typeof StatisticsSlotTypeEntries)[number];
|
||||
|
||||
export interface StatisticsItemAttribute {
|
||||
base_value: number;
|
||||
value: number;
|
||||
effects: StatisticsItemAttributeEffect[];
|
||||
}
|
||||
|
||||
export interface StatisticsItem {
|
||||
type_id: number;
|
||||
quantity: number;
|
||||
flag: number;
|
||||
charge: StatisticsItem | undefined;
|
||||
state: State;
|
||||
max_state: State;
|
||||
attributes: Map<number, StatisticsItemAttribute>;
|
||||
effects: number[];
|
||||
}
|
||||
|
||||
const StatisticsSlotEntries = ["hislot", "medslot", "lowslot", "subsystem", "rig", "launcher", "turret"] as const;
|
||||
export type StatisticsSlotType = (typeof StatisticsSlotEntries)[number];
|
||||
|
||||
type StatisticsSlots = {
|
||||
export type StatisticsSlots = {
|
||||
[key in StatisticsSlotType]: number;
|
||||
};
|
||||
|
||||
interface Statistics {
|
||||
hull: StatisticsItem;
|
||||
items: StatisticsItem[];
|
||||
skills: StatisticsItem[];
|
||||
char: StatisticsItem;
|
||||
structure: StatisticsItem;
|
||||
target: StatisticsItem;
|
||||
const SlotAttributeMapping: Record<StatisticsSlotType, [string, string | null]> = {
|
||||
High: ["hiSlots", "hiSlotModifier"],
|
||||
Medium: ["medSlots", "medSlotModifier"],
|
||||
Low: ["lowSlots", "lowSlotModifier"],
|
||||
SubSystem: ["maxSubSystems", null],
|
||||
Rig: ["rigSlots", null],
|
||||
Launcher: ["launcherSlotsLeft", "launcherHardPointModifier"],
|
||||
Turret: ["turretSlotsLeft", "turretHardPointModifier"],
|
||||
};
|
||||
|
||||
interface Statistics extends Calculation {
|
||||
slots: StatisticsSlots;
|
||||
}
|
||||
|
||||
const SlotAttributeMapping: Record<StatisticsSlotType, [string, string | null]> = {
|
||||
hislot: ["hiSlots", "hiSlotModifier"],
|
||||
medslot: ["medSlots", "medSlotModifier"],
|
||||
lowslot: ["lowSlots", "lowSlotModifier"],
|
||||
subsystem: ["maxSubSystems", null],
|
||||
rig: ["rigSlots", null],
|
||||
launcher: ["launcherSlotsLeft", "launcherHardPointModifier"],
|
||||
turret: ["turretSlotsLeft", "turretHardPointModifier"],
|
||||
};
|
||||
|
||||
const StatisticsContext = React.createContext<Statistics | null>(null);
|
||||
|
||||
export const useStatistics = () => {
|
||||
@@ -69,14 +38,8 @@ interface StatisticsProps {
|
||||
}
|
||||
|
||||
const CalculateSlots = (eveData: EveData, statistics: Statistics) => {
|
||||
/* Set all slots to zero. */
|
||||
statistics.slots = StatisticsSlotEntries.reduce((acc, slot) => {
|
||||
acc[slot] = 0;
|
||||
return acc;
|
||||
}, {} as StatisticsSlots);
|
||||
|
||||
/* Find the statistics of the hull. */
|
||||
for (const slot of StatisticsSlotEntries) {
|
||||
for (const slot of StatisticsSlotTypeEntries) {
|
||||
const attributeId = SlotAttributeMapping[slot][0];
|
||||
|
||||
const attribute = statistics.hull.attributes.get(eveData.attributeMapping[attributeId]);
|
||||
@@ -84,9 +47,10 @@ const CalculateSlots = (eveData: EveData, statistics: Statistics) => {
|
||||
|
||||
statistics.slots[slot] += value;
|
||||
}
|
||||
|
||||
/* Check if any items modify this value. */
|
||||
for (const item of statistics.items) {
|
||||
for (const slot of StatisticsSlotEntries) {
|
||||
for (const slot of StatisticsSlotTypeEntries) {
|
||||
const attributeId = SlotAttributeMapping[slot][1];
|
||||
if (attributeId === null) continue;
|
||||
|
||||
@@ -96,6 +60,9 @@ const CalculateSlots = (eveData: EveData, statistics: Statistics) => {
|
||||
statistics.slots[slot] += value;
|
||||
}
|
||||
}
|
||||
|
||||
/* EVE Online changed from 5 subsystems to 4, but the attributes aren't changed to match this. */
|
||||
if (statistics.slots.SubSystem === 5) statistics.slots.SubSystem = 4;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -119,7 +86,18 @@ export const StatisticsProvider = (props: StatisticsProps) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const statistics: Statistics = dogmaEngine.calculate(fit, skills);
|
||||
const statistics: Statistics = {
|
||||
...dogmaEngine.calculate(fit, skills),
|
||||
slots: {
|
||||
High: 0,
|
||||
Medium: 0,
|
||||
Low: 0,
|
||||
SubSystem: 0,
|
||||
Rig: 0,
|
||||
Launcher: 0,
|
||||
Turret: 0,
|
||||
},
|
||||
};
|
||||
|
||||
CalculateSlots(eveData, statistics);
|
||||
|
||||
|
||||
@@ -1,7 +1,2 @@
|
||||
export { StatisticsProvider, useStatistics } from "./StatisticsProvider";
|
||||
export type {
|
||||
StatisticsItem,
|
||||
StatisticsItemAttribute,
|
||||
StatisticsItemAttributeEffect,
|
||||
StatisticsSlotType,
|
||||
} from "./StatisticsProvider";
|
||||
export type { StatisticsSlots, StatisticsSlotType } from "./StatisticsProvider";
|
||||
|
||||
Reference in New Issue
Block a user