'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 (
);
}
}