import React, {useEffect, useState} from "react"; import {useTheme} from "@mui/material/styles"; import useMediaQuery from "@mui/material/useMediaQuery"; import {WeaponUrl} from "../core/api"; import ArmorTypeNames from "../types/ArmorTypeValues"; import { Grid2, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, ToggleButton, ToggleButtonGroup } from "@mui/material"; import ArmorType from "./ArmorType"; import Required from "./Required"; import {AffectedResearches} from "./building/Research"; import {IMod} from "../types/Imod"; import {Irace} from "../types/Irace"; import {ModifiersProvidesTable, getIcon} from "./ModifiersProvideTable"; import {StyledPaper} from "../commons/StyledPaper"; import {StyledTableCell} from "../commons/StyledTableCell"; import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer"; import {IWeapon, IWeaponResearch} from "../types/IUnit"; interface IWeaponFull { currentTable: string; weapon?: IWeapon; } export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, mod: IMod, race: Irace, haveReinforceMenu: Boolean}) { const theme = useTheme(); const isSmallMobile = useMediaQuery(theme.breakpoints.down('sm')); const isMediumMobile = useMediaQuery(theme.breakpoints.between('sm', 'md')); const isMobile = isSmallMobile || isMediumMobile; const columnsPerRow = isSmallMobile ? 4 : 6; const isDark = theme.palette.mode === 'dark'; const textColor = isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)'; const [weaoponFull, setWeaponFull] = useState({ currentTable: "dps", weapon: undefined, }); const [enabledResearchIds, setEnabledResearchIds] = useState>(new Set()); function applyResearchEffects(weapon: IWeapon, enabledIds: Set): IWeapon { if (enabledIds.size === 0 || !weapon.researches || weapon.researches.length === 0) { return weapon; } const modified: any = { ...weapon }; const enabledResearches = weapon.researches.filter((r: IWeaponResearch) => enabledIds.has(r.research.id)); for (const research of enabledResearches) { if (!research.effects || research.effects.length === 0) continue; for (const effect of research.effects) { const usageType: string = effect.usageType; const apply = (value: number): number => { if (usageType.includes('multiplication')) { return value * effect.modifierValue; } else if (usageType.includes('addition')) { return value + effect.modifierValue; } else if (usageType.includes('replace')) { return effect.modifierValue; } return value; }; const field = effect.field; switch (field) { case 'armourPiercing': if (!modified.weaponArmorPiercing || modified.weaponArmorPiercing.length === 0) continue; modified.weaponArmorPiercing = modified.weaponArmorPiercing.map((p: any) => ({ ...p, piercingValue: apply(p.piercingValue), })); break; case 'accuracyReductionMoving': modified.accuracyReductionMoving = apply(modified.accuracyReductionMoving); break; case 'accuracy': modified.accuracy = apply(modified.accuracy); break; case 'maxDamage': modified.maxDamage = apply(modified.maxDamage); break; case 'maxRange': modified.maxRange = apply(modified.maxRange); break; case 'minDamage': modified.minDamage = apply(modified.minDamage); break; case 'reloadTime': modified.reloadTime = apply(modified.reloadTime); break; case 'setupTime': modified.setupTime = apply(modified.setupTime); break; } } } return modified as IWeapon; } useEffect(() => { fetch(WeaponUrl + "/" + props.mod.id + "/" + props.weaponId) .then(res => res.json()) .then((weapon: IWeapon) => { setWeaponFull({ currentTable: "dps", weapon: weapon, }) }); }, []); if(weaoponFull.weapon !== undefined){ const weapon = weaoponFull.weapon const displayWeapon = applyResearchEffects(weapon, enabledResearchIds); function getPiercingK(armorType: string): number { const weaponPiercing = displayWeapon.weaponArmorPiercing.find((p) => p.armorType.name === armorType) return (typeof weaponPiercing !== "undefined" ? weaponPiercing.piercingValue : 10) / 100 } function handleChange (e: React.MouseEvent, selectedDamageTable: string) { setWeaponFull({ currentTable: selectedDamageTable, weapon: weaoponFull.weapon, }); } if(displayWeapon.reloadTime < 0.125) displayWeapon.reloadTime = 0.125 const dpsK = (weaoponFull.currentTable == "dps") ? displayWeapon.accuracy * (1 / (displayWeapon.reloadTime - (displayWeapon.reloadTime % 0.125))) : 1 const infLowPiercing = getPiercingK(ArmorTypeNames.InfantryLow) const infMedPiercing = getPiercingK(ArmorTypeNames.InfantryMedium) const infHighPiercing = getPiercingK(ArmorTypeNames.InfantryHigh) const infHeavyMedPiercing = getPiercingK(ArmorTypeNames.InfantryHeavyMedium) const infHeavyHighPiercing = getPiercingK(ArmorTypeNames.InfantryHeavyHigh) const demonPiercing = getPiercingK(ArmorTypeNames.DemonMedium) const demonHighPiercing = getPiercingK(ArmorTypeNames.DemonHigh) const commanderPiercing = getPiercingK(ArmorTypeNames.Commander) const airPiercing = getPiercingK(ArmorTypeNames.Air) const vehLowPiercing = getPiercingK(ArmorTypeNames.VehicleLow) const vehMedPiercing = getPiercingK(ArmorTypeNames.VehicleMedium) const vehHighPiercing = getPiercingK(ArmorTypeNames.VehicleHigh) const buildingLowPiercing = getPiercingK(ArmorTypeNames.BuildingLow) const buildingMedPiercing = getPiercingK(ArmorTypeNames.BuildingMedium) const buildingHighPiercing = getPiercingK(ArmorTypeNames.BuildingHigh) // UA mod const demonLowPiercing = getPiercingK(ArmorTypeNames.DemonLow) const buildingSuperPiercing = getPiercingK(ArmorTypeNames.BuildingSuper) const livingMetalPiercing = getPiercingK(ArmorTypeNames.LivingMetal) const titanPiercing = getPiercingK(ArmorTypeNames.Titan) const getTotalDamage = (damagePiercing: number, isAir: boolean = false) => { if (!isAir && !weapon.canAttackGround && !weapon.isMeleeWeapon) return "" if (isAir && !weapon.canAttackAir) return "" var minDamage = damagePiercing * displayWeapon.minDamage var maxDamage = damagePiercing * displayWeapon.maxDamage if (minDamage < displayWeapon.minDamageValue) { minDamage = displayWeapon.minDamageValue } if (maxDamage < displayWeapon.minDamageValue) { maxDamage = displayWeapon.minDamageValue } const averageDmg = (minDamage + maxDamage) / 2 return (averageDmg * dpsK).toFixed(2) } const getMoraleDamage = () => { return (displayWeapon.moraleDamage * dpsK / 2).toFixed(2) } return (
{!props.isDefault && {weapon.description} } Base damage {displayWeapon.minDamage.toFixed(2)} {displayWeapon.maxDamage !== displayWeapon.minDamage && "- " + displayWeapon.maxDamage.toFixed(2)} Accuracy {displayWeapon.accuracy.toFixed(3).replace(/[,.]?0+$/, '')} Accuracy moving {displayWeapon.setupTime === 0 && displayWeapon.accuracy - displayWeapon.accuracyReductionMoving > 0 ? (displayWeapon.accuracy - displayWeapon.accuracyReductionMoving).toFixed(3).replace(/[,.]?0+$/, '') : "-"}
Reload time {displayWeapon.reloadTime} Range {displayWeapon.maxRange ? (displayWeapon.maxRange) : "-"} {weapon.minRange ? "(min " + (weapon.minRange) + ")" : ""} Damage radius {weapon.damageRadius ? (weapon.damageRadius) : "-"}
Cost {(weapon.costRequisition > 0 || weapon.costPower > 0) ? {weapon.costRequisition > 0 &&    {weapon.costRequisition.toFixed(0)}} {weapon.costPower > 0 &&    {weapon.costPower.toFixed(0)}} {(weapon.costTimeSeconds !== undefined && weapon.costTimeSeconds > 0) &&    {weapon.costTimeSeconds}s} : - } Setup time {displayWeapon.setupTime != 0 ? (displayWeapon.setupTime).toFixed(3).replace(/[,.]?0+$/, '') : "-"} Throw force {weapon.throwForceMin != 0 ? weapon.throwForceMin + " - " + weapon.throwForceMax : "-"}
Dps One hit average damage {weapon.researches && weapon.researches.length > 0 && (
{(() => { // topological order: a research is always rendered to the right of its requirements const ordered: IWeaponResearch[] = []; const visited = new Set(); const visit = (wr: IWeaponResearch) => { if (visited.has(wr.research.id)) return; visited.add(wr.research.id); wr.researchRequired?.forEach(req => { const dep = weapon.researches.find((w: IWeaponResearch) => w.research.id === req.id); if (dep) visit(dep); }); ordered.push(wr); }; weapon.researches.forEach(visit); return ordered.map((r: IWeaponResearch) => { const rs = r.research; const selected = enabledResearchIds.has(rs.id); return ( { const next = new Set(enabledResearchIds); if (next.has(rs.id)) { next.delete(rs.id); // researches that require this one (directly or transitively) can no longer be applied let changed = true; while (changed) { changed = false; weapon.researches.forEach((wr: IWeaponResearch) => { if (next.has(wr.research.id) && wr.researchRequired?.some(req => !next.has(req.id))) { next.delete(wr.research.id); changed = true; } }); } } else { next.add(rs.id); // research can only be applied if its required researches are done // (requirements can have their own requirements, so enable transitively) const queue = [...(r.researchRequired ?? [])]; while (queue.length > 0) { const req = queue.pop()!; if (!next.has(req.id)) { next.add(req.id); const wr = weapon.researches.find((w: IWeaponResearch) => w.research.id === req.id); wr?.researchRequired?.forEach(rr => queue.push(rr)); } } } setEnabledResearchIds(next); }} sx={{ color: textColor, borderRadius: '8px', border: '1px solid', borderColor: isDark ? 'rgba(255,255,255,0.23)' : 'rgba(0,0,0,0.23)', textTransform: 'none', '&.Mui-selected': { borderColor: isDark ? 'rgba(25, 118, 210, 0.7)' : 'rgba(25, 118, 210, 0.5)', color: textColor, backgroundColor: isDark ? 'rgba(25, 118, 210, 0.3)' : 'rgba(25, 118, 210, 0.15)', '&:hover': { backgroundColor: isDark ? 'rgba(25, 118, 210, 0.4)' : 'rgba(25, 118, 210, 0.22)', }, }, }} > {rs.icon && {rs.name}/} {rs.name} ); }); })()}
)}
{isMobile ? (() => { const items = props.mod !== undefined && props.mod.technicalName === 'UltimateApocalypse' ? [ { label: , value: () => getTotalDamage(infLowPiercing) }, { label: , value: () => getTotalDamage(infMedPiercing) }, { label: , value: () => getTotalDamage(infHighPiercing) }, { label: , value: () => getTotalDamage(infHeavyMedPiercing) }, { label: , value: () => getTotalDamage(infHeavyHighPiercing) }, { label: , value: () => getTotalDamage(commanderPiercing) }, { label: , value: () => getTotalDamage(demonLowPiercing) }, { label: , value: () => getTotalDamage(demonPiercing) }, { label: , value: () => getTotalDamage(demonHighPiercing) }, { label: , value: () => getTotalDamage(airPiercing, true) }, { label: , value: () => getTotalDamage(vehLowPiercing) }, { label: , value: () => getTotalDamage(vehMedPiercing) }, { label: , value: () => getTotalDamage(vehHighPiercing) }, { label: , value: () => getTotalDamage(livingMetalPiercing) }, { label: , value: () => getTotalDamage(titanPiercing) }, { label: , value: () => getTotalDamage(buildingLowPiercing) }, { label: , value: () => getTotalDamage(buildingMedPiercing) }, { label: , value: () => getTotalDamage(buildingHighPiercing) }, { label: , value: () => getTotalDamage(buildingSuperPiercing) }, { label: (<>
Morale
), value: () => getMoraleDamage() }, ] : [ { label: , value: () => getTotalDamage(infLowPiercing) }, { label: , value: () => getTotalDamage(infMedPiercing) }, { label: , value: () => getTotalDamage(infHighPiercing) }, { label: , value: () => getTotalDamage(infHeavyMedPiercing) }, { label: , value: () => getTotalDamage(infHeavyHighPiercing) }, { label: , value: () => getTotalDamage(commanderPiercing) }, { label: , value: () => getTotalDamage(demonPiercing) }, { label: , value: () => getTotalDamage(demonHighPiercing) }, { label: , value: () => getTotalDamage(airPiercing, true) }, { label: , value: () => getTotalDamage(vehLowPiercing) }, { label: , value: () => getTotalDamage(vehMedPiercing) }, { label: , value: () => getTotalDamage(vehHighPiercing) }, { label: , value: () => getTotalDamage(buildingLowPiercing) }, { label: , value: () => getTotalDamage(buildingMedPiercing) }, { label: , value: () => getTotalDamage(buildingHighPiercing) }, { label: , value: () => getMoraleDamage() }, ]; const chunks: any[] = []; for (let i = 0; i < items.length; i += columnsPerRow) { chunks.push(items.slice(i, i + columnsPerRow)); } return chunks.map((chunk, chunkIndex) => ( {chunk.map((item, i) => ( {item.label} ))} {chunk.map((item, i) => ( {item.value()} ))}
)); })() : props.mod !== undefined && props.mod.technicalName === 'UltimateApocalypse' ? {getTotalDamage(infLowPiercing)} {getTotalDamage(infMedPiercing)} {getTotalDamage(infHighPiercing)} {getTotalDamage(infHeavyMedPiercing)} {getTotalDamage(infHeavyHighPiercing)} {getTotalDamage(commanderPiercing)} {getTotalDamage(demonLowPiercing)} {getTotalDamage(demonPiercing)} {getTotalDamage(demonHighPiercing)} {getTotalDamage(airPiercing, true)}
Morale
{getTotalDamage(vehLowPiercing)} {getTotalDamage(vehMedPiercing)} {getTotalDamage(vehHighPiercing)} {getTotalDamage(livingMetalPiercing)} {getTotalDamage(titanPiercing)} {getTotalDamage(buildingLowPiercing)} {getTotalDamage(buildingMedPiercing)} {getTotalDamage(buildingHighPiercing)} {getTotalDamage(buildingSuperPiercing)} {getMoraleDamage()}
: {getTotalDamage(infLowPiercing)} {getTotalDamage(infMedPiercing)} {getTotalDamage(infHighPiercing)} {getTotalDamage(infHeavyMedPiercing)} {getTotalDamage(infHeavyHighPiercing)} {getTotalDamage(commanderPiercing)} {getTotalDamage(demonPiercing)} {getTotalDamage(demonHighPiercing)} {getTotalDamage(airPiercing, true)} {getTotalDamage(vehLowPiercing)} {getTotalDamage(vehMedPiercing)} {getTotalDamage(vehHighPiercing)} {getTotalDamage(buildingLowPiercing)} {getTotalDamage(buildingMedPiercing)} {getTotalDamage(buildingHighPiercing)} {getMoraleDamage()}
}
{weapon.modifiers.length > 0 &&

Modifiers

} {weapon.requirements !== null && !props.isDefault && props.haveReinforceMenu && }
{!props.isDefault && props.haveReinforceMenu && Hotkey: {weapon.hotkey} } Filename: {weapon.filename}
) } else return
loading...
; }