'use client'; import React, { useEffect, useState } from 'react'; import { Accordion, AccordionDetails, AccordionSummary, Box, Button, Container, Divider, Grid2, LinearProgress, Paper, Table, TableBody, TableCell, TableRow, Tooltip, Typography, Theme, } from '@mui/material'; import { styled } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles'; import { useParams } from 'next/navigation'; import { ArrowBack, ExpandMore } from '@mui/icons-material'; import AvTimerOutlinedIcon from '@mui/icons-material/AvTimer'; import { AvailableBuildings, AvailableMods, AvailableUnits, IconUrl } from '@/src/core/api'; import { IShortWeapon, IUnit } from '@/src/types/IUnit'; import { IRaceUnits } from '@/src/types/IUnitShort'; import { IRaceBuildings } from '@/src/types/IBuildingShort'; import '@/src/css/Unit.css'; import ArmorType from '@/src/classes/ArmorType'; import Sergeant from '@/src/classes/Sergeant'; import WeaponSlot from '@/src/classes/WeaponSlot'; import UnitsTable from '@/src/classes/UnitsTable'; import { IMod } from '@/src/types/Imod'; import Vision from '@/src/classes/Vision'; import Required from '@/src/classes/Required'; import { AffectedResearches } from '@/src/classes/building/Research'; import { ModifiersProvidesTable } from '@/src/classes/ModifiersProvideTable'; import Ability from '@/src/classes/Ability'; import DeathExplosion from '@/src/classes/DeathExplosion'; import Jump from '@/src/classes/Jump'; import { DescriptionBox } from '@/src/commons/DescriptionBox'; import { BackButton } from '@/src/commons/BackButton'; const SectionTitle = styled(Typography)(({ theme }) => ({ fontWeight: 700, mb: 2, fontSize: '1.25rem', })); const StatsPaper = styled(Paper)(({ theme }) => ({ background: theme.palette.mode === 'dark' ? 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)' : 'linear-gradient(145deg, #ffffff 0%, #f5f5f5 100%)', border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'}`, borderRadius: '12px', overflow: 'hidden', '& .MuiTableBody .MuiTableRow-root': { '&:last-child td, &:last-child th': { border: 0 }, }, '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { color: theme.palette.mode === 'dark' ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)', fontWeight: 600, }, })); const SergeantAccordion = styled(Accordion)(({ theme }) => ({ background: theme.palette.mode === 'dark' ? 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)' : 'linear-gradient(145deg, #ffffff 0%, #f5f5f5 100%)', border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'}`, borderRadius: '12px', mb: 1, '&:before': { display: 'none' }, '&.Mui-expanded': { margin: '0 0 8px 0' }, })); const StyledDivider = styled(Divider)(({ theme }) => ({ my: 3, borderColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)', })); const UnitTitle = styled(Typography)(({ theme }) => ({ fontWeight: 800, mb: 1, display: 'flex', alignItems: 'center', gap: 2, })); const UnitSubtitle = styled(Typography)(({ theme }) => ({ color: theme.palette.text.secondary, fontWeight: 500, })); function Unit(unit: IUnit, mod: IMod, theme: Theme, racesUnits: IRaceUnits[], racesBuildings: IRaceBuildings[]) { const isDark = theme.palette.mode === 'dark'; const morale = (unit.moraleMax !== null) ?  {unit.moraleMax} +{unit.moraleRegeneration}/s {unit.moraleDeathPenalty > 0 && -{unit.moraleDeathPenalty}} : "-" let mapWithUnitWeapons: Map> = new Map(); unit.weapons.forEach(weapon => { const weaponMap = mapWithUnitWeapons.get(weapon.hardpoint) if (weaponMap == null) { const weaponMap = new Map() weaponMap.set(weapon.hardpointOrder, weapon.weapon) mapWithUnitWeapons.set(weapon.hardpoint, weaponMap) } else { weaponMap.set(weapon.hardpointOrder, weapon.weapon) } }) type sergeantProps = { name: string icon: String canDetect: Boolean } const SergeantShort = (props: sergeantProps) => { return ( {props.icon && }   {props.name} {props.canDetect &&  } ) } return ( {unit.icon && } {unit.name} {mod.name} ({mod.version}) Cost {unit.buildCostRequisition > 0 &&    {unit.buildCostRequisition.toFixed(0)}} {unit.buildCostPower > 0 &&    {unit.buildCostPower.toFixed(0)}} {(unit.buildCostPopulation !== undefined && unit.buildCostPopulation > 0) &&    {unit.buildCostPopulation.toFixed(0)}} {(unit.buildCostFaith !== undefined && unit.buildCostFaith > 0) &&    {unit.buildCostFaith}} {(unit.buildCostSouls !== undefined && unit.buildCostSouls > 0) &&    {unit.buildCostSouls.toFixed(0)}} {unit.capInfantry > 0 &&    {unit.capInfantry}} {unit.capSupport > 0 &&    {unit.capSupport}} {(unit.buildCostTime !== undefined && unit.buildCostTime > 0) &&    {unit.buildCostTime}s} {(unit?.reinforceTime !== 0 && unit.reinforceTime !== null && unit.squadMaxSize > 1) && Reinforce cost {unit.reinforceCostRequisition && unit.reinforceCostRequisition > 0 ?    {unit.reinforceCostRequisition.toFixed(0)} : } {unit.reinforceCostPower !== undefined && unit.reinforceCostPower > 0 &&    {unit.reinforceCostPower.toFixed(0)}} {(unit.reinforceCostPopulation !== undefined && unit.reinforceCostPopulation > 0) &&    {unit.reinforceCostPopulation.toFixed(0)}} {(unit.reinforceCostFaith !== undefined && unit.reinforceCostFaith > 0) &&    {unit.reinforceCostFaith}} {(unit.reinforceCostSouls !== undefined && unit.reinforceCostSouls > 0) &&    {unit.reinforceCostSouls.toFixed(0)}} {(unit.reinforceTime !== undefined && unit.reinforceTime > 0) &&    {unit.reinforceTime}s} } {(unit.requisitionIncome !== undefined && unit.requisitionIncome !== null || unit.powerIncome !== undefined && unit.powerIncome !== null || unit.faithIncome !== undefined && unit.faithIncome !== null) && Resource income {unit.requisitionIncome !== undefined && unit.requisitionIncome > 0 &&    {unit.requisitionIncome}} {unit.powerIncome !== undefined && unit.powerIncome > 0 &&    {unit.powerIncome}} {unit.faithIncome !== undefined && unit.faithIncome > 0 &&    {unit.faithIncome}} } {unit.squadMaxSize > 1 && Squad size {unit.squadStartSize} / {unit.squadMaxSize} } Armor type {unit.armorType2 == null ? : } Health   {unit.health} {unit.healthRegeneration > 0 && +{unit.healthRegeneration}/s} {unit.armour !== undefined && unit.armour !== 0 && {unit.armour} } Move speed {unit.moveSpeed} Morale {morale} Mass {unit.mass} Vision {unit.repairMax !== undefined && unit.repairMax !== null && Repair max {unit.repairMax} } {unit.repairSpeed !== undefined && unit.repairSpeed !== null && unit.repairCostPercent !== null && Repair {unit.repairSpeed} hp/s; {unit.repairCostPercent}% cost } {unit.mobValue != null && Mob value {unit.mobValue} } {unit.squadLimit !== undefined && unit.squadLimit !== null && Limit {unit.squadLimit} }
{unit.description} {unit.requirements !== null && } {unit.sergeants.map(s => 0} /> )} {unit.moraleMax !== null && !(unit.moraleBrakeModifiers.find(m => m.reference.includes("accuracy_weapon_modifier") && m.value === 0.2) !== undefined && unit.moraleBrakeModifiers.find(m => m.reference.includes("speed_maximum_modifier") && m.value === 1.2) !== undefined && unit.moraleBrakeModifiers.length === 2) && Morale broken } {unit.modifiers.length > 0 && Affected on } {unit.abilities.length > 0 && Abilities {unit.abilities.map(a => )} } {unit.jumps != null && } {[...mapWithUnitWeapons.keys()].sort(function (a, b) { return a - b; }).map(h => )} {unit.deathExplosions.length > 0 && {unit.deathExplosions.map(da => )} }
Hotkey: {unit.hotkey} Filename: {unit.filename}
) } export default function UnitPageClient({ initialUnit, initialMod, }: { initialUnit: IUnit | null; initialMod: IMod | null; }) { const params = useParams() as Record; const { modId, raceId, unitId } = params; const theme = useTheme(); const [unit, setUnit] = useState(initialUnit); const [mod, setMod] = useState(initialMod); const [racesUnits, setRacesUnits] = useState([]); const [racesBuildings, setRacesBuildings] = useState([]); useEffect(() => { let cancelled = false; if (unit == null) { fetch(AvailableUnits + "/" + unitId) .then(res => res.json()) .then((res: IUnit) => { if (!cancelled) setUnit(res); }); } if (mod == null) { fetch(AvailableMods + "/" + modId) .then(res => res.json()) .then((res: IMod) => { if (!cancelled) setMod(res); }); } fetch(AvailableUnits + "/mod/" + modId) .then(res => res.json()) .then((resUnits: IRaceUnits[]) => { if (cancelled) return; setRacesUnits(resUnits); if (resUnits.length <= 10) { fetch(AvailableBuildings + "/mod/" + modId) .then(res => res.json()) .then((resBuildings: IRaceBuildings[]) => { if (!cancelled) setRacesBuildings(resBuildings); }); } }); return () => { cancelled = true; }; }, [modId, raceId, unitId]); if (unit != null && mod != null) { const backRef = "/mod/" + modId + "/race/" + raceId; return ( } href={backRef} > Back to race {Unit(unit, mod, theme, racesUnits, racesBuildings)} ); } else { return ( ); } }