Compare commits

..

No commits in common. "5669a70156e8665db82916bb0ed206a28b2c7b35" and "9d4bb49713949d6e69771467e204ab551cc5a032" have entirely different histories.

27 changed files with 660 additions and 1459 deletions

View File

@ -1,6 +1,6 @@
# Soulstorm wiki frontend # Soulstorm wiki frontend
## Tech stack: ## Стек технологий:
- react (mix of classes and functions) - react (вперемешку классы и функции)
- typescript - typescript

View File

@ -1,106 +1,66 @@
import './App.css'; import './App.css';
import React from "react"; import React from "react";
import { import {AppBar, Container, createTheme, IconButton, ThemeProvider, Toolbar, Typography} from "@mui/material";
AppBar, import {MyRoutes} from "./Routes";
Box,
Container,
createTheme,
ThemeProvider,
Toolbar,
Typography,
useTheme
} from "@mui/material";
import { MyRoutes } from "./Routes";
import { styled } from '@mui/material/styles';
// Styled components
const StyledAppBar = styled(AppBar)(({ theme }) => ({
background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)',
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.3)',
borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
}));
const LogoText = styled(Typography)(({ theme }) => ({
fontSize: '1.5rem',
fontWeight: 800,
background: 'linear-gradient(135deg, #e94560 0%, #ff6b6b 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
letterSpacing: '-0.5px',
cursor: 'pointer',
'&:hover': {
transform: 'scale(1.05)',
transition: 'transform 0.2s ease',
},
}));
function App() { function App() {
const theme = useTheme(); console.log(process.env);
const themeConfig = createTheme({ const steamLogin = (event: React.MouseEvent<HTMLButtonElement>) => {
breakpoints: { (document.getElementById('submit-steam-login') as HTMLFormElement).click()
values: { };
xs: 0,
sm: 600, const theme = createTheme({
md: 900,
lg: 1320,
xl: 1536,
},
},
palette: { palette: {
primary: { primary: {
main: "#1a1a2e", main: "#191919",
},
background: {
default: '#0a0a0a',
paper: '#1a1a2e',
},
},
typography: {
fontFamily: '"Inter", "Roboto", "Helvetica", sans-serif',
},
shape: {
borderRadius: 12,
},
components: {
MuiTableCell: {
styleOverrides: {
root: {
color: 'rgba(255, 255, 255, 0.85)',
},
},
}, },
}, },
}); });
return (
<Box sx={{ minHeight: '100vh', backgroundColor: '#0a0a0a' }}>
<ThemeProvider theme={themeConfig}>
<a href="/">
<StyledAppBar position="sticky">
<Container maxWidth="lg">
<Toolbar disableGutters sx={{ justifyContent: 'space-between' }}>
{/* Left side - Logo */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<LogoText variant="h6" sx={{ textDecoration: 'none' }}>
🎮 Autogenerated wiki
</LogoText>
</Box>
{/* Right side - Navigation */} return (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <div >
</Box> <ThemeProvider theme={theme}>
</Toolbar> <AppBar position="static">
</Container> <Toolbar>
</StyledAppBar> <IconButton
</a> size="large"
edge="start"
color="inherit"
aria-label="menu"
sx={{mr: 2}}
>
</IconButton>
<Typography variant="h6" component="div" sx={{flexGrow: 1}}>
Autogeneration wiki
</Typography>
{/*<Button color="inherit" onClick={steamLogin}>
Log in through Steam
</Button>*/}
</Toolbar>
</AppBar>
<Container>
<MyRoutes/>
</Container>
</ThemeProvider>
<Container maxWidth="lg" sx={{ py: 4 }}>
<MyRoutes /> <form action="https://steamcommunity.com/openid/login" method="post">
</Container> <input type="hidden" name="openid.identity"
</ThemeProvider> value="http://specs.openid.net/auth/2.0/identifier_select"/>
</Box> <input type="hidden" name="openid.claimed_id"
); value="http://specs.openid.net/auth/2.0/identifier_select"/>
<input type="hidden" name="openid.ns" value="http://specs.openid.net/auth/2.0"/>
<input type="hidden" name="openid.mode" value="checkid_setup"/>
<input type="hidden" name="openid.realm" value={process.env.REACT_APP_HOST_URL}/>
<input type="hidden" name="openid.return_to"
value={process.env.REACT_APP_HOST_URL + "\\login"}/>
<button id="submit-steam-login" type="submit" hidden={true}></button>
</form>
</div>
);
} }
export default App; export default App;

View File

@ -6,15 +6,7 @@ import {getIcon} from "./ModifiersProvideTable";
import {IMod} from "../types/Imod"; import {IMod} from "../types/Imod";
import AbilityFull from "./AbilityFull"; import AbilityFull from "./AbilityFull";
import {Irace} from "../types/Irace"; import {Irace} from "../types/Irace";
import {styled} from '@mui/material/styles';
const StyledAccordion = styled(Accordion)(({ theme }) => ({
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
'&:before': { display: 'none' },
'&.Mui-expanded': { margin: '0 0 8px 0' },
}));
interface IAbilityProps { interface IAbilityProps {
ability: IAbilityShort, ability: IAbilityShort,
@ -26,23 +18,22 @@ function Ability(props: IAbilityProps){
const ability = props.ability const ability = props.ability
return <div className='addon-research-accordion' id={"research-" + ability.id}><StyledAccordion TransitionProps={{ unmountOnExit: true, timeout: 100 }}> return <div className='addon-research-accordion' id={"research-" + ability.id}><Accordion TransitionProps={{ unmountOnExit: true, timeout: 100 }}>
<AccordionSummary <AccordionSummary
expandIcon={<ExpandMore sx={{color: '#ffffff'}}/>} expandIcon={<ExpandMore/>}
aria-controls="panel1-content" aria-controls="panel1-content"
sx={{color: '#ffffff'}}
> >
<span style={{fontSize: 20, color: '#ffffff'}}> <span style={{fontSize: 20}}>
{ability.activationType === "Passive ability" ? <img className="abilityIcon" src="/images/PassiveIcon.gif"/> : <img className="abilityIcon" src={getIcon(ability.icon)}/>} {ability.activationType === "Passive ability" ? <img className="abilityIcon" src="/images/PassiveIcon.gif"/> : <img className="abilityIcon" src={getIcon(ability.icon)}/>}
&nbsp; {ability.name ? ability.name : ability.fileName.charAt(0).toUpperCase() + ability.fileName.slice(1).replaceAll('_', ' ').replace('.rgd', '')} &nbsp; {ability.name ? ability.name : ability.fileName.charAt(0).toUpperCase() + ability.fileName.slice(1).replaceAll('_', ' ').replace('.rgd', '')}
<i style={{fontSize: 12, color: 'rgba(255,255,255,0.5)'}}> ({ability.activationType})</i> <i style={{fontSize: 12}}> ({ability.activationType})</i>
</span> </span>
</AccordionSummary> </AccordionSummary>
<AccordionDetails sx={{color: 'rgba(255,255,255,0.85)'}}> <AccordionDetails>
<AbilityFull abilityId={ability.id} mod={props.mod} race={props.race} /> <AbilityFull abilityId={ability.id} mod={props.mod} race={props.race} />
</AccordionDetails> </AccordionDetails>
</StyledAccordion></div> </Accordion></div>
} }

View File

@ -27,7 +27,6 @@ import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
import ability from "./Ability"; import ability from "./Ability";
import AbilitySpawnedObject from "./AbilitySpawnedObject"; import AbilitySpawnedObject from "./AbilitySpawnedObject";
import DpsTable from "./DpsTable"; import DpsTable from "./DpsTable";
import {DescriptionBox} from "../commons/DescriptionBox";
interface IAbilityFullState { interface IAbilityFullState {
ability?: IAbilityFill; ability?: IAbilityFill;
@ -42,33 +41,16 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
const StyledTableCell = styled(TableCell)(({theme}) => ({ const StyledTableCell = styled(TableCell)(({theme}) => ({
[`&.${tableCellClasses.head}`]: { [`&.${tableCellClasses.head}`]: {
backgroundColor: 'rgba(233, 69, 96, 0.15)', backgroundColor: "rgb(244,244,244)",
fontWeight: 700,
marginRight: 'auto', marginRight: 'auto',
marginLeft: 'auto', marginLeft: 'auto',
paddingLeft: 10, paddingLeft: 10
borderBottom: '2px solid rgba(233, 69, 96, 0.3)',
}, },
[`&.${tableCellClasses.body}`]: { [`&.${tableCellClasses.body}`]: {
fontSize: 12, fontSize: 12,
textAlign: 'center', textAlign: 'center',
color: 'rgba(255, 255, 255, 0.85)',
paddingRight: 18, paddingRight: 18,
paddingLeft: 10, paddingLeft: 10
},
}));
const StyledPaper = styled(Paper)(({ theme }) => ({
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
overflow: 'hidden',
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': {
color: 'rgba(255, 255, 255, 0.9)',
},
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': {
color: '#e94560',
fontWeight: 600,
}, },
})); }));
@ -108,21 +90,21 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
const showTargetTable = !showDpsTable && ability.abilityEnvironment == null && (ability.targetFilter.length > 0 || ability.radius !== 0) const showTargetTable = !showDpsTable && ability.abilityEnvironment == null && (ability.targetFilter.length > 0 || ability.radius !== 0)
return ( return (
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}> <div>
<Grid2 container spacing={2}> <Grid2 container spacing={2}>
<Grid2 size={{xs: 12}}> <Grid2 size={{xs: 12}}>
{ability.initialDelayTime !== undefined && props.isChild === true && {ability.initialDelayTime !== undefined && props.isChild === true &&
<b><br/><center style={{color: 'rgba(255,255,255,0.7)'}}>&nbsp;After {ability.initialDelayTime.toFixed(1)}s&nbsp; <b><br/><center>&nbsp;After {ability.initialDelayTime.toFixed(1)}s&nbsp;
<img style={{verticalAlign: "top"}} <img style={{verticalAlign: "top"}}
src="/images/Time_icon.webp"/></center></b>} src="/images/Time_icon.webp"/></center></b>}
</Grid2> </Grid2>
<Grid2 size={{xs: 12, md: 4}}> <Grid2 size={{xs: 12, md: 4}}>
<TableContainer component={StyledPaper} elevation={0}> <TableContainer component={Paper}>
<Table size="small" aria-label="a dense table"> <Table size="small" aria-label="a dense table">
<TableBody id="unit-stats-table"> <TableBody id="unit-stats-table">
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow>
<TableCell component="th" scope="row" >Affected on</TableCell> <TableCell component="th" scope="row">Affected on</TableCell>
<TableCell>{ability.areaFilter}</TableCell> <TableCell component="th" scope="row">{ability.areaFilter}</TableCell>
</TableRow> </TableRow>
{props.isChild !== true && (ability.costPower !== undefined && ability.costPower > 0 || {props.isChild !== true && (ability.costPower !== undefined && ability.costPower > 0 ||
ability.costRequisition !== undefined && ability.costRequisition > 0 || ability.costRequisition !== undefined && ability.costRequisition > 0 ||
@ -131,8 +113,8 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Cost</TableCell> <TableCell component="th" scope="row">Cost</TableCell>
<TableCell> <TableCell component="th" scope="row">
{ability.costRequisition !== undefined && ability.costRequisition > 0 && {ability.costRequisition !== undefined && ability.costRequisition > 0 &&
<span>&nbsp;<img style={{verticalAlign: "top"}} <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_requisition.gif"/>&nbsp; src="/images/Resource_requisition.gif"/>&nbsp;
@ -153,19 +135,19 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
{props.isChild !== true && ability.rechargeTime !== undefined && ability.rechargeTime > 0 && ability.activation !== "On/off ability" && {props.isChild !== true && ability.rechargeTime !== undefined && ability.rechargeTime > 0 && ability.activation !== "On/off ability" &&
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
<TableCell component="th" scope="row" >Recharge time</TableCell> <TableCell component="th" scope="row">Recharge time</TableCell>
<TableCell> <TableCell component="th" scope="row">
<img style={{verticalAlign: "top"}} <img style={{verticalAlign: "top"}}
src="/images/Time_icon.webp"/>&nbsp; src="/images/Time_icon.webp"/>&nbsp;
{ability.rechargeTime}s&nbsp; {ability.rechargeTime}s&nbsp;
{ability.rechargeTimerGlobal && <i style={{fontSize: 10, color: 'rgba(255,255,255,0.5)'}}>global cooldown</i>} {ability.rechargeTimerGlobal && <i style={{fontSize: 10}}>global cooldown</i>}
</TableCell> </TableCell>
</TableRow> </TableRow>
} }
{props.isChild !== true && ability.initialDelayTime !== undefined && ability.initialDelayTime > 0 && {props.isChild !== true && ability.initialDelayTime !== undefined && ability.initialDelayTime > 0 &&
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
<TableCell component="th" scope="row" >Delay after cast</TableCell> <TableCell component="th" scope="row">Delay after cast</TableCell>
<TableCell> <TableCell component="th" scope="row">
<img style={{verticalAlign: "top"}} <img style={{verticalAlign: "top"}}
src="/images/Time_icon.webp"/>&nbsp; src="/images/Time_icon.webp"/>&nbsp;
{ability.initialDelayTime.toFixed(1)}s {ability.initialDelayTime.toFixed(1)}s
@ -174,8 +156,8 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
} }
{ability.durationTime !== undefined && ability.durationTime > 1.5 && {ability.durationTime !== undefined && ability.durationTime > 1.5 &&
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
<TableCell component="th" scope="row" >Duration time</TableCell> <TableCell component="th" scope="row">Duration time</TableCell>
<TableCell> <TableCell component="th" scope="row">
<img style={{verticalAlign: "top"}} <img style={{verticalAlign: "top"}}
src="/images/Time_icon.webp"/>&nbsp; src="/images/Time_icon.webp"/>&nbsp;
{ability.durationTime.toFixed(1)}s {ability.durationTime.toFixed(1)}s
@ -184,16 +166,16 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
} }
{ability.areaEffect !== null && ability.radius !== undefined && ability.radius > 0 && {ability.areaEffect !== null && ability.radius !== undefined && ability.radius > 0 &&
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
<TableCell component="th" scope="row" >Radius</TableCell> <TableCell component="th" scope="row">Radius</TableCell>
<TableCell> <TableCell component="th" scope="row">
{ability.radius.toFixed(0)}&nbsp;<i style={{fontSize: 10, color: 'rgba(255,255,255,0.5)'}}>{ability.areaEffect}</i> {ability.radius.toFixed(0)}&nbsp;<i style={{fontSize: 10}}>{ability.areaEffect}</i>
</TableCell> </TableCell>
</TableRow> </TableRow>
} }
{props.isChild !== true && ability.range !== undefined && ability.areaFilter !== "Own" && ability.range > 0 && {props.isChild !== true && ability.range !== undefined && ability.areaFilter !== "Own" && ability.range > 0 &&
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
<TableCell component="th" scope="row" >Range</TableCell> <TableCell component="th" scope="row">Range</TableCell>
<TableCell> <TableCell component="th" scope="row">
{ability.range.toFixed(0)} {ability.range.toFixed(0)}
</TableCell> </TableCell>
</TableRow> </TableRow>
@ -202,20 +184,26 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Throw force</TableCell> <TableCell component="th" scope="row">Throw force</TableCell>
<TableCell>{ability.throwForceMin != 0 ? ability.throwForceMin + " - " + ability.throwForceMax : "-"} <TableCell component="th"
scope="row">{ability.throwForceMin != 0 ? ability.throwForceMin + " - " + ability.throwForceMax : "-"}
</TableCell> </TableCell>
</TableRow>} </TableRow>}
<TableRow>
</TableRow>
</TableBody> </TableBody>
</Table> </Table>
</TableContainer><br/> </TableContainer><br/>
</Grid2> </Grid2>
<Grid2 size={8}> <Grid2 size={8}>
{props.isChild !== true && <DescriptionBox>{ability.description}</DescriptionBox>} <div style={{whiteSpace: "pre-wrap"}}>
{props.isChild !== true && ability.description}
</div>
</Grid2> </Grid2>
{showDpsTable ? {showDpsTable ?
<Grid2 size={12}> <Grid2 size={12}>
{(ability.refreshTime !== undefined && ability.durationTime !== undefined && ability.refreshTime * 2 < ability.durationTime) && <i style={{color: 'rgba(255,255,255,0.7)'}}>Every <b style={{color: '#ffffff'}}>{ability.refreshTime.toFixed(1)}s</b> deal damege:</i>} {(ability.refreshTime !== undefined && ability.durationTime !== undefined && ability.refreshTime * 2 < ability.durationTime) && <i>Every <b>{ability.refreshTime.toFixed(1)}s</b> deal damege:</i>}
<DpsTable mod={props.mod} minDamageValue={ability.minDamageValue} minDamage={ability.minDamage} maxDamage={ability.maxDamage} piercings={ability.piercings} targetFilter={ability.targetFilter} moraleDamage={ability.moraleDamage} <DpsTable mod={props.mod} minDamageValue={ability.minDamageValue} minDamage={ability.minDamage} maxDamage={ability.maxDamage} piercings={ability.piercings} targetFilter={ability.targetFilter} moraleDamage={ability.moraleDamage}
/> />
</Grid2> : showTargetTable ? </Grid2> : showTargetTable ?
@ -247,7 +235,7 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow>
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryLow)}</StyledTableCell> <StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryLow)}</StyledTableCell>
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryMedium)}</StyledTableCell> <StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryMedium)}</StyledTableCell>
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryHigh)}</StyledTableCell> <StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryHigh)}</StyledTableCell>
@ -284,7 +272,7 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow>
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.VehicleLow)}</StyledTableCell> <StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.VehicleLow)}</StyledTableCell>
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.VehicleMedium)}</StyledTableCell> <StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.VehicleMedium)}</StyledTableCell>
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.VehicleHigh)}</StyledTableCell> <StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.VehicleHigh)}</StyledTableCell>
@ -333,7 +321,7 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow>
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryLow)}</StyledTableCell> <StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryLow)}</StyledTableCell>
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryMedium)}</StyledTableCell> <StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryMedium)}</StyledTableCell>
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryHigh)}</StyledTableCell> <StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryHigh)}</StyledTableCell>
@ -364,10 +352,10 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
<Grid2 size={{xs: 12, md: 12}}> <Grid2 size={{xs: 12, md: 12}}>
<Required requirement={ability.requirements} modId={props.mod.id} raceId={props.race.id}/> <Required requirement={ability.requirements} modId={props.mod.id} raceId={props.race.id}/>
</Grid2>} </Grid2>}
{props.isChild !== true && ability.activation !== "Passive ability" && <b className="hotkey" >Hotkey: {ability.hotkey}</b>} {ability.activation !== "Passive ability" && props.isChild !== true && <b className="hotkey">Hotkey: {ability.hotkey}</b>}
</div> </div>
) )
} else return <div style={{color: 'rgba(255, 255, 255, 0.5)'}}>loading...</div>; } else return <div>loading...</div>;

View File

@ -6,8 +6,7 @@ import {
TableBody, TableBody,
TableCell, TableContainer, TableCell, TableContainer,
TableRow, TableRow,
Tooltip, Tooltip
Typography
} from "@mui/material"; } from "@mui/material";
import {ExpandMore} from "@mui/icons-material"; import {ExpandMore} from "@mui/icons-material";
import React from "react"; import React from "react";
@ -22,28 +21,6 @@ import ArmorType from "./ArmorType";
import Vision from "./Vision"; import Vision from "./Vision";
import Ability from "./Ability"; import Ability from "./Ability";
import DeathExplosion from "./DeathExplosion"; import DeathExplosion from "./DeathExplosion";
import {styled} from '@mui/material/styles';
import {DescriptionBox} from "../commons/DescriptionBox";
const SectionTitle = styled(Typography)(({ theme }) => ({
fontWeight: 700,
mb: 2,
fontSize: '1.25rem',
}));
const StyledPaper = styled(Paper)(({ theme }) => ({
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
overflow: 'hidden',
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': {
color: 'rgba(255, 255, 255, 0.9)',
},
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': {
color: '#e94560',
fontWeight: 600,
},
}));
interface AbilityEnvironmentProps { interface AbilityEnvironmentProps {
@ -56,22 +33,21 @@ function AbilitySpawnedObject(props: AbilityEnvironmentProps) {
const abilityEnvironment = props.abilityEnvironment const abilityEnvironment = props.abilityEnvironment
return <div style={{color: 'rgba(255, 255, 255, 0.85)'}}> return <div>
<SectionTitle> <h3> {props.abilityEnvironment.name ?? props.abilityEnvironment.filename.charAt(0).toUpperCase() + props.abilityEnvironment.filename.slice(1).replaceAll('_', ' ').replace('.rgd', '')}
{props.abilityEnvironment.name ?? props.abilityEnvironment.filename.charAt(0).toUpperCase() + props.abilityEnvironment.filename.slice(1).replaceAll('_', ' ').replace('.rgd', '')} <i style={{fontSize: 11}}> (Spawned object)</i>
<i style={{fontSize: 11, color: 'rgba(255,255,255,0.5)'}}> (Spawned object)</i> </h3>
</SectionTitle>
<Grid2 container spacing={2}> <Grid2 container spacing={2}>
<Grid2 size={{xs: 12, md: 4}}> <Grid2 size={{xs: 12, md: 4}}>
<TableContainer component={StyledPaper} elevation={0}> <TableContainer component={Paper}>
<Table size="small" aria-label="a dense table"> <Table size="small" aria-label="a dense table">
<TableBody id="unit-stats-table"> <TableBody id="unit-stats-table">
{abilityEnvironment.armorType !== null && {abilityEnvironment.armorType !== null &&
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Armor type</TableCell> <TableCell component="th" scope="row">Armor type</TableCell>
<TableCell> <TableCell component="th" scope="row">
{abilityEnvironment.armorType2 == null ? {abilityEnvironment.armorType2 == null ?
<ArmorType name={abilityEnvironment.armorType.name} compact={false}/> : <ArmorType name={abilityEnvironment.armorType.name} compact={false}/> :
<Tooltip <Tooltip
@ -85,8 +61,8 @@ function AbilitySpawnedObject(props: AbilityEnvironmentProps) {
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Health</TableCell> <TableCell component="th" scope="row">Health</TableCell>
<TableCell> <TableCell component="th" scope="row">
<img style={{verticalAlign: "top"}} <img style={{verticalAlign: "top"}}
src="/images/Health_icon.webp"/>&nbsp; src="/images/Health_icon.webp"/>&nbsp;
{abilityEnvironment.health} {abilityEnvironment.healthRegeneration != null && abilityEnvironment.healthRegeneration > 0 && {abilityEnvironment.health} {abilityEnvironment.healthRegeneration != null && abilityEnvironment.healthRegeneration > 0 &&
@ -101,16 +77,18 @@ function AbilitySpawnedObject(props: AbilityEnvironmentProps) {
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Move speed</TableCell> <TableCell component="th" scope="row">Move speed</TableCell>
<TableCell>{abilityEnvironment.moveSpeed}</TableCell> <TableCell component="th" scope="row">
{abilityEnvironment.moveSpeed}
</TableCell>
</TableRow> </TableRow>
} }
{abilityEnvironment.sightRadius != null && {abilityEnvironment.sightRadius != null &&
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Vision</TableCell> <TableCell component="th" scope="row">Vision</TableCell>
<TableCell> <TableCell component="th" scope="row">
<Vision sight={abilityEnvironment.sightRadius ?? 0} <Vision sight={abilityEnvironment.sightRadius ?? 0}
detect={abilityEnvironment.detectRadius ?? 0}/> detect={abilityEnvironment.detectRadius ?? 0}/>
</TableCell> </TableCell>
@ -118,8 +96,8 @@ function AbilitySpawnedObject(props: AbilityEnvironmentProps) {
} }
{abilityEnvironment.lifetime != null && {abilityEnvironment.lifetime != null &&
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
<TableCell component="th" scope="row" >Lifetime</TableCell> <TableCell component="th" scope="row">Lifetime</TableCell>
<TableCell> <TableCell component="th" scope="row">
<img style={{verticalAlign: "top"}} <img style={{verticalAlign: "top"}}
src="/images/Time_icon.webp"/>&nbsp; src="/images/Time_icon.webp"/>&nbsp;
{abilityEnvironment.lifetime.toFixed(0)}s&nbsp; {abilityEnvironment.lifetime.toFixed(0)}s&nbsp;
@ -131,9 +109,9 @@ function AbilitySpawnedObject(props: AbilityEnvironmentProps) {
</TableContainer> </TableContainer>
</Grid2> </Grid2>
<Grid2 size={{xs: 12, md: 8}}> <Grid2 size={{xs: 12, md: 8}}>
<DescriptionBox> <div style={{whiteSpace: "pre-wrap"}}>
{abilityEnvironment.description} {abilityEnvironment.description}
</DescriptionBox> </div>
</Grid2> </Grid2>
{abilityEnvironment.abilities.length > 0 && <Grid2 size={12}> {abilityEnvironment.abilities.length > 0 && <Grid2 size={12}>
{abilityEnvironment.abilities.map(a => {abilityEnvironment.abilities.map(a =>

View File

@ -24,29 +24,6 @@ import {IDeathExplosion} from "../types/IDeathExplosion";
import Required from "./Required"; import Required from "./Required";
import ArmorTypeNames from "../types/ArmorTypeValues"; import ArmorTypeNames from "../types/ArmorTypeValues";
import DpsTable from "./DpsTable"; import DpsTable from "./DpsTable";
import {styled} from '@mui/material/styles';
const StyledAccordion = styled(Accordion)(({ theme }) => ({
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
'&:before': { display: 'none' },
'&.Mui-expanded': { margin: '0 0 8px 0' },
}));
const StyledPaper = styled(Paper)(({ theme }) => ({
background: 'rgba(255, 255, 255, 0.03)',
border: '1px solid rgba(255, 255, 255, 0.05)',
borderRadius: '12px',
overflow: 'hidden',
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': {
color: 'rgba(255, 255, 255, 0.85)',
},
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': {
color: '#e94560',
fontWeight: 600,
},
}));
interface DeathExpProps { interface DeathExpProps {
@ -60,37 +37,44 @@ function DeathExplosion(props: DeathExpProps) {
const chanceTotalNotZero = deathExplosion.globalChance * deathExplosion.chance != 0 const chanceTotalNotZero = deathExplosion.globalChance * deathExplosion.chance != 0
return chanceTotalNotZero ? return chanceTotalNotZero ?
<div className='addon-research-accordion' id='death-explosion'><StyledAccordion> <div className='addon-research-accordion' id='death-explosion'><Accordion>
<AccordionSummary <AccordionSummary
expandIcon={<ExpandMore sx={{color: '#ffffff'}}/>} expandIcon={<ExpandMore/>}
aria-controls="panel1-content" aria-controls="panel1-content"
sx={{color: '#ffffff'}}
> >
<span style={{fontSize: 20, color: '#ffffff'}}> <span style={{fontSize: 20}}>
Death Explosion Death Explosion
</span> </span>
</AccordionSummary> </AccordionSummary>
<AccordionDetails sx={{color: 'rgba(255,255,255,0.85)'}}> <AccordionDetails>
<Grid2 container spacing={2}> <Grid2 container spacing={2}>
<Grid2 size={{xs: 12, md: 4}}> <Grid2 size={{xs: 12, md: 4}}>
<TableContainer component={StyledPaper} elevation={0}> <TableContainer component={Paper}>
<Table size="small" aria-label="a dense table"> <Table size="small" aria-label="a dense table">
<TableBody id="unit-stats-table"> <TableBody id="unit-stats-table">
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Chance</TableCell> <TableCell component="th" scope="row">Chance</TableCell>
<TableCell>{deathExplosion.globalChance * deathExplosion.chance}%</TableCell> <TableCell component="th" scope="row">
{deathExplosion.globalChance * deathExplosion.chance}%
</TableCell>
</TableRow> </TableRow>
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Damage radius</TableCell> <TableCell component="th" scope="row">Damage radius</TableCell>
<TableCell>{deathExplosion.damageRadius}</TableCell> <TableCell component="th" scope="row">
{deathExplosion.damageRadius}
</TableCell>
</TableRow> </TableRow>
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow
<TableCell component="th" scope="row" >Throw force</TableCell> sx={{'&:last-child td, &:last-child th': {border: 0}}}
<TableCell>{deathExplosion.throwForceMin} - {deathExplosion.throwForceMax}</TableCell> >
<TableCell component="th" scope="row">Throw force</TableCell>
<TableCell component="th" scope="row">
{deathExplosion.throwForceMin} - {deathExplosion.throwForceMax}
</TableCell>
</TableRow> </TableRow>
</TableBody> </TableBody>
</Table> </Table>
@ -104,7 +88,7 @@ function DeathExplosion(props: DeathExpProps) {
</Grid2> </Grid2>
</Grid2> </Grid2>
</AccordionDetails> </AccordionDetails>
</StyledAccordion></div> : <span></span> </Accordion></div> : <span></span>
} }

View File

@ -19,19 +19,16 @@ export default function DpsTable(props: { mod: IMod, minDamageValue?: number, mi
const StyledTableCell = styled(TableCell)(({theme}) => ({ const StyledTableCell = styled(TableCell)(({theme}) => ({
[`&.${tableCellClasses.head}`]: { [`&.${tableCellClasses.head}`]: {
backgroundColor: 'rgba(233, 69, 96, 0.15)', backgroundColor: "rgb(244,244,244)",
fontWeight: 700,
marginRight: 'auto', marginRight: 'auto',
marginLeft: 'auto', marginLeft: 'auto',
paddingLeft: 10, paddingLeft: 10
borderBottom: '2px solid rgba(233, 69, 96, 0.3)',
}, },
[`&.${tableCellClasses.body}`]: { [`&.${tableCellClasses.body}`]: {
fontSize: 12, fontSize: 12,
textAlign: 'center', textAlign: 'center',
color: 'rgba(255, 255, 255, 0.85)',
paddingRight: 18, paddingRight: 18,
paddingLeft: 10, paddingLeft: 10
}, },
})); }));
@ -161,7 +158,7 @@ export default function DpsTable(props: { mod: IMod, minDamageValue?: number, mi
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow>
<StyledTableCell>{getTotalDamage(vehLowPiercing)}</StyledTableCell> <StyledTableCell>{getTotalDamage(vehLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(vehMedPiercing)}</StyledTableCell> <StyledTableCell>{getTotalDamage(vehMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(vehHighPiercing)}</StyledTableCell> <StyledTableCell>{getTotalDamage(vehHighPiercing)}</StyledTableCell>
@ -215,7 +212,7 @@ export default function DpsTable(props: { mod: IMod, minDamageValue?: number, mi
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow>
<StyledTableCell>{getTotalDamage(infLowPiercing)}</StyledTableCell> <StyledTableCell>{getTotalDamage(infLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infMedPiercing)}</StyledTableCell> <StyledTableCell>{getTotalDamage(infMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infHighPiercing)}</StyledTableCell> <StyledTableCell>{getTotalDamage(infHighPiercing)}</StyledTableCell>

View File

@ -6,20 +6,6 @@ import {Irace} from "../types/Irace";
import {IAffectedData} from "../types/IAffectedData"; import {IAffectedData} from "../types/IAffectedData";
import {IMod} from "../types/Imod"; import {IMod} from "../types/Imod";
import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer"; import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
import {styled} from '@mui/material/styles';
import {StyledLink} from "../commons/StyledLink";
// Styled components for dark theme
const StyledPaper = styled(Paper)(({ theme }) => ({
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
overflow: 'hidden',
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': {
color: 'rgba(255, 255, 255, 0.85)',
},
}));
interface IModifiersProvidesTable { interface IModifiersProvidesTable {
modifiers: IModifier[], modifiers: IModifier[],
@ -156,14 +142,15 @@ export function ModifiersProvidesAddonTable(props: IModifiersProvidesTable) {
let noWeaponMods = props.modifiers.filter(m => !m.reference.includes("default_weapon_modifier_hardpoint")) let noWeaponMods = props.modifiers.filter(m => !m.reference.includes("default_weapon_modifier_hardpoint"))
return ( return (
<TableContainer component={StyledPaper} elevation={0}> <TableContainer component={Paper}>
<Table size="small" aria-label="a dense table"> <Table size="small" aria-label="a dense table">
{noWeaponMods.map(m => {noWeaponMods.map(m =>
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" sx={{color: 'rgba(255,255,255,0.85)'}}>{getModName(m.reference)}</TableCell> <TableCell component="th" scope="row">{getModName(m.reference)}</TableCell>
<TableCell component="th" scope="row" sx={{color: 'rgba(255,255,255,0.85)'}}>{getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)}</TableCell> <TableCell component="th"
scope="row">{getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)}</TableCell>
</TableRow> </TableRow>
)} )}
</Table> </Table>
@ -181,37 +168,37 @@ export function ModifiersProvidesTable(props: IModifiersProvidesResearchTable) {
var affectedUnit = props.affectedData.affectedUnits.find(au => au.filename.replaceAll('.rgd', '').split(";")[0] === target || au.filename.replaceAll('.rgd', '').split(";")[1] === target); var affectedUnit = props.affectedData.affectedUnits.find(au => au.filename.replaceAll('.rgd', '').split(";")[0] === target || au.filename.replaceAll('.rgd', '').split(";")[1] === target);
if (affectedUnit != null) { if (affectedUnit != null) {
return <span><img style={{verticalAlign: "top", height: 25}} return <span><img style={{verticalAlign: "top", height: 25}}
src={getIcon(affectedUnit.icon)}/><StyledLink src={getIcon(affectedUnit.icon)}/><a
href={`/mod/${props.modId}/race/${props.race.id}/unit/${affectedUnit.id}`}> href={`/mod/${props.modId}/race/${props.race.id}/unit/${affectedUnit.id}`}>
{affectedUnit.name} {affectedUnit.name}
</StyledLink></span> </a></span>
} }
var affectedSergeant = props.affectedData.affectedSergeants.find(as => as.filename.replaceAll('.rgd', '') === target); var affectedSergeant = props.affectedData.affectedSergeants.find(as => as.filename.replaceAll('.rgd', '') === target);
if (affectedSergeant != null) { if (affectedSergeant != null) {
return <span><img style={{verticalAlign: "top", height: 25}} return <span><img style={{verticalAlign: "top", height: 25}}
src={getIcon(affectedSergeant.icon)}/>{affectedSergeant.name} at <img src={getIcon(affectedSergeant.icon)}/>{affectedSergeant.name} at <img
style={{verticalAlign: "top", height: 25}} style={{verticalAlign: "top", height: 25}}
src={getIcon(affectedSergeant.unit.icon)}/><StyledLink src={getIcon(affectedSergeant.unit.icon)}/><a
href={`/mod/${props.modId}/race/${props.race.id}/unit/${affectedSergeant.unit.id}`}> href={`/mod/${props.modId}/race/${props.race.id}/unit/${affectedSergeant.unit.id}`}>
{affectedSergeant.unit.name} {affectedSergeant.unit.name}
</StyledLink></span> </a></span>
} }
var affectedBuilding = props.affectedData.affectedBuildings.find(ab => ab.filename.replaceAll('.rgd', '') === target); var affectedBuilding = props.affectedData.affectedBuildings.find(ab => ab.filename.replaceAll('.rgd', '') === target);
if (affectedBuilding != null) { if (affectedBuilding != null) {
return <span><img style={{verticalAlign: "top", height: 25}} src={getIcon(affectedBuilding.icon)}/><StyledLink return <span><img style={{verticalAlign: "top", height: 25}} src={getIcon(affectedBuilding.icon)}/><a
href={`/mod/${props.modId}/race/${props.race.id}/building/${affectedBuilding.id}`}> href={`/mod/${props.modId}/race/${props.race.id}/building/${affectedBuilding.id}`}>
{affectedBuilding.name} {affectedBuilding.name}
</StyledLink> </span> </a> </span>
} }
var affectedWeapon = props.affectedData.affectedWeapons.find(aw => aw.filename.replaceAll('.rgd', '') === target); var affectedWeapon = props.affectedData.affectedWeapons.find(aw => aw.filename.replaceAll('.rgd', '') === target);
if (affectedWeapon != null) { if (affectedWeapon != null) {
return <span>{affectedWeapon.name ? affectedWeapon.name : affectedWeapon.filename.replaceAll('_', ' ').replace('.rgd', '')} at { return <span>{affectedWeapon.name ? affectedWeapon.name : affectedWeapon.filename.replaceAll('_', ' ').replace('.rgd', '')} at {
affectedWeapon.units.map(unit => affectedWeapon.units.map(unit =>
<span>{affectedWeapon != null && affectedWeapon.units.length + affectedWeapon.sergeants.length > 1 ? <br/> : <span></span>}<img style={{verticalAlign: "top", height: 25}} src={getIcon(unit.icon)}/><StyledLink <span>{affectedWeapon != null && affectedWeapon.units.length + affectedWeapon.sergeants.length > 1 ? <br/> : <span></span>}<img style={{verticalAlign: "top", height: 25}} src={getIcon(unit.icon)}/><a
href={`/mod/${props.modId}/race/${props.race.id}/unit/${unit.id}`}> href={`/mod/${props.modId}/race/${props.race.id}/unit/${unit.id}`}>
{unit.name} {unit.name}
</StyledLink> </span> </a> </span>
) )
} }
{ {
@ -219,18 +206,18 @@ export function ModifiersProvidesTable(props: IModifiersProvidesResearchTable) {
<span>{affectedWeapon != null && affectedWeapon.units.length + affectedWeapon.sergeants.length > 1 ? <br/> : <span></span>}<img style={{verticalAlign: "top", height: 25}} <span>{affectedWeapon != null && affectedWeapon.units.length + affectedWeapon.sergeants.length > 1 ? <br/> : <span></span>}<img style={{verticalAlign: "top", height: 25}}
src={getIcon(affectedSergeant.icon)}/>{affectedSergeant.name} at <img src={getIcon(affectedSergeant.icon)}/>{affectedSergeant.name} at <img
style={{verticalAlign: "top", height: 25}} style={{verticalAlign: "top", height: 25}}
src={getIcon(affectedSergeant.unit.icon)}/><StyledLink src={getIcon(affectedSergeant.unit.icon)}/><a
href={`/mod/${props.modId}/race/${props.race.id}/unit/${affectedSergeant.unit.id}`}> href={`/mod/${props.modId}/race/${props.race.id}/unit/${affectedSergeant.unit.id}`}>
{affectedSergeant.unit.name} {affectedSergeant.unit.name}
</StyledLink> </span> </a> </span>
) )
} }
{ {
affectedWeapon.buildings.map(affectedBuilding => affectedWeapon.buildings.map(affectedBuilding =>
<span><img style={{verticalAlign: "top", height: 25}} src={getIcon(affectedBuilding.icon)}/><StyledLink <span><img style={{verticalAlign: "top", height: 25}} src={getIcon(affectedBuilding.icon)}/><a
href={`/mod/${props.modId}/race/${props.race.id}/building/${affectedBuilding.id}`}> href={`/mod/${props.modId}/race/${props.race.id}/building/${affectedBuilding.id}`}>
{affectedBuilding.name} {affectedBuilding.name}
</StyledLink> </span> </a> </span>
) )
} }
</span> </span>
@ -241,21 +228,23 @@ export function ModifiersProvidesTable(props: IModifiersProvidesResearchTable) {
} }
return ( return (
<TableContainer component={StyledPaper} elevation={0}> <TableContainer component={Paper}>
<Table size="small" aria-label="a dense table"> <Table size="small" aria-label="a dense table">
{mods.map(m => {mods.map(m =>
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" sx={{color: 'rgba(255,255,255,0.85)'}}>{getTarget(m.target)}</TableCell> <TableCell component="th" scope="row">{getTarget(m.target)}</TableCell>
<TableCell component="th" scope="row" sx={{color: 'rgba(255,255,255,0.85)', minWidth: 120}}>{getModName(m.reference)}</TableCell> <TableCell style={{minWidth: 120}} component="th"
<TableCell component="th" scope="row" sx={{color: 'rgba(255,255,255,0.85)'}}>{getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)} scope="row">{getModName(m.reference)}</TableCell>
<TableCell component="th"
scope="row">{getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)}
{m.maxLifeTime !== undefined && m.maxLifeTime > 0 && {m.maxLifeTime !== undefined && m.maxLifeTime > 0 &&
<span><img style={{verticalAlign: "top"}} src='/images/Time_icon.webp'/>{m.maxLifeTime}s <span><img style={{verticalAlign: "top"}} src='/images/Time_icon.webp'/>{m.maxLifeTime}s
</span>} </span>}
<i style={{fontSize: 11}}>{m.exclusive ? " does not stack" : " does stack"}</i> <i style={{fontSize: 11}}>{m.exclusive ? " does not stuck" : " does stuck"}</i>
{m.probabilityOfApplying !== undefined && m.probabilityOfApplying !== 1.0 && {m.probabilityOfApplying !== undefined && m.probabilityOfApplying !== 1.0 &&
<span><i style={{fontSize: 11, color: 'rgba(255,255,255,0.5)'}}> ({m.probabilityOfApplying * 100}% chance)</i> <span><i style={{fontSize: 11}}> ({m.probabilityOfApplying * 100}% chance)</i>
</span>} </span>}
</TableCell> </TableCell>
</TableRow> </TableRow>

View File

@ -5,7 +5,6 @@ import {IconUrl} from "../core/api";
import {IResearchRequirements} from "../types/IResearchRequirements"; import {IResearchRequirements} from "../types/IResearchRequirements";
import {IRequirement, IRequirementLimitByBuildingDto, IRequirementSquadsDto} from "../types/IRequirement"; import {IRequirement, IRequirementLimitByBuildingDto, IRequirementSquadsDto} from "../types/IRequirement";
import {IUnitShort} from "../types/IUnitShort"; import {IUnitShort} from "../types/IUnitShort";
import {StyledLink} from "../commons/StyledLink";
interface IRequiredProps{ interface IRequiredProps{
requirement: IRequirement, requirement: IRequirement,
@ -21,9 +20,9 @@ function Required (props: IRequiredProps) {
<span>&nbsp; Global addon: <img style={{verticalAlign: "top", height: 25}} <span>&nbsp; Global addon: <img style={{verticalAlign: "top", height: 25}}
src={getIcon(rga.icon)}/> src={getIcon(rga.icon)}/>
&nbsp;<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${rga.buildingId}#addon-${rga.id}`}> &nbsp;<a href={`/mod/${props.modId}/race/${props.raceId}/building/${rga.buildingId}#addon-${rga.id}`}>
{rga.name} {rga.name}
</StyledLink></span> </a></span>
)}</div> )}</div>
} }
@ -31,29 +30,29 @@ function Required (props: IRequiredProps) {
return requirementBuildings.length > 0 && ( return requirementBuildings.length > 0 && (
<div>&nbsp; Buildings: <img style={{verticalAlign: "top", height: 25}} <div>&nbsp; Buildings: <img style={{verticalAlign: "top", height: 25}}
src={IconUrl + requirementBuildings[0].icon.replaceAll('\\', '/')}/> src={IconUrl + requirementBuildings[0].icon.replaceAll('\\', '/')}/>
&nbsp;{" "}<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${requirementBuildings[0].id}`}> &nbsp;{" "}<a href={`/mod/${props.modId}/race/${props.raceId}/building/${requirementBuildings[0].id}`}>
{requirementBuildings[0].name} {requirementBuildings[0].name}
</StyledLink> {requirementBuildings[1] !== undefined && <span>or &nbsp;<img style={{verticalAlign: "top", height: 25}} </a> {requirementBuildings[1] !== undefined && <span>or &nbsp;<img style={{verticalAlign: "top", height: 25}}
src={IconUrl + requirementBuildings[1].icon.replaceAll('\\', '/')}/> src={IconUrl + requirementBuildings[1].icon.replaceAll('\\', '/')}/>
&nbsp;{" "}<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${requirementBuildings[1].id}`}> &nbsp;{" "}<a href={`/mod/${props.modId}/race/${props.raceId}/building/${requirementBuildings[1].id}`}>
{requirementBuildings[1].name}</StyledLink></span> } </div> {requirementBuildings[1].name}</a></span> } </div>
); );
} }
function renderRequiremenStructureExclusive(buildingExclusive: IBuildingShort) { function renderRequiremenStructureExclusive(buildingExclusive: IBuildingShort) {
return <div>&nbsp; Can't be build with: <img style={{verticalAlign: "top", height: 25}} return <div>&nbsp; Can't be build with: <img style={{verticalAlign: "top", height: 25}}
src={IconUrl + buildingExclusive.icon.replaceAll('\\', '/')}/> src={IconUrl + buildingExclusive.icon.replaceAll('\\', '/')}/>
&nbsp;{" "}<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${buildingExclusive.id}`}> &nbsp;{" "}<a href={`/mod/${props.modId}/race/${props.raceId}/building/${buildingExclusive.id}`}>
{buildingExclusive.name} {buildingExclusive.name}
</StyledLink> </div> </a> </div>
} }
function renderRequiremenAddonExclusive(addonShort: IBuildingAddonShort) { function renderRequiremenAddonExclusive(addonShort: IBuildingAddonShort) {
return <div>&nbsp; Can't be build with: <img style={{verticalAlign: "top", height: 25}} return <div>&nbsp; Can't be build with: <img style={{verticalAlign: "top", height: 25}}
src={getIcon(addonShort.icon)}/> src={getIcon(addonShort.icon)}/>
&nbsp;<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${addonShort.buildingId}#addon-${addonShort.id}`}> &nbsp;<a href={`/mod/${props.modId}/race/${props.raceId}/building/${addonShort.buildingId}#addon-${addonShort.id}`}>
{addonShort.name} {addonShort.name}
</StyledLink></div> </a></div>
} }
function renderRequirementOwnership(requirementOwnership: string[],) { function renderRequirementOwnership(requirementOwnership: string[],) {
@ -71,8 +70,8 @@ function Required (props: IRequiredProps) {
function renderLimitPerBuilding(limitPerBuilding: IRequirementLimitByBuildingDto,) { function renderLimitPerBuilding(limitPerBuilding: IRequirementLimitByBuildingDto,) {
return <span>&nbsp; One <img style={{verticalAlign: "top", height: 25}} return <span>&nbsp; One <img style={{verticalAlign: "top", height: 25}}
src={IconUrl + limitPerBuilding.building.icon.replaceAll('\\', '/')}/> src={IconUrl + limitPerBuilding.building.icon.replaceAll('\\', '/')}/>
&nbsp;{" "}<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${limitPerBuilding.building.id}`}> &nbsp;{" "}<a href={`/mod/${props.modId}/race/${props.raceId}/building/${limitPerBuilding.building.id}`}>
{limitPerBuilding.building.name} </StyledLink> allow produce max {limitPerBuilding.limit}</span> {limitPerBuilding.building.name} </a> allow produce max {limitPerBuilding.limit}</span>
} }
function renderRequirementResearches(requirementResearches: IResearchRequirements[],) { function renderRequirementResearches(requirementResearches: IResearchRequirements[],) {
@ -80,9 +79,9 @@ function Required (props: IRequiredProps) {
<span>&nbsp; Research {rs.researchMustNotBeComplete ? "not" : ""}: <img style={{verticalAlign: "top", height: 25}} <span>&nbsp; Research {rs.researchMustNotBeComplete ? "not" : ""}: <img style={{verticalAlign: "top", height: 25}}
src={getIcon(rs.researchShortDto.icon)}/> src={getIcon(rs.researchShortDto.icon)}/>
&nbsp;<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${rs.researchShortDto.buildingId}#research-${rs.researchShortDto.id}`}> &nbsp;<a href={`/mod/${props.modId}/race/${props.raceId}/building/${rs.researchShortDto.buildingId}#research-${rs.researchShortDto.id}`}>
{rs.researchShortDto.name} {rs.researchShortDto.name}
</StyledLink></span> </a></span>
)}</div> )}</div>
} }
@ -91,9 +90,9 @@ function Required (props: IRequiredProps) {
<span>&nbsp; Squad: {rs.count} <img style={{verticalAlign: "top", height: 25}} <span>&nbsp; Squad: {rs.count} <img style={{verticalAlign: "top", height: 25}}
src={getIcon(rs.squad.icon)}/> src={getIcon(rs.squad.icon)}/>
&nbsp;<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/unit/${rs.squad.id}`}> &nbsp;<a href={`/mod/${props.modId}/race/${props.raceId}/unit/${rs.squad.id}`}>
{rs.squad.name} {rs.squad.name}
</StyledLink></span> </a></span>
)}</div> )}</div>
} }
@ -102,12 +101,12 @@ function Required (props: IRequiredProps) {
return requirementResearches.length > 0 && ( return requirementResearches.length > 0 && (
<div>&nbsp; Researches: <span> <img style={{verticalAlign: "top", height: 25}} <div>&nbsp; Researches: <span> <img style={{verticalAlign: "top", height: 25}}
src={getIcon(requirementResearches[0].researchShortDto.icon)}/> src={getIcon(requirementResearches[0].researchShortDto.icon)}/>
&nbsp;<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${requirementResearches[0].researchShortDto.buildingId}#research-${requirementResearches[0].researchShortDto.id}`}> &nbsp;<a href={`/mod/${props.modId}/race/${props.raceId}/building/${requirementResearches[0].researchShortDto.buildingId}#research-${requirementResearches[0].researchShortDto.id}`}>
{requirementResearches[0].researchShortDto.name} {requirementResearches[0].researchShortDto.name}
</StyledLink></span> {requirementResearches[1] != null && <span> or <img style={{verticalAlign: "top", height: 25}} src={getIcon(requirementResearches[1].researchShortDto.icon)}/> </a></span> {requirementResearches[1] != null && <span> or <img style={{verticalAlign: "top", height: 25}} src={getIcon(requirementResearches[1].researchShortDto.icon)}/>
&nbsp; <StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${requirementResearches[1].researchShortDto.buildingId}#research-${requirementResearches[1].researchShortDto.id}`}> &nbsp; <a href={`/mod/${props.modId}/race/${props.raceId}/building/${requirementResearches[1].researchShortDto.buildingId}#research-${requirementResearches[1].researchShortDto.id}`}>
{requirementResearches[1].researchShortDto.name} {requirementResearches[1].researchShortDto.name}
</StyledLink></span>} </div> </a></span>} </div>
); );
} }
@ -115,48 +114,48 @@ function Required (props: IRequiredProps) {
return ( return (
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}> <div>
<h4 style={{color: '#ffffff', fontWeight: 700}}>Required: </h4> <h4>Required: </h4>
{requirement.requiredTotalPop !== undefined && requirement.requiredTotalPop !== null && {requirement.requiredTotalPop !== undefined && requirement.requiredTotalPop !== null &&
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>&nbsp; Population: <img style={{verticalAlign: "top", height: 25}} <div>&nbsp; Population: <img style={{verticalAlign: "top", height: 25}}
src="/images/Resource_orksquadcap.gif"/>&nbsp; src="/images/Resource_orksquadcap.gif"/>&nbsp;
{requirement.requiredTotalPop}</div> {requirement.requiredTotalPop}</div>
} }
{requirement.mobBonus !== null && requirement.mobBonus !== undefined && {requirement.mobBonus !== null && requirement.mobBonus !== undefined &&
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>&nbsp; Mob requirement: <img style={{verticalAlign: "top", height: 25}} <div>&nbsp; Mob requirement: <img style={{verticalAlign: "top", height: 25}}
src="/images/Mob_bonus.gif"/>&nbsp; src="/images/Mob_bonus.gif"/>&nbsp;
{requirement.mobBonus.mobvalueRequired} in radius {requirement.mobBonus.proximityRequired}</div> {requirement.mobBonus.mobvalueRequired} in radius {requirement.mobBonus.proximityRequired}</div>
} }
{requirement.requireAddon !== null && {requirement.requireAddon !== null &&
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>&nbsp; Addon: <img style={{verticalAlign: "top", height: 25}} <div>&nbsp; Addon: <img style={{verticalAlign: "top", height: 25}}
src={IconUrl + requirement.requireAddon.icon.replaceAll('\\', '/')}/>&nbsp; src={IconUrl + requirement.requireAddon.icon.replaceAll('\\', '/')}/>&nbsp;
<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${requirement.requireAddon.buildingId}#addon-${requirement.requireAddon.id}`}> <a href={`/mod/${props.modId}/race/${props.raceId}/building/${requirement.requireAddon.buildingId}#addon-${requirement.requireAddon.id}`}>
{requirement.requireAddon.name} {requirement.requireAddon.name}
</StyledLink> </a>
{requirement.replaceWhenDone && <i> (old provides replace)</i>} {requirement.replaceWhenDone && <i> (old provides replace)</i>}
</div> </div>
} }
{requirement.requirementAddonExclusive != null && {requirement.requirementAddonExclusive != null &&
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderRequiremenAddonExclusive(requirement.requirementAddonExclusive,)}</div>} renderRequiremenAddonExclusive(requirement.requirementAddonExclusive,)}
{requirement.requirementBuildings.map(b => {requirement.requirementBuildings.map(b =>
<div key={b.id} style={{color: 'rgba(255, 255, 255, 0.85)'}}>&nbsp; Building: <img style={{verticalAlign: "top", height: 25}} <div key={b.id}>&nbsp; Building: <img style={{verticalAlign: "top", height: 25}}
src={IconUrl + b.icon.replaceAll('\\', '/')}/>&nbsp; src={IconUrl + b.icon.replaceAll('\\', '/')}/>&nbsp;
<StyledLink href= {'/mod/'+ props.modId +'/race/'+ props.raceId +'/building/' + b.id + "/"}>{b.name}</StyledLink></div>) <a href= {'/mod/'+ props.modId +'/race/'+ props.raceId +'/building/' + b.id + "/"}>{b.name}</a></div>)
} }
{requirement.requirementResearches.length !== 0 && {requirement.requirementResearches.length !== 0 &&
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderRequirementResearches(requirement.requirementResearches,)}</div>} renderRequirementResearches(requirement.requirementResearches,)}
{requirement.requirementSquads.length !== 0 && {requirement.requirementSquads.length !== 0 &&
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderRequirementSquads(requirement.requirementSquads,)}</div>} renderRequirementSquads(requirement.requirementSquads,)}
{requirement.requirementResearchesEither.length !== 0 && {requirement.requirementResearchesEither.length !== 0 &&
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderRequirementResearchesEither(requirement.requirementResearchesEither,)}</div>} renderRequirementResearchesEither(requirement.requirementResearchesEither,)}
{requirement.requirementBuildingsEither.length !== 0 && {requirement.requirementBuildingsEither.length !== 0 &&
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderRequirementBuildingEither(requirement.requirementBuildingsEither,)}</div>} renderRequirementBuildingEither(requirement.requirementBuildingsEither,)}
{requirement.requirementStructureExclusive != null && {requirement.requirementStructureExclusive != null &&
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderRequiremenStructureExclusive(requirement.requirementStructureExclusive,)}</div>} renderRequiremenStructureExclusive(requirement.requirementStructureExclusive,)}
{requirement.limitByBuilding !== undefined && requirement.limitByBuilding !== null && <div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderLimitPerBuilding(requirement.limitByBuilding)}</div>} {requirement.limitByBuilding !== undefined && requirement.limitByBuilding !== null && renderLimitPerBuilding(requirement.limitByBuilding)}
{requirement.requirementsGlobalAddons.length !== 0 && {requirement.requirementsGlobalAddons.length !== 0 &&
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderRequirementGlobalAddons(requirement.requirementsGlobalAddons,)}</div>} renderRequirementGlobalAddons(requirement.requirementsGlobalAddons,)}
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderRequirementOwnership(requirement.requiredOwnership)}</div> {renderRequirementOwnership(requirement.requiredOwnership)}
</div> </div>
) )
} }

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import {ISergeant, IShortWeapon} from "../types/IUnit"; import {ISergeant, IShortWeapon} from "../types/IUnit";
import {Grid2, Paper, Table, TableBody, TableCell, TableContainer, TableRow, Tooltip, Typography} from "@mui/material"; import {Grid2, Paper, Table, TableBody, TableCell, TableContainer, TableRow, Tooltip} from "@mui/material";
import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer"; import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
import ArmorType from "./ArmorType"; import ArmorType from "./ArmorType";
import WeaponSlot from "./WeaponSlot"; import WeaponSlot from "./WeaponSlot";
@ -12,28 +12,6 @@ import Required from "./Required";
import {ModifiersProvidesTable} from "./ModifiersProvideTable"; import {ModifiersProvidesTable} from "./ModifiersProvideTable";
import Ability from "./Ability"; import Ability from "./Ability";
import DeathExplosion from "./DeathExplosion"; import DeathExplosion from "./DeathExplosion";
import {styled} from '@mui/material/styles';
import {DescriptionBox} from "../commons/DescriptionBox";
const StyledPaper = styled(Paper)(({ theme }) => ({
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
overflow: 'hidden',
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': {
color: 'rgba(255, 255, 255, 0.9)',
},
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': {
color: '#e94560',
fontWeight: 600,
},
}));
const SectionTitle = styled(Typography)(({ theme }) => ({
fontWeight: 700,
mb: 2,
fontSize: '1.25rem',
}));
export interface SergeantProps { export interface SergeantProps {
@ -60,17 +38,17 @@ const Sergeant = (props: SergeantProps) => {
}) })
return ( return (
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}> <div>
<Grid2 container spacing={2}> <Grid2 container spacing={2}>
<Grid2 size= {{xs: 12, md: 4}}> <Grid2 size= {{xs: 12, md: 4}}>
<TableContainer component={StyledPaper} elevation={0}> <TableContainer component={Paper}>
<Table size="small" aria-label="a dense table"> <Table size="small" aria-label="a dense table">
<TableBody id="unit-stats-table"> <TableBody id="unit-stats-table">
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Cost</TableCell> <TableCell component="th" scope="row">Cost</TableCell>
<TableCell> <TableCell component="th" scope="row">
{sergeant.buildCostRequisition > 0 && {sergeant.buildCostRequisition > 0 &&
<span>&nbsp;<img style={{verticalAlign: "top"}} <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_requisition.gif"/>&nbsp; src="/images/Resource_requisition.gif"/>&nbsp;
@ -94,7 +72,7 @@ const Sergeant = (props: SergeantProps) => {
</TableRow> </TableRow>
{(sergeant.requisitionIncome !== undefined && sergeant.requisitionIncome !== null || sergeant.powerIncome !== undefined && sergeant.powerIncome !== null || sergeant.faithIncome !== undefined && sergeant.faithIncome !== null) && {(sergeant.requisitionIncome !== undefined && sergeant.requisitionIncome !== null || sergeant.powerIncome !== undefined && sergeant.powerIncome !== null || sergeant.faithIncome !== undefined && sergeant.faithIncome !== null) &&
<TableRow> <TableRow>
<TableCell >Resource income</TableCell> <TableCell>Resource income</TableCell>
<TableCell> <TableCell>
{sergeant.requisitionIncome !== undefined && sergeant.requisitionIncome > 0 && {sergeant.requisitionIncome !== undefined && sergeant.requisitionIncome > 0 &&
<span>&nbsp;<img style={{verticalAlign: "top"}} <span>&nbsp;<img style={{verticalAlign: "top"}}
@ -114,8 +92,8 @@ const Sergeant = (props: SergeantProps) => {
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Armor type</TableCell> <TableCell component="th" scope="row">Armor type</TableCell>
<TableCell> <TableCell component="th" scope="row">
{sergeant.armorType2 == null ? <ArmorType name={sergeant.armorType.name} compact={false}/> {sergeant.armorType2 == null ? <ArmorType name={sergeant.armorType.name} compact={false}/>
: <Tooltip title={"upgrade/debuff can turns to " + sergeant.armorType2.name}><span><ArmorType name={sergeant.armorType.name} compact={false}/></span></Tooltip> } : <Tooltip title={"upgrade/debuff can turns to " + sergeant.armorType2.name}><span><ArmorType name={sergeant.armorType.name} compact={false}/></span></Tooltip> }
</TableCell> </TableCell>
@ -123,8 +101,8 @@ const Sergeant = (props: SergeantProps) => {
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Health</TableCell> <TableCell component="th" scope="row">Health</TableCell>
<TableCell> <TableCell component="th" scope="row">
<span><img style={{verticalAlign: "top"}} <span><img style={{verticalAlign: "top"}}
src="/images/Health_icon.webp"/>&nbsp; src="/images/Health_icon.webp"/>&nbsp;
{sergeant.health} {sergeant.healthRegeneration > 0 && {sergeant.health} {sergeant.healthRegeneration > 0 &&
@ -134,8 +112,8 @@ const Sergeant = (props: SergeantProps) => {
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Morale</TableCell> <TableCell component="th" scope="row">Morale</TableCell>
<TableCell> <TableCell component="th" scope="row">
{sergeant.moraleIncomeMax && <span><img style={{verticalAlign: "top"}} {sergeant.moraleIncomeMax && <span><img style={{verticalAlign: "top"}}
src="/images/ARM_Morale.webp"/> +{sergeant.moraleIncomeMax}&nbsp;</span>} src="/images/ARM_Morale.webp"/> +{sergeant.moraleIncomeMax}&nbsp;</span>}
{sergeant.moraleRate && <span>+ {sergeant.moraleRate}/s</span> } {sergeant.moraleRate && <span>+ {sergeant.moraleRate}/s</span> }
@ -146,20 +124,22 @@ const Sergeant = (props: SergeantProps) => {
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Mass</TableCell> <TableCell component="th" scope="row">Mass</TableCell>
<TableCell>{sergeant.mass}</TableCell> <TableCell component="th" scope="row">
{sergeant.mass}
</TableCell>
</TableRow> </TableRow>
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Vision</TableCell> <TableCell component="th" scope="row">Vision</TableCell>
<TableCell> <TableCell component="th" scope="row">
<Vision sight={sergeant.sightRadius} detect={sergeant.detectRadius}/> <Vision sight={sergeant.sightRadius} detect={sergeant.detectRadius}/>
</TableCell> </TableCell>
</TableRow> </TableRow>
{sergeant.mobValue != null && {sergeant.mobValue != null &&
<TableRow> <TableRow>
<TableCell component="th" scope="row" >Mob value</TableCell> <TableCell>Mob value</TableCell>
<TableCell> <TableCell>
<span> <img style={{height: 20, verticalAlign: "top"}} src="/images/Mob_bonus.gif"/> {sergeant.mobValue} </span> <span> <img style={{height: 20, verticalAlign: "top"}} src="/images/Mob_bonus.gif"/> {sergeant.mobValue} </span>
</TableCell> </TableCell>
@ -170,20 +150,20 @@ const Sergeant = (props: SergeantProps) => {
</TableContainer> </TableContainer>
</Grid2> </Grid2>
<Grid2 size={8}> <Grid2 size={8}>
<DescriptionBox> <div style={{whiteSpace: "pre-wrap"}}>
{sergeant.description} {sergeant.description}
</DescriptionBox> </div>
</Grid2> </Grid2>
{sergeant.requirements !== null && {sergeant.requirements !== null &&
<Grid2 size={{xs: 12, md: 12}}> <Grid2 size={{xs: 12, md: 12}}>
<Required requirement={sergeant.requirements} modId={props.mod.id} raceId={props.race.id}/> <Required requirement={sergeant.requirements} modId={props.mod.id} raceId={props.race.id}/>
</Grid2>} </Grid2>}
{sergeant.modifiers.length > 0 && <Grid2 size={12}> {sergeant.modifiers.length > 0 && <Grid2 size={12}>
<SectionTitle>Affected on</SectionTitle> <h3>Affected on</h3>
<ModifiersProvidesTable modifiers={sergeant.modifiers} modId={props.mod.id} race={props.race} affectedData={null}/> <ModifiersProvidesTable modifiers={sergeant.modifiers} modId={props.mod.id} race={props.race} affectedData={null}/>
</Grid2>} </Grid2>}
{sergeant.abilities.length > 0 && <Grid2 size={12}> {sergeant.abilities.length > 0 && <Grid2 size={12}>
<SectionTitle>Abilities</SectionTitle> <h3>Abilities</h3>
{sergeant.abilities.map(a => {sergeant.abilities.map(a =>
<Ability mod={props.mod} ability={a} race={props.race}/> <Ability mod={props.mod} ability={a} race={props.race}/>
)} )}
@ -205,7 +185,7 @@ const Sergeant = (props: SergeantProps) => {
{renderAffectedResearches(sergeant.affectedResearches, props.mod.id, props.race.id)} {renderAffectedResearches(sergeant.affectedResearches, props.mod.id, props.race.id)}
</Grid2> </Grid2>
</Grid2> </Grid2>
<b className="hotkey" >Hotkey: {sergeant.hotkey}</b> <b className="hotkey">Hotkey: {sergeant.hotkey}</b>
</div>) </div>)
}; };

View File

@ -1,45 +1,10 @@
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import {AvailableBuildings, AvailableUnits} from "../core/api"; import {AvailableBuildings, AvailableUnits} from "../core/api";
import {Grid2, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper} from "@mui/material"; import {Grid2, Link, Table, TableBody, TableCell, TableContainer, TableHead, TableRow} from "@mui/material";
import {styled} from '@mui/material/styles';
import {IRaceUnits, IUnitShort} from "../types/IUnitShort"; import {IRaceUnits, IUnitShort} from "../types/IUnitShort";
import {IBuildingShort, IRaceBuildings} from "../types/IBuildingShort"; import {IBuildingShort, IRaceBuildings} from "../types/IBuildingShort";
import {StyledLink} from "../commons/StyledLink";
// Styled components for dark theme
const StyledPaper = styled(Paper)(({ theme }) => ({
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
overflow: 'hidden',
}));
const StyledTable = styled(Table)(({ theme }) => ({
'& .MuiTableCell-head': {
backgroundColor: 'rgba(233, 69, 96, 0.15)',
fontWeight: 700,
fontSize: '1rem',
borderBottom: '2px solid rgba(233, 69, 96, 0.3)',
},
'& .MuiTableCell-body': {
color: 'rgba(255, 255, 255, 0.85)',
borderBottom: '1px solid rgba(255, 255, 255, 0.05)',
fontSize: '0.9rem',
},
'& .MuiTableRow-hover:hover': {
backgroundColor: 'rgba(233, 69, 96, 0.08)',
},
}));
const StyledHeaderLink = styled(StyledLink)(({ theme }) => ({
fontWeight: 700,
fontSize: '1.1rem',
'&:hover': {
color: '#e94560',
},
}));
interface IUnitsTable { interface IUnitsTable {
racesUnits: IRaceUnits[] racesUnits: IRaceUnits[]
racesBuildings: IRaceBuildings[] racesBuildings: IRaceBuildings[]
@ -47,7 +12,6 @@ interface IUnitsTable {
export default function UnitsTable(prop: {modId: number}) { export default function UnitsTable(prop: {modId: number}) {
const [unitsTable, setUnitsTable] = useState<IUnitsTable>({ const [unitsTable, setUnitsTable] = useState<IUnitsTable>({
racesUnits: [], racesUnits: [],
racesBuildings: [] racesBuildings: []
@ -55,19 +19,15 @@ export default function UnitsTable(prop: {modId: number}) {
function getUnitRef(modId: number, raceId: string, unit: IUnitShort) { function getUnitRef(modId: number, raceId: string, unit: IUnitShort) {
return <span style={{fontSize: 11}}> return <span style={{fontSize: 11}}><Link href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id}>
<StyledLink href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id}> {unit.name}
{unit.name} </Link><br/></span>
</StyledLink><br/>
</span>
} }
function getBuildingRef(modId: number, raceId: string, building: IBuildingShort) { function getBuildingRef(modId: number, raceId: string, building: IBuildingShort) {
return <span style={{fontSize: 11}}> return <span style={{fontSize: 11}}><Link href={"/mod/" + modId + "/race/" + raceId + "/building/" + building.id}>
<StyledLink href={"/mod/" + modId + "/race/" + raceId + "/building/" + building.id}> {building.name}
{building.name} <br/></Link></span>
</StyledLink><br/>
</span>
} }
useEffect(() => { useEffect(() => {
@ -139,31 +99,31 @@ export default function UnitsTable(prop: {modId: number}) {
} }
function generateRaceUnitTable(racesUnitsPart: IRaceUnits[], racesBuildings: IRaceBuildings[]){ function generateRaceUnitTable(racesUnitsPart: IRaceUnits[], racesBuildings: IRaceBuildings[]){
return (<StyledPaper elevation={0}> return (<TableContainer>
{<StyledTable sx={{minWidth: 650}} size="small" aria-label="a dense table"> {<Table sx={{minWidth: 650}} size="small" aria-label="a dense table">
<TableHead> <TableHead>
<TableRow> <TableRow>
{racesUnitsPart.map(raceUnits => <TableCell><h2><StyledHeaderLink {racesUnitsPart.map(raceUnits => <TableCell><h2><Link color="inherit"
href={"/mod/" + prop.modId + "/race/" + raceUnits.race.id}>{raceUnits.race.name}</StyledHeaderLink> href={"/mod/" + prop.modId + "/race/" + raceUnits.race.id}>{raceUnits.race.name}</Link>
</h2></TableCell>)} </h2></TableCell>)}
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
<TableRow> <TableRow style={{verticalAlign: 'top'}}>
{racesUnitsPart.map(raceUnits => {racesUnitsPart.map(raceUnits =>
<TableCell> <TableCell>
{getUnitsRef(raceUnits.infantry.concat(raceUnits.support), raceUnits.race.id)} {getUnitsRef(raceUnits.infantry.concat(raceUnits.support), raceUnits.race.id)}
</TableCell>) </TableCell>)
} }
</TableRow> </TableRow>
<TableRow> <TableRow style={{verticalAlign: 'top'}}>
{racesUnitsPart.map(raceUnits => {racesUnitsPart.map(raceUnits =>
<TableCell> <TableCell>
{getUnitsRef(raceUnits.tech, raceUnits.race.id)} {getUnitsRef(raceUnits.tech, raceUnits.race.id)}
</TableCell>) </TableCell>)
} }
</TableRow> </TableRow>
<TableRow> <TableRow style={{verticalAlign: 'top'}}>
{racesBuildings.map(raceBuilding => {racesBuildings.map(raceBuilding =>
<TableCell> <TableCell>
{raceBuilding.buildings.map(building => getBuildingRef(prop.modId, raceBuilding.race.id, building))} {raceBuilding.buildings.map(building => getBuildingRef(prop.modId, raceBuilding.race.id, building))}
@ -171,8 +131,8 @@ export default function UnitsTable(prop: {modId: number}) {
} }
</TableRow> </TableRow>
</TableBody> </TableBody>
</StyledTable>} </Table>}
</StyledPaper>) </TableContainer>)
} }
function generateRaceUnitTables(racesUnits: IRaceUnits[], racesBuildings: IRaceBuildings[], raceCount: number){ function generateRaceUnitTables(racesUnits: IRaceUnits[], racesBuildings: IRaceBuildings[], raceCount: number){
@ -196,8 +156,8 @@ export default function UnitsTable(prop: {modId: number}) {
function generateRaceList(unitsTable: IUnitsTable){ function generateRaceList(unitsTable: IUnitsTable){
return unitsTable.racesUnits.sort(function (a, b) { return unitsTable.racesUnits.sort(function (a, b) {
return a.race.name.localeCompare(b.race.name); return a.race.name.localeCompare(b.race.name);
}).map(raceUnits => <h5 key={raceUnits.race.id}><StyledLink }).map(raceUnits => <h5 key={raceUnits.race.id}><Link color="inherit"
href={"/mod/" + prop.modId + "/race/" + raceUnits.race.id}>{raceUnits.race.name}</StyledLink><br/> href={"/mod/" + prop.modId + "/race/" + raceUnits.race.id}>{raceUnits.race.name}</Link><br/>
</h5>) </h5>)
} }

View File

@ -6,15 +6,6 @@ import {ExpandMore} from "@mui/icons-material";
import {IMod} from "../types/Imod"; import {IMod} from "../types/Imod";
import {Irace} from "../types/Irace"; import {Irace} from "../types/Irace";
import WeaponFull from "./WeaponFull"; import WeaponFull from "./WeaponFull";
import {styled} from '@mui/material/styles';
const StyledAccordion = styled(Accordion)(({ theme }) => ({
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
'&:before': { display: 'none' },
'&.Mui-expanded': { margin: '0 0 8px 0' },
}));
interface IWeaponProps { interface IWeaponProps {
weapon: IShortWeapon, weapon: IShortWeapon,
@ -43,26 +34,23 @@ class Weapon extends React.Component<IWeaponProps, any> {
const weapon = this.props.weapon const weapon = this.props.weapon
return ( return (
<div style={{marginBottom: 10}}><StyledAccordion TransitionProps={{ unmountOnExit: true, timeout: 100 }}> <div style={{marginBottom: 10}}><Accordion TransitionProps={{ unmountOnExit: true, timeout: 100 }}>
<AccordionSummary <AccordionSummary
expandIcon={<ExpandMore sx={{color: '#ffffff'}}/>} expandIcon={<ExpandMore/>}
aria-controls="panel1-content" aria-controls="panel1-content"
id="panel1-header" id="panel1-header"
sx={{color: '#ffffff'}}
> >
<span style={{fontSize: 18, color: '#ffffff'}}> {!this.props.isDefault && weapon.icon && !weapon.icon.endsWith("upgrade.png") ? <span style={{fontSize: 18}}> {!this.props.isDefault && weapon.icon && !weapon.icon.endsWith("upgrade.png") ?
<img className="weaponIcon" src={IconUrl + weapon.icon.replaceAll('\\', '/')}/> : ( <img className="weaponIcon" src={IconUrl + weapon.icon.replaceAll('\\', '/')}/> : (
weapon.isMeleeWeapon ? weapon.isMeleeWeapon ?
<img className="weaponIcon" src="/images/MeleeStance_icon_bw.jpg"/> : <img className="weaponIcon" src="/images/MeleeStance_icon_bw.jpg"/> :
<img className="weaponIcon" src="/images/RangedStance_icon_bw.jpg"/> <img className="weaponIcon" src="/images/RangedStance_icon_bw.jpg"/>
)} {weapon.name ? weapon.name : this.humanReadableName(weapon.filename)} )} {weapon.name ? weapon.name : this.humanReadableName(weapon.filename)} </span>
{this.props.isDefault && <i style={{fontSize: 12, color: 'rgba(255,255,255,0.5)'}}> (default)</i>}
</span>
</AccordionSummary> </AccordionSummary>
<AccordionDetails sx={{color: 'rgba(255,255,255,0.85)'}}> <AccordionDetails>
<WeaponFull weaponId={weapon.id} isDefault={this.props.isDefault} mod={this.props.mod} race={this.props.race} haveReinforceMenu={this.props.haveReinforceMenu}/> <WeaponFull weaponId={weapon.id} isDefault={this.props.isDefault} mod={this.props.mod} race={this.props.race} haveReinforceMenu={this.props.haveReinforceMenu}/>
</AccordionDetails> </AccordionDetails>
</StyledAccordion></div> </Accordion></div>
); );
} }
} }

View File

@ -22,8 +22,6 @@ import {Irace} from "../types/Irace";
import {IRaceUnits} from "../types/IUnitShort"; import {IRaceUnits} from "../types/IUnitShort";
import {IRaceBuildings} from "../types/IBuildingShort"; import {IRaceBuildings} from "../types/IBuildingShort";
import {ModifiersProvidesTable} from "./ModifiersProvideTable"; import {ModifiersProvidesTable} from "./ModifiersProvideTable";
import {DescriptionBox} from "../commons/DescriptionBox";
import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
interface IWeaponFull { interface IWeaponFull {
currentTable: string; currentTable: string;
@ -39,32 +37,16 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
const StyledTableCell = styled(TableCell)(({theme}) => ({ const StyledTableCell = styled(TableCell)(({theme}) => ({
[`&.${tableCellClasses.head}`]: { [`&.${tableCellClasses.head}`]: {
backgroundColor: 'rgba(233, 69, 96, 0.15)', backgroundColor: "rgb(244,244,244)",
marginRight: 'auto', marginRight: 'auto',
marginLeft: 'auto', marginLeft: 'auto',
paddingLeft: 10, paddingLeft: 10
borderBottom: '2px solid rgba(233, 69, 96, 0.3)',
}, },
[`&.${tableCellClasses.body}`]: { [`&.${tableCellClasses.body}`]: {
fontSize: 12, fontSize: 12,
textAlign: 'center', textAlign: 'center',
color: 'rgba(255, 255, 255, 0.85)',
paddingRight: 18, paddingRight: 18,
paddingLeft: 10, paddingLeft: 10
},
}));
const StyledPaper = styled(Paper)(({ theme }) => ({
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
overflow: 'hidden',
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': {
color: 'rgba(255, 255, 255, 0.9)',
},
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': {
color: '#e94560',
fontWeight: 600,
}, },
})); }));
@ -151,76 +133,93 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
return ( return (
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}> <div>
<Grid2 container spacing={2}> <Grid2 container spacing={2}>
{!props.isDefault && <Grid2 size={12}> <Grid2 size={12}>
<DescriptionBox> <div style={{whiteSpace: "pre-wrap"}}>
<span className="weaponDescription">{weapon.description}</span> <span
</DescriptionBox> className="weaponDescription">{props.isDefault ? "Default weapon" : weapon.description}</span>
</Grid2>} </div>
</Grid2>
<Grid2 size={{xs: 12, md: 4}}> <Grid2 size={{xs: 12, md: 4}}>
<TableContainer component={StyledPaper} elevation={0}> <TableContainer component={Paper}>
<Table size="small" aria-label="a dense table"> <Table size="small" aria-label="a dense table">
{/*<TableHead><TableRow><TableCell component="th" scope="row"><b>Damage</b></TableCell><TableCell/></TableRow></TableHead>*/}
<TableBody> <TableBody>
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Base damage</TableCell> <TableCell component="th" scope="row">Base damage</TableCell>
<TableCell>{weapon.minDamage} {weapon.maxDamage !== weapon.minDamage && "- " + weapon.maxDamage}</TableCell> <TableCell component="th"
scope="row">{weapon.minDamage} {weapon.maxDamage !== weapon.minDamage && "- " + weapon.maxDamage}
</TableCell>
</TableRow> </TableRow>
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Accuracy</TableCell> <TableCell component="th" scope="row">Accuracy</TableCell>
<TableCell>{weapon.accuracy.toFixed(3).replace(/[,.]?0+$/, '')}</TableCell> <TableCell component="th"
scope="row">{weapon.accuracy.toFixed(3).replace(/[,.]?0+$/, '')}
</TableCell>
</TableRow> </TableRow>
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Accuracy moving</TableCell> <TableCell component="th" scope="row">Accuracy moving</TableCell>
<TableCell>{weapon.setupTime === 0 && weapon.accuracy - weapon.accuracyReductionMoving > 0 ? (weapon.accuracy - weapon.accuracyReductionMoving).toFixed(3).replace(/[,.]?0+$/, '') : "-"}</TableCell> <TableCell component="th"
scope="row">{weapon.setupTime === 0 && weapon.accuracy - weapon.accuracyReductionMoving > 0 ? (weapon.accuracy - weapon.accuracyReductionMoving).toFixed(3).replace(/[,.]?0+$/, '') : "-"}
</TableCell>
</TableRow> </TableRow>
</TableBody> </TableBody>
</Table> </Table>
</TableContainer> </TableContainer>
</Grid2> </Grid2>
<Grid2 size={{xs: 12, md: 4}}> <Grid2 size={{xs: 12, md: 4}}>
<TableContainer component={StyledPaper} elevation={0}> <TableContainer component={Paper}>
<Table size="small" aria-label="a dense table"> <Table size="small" aria-label="a dense table">
<TableBody> <TableBody>
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Reload time</TableCell> <TableCell component="th" scope="row">Reload time</TableCell>
<TableCell>{weapon.reloadTime}</TableCell> <TableCell component="th"
scope="row">{weapon.reloadTime}
</TableCell>
</TableRow> </TableRow>
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Range</TableCell> <TableCell component="th" scope="row">Range</TableCell>
<TableCell>{weapon.maxRange ? (weapon.maxRange) : "-"} {weapon.minRange ? "(min " + (weapon.minRange) + ")" : ""}</TableCell> <TableCell component="th"
scope="row">{weapon.maxRange ? (weapon.maxRange) : "-"} {weapon.minRange ? "(min " + (weapon.minRange) + ")" : ""}
</TableCell>
</TableRow> </TableRow>
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Damage radius</TableCell> <TableCell component="th" scope="row">Damage radius</TableCell>
<TableCell>{weapon.damageRadius ? (weapon.damageRadius) : "-"}</TableCell> <TableCell component="th"
scope="row">{weapon.damageRadius ? (weapon.damageRadius) : "-"}
</TableCell>
</TableRow> </TableRow>
</TableBody> </TableBody>
</Table> </Table>
</TableContainer> </TableContainer>
</Grid2> </Grid2>
<Grid2 size={{xs: 12, md: 4}}> <Grid2 size={{xs: 12, md: 4}}>
<TableContainer component={StyledPaper} elevation={0}> <TableContainer component={Paper}>
<Table size="small" aria-label="a dense table"> <Table size="small" aria-label="a dense table">
<TableBody> <TableBody>
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Cost</TableCell> <TableCell component="th" scope="row">Cost</TableCell>
{(weapon.costRequisition > 0 || weapon.costPower > 0) ? {(weapon.costRequisition > 0 || weapon.costPower > 0) ?
<TableCell> <TableCell component="th"
scope="row">
{weapon.costRequisition > 0 && {weapon.costRequisition > 0 &&
<span>&nbsp;<img style={{verticalAlign: "top"}} <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_requisition.gif"/>&nbsp; src="/images/Resource_requisition.gif"/>&nbsp;
@ -229,24 +228,24 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
<span>&nbsp;<img style={{verticalAlign: "top"}} <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_power.gif"/>&nbsp; src="/images/Resource_power.gif"/>&nbsp;
{weapon.costPower.toFixed(0)}</span>} {weapon.costPower.toFixed(0)}</span>}
{(weapon.costTimeSeconds !== undefined && weapon.costTimeSeconds > 0) && </TableCell> : <TableCell component="th" scope="row"> - </TableCell>
<span>&nbsp;<AvTimerOutlinedIcon
style={{verticalAlign: "top", fontSize: "18px"}}/>&nbsp;
{weapon.costTimeSeconds}s</span>}
</TableCell> : <TableCell> - </TableCell>
} }
</TableRow> </TableRow>
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Setup time</TableCell> <TableCell component="th" scope="row">Setup time</TableCell>
<TableCell>{weapon.setupTime != 0 ? (weapon.setupTime).toFixed(3).replace(/[,.]?0+$/, '') : "-"}</TableCell> <TableCell component="th"
scope="row">{weapon.setupTime != 0 ? (weapon.setupTime).toFixed(3).replace(/[,.]?0+$/, '') : "-"}
</TableCell>
</TableRow> </TableRow>
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Throw force</TableCell> <TableCell component="th" scope="row">Throw force</TableCell>
<TableCell>{weapon.throwForceMin != 0 ? weapon.throwForceMin + " - " + weapon.throwForceMax : "-"}</TableCell> <TableCell component="th"
scope="row">{weapon.throwForceMin != 0 ? weapon.throwForceMin + " - " + weapon.throwForceMax : "-"}
</TableCell>
</TableRow> </TableRow>
</TableBody> </TableBody>
</Table> </Table>
@ -260,10 +259,9 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
aria-label="Platform" aria-label="Platform"
value={weaoponFull.currentTable} value={weaoponFull.currentTable}
onChange={handleChange} onChange={handleChange}
sx={{ mb: 2 }}
> >
<ToggleButton size="small" value="dps" sx={{ color: '#ffffff', '&.Mui-selected': { borderColor: '#e94560', color: '#e94560', backgroundColor: 'rgba(233, 69, 96, 0.15)' } }}>Dps</ToggleButton> <ToggleButton size="small" value="dps">Dps</ToggleButton>
<ToggleButton size="small" value="one hit" sx={{ color: '#ffffff', '&.Mui-selected': {borderColor: '#e94560', color: '#e94560', backgroundColor: 'rgba(233, 69, 96, 0.15)' } }}>One hit average damage</ToggleButton> <ToggleButton size="small" value="one hit">One hit average damage</ToggleButton>
</ToggleButtonGroup> </ToggleButtonGroup>
<TableContainer> <TableContainer>
{ props.mod !== undefined && props.mod.technicalName === 'UltimateApocalypse' ? { props.mod !== undefined && props.mod.technicalName === 'UltimateApocalypse' ?
@ -292,7 +290,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow>
<StyledTableCell>{getTotalDamage(infLowPiercing)}</StyledTableCell> <StyledTableCell>{getTotalDamage(infLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infMedPiercing)}</StyledTableCell> <StyledTableCell>{getTotalDamage(infMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infHighPiercing)}</StyledTableCell> <StyledTableCell>{getTotalDamage(infHighPiercing)}</StyledTableCell>
@ -327,13 +325,13 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
name={ArmorTypeNames.BuildingSuper}/></StyledTableCell> name={ArmorTypeNames.BuildingSuper}/></StyledTableCell>
<StyledTableCell><img style={{verticalAlign: "top"}} <StyledTableCell><img style={{verticalAlign: "top"}}
src="/images/ARM_Morale.webp"/> src="/images/ARM_Morale.webp"/>
<div style={{width: 20, fontSize: 12, height: 50, color: 'rgba(255,255,255,0.7)'}}> <div style={{width: 20, fontSize: 12, height: 50}}>
<i>Morale</i></div> <i>Morale</i></div>
</StyledTableCell> </StyledTableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow>
<StyledTableCell>{getTotalDamage(vehLowPiercing)}</StyledTableCell> <StyledTableCell>{getTotalDamage(vehLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(vehMedPiercing)}</StyledTableCell> <StyledTableCell>{getTotalDamage(vehMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(vehHighPiercing)}</StyledTableCell> <StyledTableCell>{getTotalDamage(vehHighPiercing)}</StyledTableCell>
@ -381,13 +379,13 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
name={ArmorTypeNames.BuildingHigh}/></StyledTableCell> name={ArmorTypeNames.BuildingHigh}/></StyledTableCell>
<StyledTableCell><img style={{verticalAlign: "top"}} <StyledTableCell><img style={{verticalAlign: "top"}}
src="/images/ARM_Morale.webp"/> src="/images/ARM_Morale.webp"/>
<div style={{width: 20, fontSize: 12, height: 50, color: 'rgba(255,255,255,0.7)'}}> <div style={{width: 20, fontSize: 12, height: 50}}>
<i>Morale</i></div> <i>Morale</i></div>
</StyledTableCell> </StyledTableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow>
<StyledTableCell>{getTotalDamage(infLowPiercing)}</StyledTableCell> <StyledTableCell>{getTotalDamage(infLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infMedPiercing)}</StyledTableCell> <StyledTableCell>{getTotalDamage(infMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infHighPiercing)}</StyledTableCell> <StyledTableCell>{getTotalDamage(infHighPiercing)}</StyledTableCell>
@ -413,7 +411,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
</Grid2> </Grid2>
{weapon.modifiers.length > 0 && <Grid2 size={12}> {weapon.modifiers.length > 0 && <Grid2 size={12}>
<h3 style={{color: '#ffffff', fontWeight: 700}}>Modifiers</h3> <h3>Modifiers</h3>
<ModifiersProvidesTable modifiers={weapon.modifiers} modId={props.mod.id} race={props.race} affectedData={null}/> <ModifiersProvidesTable modifiers={weapon.modifiers} modId={props.mod.id} race={props.race} affectedData={null}/>
</Grid2>} </Grid2>}
{weapon.requirements !== null && !props.isDefault && props.haveReinforceMenu && {weapon.requirements !== null && !props.isDefault && props.haveReinforceMenu &&
@ -424,10 +422,10 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
{renderAffectedResearches(weapon.affectedResearches, props.mod.id, props.race.id)} {renderAffectedResearches(weapon.affectedResearches, props.mod.id, props.race.id)}
</Grid2> </Grid2>
</Grid2> </Grid2>
{!props.isDefault && props.haveReinforceMenu && <b className="hotkey" >Hotkey: {weapon.hotkey}</b>} {!props.isDefault && props.haveReinforceMenu && <b className="hotkey">Hotkey: {weapon.hotkey}</b>}
</div> </div>
) )
} else return <div style={{color: 'rgba(255, 255, 255, 0.5)'}}>loading...</div>; } else return <div>loading...</div>;

View File

@ -4,14 +4,6 @@ import {IShortWeapon, IWeapon} from "../types/IUnit";
import {IMod} from "../types/Imod"; import {IMod} from "../types/Imod";
import {Irace} from "../types/Irace"; import {Irace} from "../types/Irace";
import WeaponShort from "./WeaponFull"; import WeaponShort from "./WeaponFull";
import {Typography} from "@mui/material";
import {styled} from '@mui/material/styles';
const SectionTitle = styled(Typography)(({ theme }) => ({
fontWeight: 700,
mb: 2,
fontSize: '1.25rem',
}));
export interface WeaponSlotProps { export interface WeaponSlotProps {
hardpoint: number, hardpoint: number,
@ -41,7 +33,7 @@ const WeaponSlot= (props: WeaponSlotProps) => {
return ( return (
<div> <div>
<SectionTitle>{header}</SectionTitle> <h3>{header}</h3>
{weaponsSorted.filter(wp => !wp[1].filename.includes("dummy")).map(wp => {weaponsSorted.filter(wp => !wp[1].filename.includes("dummy")).map(wp =>
<div style={{marginLeft: 20}}><Weapon race={props.race} mod={props.mod} isDefault={!haveDummy || wp[1].filename === firstWeapon.filename} weapon={wp[1]} haveReinforceMenu={haveReinforceMenu}/></div> <div style={{marginLeft: 20}}><Weapon race={props.race} mod={props.mod} isDefault={!haveDummy || wp[1].filename === firstWeapon.filename} weapon={wp[1]} haveReinforceMenu={haveReinforceMenu}/></div>

View File

@ -9,8 +9,7 @@ import {
TableBody, TableBody,
TableCell, TableCell,
TableContainer, TableContainer,
TableRow, TableRow
Typography
} from "@mui/material"; } from "@mui/material";
import {IBuilding, IBuildingAddon} from "../../types/IBuilding"; import {IBuilding, IBuildingAddon} from "../../types/IBuilding";
import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer"; import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
@ -18,30 +17,6 @@ import {ExpandMore} from "@mui/icons-material";
import {IMod} from "../../types/Imod"; import {IMod} from "../../types/Imod";
import {getIcon, ModifiersProvidesTable,} from "../ModifiersProvideTable"; import {getIcon, ModifiersProvidesTable,} from "../ModifiersProvideTable";
import Required from "../Required"; import Required from "../Required";
import {styled} from '@mui/material/styles';
import {DescriptionBox} from "../../commons/DescriptionBox";
const StyledAccordion = styled(Accordion)(({ theme }) => ({
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
'&:before': { display: 'none' },
'&.Mui-expanded': { margin: '0 0 8px 0' },
}));
const StyledPaper = styled(Paper)(({ theme }) => ({
background: 'rgba(255, 255, 255, 0.03)',
border: '1px solid rgba(255, 255, 255, 0.05)',
borderRadius: '12px',
overflow: 'hidden',
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': {
color: 'rgba(255, 255, 255, 0.85)',
},
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': {
color: '#e94560',
fontWeight: 600,
},
}));
interface IBuildingAddonProps { interface IBuildingAddonProps {
addon: IBuildingAddon, addon: IBuildingAddon,
@ -56,28 +31,27 @@ function BuildingAddon(props: IBuildingAddonProps){
return <div className='addon-research-accordion' id={"addon-" + addon.id} ><StyledAccordion> return <div className='addon-research-accordion' id={"addon-" + addon.id} ><Accordion>
<AccordionSummary <AccordionSummary
expandIcon={<ExpandMore sx={{color: '#ffffff'}}/>} expandIcon={<ExpandMore/>}
aria-controls="panel1-content" aria-controls="panel1-content"
sx={{color: '#ffffff'}}
> >
<span style={{fontSize: 20, color: '#ffffff'}}> <span style={{fontSize: 20}}>
<img className="sergeantIcon" src={getIcon(addon.icon)}/> <img className="sergeantIcon" src={getIcon(addon.icon)}/>
&nbsp; {addon.name} &nbsp; {addon.name}
</span> </span>
</AccordionSummary> </AccordionSummary>
<AccordionDetails sx={{color: 'rgba(255,255,255,0.85)'}}> <AccordionDetails>
<Grid2 container spacing={2}> <Grid2 container spacing={2}>
<Grid2 size={{xs: 12, md: 4}}> <Grid2 size={{xs: 12, md: 4}}>
<TableContainer component={StyledPaper} elevation={0}> <TableContainer component={Paper}>
<Table size="small" aria-label="a dense table"> <Table size="small" aria-label="a dense table">
<TableBody id="unit-stats-table"> <TableBody id="unit-stats-table">
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Cost</TableCell> <TableCell component="th" scope="row">Cost</TableCell>
<TableCell> <TableCell component="th" scope="row">
{addon.addonCostRequisition > 0 && {addon.addonCostRequisition > 0 &&
<span>&nbsp;<img style={{verticalAlign: "top"}} <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_requisition.gif"/>&nbsp; src="/images/Resource_requisition.gif"/>&nbsp;
@ -109,9 +83,9 @@ function BuildingAddon(props: IBuildingAddonProps){
</TableContainer><br/> </TableContainer><br/>
</Grid2> </Grid2>
<Grid2 size={{xs: 12, md: 8}}> <Grid2 size={{xs: 12, md: 8}}>
<DescriptionBox> <div style={{whiteSpace: "pre-wrap"}}>
{addon.description} {addon.description}
</DescriptionBox> </div>
</Grid2> </Grid2>
<ModifiersProvidesTable modifiers={addon.addonModifiers} affectedData={addon.affectedData} modId={props.mod.id} race={building.race}/> <ModifiersProvidesTable modifiers={addon.addonModifiers} affectedData={addon.affectedData} modId={props.mod.id} race={building.race}/>
{addon.addonRequirement !== null && {addon.addonRequirement !== null &&
@ -119,9 +93,9 @@ function BuildingAddon(props: IBuildingAddonProps){
<Required requirement={addon.addonRequirement} modId={building.modId} raceId={building.race.id}/> <Required requirement={addon.addonRequirement} modId={building.modId} raceId={building.race.id}/>
</Grid2>} </Grid2>}
</Grid2> </Grid2>
<b className="hotkey" >Hotkey: {addon.hotkey}</b> <b className="hotkey">Hotkey: {addon.hotkey}</b>
</AccordionDetails> </AccordionDetails>
</StyledAccordion></div> </Accordion></div>
} }

View File

@ -20,24 +20,6 @@ import React from "react";
import {IBuilding} from "../../types/IBuilding"; import {IBuilding} from "../../types/IBuilding";
import {IResearchShort} from "../../types/IResearchShort"; import {IResearchShort} from "../../types/IResearchShort";
import ResearchFull from "./ResearchFull"; import ResearchFull from "./ResearchFull";
import {styled} from '@mui/material/styles';
const StyledAccordion = styled(Accordion)(({ theme }) => ({
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
'&:before': { display: 'none' },
'&.Mui-expanded': { margin: '0 0 8px 0' },
}));
const StyledLink = styled('a')(({ theme }) => ({
color: '#e94560',
textDecoration: 'none',
'&:hover': {
color: '#ff6b6b',
textDecoration: 'underline',
},
}));
interface IResearchProps { interface IResearchProps {
@ -51,21 +33,20 @@ function Research(props: IResearchProps){
const research = props.research const research = props.research
const building = props.building const building = props.building
return <div className='addon-research-accordion' id={"research-" + research.id}><StyledAccordion TransitionProps={{ unmountOnExit: true, timeout: 100 }}> return <div className='addon-research-accordion' id={"research-" + research.id}><Accordion TransitionProps={{ unmountOnExit: true, timeout: 100 }}>
<AccordionSummary <AccordionSummary
expandIcon={<ExpandMore sx={{color: '#ffffff'}}/>} expandIcon={<ExpandMore/>}
aria-controls="panel1-content" aria-controls="panel1-content"
sx={{color: '#ffffff'}}
> >
<span style={{fontSize: 20, color: '#ffffff'}}> <span style={{fontSize: 20}}>
<img className="sergeantIcon" src={getIcon(research.icon)}/> <img className="sergeantIcon" src={getIcon(research.icon)}/>
&nbsp; {research.name} &nbsp; {research.name}
</span> </span>
</AccordionSummary> </AccordionSummary>
<AccordionDetails sx={{color: 'rgba(255,255,255,0.85)'}}> <AccordionDetails>
<ResearchFull id={research.id} building={building}/> <ResearchFull id={research.id} building={building}/>
</AccordionDetails> </AccordionDetails>
</StyledAccordion></div> </Accordion></div>
} }
@ -74,12 +55,12 @@ export function renderAffectedResearches(researches: IResearchShort[], modId: nu
function researchLink(rs: IResearchShort) { function researchLink(rs: IResearchShort) {
return <span><img style={{verticalAlign: "top", height: 25}} return <span><img style={{verticalAlign: "top", height: 25}}
src={getIcon(rs.icon)}/> src={getIcon(rs.icon)}/>
&nbsp;<StyledLink href={`/mod/${modId}/race/${raceId}/building/${rs.buildingId}#research-${rs.id}`}> &nbsp;<a href={`/mod/${modId}/race/${raceId}/building/${rs.buildingId}#research-${rs.id}`}>
{rs.name} {rs.name}
</StyledLink></span> </a></span>
} }
return <div style={{color: 'rgba(255, 255, 255, 0.85)'}}> {researches.map(rs => return <div> {researches.map(rs =>
rs.buildingId == null ? <span key={rs.id}></span> : rs.buildingId == null ? <span key={rs.id}></span> :
<span key={rs.id}>Research affect: {researchLink(rs)}<br/></span> <span key={rs.id}>Research affect: {researchLink(rs)}<br/></span>
)}</div> )}</div>

View File

@ -10,8 +10,7 @@ import {
TableBody, TableBody,
TableCell, TableCell,
TableContainer, TableContainer,
TableRow, TableRow
Typography
} from "@mui/material"; } from "@mui/material";
import {ExpandMore} from "@mui/icons-material"; import {ExpandMore} from "@mui/icons-material";
import {getIcon, ModifiersProvidesTable} from "../ModifiersProvideTable"; import {getIcon, ModifiersProvidesTable} from "../ModifiersProvideTable";
@ -22,22 +21,6 @@ import {IBuilding} from "../../types/IBuilding";
import {IResearchShort} from "../../types/IResearchShort"; import {IResearchShort} from "../../types/IResearchShort";
import {ResearchUrl, WeaponUrl} from "../../core/api"; import {ResearchUrl, WeaponUrl} from "../../core/api";
import {IWeapon} from "../../types/IUnit"; import {IWeapon} from "../../types/IUnit";
import {styled} from '@mui/material/styles';
import {DescriptionBox} from "../../commons/DescriptionBox";
const StyledPaper = styled(Paper)(({ theme }) => ({
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
overflow: 'hidden',
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': {
color: 'rgba(255, 255, 255, 0.9)',
},
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': {
color: '#e94560',
fontWeight: 600,
},
}));
interface IResearchFull { interface IResearchFull {
@ -71,14 +54,14 @@ function ResearchFull(props: { id: number, building: IBuilding }){
<Grid2 container spacing={2}> <Grid2 container spacing={2}>
<Grid2 size={{xs: 12, md: 4}}> <Grid2 size={{xs: 12, md: 4}}>
<TableContainer component={StyledPaper} elevation={0}> <TableContainer component={Paper}>
<Table size="small" aria-label="a dense table"> <Table size="small" aria-label="a dense table">
<TableBody id="unit-stats-table"> <TableBody id="unit-stats-table">
<TableRow <TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}} sx={{'&:last-child td, &:last-child th': {border: 0}}}
> >
<TableCell component="th" scope="row" >Cost</TableCell> <TableCell component="th" scope="row">Cost</TableCell>
<TableCell> <TableCell component="th" scope="row">
{research.costRequisition > 0 && {research.costRequisition > 0 &&
<span>&nbsp;<img style={{verticalAlign: "top"}} <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_requisition.gif"/>&nbsp; src="/images/Resource_requisition.gif"/>&nbsp;
@ -110,9 +93,9 @@ function ResearchFull(props: { id: number, building: IBuilding }){
</TableContainer><br/> </TableContainer><br/>
</Grid2> </Grid2>
<Grid2 size={{xs: 12, md: 8}}> <Grid2 size={{xs: 12, md: 8}}>
<DescriptionBox> <div style={{whiteSpace: "pre-wrap"}}>
{research.description} {research.description}
</DescriptionBox> </div>
</Grid2> </Grid2>
<Grid2 size={{xs: 12, md: 12}}> <Grid2 size={{xs: 12, md: 12}}>
@ -124,9 +107,9 @@ function ResearchFull(props: { id: number, building: IBuilding }){
<Required requirement={research.requirements} modId={building.modId} raceId={building.race.id}/> <Required requirement={research.requirements} modId={building.modId} raceId={building.race.id}/>
</Grid2>} </Grid2>}
</Grid2> </Grid2>
<b className="hotkey" >Hotkey: {research.hotkey}</b> <b className="hotkey">Hotkey: {research.hotkey}</b>
</div> </div>
} else return <div style={{color: 'rgba(255, 255, 255, 0.5)'}}>loading...</div> } else return <div>loading...</div>
} }
export default ResearchFull; export default ResearchFull;

View File

@ -1,14 +0,0 @@
import {Button, Link} from "@mui/material";
import {styled} from '@mui/material/styles';
export const BackButton = styled(Button)(({ theme }) => ({
marginBottom: theme.spacing(3),
color: '#e94560BB',
borderColor: 'rgba(233, 69, 96, 0.5)',
textTransform: 'none',
fontWeight: 600,
'&:hover': {
borderColor: '#e94560',
backgroundColor: 'rgba(233, 69, 96, 0.1)',
},
}));

View File

@ -1,12 +0,0 @@
import {styled} from '@mui/material/styles';
export const DescriptionBox = styled('div')(({ theme }) => ({
color: 'rgba(255, 255, 255, 0.85)',
lineHeight: 1.7,
fontSize: '0.95rem',
padding: theme.spacing(3),
background: 'rgba(255, 255, 255, 0.03)',
borderRadius: '12px',
border: '1px solid rgba(255, 255, 255, 0.05)',
whiteSpace: 'pre-wrap',
}));

View File

@ -1,12 +0,0 @@
import {Link} from "@mui/material";
import {styled} from '@mui/material/styles';
export const StyledLink = styled(Link)(({ theme }) => ({
color: '#e94560BB',
textDecoration: 'none',
transition: 'all 0.2s ease',
'&:hover': {
color: '#ff6b6bBB',
textDecoration: 'underline',
},
}));

View File

@ -1,43 +1,13 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
body { body {
margin: 0; margin: 0;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif; sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
background-color: #0a0a0a;
color: rgba(255, 255, 255, 0.85);
} }
code { code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace; monospace;
background: rgba(255, 255, 255, 0.1);
padding: 2px 6px;
border-radius: 4px;
}
* {
scrollbar-width: thin;
scrollbar-color: #e94560 #1a1a2e;
}
*::-webkit-scrollbar {
width: 8px;
}
*::-webkit-scrollbar-track {
background: #1a1a2e;
}
*::-webkit-scrollbar-thumb {
background-color: #e94560;
border-radius: 4px;
}
a {
color: inherit;
text-decoration: none;
} }

View File

@ -5,9 +5,6 @@ import {IShortWeapon} from "../types/IUnit";
import '../css/Building.css' import '../css/Building.css'
import { import {
Button, Button,
Box,
Container,
Divider,
Grid2, Grid2,
Link, Link,
ListItem, ListItem,
@ -15,10 +12,11 @@ import {
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableContainer,
TableRow, TableRow,
Tooltip, Tooltip
Typography, LinearProgress
} from "@mui/material"; } from "@mui/material";
import {ArrowBack} from "@mui/icons-material";
import ArmorType from "../classes/ArmorType"; import ArmorType from "../classes/ArmorType";
import AvTimerOutlinedIcon from '@mui/icons-material/AvTimer'; import AvTimerOutlinedIcon from '@mui/icons-material/AvTimer';
import WeaponSlot from "../classes/WeaponSlot"; import WeaponSlot from "../classes/WeaponSlot";
@ -33,31 +31,6 @@ import Required from "../classes/Required";
import {ModifiersProvidesTable} from "../classes/ModifiersProvideTable"; import {ModifiersProvidesTable} from "../classes/ModifiersProvideTable";
import Ability from "../classes/Ability"; import Ability from "../classes/Ability";
import DeathExplosion from "../classes/DeathExplosion"; import DeathExplosion from "../classes/DeathExplosion";
import {styled} from '@mui/material/styles';
import {DescriptionBox} from "../commons/DescriptionBox";
import {ArrowBack} from "@mui/icons-material";
import {BackButton} from "../commons/BackButton";
const SectionTitle = styled(Typography)(({ theme }) => ({
fontWeight: 700,
mb: 2,
fontSize: '1.25rem',
}));
const StatsPaper = styled(Paper)(({ theme }) => ({
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
overflow: 'hidden',
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': {
color: 'rgba(255, 255, 255, 0.9)',
},
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': {
color: '#e94560',
fontWeight: 600,
},
}));
interface UintPageState { interface UintPageState {
building: IBuilding, building: IBuilding,
@ -66,30 +39,18 @@ interface UintPageState {
function Unit (unit: IUnitShort, modId: number, raceId: String) { function Unit (unit: IUnitShort, modId: number, raceId: String) {
return (<Grid2 key={unit.id} size={{xs: 12, md: 3}}> return (<Grid2 key={unit.id} size={{xs: 12, md: 3}}><Link href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id}><ListItem>
<Link href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id} sx={{color: 'rgba(255,255,255,0.85)', textDecoration: 'none', '&:hover': {color: '#e94560'}}}> {unit.icon && <img className="unitIcon" src={IconUrl + unit.icon.replaceAll('\\', '/')}/> }
<ListItem sx={{ {unit.name}
color: 'rgba(255,255,255,0.85)', {unit.canDetect && <span>&nbsp;<img style={{verticalAlign: "top"}}
padding: '8px 12px', src="/images/DETECT_YES.webp"/></span>}
background: 'rgba(255,255,255,0.03)', </ListItem></Link></Grid2>)
borderRadius: '8px',
border: '1px solid rgba(255,255,255,0.05)',
transition: 'all 0.2s ease',
'&:hover': { background: 'rgba(233, 69, 96, 0.1)', borderColor: 'rgba(233, 69, 96, 0.3)' }
}}>
{unit.icon && <img className="unitIcon" src={IconUrl + unit.icon.replaceAll('\\', '/')}/> }
{unit.name}
{unit.canDetect && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/DETECT_YES.webp"/></span>}
</ListItem>
</Link>
</Grid2>)
} }
function Building(building: IBuilding, mod: IMod) { function Building(building: IBuilding, mod: IMod) {
document.title = building.name + " — " + mod.name document.title = building.name
let mapBuildingWeapons: Map<number, Map<number, IShortWeapon>> = new Map(); let mapBuildingWeapons: Map<number, Map<number, IShortWeapon>> = new Map();
@ -111,35 +72,21 @@ function Building(building: IBuilding, mod: IMod) {
} }
return ( return (
<Box> <div>
<Box sx={{ mb: 3 }}> <h1>{mod.name} ({mod.version})</h1>
<Typography variant="h4" component="h2" sx={{ <h2>{building.icon &&
fontWeight: 800, <img className="unitIcon" src={IconUrl + building.icon.replaceAll('\\', '/')}/>} {buildingName} </h2>
mb: 1,
display: 'flex',
alignItems: 'center',
gap: 2,
}}>
{building.icon &&
<img className="unitIcon" src={IconUrl + building.icon.replaceAll('\\', '/')}/>}
{buildingName}
</Typography>
<Typography variant="subtitle1" sx={{
color: 'rgba(255, 255, 255, 0.6)',
fontWeight: 500,
}}>
{mod.name} ({mod.version})
</Typography>
</Box>
<Grid2 container spacing={3}> <Grid2 container spacing={2}>
<Grid2 size={{xs: 12, md: 4}}> <Grid2 size={{xs: 12, md: 4}}>
<StatsPaper elevation={0}> <TableContainer component={Paper}>
<Table size="small" aria-label="a dense table"> <Table size="small" aria-label="a dense table">
<TableBody id="unit-stats-table"> <TableBody id="unit-stats-table">
<TableRow> <TableRow
<TableCell component="th" scope="row" >Cost</TableCell> sx={{'&:last-child td, &:last-child th': {border: 0}}}
<TableCell> >
<TableCell component="th" scope="row">Cost</TableCell>
<TableCell component="th" scope="row">
{building.buildCostRequisition > 0 && {building.buildCostRequisition > 0 &&
<span>&nbsp;<img style={{verticalAlign: "top"}} <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_requisition.gif"/>&nbsp; src="/images/Resource_requisition.gif"/>&nbsp;
@ -167,7 +114,7 @@ function Building(building: IBuilding, mod: IMod) {
</TableRow> </TableRow>
{(building.requisitionIncome !== undefined && building.requisitionIncome > 0 || building.powerIncome !== undefined && building.powerIncome !== null || building.faithIncome !== undefined && building.faithIncome !== null) && {(building.requisitionIncome !== undefined && building.requisitionIncome > 0 || building.powerIncome !== undefined && building.powerIncome !== null || building.faithIncome !== undefined && building.faithIncome !== null) &&
<TableRow> <TableRow>
<TableCell >Resource income</TableCell> <TableCell>Resource income</TableCell>
<TableCell> <TableCell>
{building.requisitionIncome !== undefined && building.requisitionIncome != null && {building.requisitionIncome !== undefined && building.requisitionIncome != null &&
<span>&nbsp;<img style={{verticalAlign: "top"}} <span>&nbsp;<img style={{verticalAlign: "top"}}
@ -184,52 +131,62 @@ function Building(building: IBuilding, mod: IMod) {
</TableCell> </TableCell>
</TableRow> </TableRow>
} }
<TableRow> <TableRow
<TableCell component="th" scope="row" >Armor type</TableCell> sx={{'&:last-child td, &:last-child th': {border: 0}}}
<TableCell> >
<TableCell component="th" scope="row">Armor type</TableCell>
<TableCell component="th" scope="row">
{building.armorType2 == null ? <ArmorType name={building.armorType.name} compact={false}/> {building.armorType2 == null ? <ArmorType name={building.armorType.name} compact={false}/>
: <Tooltip title={"upgrade/debuff can turns to " + building.armorType2.name}><span><ArmorType name={building.armorType.name} compact={false}/></span></Tooltip> } : <Tooltip title={"upgrade/debuff can turns to " + building.armorType2.name}><span><ArmorType name={building.armorType.name} compact={false}/></span></Tooltip> }
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow
<TableCell component="th" scope="row" >Health</TableCell> sx={{'&:last-child td, &:last-child th': {border: 0}}}
<TableCell> >
<TableCell component="th" scope="row">Health</TableCell>
<TableCell component="th" scope="row">
<span><img style={{verticalAlign: "top"}} <span><img style={{verticalAlign: "top"}}
src="/images/Health_icon.webp"/>&nbsp; src="/images/Health_icon.webp"/>&nbsp;
{building.health} {building.healthRegeneration > 0 && {building.health} {building.healthRegeneration > 0 &&
<span>+{building.healthRegeneration}/s</span>} </span> <span>+{building.healthRegeneration}/s</span>} </span>
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow
<TableCell component="th" scope="row" >Sight</TableCell> sx={{'&:last-child td, &:last-child th': {border: 0}}}
<TableCell> >
<TableCell component="th" scope="row">Sight</TableCell>
<TableCell component="th" scope="row">
<Vision sight={building.sightRadius} detect={building.detectRadius}/> <Vision sight={building.sightRadius} detect={building.detectRadius}/>
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow
<TableCell component="th" scope="row" >Repair max</TableCell> sx={{'&:last-child td, &:last-child th': {border: 0}}}
<TableCell>{building.repairMax}</TableCell> >
<TableCell component="th" scope="row">Repair max</TableCell>
<TableCell component="th" scope="row">
{building.repairMax}
</TableCell>
</TableRow> </TableRow>
</TableBody> </TableBody>
</Table> </Table>
</StatsPaper> </TableContainer>
</Grid2> </Grid2>
<Grid2 size={{xs: 12, md: 8}}> <Grid2 size={8}>
<DescriptionBox> <div style={{whiteSpace: "pre-wrap"}}>
{building.description} {building.description}
</DescriptionBox> </div>
</Grid2> </Grid2>
{building.requirements !== null && {building.requirements !== null &&
<Grid2 size={{xs: 12, md: 12}}> <Grid2 size={{xs: 12, md: 12}}>
<Required requirement={building.requirements} modId={building.modId} raceId={building.race.id}/> <Required requirement={building.requirements} modId={building.modId} raceId={building.race.id}/>
</Grid2>} </Grid2>}
{building.modifiers.length > 0 && {building.modifiers.length > 0 &&
<Grid2 size={{xs: 12, md: 12}}> <Grid2 size={{xs: 12, md: 12}} >
<SectionTitle>Affected on</SectionTitle> <h3>Affected on</h3>
<ModifiersProvidesTable modifiers={building.modifiers} race={building.race} modId={building.modId} affectedData={building.affectedData} /> <ModifiersProvidesTable modifiers={building.modifiers} race={building.race} modId={building.modId} affectedData={building.affectedData} />
</Grid2>} </Grid2>}
{building.units.length > 0 && <Grid2 size={12}> {building.units.length > 0 && <Grid2 size={12}>
<SectionTitle>Unit production</SectionTitle> <h3>Unit production</h3>
<Grid2 container spacing={2}> <Grid2 container spacing={2}>
{building.units.map(unit => {building.units.map(unit =>
Unit(unit, mod.id, building.race.id) Unit(unit, mod.id, building.race.id)
@ -237,7 +194,7 @@ function Building(building: IBuilding, mod: IMod) {
</Grid2> </Grid2>
</Grid2>} </Grid2>}
{building.abilities.length > 0 && <Grid2 size={12}> {building.abilities.length > 0 && <Grid2 size={12}>
<SectionTitle>Abilities</SectionTitle> <h3>Abilities</h3>
{building.abilities.map(a => {building.abilities.map(a =>
<Ability mod={mod} ability={a} race={building.race}/> <Ability mod={mod} ability={a} race={building.race}/>
)} )}
@ -250,13 +207,13 @@ function Building(building: IBuilding, mod: IMod) {
</Grid2> </Grid2>
} }
{building.addons.length > 0 && <Grid2 size={12}> {building.addons.length > 0 && <Grid2 size={12}>
<SectionTitle>Addons</SectionTitle> <h3>Addons</h3>
{building.addons.map(b => {building.addons.map(b =>
<BuildingAddon mod={mod} addon={b} building={building}/> <BuildingAddon mod={mod} addon={b} building={building}/>
)} )}
</Grid2> } </Grid2> }
{building.researches.length > 0 && <Grid2 size={12}> {building.researches.length > 0 && <Grid2 size={12}>
<SectionTitle>Researches</SectionTitle> <h3>Researches</h3>
{building.researches.map(r => {building.researches.map(r =>
<Research key={r.id} research={r} building={building} mod={mod}/> <Research key={r.id} research={r} building={building} mod={mod}/>
)} )}
@ -270,12 +227,10 @@ function Building(building: IBuilding, mod: IMod) {
{renderAffectedResearches(building.affectedResearches, mod.id, building.race.id)} {renderAffectedResearches(building.affectedResearches, mod.id, building.race.id)}
</Grid2> </Grid2>
</Grid2> </Grid2>
<Box sx={{ mt: 3, mb: 2, color: '#e94560', fontWeight: 600, fontSize: '0.85rem' }}> <b className="hotkey">Hotkey: {building.hotkey}</b>
Hotkey: {building.hotkey} <hr/>
</Box>
<Divider sx={{ my: 3, borderColor: 'rgba(255, 255, 255, 0.1)' }} />
<UnitsTable modId={mod.id}/> <UnitsTable modId={mod.id}/>
</Box>) </div>)
} }
class BuildingPage extends React.Component<any, UintPageState> { class BuildingPage extends React.Component<any, UintPageState> {
@ -301,24 +256,12 @@ class BuildingPage extends React.Component<any, UintPageState> {
if (this.state != null && this.state.building != null && this.state.mod != null) { if (this.state != null && this.state.building != null && this.state.mod != null) {
const backRef = "/mod/" + this.props.match.params.modId + "/race/" + this.props.match.params.raceId const backRef = "/mod/" + this.props.match.params.modId + "/race/" + this.props.match.params.raceId
return ( return <div><a href={backRef}><Button id="back-button" variant="contained"
<Container maxWidth="lg" sx={{ py: 4 }}> startIcon={<ArrowBack/>}> Back</Button></a>
<BackButton {Building(this.state.building, this.state.mod)}
variant="outlined" </div>;
startIcon={<ArrowBack/>}
href={backRef}
>
Back to race
</BackButton>
{Building(this.state.building, this.state.mod)}
</Container>
);
} else { } else {
return ( return "loading...";
<Box sx={{ display: 'flex', justifyContent: 'center', py: 8 }}>
<LinearProgress sx={{ width: '200px' }} />
</Box>
);
} }
} }
} }

View File

@ -1,48 +1,17 @@
import {AvailableMods, AvailableRacesPart, AvailableUnits, IconUrl} from "../core/api"; import {AvailableMods, AvailableRacesPart, AvailableUnits, IconUrl} from "../core/api";
import React, { useState } from "react"; import React, {useEffect, useRef, useState} from "react";
import {NavLink, useLocation, useParams} from "react-router-dom";
import {withRouter} from "../core/withrouter"; import {withRouter} from "../core/withrouter";
import {IMod} from "../types/Imod"; import {IMod} from "../types/Imod";
import { import {Irace} from "../types/Irace";
ArrowBack, import {Button, ListItem, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow} from "@mui/material";
} from "@mui/icons-material"; import {ArrowBack} from "@mui/icons-material";
import { import {IRaceUnits, IUnitShort} from "../types/IUnitShort";
Box,
Button,
Container,
Paper,
Typography,
Divider,
LinearProgress
} from "@mui/material";
import {styled} from '@mui/material/styles';
import UnitsTable from "../classes/UnitsTable"; import UnitsTable from "../classes/UnitsTable";
import {BackButton} from "../commons/BackButton";
// Styled components
const PageHeader = styled(Paper)(({ theme }) => ({
padding: theme.spacing(4, 4),
marginBottom: theme.spacing(4),
background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)',
borderRadius: '16px',
position: 'relative',
overflow: 'hidden',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'radial-gradient(circle at 30% 50%, rgba(233, 69, 96, 0.1) 0%, transparent 50%)',
pointerEvents: 'none',
},
}));
interface ModPageState { interface ModPageState {
mod: IMod | null, mod: IMod,
loading: boolean,
} }
@ -51,80 +20,31 @@ class ModPage extends React.Component<any, ModPageState> {
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.state = {
mod: null,
loading: true
};
fetch(AvailableMods + "/" + this.props.match.params.modId) fetch(AvailableMods + "/" + this.props.match.params.modId)
.then(res => res.json()) .then(res => res.json())
.then((res: IMod) => { .then((res: IMod) => {
this.setState({ this.setState({
mod: res, mod : res
loading: false
}); });
})
.catch(() => {
this.setState({ loading: false });
}); });
} }
render() { render() {
if (this.state.loading) { if(this.state != null && this.state.mod != null ){
return (
<Container maxWidth="lg" sx={{ py: 4 }}>
<LinearProgress sx={{ width: '200px', mx: 'auto', display: 'block' }} />
</Container>
);
}
if (this.state != null && this.state.mod != null) { document.title = this.state.mod.name
document.title = this.state.mod.name + " — ModWiki";
return ( return <div>
<Container maxWidth="lg" sx={{ py: 4 }}> <a href="/"><Button id="back-button" variant="contained"
<BackButton startIcon={<ArrowBack/>}> Back</Button></a>
variant="outlined" <h1>{this.state.mod.name} ({this.state.mod.version})</h1>
startIcon={<ArrowBack/>} <UnitsTable modId={this.state.mod.id}/>
href="/" </div>;
>
Back to mods list
</BackButton>
<PageHeader elevation={0}>
<Typography variant="h3" component="h1" sx={{
fontWeight: 800,
mb: 1,
background: 'linear-gradient(135deg, #ffffff 0%, #e0e0e0 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}>
{this.state.mod.name}
</Typography>
<Typography variant="h6" sx={{
color: 'rgba(255, 255, 255, 0.7)',
fontWeight: 500,
}}>
Version {this.state.mod.version}
</Typography>
</PageHeader>
<Divider sx={{ my: 4, borderColor: 'rgba(255, 255, 255, 0.1)' }} />
<Box>
<UnitsTable modId={this.state.mod.id}/>
</Box>
</Container>
);
} else { } else {
return ( return "";
<Container maxWidth="lg" sx={{ py: 8, textAlign: 'center' }}>
<Typography variant="h5" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
Mod not found
</Typography>
</Container>
);
} }
} }
} }

View File

@ -1,272 +1,49 @@
import {AvailableMods} from "../core/api"; import {AvailableMods} from "../core/api";
import React from "react"; import React, {useEffect, useState} from "react";
import { NavLink } from "react-router-dom"; import {NavLink} from "react-router-dom";
import { IMod } from "../types/Imod"; import {IMod} from "../types/Imod";
import {
Box,
Container,
Typography,
Grid,
Card,
CardContent,
CardActions,
Chip,
Button,
Divider,
useTheme,
Paper,
LinearProgress
} from "@mui/material";
import { styled } from '@mui/material/styles';
// Styled components function Mods (mods: IMod[]) {
const PageHeader = styled(Paper)(({ theme }) => ({
padding: theme.spacing(6, 4),
marginBottom: theme.spacing(6),
background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)',
borderRadius: '16px',
position: 'relative',
overflow: 'hidden',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'radial-gradient(circle at 30% 50%, rgba(233, 69, 96, 0.1) 0%, transparent 50%)',
pointerEvents: 'none',
},
}));
const ModCard = styled(Card)(({ theme }) => ({ let mapWithModVersions: Map<String, IMod[]> = new Map();
height: '100%',
display: 'flex',
flexDirection: 'column',
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '16px',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
position: 'relative',
overflow: 'hidden',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: 'linear-gradient(90deg, #e94560 0%, #ff6b6b 100%)',
transform: 'scaleX(0)',
transition: 'transform 0.3s ease',
},
'&:hover': {
transform: 'translateY(-8px)',
boxShadow: '0 20px 40px rgba(233, 69, 96, 0.2)',
borderColor: 'rgba(233, 69, 96, 0.3)',
'&::before': {
transform: 'scaleX(1)',
},
},
}));
const VersionLink = styled(NavLink)(({ theme }) => ({ mods.forEach( mod => {
display: 'flex', const versionList = mapWithModVersions.get(mod.name)
alignItems: 'center', if(versionList == null){
padding: theme.spacing(1.5, 2), mapWithModVersions.set(mod.name, new Array(mod))
marginBottom: theme.spacing(1), } else {
background: 'rgba(255, 255, 255, 0.05)', versionList.push(mod)
borderRadius: '10px', }
textDecoration: 'none', })
color: 'inherit',
transition: 'all 0.2s ease',
border: '1px solid rgba(255, 255, 255, 0.1)',
'&:hover': {
background: 'rgba(233, 69, 96, 0.15)',
borderColor: 'rgba(233, 69, 96, 0.3)',
transform: 'translateX(4px)',
},
}));
interface ModsProps {
mods: IMod[];
}
function Mods({ mods }: ModsProps) {
const theme = useTheme();
// Sort all mods by order, then group by name
const sortedMods = [...mods].sort((a, b) => a.order - b.order);
const mapWithModVersions = new Map<String, IMod[]>();
sortedMods.forEach(mod => {
const versionList = mapWithModVersions.get(mod.name);
if (versionList == null) {
mapWithModVersions.set(mod.name, [mod]);
} else {
versionList.push(mod);
}
});
const getLatestVersion = (modName: String) => {
const sameMods = mapWithModVersions.get(modName) ?? [];
// The latest version is the one with the maximum id
const latest = sameMods.reduce((max, mod) => mod.id > max.id ? mod : max, sameMods[0]);
const nonBetaMods = sameMods.filter(m => !m.isBeta);
const betaMods = sameMods.filter(m => m.isBeta);
return {
latest: latest,
allVersions: sameMods,
hasBeta: betaMods.length > 0,
betaVersion: betaMods[0]
};
};
function ModCardComponent(modName: String, index: number) {
const sameMods = mapWithModVersions.get(modName) ?? [];
const { latest, hasBeta, betaVersion } = getLatestVersion(modName);
return ( function Mod (modName: String) {
<ModCard sx={{ animationDelay: `${index * 100}ms` }}> let sameMods = mapWithModVersions.get(modName) ?? []
<CardContent sx={{ flexGrow: 1, p: 3 }}>
<Box sx={{ mb: 2 }}>
<Typography variant="h5" component="h2" sx={{
fontWeight: 700,
mb: 1,
background: 'linear-gradient(135deg, #ffffff 0%, #e0e0e0 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}>
{modName}
</Typography>
<Typography variant="body2" sx={{
color: 'rgba(255, 255, 255, 0.7)',
lineHeight: 1.6,
}}>
</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 1, mb: 2 }}> let lastBeta = sameMods.filter((m) => m.isBeta).reverse()[0]
<Chip
label={`${sameMods.length} versions`}
size="small"
sx={{
backgroundColor: 'rgba(233, 69, 96, 0.2)',
color: '#ff6b6b',
fontWeight: 600,
}}
/>
{hasBeta && (
<Chip
label="Beta"
size="small"
color="warning"
sx={{ fontWeight: 600 }}
/>
)}
</Box>
<Divider sx={{ my: 2, borderColor: 'rgba(255, 255, 255, 0.1)' }} /> return(
<div>
<Typography variant="subtitle2" sx={{ <h1>{modName}</h1>
color: 'rgba(255, 255, 255, 0.9)', {modName == "Unification new races" && <ul><a href="https://dawn-of-war-unification-mod.fandom.com/">Official unification wiki</a></ul>}
fontWeight: 600, {sameMods.filter((m) => !m.isBeta).map(mod =>
mb: 1.5, <ul><NavLink state={mod.id} to= {"/mod/" + mod.id} >{mod.version}</NavLink></ul>)}
}}> {sameMods.find((m) => m.isBeta) != null && <div><i>Beta versions:</i></div>}
Main versions: {lastBeta != null &&
</Typography> <ul><NavLink state={lastBeta.id} to={"/mod/" + lastBeta.id}>{lastBeta.version}</NavLink></ul>
}
{sameMods.filter(m => !m.isBeta).map(mod => ( </div>
<VersionLink key={mod.id} to={"/mod/" + mod.id} state={mod.id}> )
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
<Typography variant="body2" sx={{ fontWeight: 600, color: 'rgba(255, 255, 255, 0.7)' }}>
Version {mod.version}
</Typography>
<Typography variant="caption" sx={{
color: 'rgba(255, 255, 255, 0.5)',
}}>
</Typography>
</Box>
</VersionLink>
))}
{hasBeta && (
<Box sx={{ mt: 2 }}>
<Typography variant="subtitle2" sx={{
color: 'rgba(255, 193, 7, 0.9)',
fontWeight: 600,
mb: 1,
}}>
Beta versions:
</Typography>
{betaVersion && (
<VersionLink to={"/mod/" + betaVersion.id} state={betaVersion.id}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
Version {betaVersion.version} (Beta)
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
</Typography>
</Box>
</VersionLink>
)}
</Box>
)}
</CardContent>
<CardActions sx={{ px: 3, pb: 3 }}>
<Button
size="medium"
variant="outlined"
component={NavLink}
to={"/mod/" + latest?.id}
state={latest?.id}
sx={{
width: '100%',
color: '#e94560',
borderColor: 'rgba(233, 69, 96, 0.5)',
fontWeight: 600,
borderRadius: '10px',
textTransform: 'none',
fontSize: '1rem',
py: 1.5,
'&:hover': {
borderColor: '#e94560',
backgroundColor: 'rgba(233, 69, 96, 0.1)',
transform: 'translateY(-2px)',
boxShadow: '0 8px 20px rgba(233, 69, 96, 0.2)',
},
}}
>
Open latest {latest?.version ?? ''}
</Button>
</CardActions>
</ModCard>
);
} }
return ( return(
<Box> [...mapWithModVersions.keys()].map(m => Mod(m))
)
<Grid container spacing={3}>
{[...new Set(sortedMods.map(m => m.name))].map((modName, index) => (
<Grid item xs={12} sm={6} md={4} >
{ModCardComponent(modName, index)}
</Grid>
))}
</Grid>
</Box>
);
} }
interface ModsPageState { interface ModsPageState {
mods: IMod[]; mods: IMod[]
loading: boolean;
} }
class ModsPage extends React.Component<any, ModsPageState> { class ModsPage extends React.Component<any, ModsPageState> {
@ -274,36 +51,22 @@ class ModsPage extends React.Component<any, ModsPageState> {
constructor({props}: { props: any }) { constructor({props}: { props: any }) {
super(props); super(props);
this.state = { this.state = {
mods: [], mods: []
loading: true }
};
} }
async componentDidMount() { async componentDidMount() {
try { const response = await fetch(AvailableMods);
const response = await fetch(AvailableMods); const data: IMod[] = await response.json();
const data: IMod[] = await response.json(); console.log(data);
this.setState({ this.setState({
mods: data, mods : data
loading: false });
});
} catch (error) {
console.error('Error loading mods:', error);
this.setState({ loading: false });
}
} }
render() { render() {
if (this.state.loading) { return <div>{Mods(this.state.mods)}</div>;
return (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 8 }}>
<LinearProgress sx={{ width: '200px' }} />
</Box>
);
}
return <Mods mods={this.state.mods} />;
} }
} }
export default ModsPage; export default ModsPage

View File

@ -8,43 +8,29 @@ import {
Accordion, Accordion,
AccordionDetails, AccordionDetails,
AccordionSummary, AccordionSummary,
Box,
Button, Button,
Container, ButtonGroup,
Divider, Grid2, Link,
Grid2, LinearProgress,
Link,
List, List,
ListItem, ListItem,
ListSubheader,
Paper, Paper,
Typography, Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
ToggleButton,
ToggleButtonClassKey,
ToggleButtonGroup,
Typography
} from "@mui/material"; } from "@mui/material";
import {ArrowBack, ExpandMore} from "@mui/icons-material"; import {ArrowBack, ArrowDropDown, ExpandMore} from "@mui/icons-material";
import ArmorType from "../classes/ArmorType";
import Weapon from "../classes/Weapon";
import {IRaceUnits, IUnitShort} from "../types/IUnitShort"; import {IRaceUnits, IUnitShort} from "../types/IUnitShort";
import {IBuildingShort, IRaceBuildings} from "../types/IBuildingShort"; import {IBuildingShort, IRaceBuildings} from "../types/IBuildingShort";
import {styled} from '@mui/material/styles';
import {StyledLink} from "../commons/StyledLink";
import {BackButton} from "../commons/BackButton";
const SectionTitle = styled(Typography)(({ theme }) => ({
fontWeight: 700,
mb: 2,
fontSize: '1.25rem',
}));
const UnitCard = styled(Paper)(({ theme }) => ({
background: 'rgba(255, 255, 255, 0.03)',
border: '1px solid rgba(255, 255, 255, 0.05)',
borderRadius: '10px',
padding: theme.spacing(1.5, 2),
transition: 'all 0.2s ease',
'&:hover': {
background: 'rgba(233, 69, 96, 0.1)',
borderColor: 'rgba(233, 69, 96, 0.3)',
},
}));
const BuildingCard = styled(UnitCard)(({ theme }) => ({}));
interface RacePageState { interface RacePageState {
mod: IMod, mod: IMod,
@ -55,27 +41,13 @@ interface RacePageState {
function Unit(unit: IUnitShort, modId: number, raceId: String) { function Unit(unit: IUnitShort, modId: number, raceId: String) {
return (<Link href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id} sx={{ return (<Link href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id}><ListItem>
color: 'rgba(255,255,255,0.85)', {unit.icon && <img className="unitIcon" src={IconUrl + unit.icon.replaceAll('\\', '/')}/>}
textDecoration: 'none', {unit.name}
display: 'block', {unit.canDetect && <span>&nbsp;<img style={{verticalAlign: "top"}}
'&:hover': { color: '#e94560' } src="/images/DETECT_YES.webp"/></span>}
}}>
<ListItem sx={{ </ListItem></Link>)
color: 'rgba(255,255,255,0.85)',
padding: '6px 12px',
background: 'rgba(255,255,255,0.02)',
borderRadius: '8px',
border: '1px solid rgba(255,255,255,0.05)',
transition: 'all 0.2s ease',
'&:hover': { background: 'rgba(233, 69, 96, 0.1)', borderColor: 'rgba(233, 69, 96, 0.3)' }
}}>
{unit.icon && <img className="unitIcon" src={IconUrl + unit.icon.replaceAll('\\', '/')}/>}
{unit.name}
{unit.canDetect && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/DETECT_YES.webp"/></span>}
</ListItem>
</Link>)
} }
function UnitSmall(unit: IUnitShort, modId: number, raceId: String) { function UnitSmall(unit: IUnitShort, modId: number, raceId: String) {
@ -87,33 +59,23 @@ function UnitSmall(unit: IUnitShort, modId: number, raceId: String) {
unitName = unit.name; unitName = unit.name;
} }
return (<StyledLink href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id} > return (<Link href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id}>
{unit.icon && <img className="unitIconSmall" src={IconUrl + unit.icon.replaceAll('\\', '/')}/>} {unit.icon && <img className="unitIconSmall" src={IconUrl + unit.icon.replaceAll('\\', '/')}/>}
<span style={{fontSize: 14}}>{unitName}</span> <span style={{fontSize: 12}}>{unitName}</span>
{unit.canDetect && <span>&nbsp;<img {unit.canDetect && <span>&nbsp;<img
src="/images/DETECT_YES.webp"/></span>}<br/></StyledLink>) src="/images/DETECT_YES.webp"/></span>}<br/></Link>)
} }
function Building(building: IBuildingShort, modId: number, raceId: String) { function Building(building: IBuildingShort, modId: number, raceId: String) {
return (<Grid2 size={{xs: 12, md: 3}}> return (<Grid2 size={{xs: 12, md: 3}}><Link href={"/mod/" + modId + "/race/" + raceId + "/building/" + building.id}><ListItem>
<Link href={"/mod/" + modId + "/race/" + raceId + "/building/" + building.id} sx={{ {building.icon && <img className="unitIcon" src={IconUrl + building.icon.replaceAll('\\', '/')}/>}
textDecoration: 'none', {building.name}
display: 'block', {building.canDetect && <span>&nbsp;<img style={{verticalAlign: "top"}}
'&:hover': { color: '#e94560' } src="/images/DETECT_YES.webp"/></span>}
}}>
<BuildingCard elevation={0}> </ListItem></Link>
<ListItem sx={{ {building.units.map(unit => UnitSmall(unit, modId, raceId))}
padding: '8px 12px',
}}>
{building.icon && <img className="unitIcon" src={IconUrl + building.icon.replaceAll('\\', '/')}/>}
<StyledLink>{building.name}</StyledLink>
{building.canDetect && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/DETECT_YES.webp"/></span>}
</ListItem>
{building.units.map(unit => UnitSmall(unit, modId, raceId))}
</BuildingCard>
</Link>
</Grid2>) </Grid2>)
} }
@ -165,61 +127,59 @@ class Units extends React.Component<UnitsProps, UnitsState> {
document.title = this.state.buildings?.race.name document.title = this.state.buildings?.race.name
} }
const accordionSx = {
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
'&:before': { display: 'none' },
'&.Mui-expanded': { margin: '0 0 8px 0' },
};
return (this.state.buildings != null ? return (this.state.buildings != null ?
<Box> <div>
<Grid2 container spacing={2}> <Grid2 container spacing={2}>
{this.state.buildings.buildings.map(building => Building(building, this.props.modId, this.props.raceId))} {this.state.buildings.buildings.map(building => Building(building, this.props.modId, this.props.raceId))}
</Grid2> </Grid2>
{this.state.buildings.buildingsAdvanced.length > 0 && <Box sx={{ mt: 3 }}> {this.state.buildings.buildingsAdvanced.length > 0 && <div><hr/><Grid2 container spacing={2}>
<Divider sx={{ my: 2, borderColor: 'rgba(255, 255, 255, 0.1)' }} /> {this.state.buildings.buildingsAdvanced.map(building => Building(building, this.props.modId, this.props.raceId))}
<SectionTitle>Advanced buildings</SectionTitle> </Grid2><br/></div>}
<Grid2 container spacing={2}> <Grid2>
{this.state.buildings.buildingsAdvanced.map(building => Building(building, this.props.modId, this.props.raceId))} <Grid2 size={{xs: 12, md: 12}}>
</Grid2><br/></Box>} <Accordion>
<Box sx={{ mt: 3 }}> <AccordionSummary
<Accordion sx={accordionSx}> expandIcon={<ExpandMore/>}
<AccordionSummary aria-controls="units-accordion"
expandIcon={<ExpandMore sx={{color: '#ffffff'}}/>} id="units-accordion"
aria-controls="units-accordion" >
id="units-accordion" <h3>All units</h3>
sx={{color: '#ffffff'}} </AccordionSummary>
> <AccordionDetails>
<SectionTitle sx={{ mb: 0 }}>All units</SectionTitle> <Grid2 container spacing={2}>
</AccordionSummary> <Grid2 size={{xs: 12, md: 4}}>
<AccordionDetails> <h3>Infantry</h3>
<Grid2 container spacing={2}> {this.state.units.infantry.map(unit => Unit(unit, this.props.modId, this.props.raceId))}
<Grid2 size={{xs: 12, md: 4}}> </Grid2>
<Typography variant="h6" sx={{ color: '#ffffff', fontWeight: 600, mb: 2 }}>Infantry</Typography> <Grid2 size={{xs: 12, md: 4}}>
{this.state.units.infantry.map(unit => Unit(unit, this.props.modId, this.props.raceId))} <h3>Tech</h3>
<List sx={{
width: '100%',
maxWidth: 360,
bgcolor: 'background.paper'
}}>
{this.state.units.tech.map(unit => Unit(unit, this.props.modId, this.props.raceId))}
</List>
</Grid2>
<Grid2 size={{xs: 12, md: 4}}>
<h3>Support</h3>
<List sx={{
width: '100%',
maxWidth: 360,
bgcolor: 'background.paper'
}}>
{this.state.units.support.map(unit => Unit(unit, this.props.modId, this.props.raceId))}
</List>
</Grid2>
</Grid2> </Grid2>
<Grid2 size={{xs: 12, md: 4}}> </AccordionDetails>
<Typography variant="h6" sx={{ color: '#ffffff', fontWeight: 600, mb: 2 }}>Tech</Typography> </Accordion>
<List> </Grid2><br/>
{this.state.units.tech.map(unit => Unit(unit, this.props.modId, this.props.raceId))} </Grid2>
</List> </div> : "Loading"
</Grid2>
<Grid2 size={{xs: 12, md: 4}}>
<Typography variant="h6" sx={{ color: '#ffffff', fontWeight: 600, mb: 2 }}>Support</Typography>
<List>
{this.state.units.support.map(unit => Unit(unit, this.props.modId, this.props.raceId))}
</List>
</Grid2>
</Grid2>
</AccordionDetails>
</Accordion>
</Box>
</Box> : <Box sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>Loading</Box>
) )
} else { } else {
return <Box sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>Loading...</Box>; return "Loading...";
} }
} }
} }
@ -228,6 +188,7 @@ class RacePageFast extends React.Component<any, RacePageState> {
constructor(props: any) { constructor(props: any) {
super(props); super(props);
console.log(this.props.match.params.id);
} }
@ -253,43 +214,13 @@ class RacePageFast extends React.Component<any, RacePageState> {
if (this.state != null && this.state.mod != null && this.state.race != null) { if (this.state != null && this.state.mod != null && this.state.race != null) {
const backRef = "/mod/" + this.state.mod.id const backRef = "/mod/" + this.state.mod.id
return ( return <div><Link href={backRef}><Button id="back-button" variant="contained"
<Container maxWidth="lg" sx={{ py: 4 }}> startIcon={<ArrowBack/>}> Back</Button></Link>
<BackButton <h1>{this.state.mod.name} ({this.state.mod.version}) - {this.state.race.name}</h1>
variant="outlined" <Units raceId={this.state.race.id} modId={this.state.mod.id}/>
startIcon={<ArrowBack/>} </div>;
href={backRef}
>
Back to mod
</BackButton>
<Box sx={{ mb: 4 }}>
<Typography variant="h3" component="h1" sx={{
fontWeight: 800,
mb: 1,
background: 'linear-gradient(135deg, #ffffff 0%, #e0e0e0 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}>
{this.state.race.name}
</Typography>
<Typography variant="h6" sx={{
color: 'rgba(255, 255, 255, 0.6)',
fontWeight: 500,
}}>
{this.state.mod.name} ({this.state.mod.version})
</Typography>
</Box>
<Units raceId={this.state.race.id} modId={this.state.mod.id}/>
</Container>
);
} else { } else {
return ( return "loading...";
<Container maxWidth="lg" sx={{ py: 4 }}>
<LinearProgress sx={{ width: '200px', mx: 'auto', display: 'block' }} />
</Container>
);
} }
} }
} }

View File

@ -7,18 +7,15 @@ import {
Accordion, Accordion,
AccordionDetails, AccordionDetails,
AccordionSummary, AccordionSummary,
Box,
Button, Button,
Container, Grid2,
Divider,
Grid2, LinearProgress,
Paper, Paper,
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableContainer,
TableRow, TableRow,
Tooltip, Tooltip
Typography,
} from "@mui/material"; } from "@mui/material";
import {ArrowBack, ExpandMore} from "@mui/icons-material"; import {ArrowBack, ExpandMore} from "@mui/icons-material";
import ArmorType from "../classes/ArmorType"; import ArmorType from "../classes/ArmorType";
@ -33,30 +30,6 @@ import {renderAffectedResearches} from "../classes/building/Research";
import {ModifiersProvidesTable} from "../classes/ModifiersProvideTable"; import {ModifiersProvidesTable} from "../classes/ModifiersProvideTable";
import Ability from "../classes/Ability"; import Ability from "../classes/Ability";
import DeathExplosion from "../classes/DeathExplosion"; import DeathExplosion from "../classes/DeathExplosion";
import {styled} from '@mui/material/styles';
import {DescriptionBox} from "../commons/DescriptionBox";
import {BackButton} from "../commons/BackButton";
const SectionTitle = styled(Typography)(({ theme }) => ({
fontWeight: 700,
mb: 2,
fontSize: '1.25rem',
}));
const StatsPaper = styled(Paper)(({ theme }) => ({
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
overflow: 'hidden',
'& .MuiTableBody .MuiTableRow-root': {
'&:last-child td, &:last-child th': { border: 0 },
},
'& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': {
color: '#e94560',
fontWeight: 600,
},
}));
interface UintPageState { interface UintPageState {
unit: IUnit, unit: IUnit,
@ -66,7 +39,7 @@ interface UintPageState {
function Unit(unit: IUnit, mod: IMod) { function Unit(unit: IUnit, mod: IMod) {
document.title = unit.name + " — " + mod.name document.title = unit.name
const morale = (unit.moraleMax !== null) ? <span> const morale = (unit.moraleMax !== null) ? <span>
<img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/>&nbsp;{unit.moraleMax} <img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/>&nbsp;{unit.moraleMax}
@ -98,7 +71,7 @@ function Unit(unit: IUnit, mod: IMod) {
const SergeantShort = (props: sergeantProps) => { const SergeantShort = (props: sergeantProps) => {
return ( return (
<span style={{fontSize: 20, color: '#ffffff'}}> <span style={{fontSize: 20}}>
{props.icon && <img className="sergeantIcon" src={IconUrl + props.icon.replaceAll('\\', '/')}/> } {props.icon && <img className="sergeantIcon" src={IconUrl + props.icon.replaceAll('\\', '/')}/> }
&nbsp; {props.name} &nbsp; {props.name}
{props.canDetect && <span>&nbsp;<img style={{verticalAlign: "top"}} {props.canDetect && <span>&nbsp;<img style={{verticalAlign: "top"}}
@ -109,35 +82,21 @@ function Unit(unit: IUnit, mod: IMod) {
return ( return (
<Box> <div>
<Box sx={{ mb: 3 }}> <h1>{mod.name} ({mod.version})</h1>
<Typography variant="h4" component="h2" sx={{ <h2>{unit.icon &&
fontWeight: 800, <img className="unitIcon" src={IconUrl + unit.icon.replaceAll('\\', '/')}/>} {unit.name} </h2>
mb: 1,
display: 'flex',
alignItems: 'center',
gap: 2,
}}>
{unit.icon &&
<img className="unitIcon" src={IconUrl + unit.icon.replaceAll('\\', '/')}/>}
{unit.name}
</Typography>
<Typography variant="subtitle1" sx={{
color: 'rgba(255, 255, 255, 0.6)',
fontWeight: 500,
}}>
{mod.name} ({mod.version})
</Typography>
</Box>
<Grid2 container spacing={3}> <Grid2 container spacing={2}>
<Grid2 size={{xs: 12, md: 4}}> <Grid2 size={{xs: 12, md: 4}}>
<StatsPaper > <TableContainer component={Paper}>
<Table size="small" aria-label="a dense table"> <Table size="small" aria-label="a dense table">
<TableBody id="unit-stats-table" > <TableBody id="unit-stats-table">
<TableRow> <TableRow
<TableCell component="th" scope="row" >Cost</TableCell> sx={{'&:last-child td, &:last-child th': {border: 0}}}
<TableCell> >
<TableCell component="th" scope="row">Cost</TableCell>
<TableCell component="th" scope="row">
{unit.buildCostRequisition > 0 && {unit.buildCostRequisition > 0 &&
<span>&nbsp;<img style={{verticalAlign: "top"}} <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_requisition.gif"/>&nbsp; src="/images/Resource_requisition.gif"/>&nbsp;
@ -172,7 +131,7 @@ function Unit(unit: IUnit, mod: IMod) {
</TableRow> </TableRow>
{(unit?.reinforceTime !== 0 && unit.reinforceTime !== null && unit.squadMaxSize > 1) && {(unit?.reinforceTime !== 0 && unit.reinforceTime !== null && unit.squadMaxSize > 1) &&
<TableRow> <TableRow>
<TableCell >Reinforce cost</TableCell> <TableCell>Reinforce cost</TableCell>
<TableCell> <TableCell>
{unit.reinforceCostRequisition && unit.reinforceCostRequisition > 0 ? {unit.reinforceCostRequisition && unit.reinforceCostRequisition > 0 ?
<span>&nbsp;<img style={{verticalAlign: "top"}} <span>&nbsp;<img style={{verticalAlign: "top"}}
@ -203,7 +162,7 @@ function Unit(unit: IUnit, mod: IMod) {
} }
{(unit.requisitionIncome !== undefined && unit.requisitionIncome !== null || unit.powerIncome !== undefined && unit.powerIncome !== null || unit.faithIncome !== undefined && unit.faithIncome !== null) && {(unit.requisitionIncome !== undefined && unit.requisitionIncome !== null || unit.powerIncome !== undefined && unit.powerIncome !== null || unit.faithIncome !== undefined && unit.faithIncome !== null) &&
<TableRow> <TableRow>
<TableCell >Resource income</TableCell> <TableCell>Resource income</TableCell>
<TableCell> <TableCell>
{unit.requisitionIncome !== undefined && unit.requisitionIncome > 0 && {unit.requisitionIncome !== undefined && unit.requisitionIncome > 0 &&
<span>&nbsp;<img style={{verticalAlign: "top"}} <span>&nbsp;<img style={{verticalAlign: "top"}}
@ -222,19 +181,25 @@ function Unit(unit: IUnit, mod: IMod) {
} }
{unit.squadMaxSize > 1 && {unit.squadMaxSize > 1 &&
<TableRow> <TableRow>
<TableCell >Squad size</TableCell> <TableCell>Squad size</TableCell>
<TableCell>{unit.squadStartSize} / {unit.squadMaxSize}</TableCell> <TableCell>
{unit.squadStartSize} / {unit.squadMaxSize}
</TableCell>
</TableRow> </TableRow>
} }
<TableRow> <TableRow
<TableCell >Armor type</TableCell> sx={{'&:last-child td, &:last-child th': {border: 0}}}
<TableCell> >
<TableCell component="th" scope="row">Armor type</TableCell>
<TableCell component="th" scope="row">
{unit.armorType2 == null ? <ArmorType name={unit.armorType.name} compact={false}/> : <Tooltip title={"upgrade/debuff can turns to " + unit.armorType2.name}><span><ArmorType name={unit.armorType.name} compact={false}/></span></Tooltip> } {unit.armorType2 == null ? <ArmorType name={unit.armorType.name} compact={false}/> : <Tooltip title={"upgrade/debuff can turns to " + unit.armorType2.name}><span><ArmorType name={unit.armorType.name} compact={false}/></span></Tooltip> }
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow
<TableCell >Health</TableCell> sx={{'&:last-child td, &:last-child th': {border: 0}}}
<TableCell> >
<TableCell component="th" scope="row">Health</TableCell>
<TableCell component="th" scope="row">
<img style={{verticalAlign: "top"}} <img style={{verticalAlign: "top"}}
src="/images/Health_icon.webp"/>&nbsp; src="/images/Health_icon.webp"/>&nbsp;
{unit.health} {unit.healthRegeneration > 0 && {unit.health} {unit.healthRegeneration > 0 &&
@ -242,54 +207,78 @@ function Unit(unit: IUnit, mod: IMod) {
{unit.armour !== undefined && unit.armour !== 0 && <span><img style={{height: 20, verticalAlign: "top"}} src="/images/defence.png"/>{unit.armour} </span> } {unit.armour !== undefined && unit.armour !== 0 && <span><img style={{height: 20, verticalAlign: "top"}} src="/images/defence.png"/>{unit.armour} </span> }
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow
<TableCell >Move speed</TableCell> sx={{'&:last-child td, &:last-child th': {border: 0}}}
<TableCell>{unit.moveSpeed}</TableCell> >
<TableCell component="th" scope="row">Move speed</TableCell>
<TableCell component="th" scope="row">
{unit.moveSpeed}
</TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow
<TableCell >Morale</TableCell> sx={{'&:last-child td, &:last-child th': {border: 0}}}
<TableCell>{morale}</TableCell> >
<TableCell component="th" scope="row">Morale</TableCell>
<TableCell component="th" scope="row">
{morale}
</TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow
<TableCell >Mass</TableCell> sx={{'&:last-child td, &:last-child th': {border: 0}}}
<TableCell>{unit.mass}</TableCell> >
<TableCell component="th" scope="row">Mass</TableCell>
<TableCell component="th" scope="row">
{unit.mass}
</TableCell>
</TableRow> </TableRow>
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow
<TableCell >Vision</TableCell> sx={{'&:last-child td, &:last-child th': {border: 0}}}
<TableCell><Vision sight={unit.sightRadius} detect={unit.detectRadius}/></TableCell> >
<TableCell component="th" scope="row">Vision</TableCell>
<TableCell component="th" scope="row">
<Vision sight={unit.sightRadius} detect={unit.detectRadius}/>
</TableCell>
</TableRow> </TableRow>
{unit.repairMax !== undefined && unit.repairMax !== null && {unit.repairMax !== undefined && unit.repairMax !== null &&
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow>
<TableCell >Repair max</TableCell> <TableCell>Repair max</TableCell>
<TableCell>{unit.repairMax}</TableCell> <TableCell>
{unit.repairMax}
</TableCell>
</TableRow> </TableRow>
} }
{unit.repairSpeed !== undefined && unit.repairSpeed !== null && unit.repairCostPercent !== null && {unit.repairSpeed !== undefined && unit.repairSpeed !== null && unit.repairCostPercent !== null &&
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow>
<TableCell >Repair</TableCell> <TableCell>Repair</TableCell>
<TableCell>{unit.repairSpeed} hp/s; {unit.repairCostPercent}% cost</TableCell> <TableCell>
{unit.repairSpeed} hp/s; {unit.repairCostPercent}% cost
</TableCell>
</TableRow> </TableRow>
} }
{unit.mobValue != null && {unit.mobValue != null &&
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow>
<TableCell >Mob value</TableCell> <TableCell>Mob value</TableCell>
<TableCell><span> <img style={{height: 20, verticalAlign: "top"}} src="/images/Mob_bonus.gif"/> {unit.mobValue} </span></TableCell> <TableCell>
<span> <img style={{height: 20, verticalAlign: "top"}} src="/images/Mob_bonus.gif"/> {unit.mobValue} </span>
</TableCell>
</TableRow> </TableRow>
} }
{unit.squadLimit !== undefined && unit.squadLimit !== null && {unit.squadLimit !== undefined && unit.squadLimit !== null &&
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}> <TableRow>
<TableCell >Limit</TableCell> <TableCell>Limit</TableCell>
<TableCell>{unit.squadLimit}</TableCell> <TableCell>
{unit.squadLimit}
</TableCell>
</TableRow> </TableRow>
} }
</TableBody> </TableBody>
</Table> </Table>
</StatsPaper> </TableContainer>
</Grid2> </Grid2>
<Grid2 size={{xs: 12, md: 8}}> <Grid2 size={8}>
<DescriptionBox> <div style={{whiteSpace: "pre-wrap"}}>
{unit.description} {unit.description}
</DescriptionBox> </div>
</Grid2> </Grid2>
{unit.requirements !== null && {unit.requirements !== null &&
<Grid2 size={{xs: 12, md: 12}}> <Grid2 size={{xs: 12, md: 12}}>
@ -297,21 +286,19 @@ function Unit(unit: IUnit, mod: IMod) {
</Grid2>} </Grid2>}
<Grid2 size={12}> <Grid2 size={12}>
{unit.sergeants.map(s => {unit.sergeants.map(s =>
<Accordion key={s.id} sx={{ <Accordion key={s.id}>
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
mb: 1,
'&:before': { display: 'none' },
'&.Mui-expanded': { margin: '0 0 8px 0' },
}}>
<AccordionSummary <AccordionSummary
sx={{color: '#ffffff'}}> expandIcon={<ExpandMore/>}
aria-controls="panel1-content"
id="panel1-header"
>
<SergeantShort name={s.name} icon={s.icon} canDetect={s.detectRadius > 0}/> <SergeantShort name={s.name} icon={s.icon} canDetect={s.detectRadius > 0}/>
</AccordionSummary> </AccordionSummary>
<AccordionDetails sx={{color: 'rgba(255, 255, 255, 0.85)'}}> <AccordionDetails>
<Sergeant mod={mod} sergeant={s} race={unit.race}/> <Sergeant mod={mod} sergeant={s} race={unit.race}/>
<hr/>
</AccordionDetails> </AccordionDetails>
</Accordion>)} </Accordion>)}
</Grid2> </Grid2>
@ -319,17 +306,17 @@ function Unit(unit: IUnit, mod: IMod) {
unit.moraleBrakeModifiers.find(m => m.reference.includes("speed_maximum_modifier") && m.value === 1.2) !== undefined && unit.moraleBrakeModifiers.find(m => m.reference.includes("speed_maximum_modifier") && m.value === 1.2) !== undefined &&
unit.moraleBrakeModifiers.length === 2) unit.moraleBrakeModifiers.length === 2)
&& <Grid2 size={12}> && <Grid2 size={12}>
<SectionTitle><img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/> Morale broken unusual behavior <img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/></SectionTitle> <h3><img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/> Morale broken unusual behavior <img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/></h3>
<ModifiersProvidesTable modifiers={unit.moraleBrakeModifiers} modId={unit.modId} race={unit.race} affectedData={unit.affectedData}/> <ModifiersProvidesTable modifiers={unit.moraleBrakeModifiers} modId={unit.modId} race={unit.race} affectedData={unit.affectedData}/>
</Grid2>} </Grid2>}
{unit.modifiers.length > 0 && <Grid2 size={12}> {unit.modifiers.length > 0 && <Grid2 size={12}>
<SectionTitle>Affected on</SectionTitle> <h3>Affected on</h3>
<ModifiersProvidesTable modifiers={unit.modifiers} modId={unit.modId} race={unit.race} affectedData={unit.affectedData}/> <ModifiersProvidesTable modifiers={unit.modifiers} modId={unit.modId} race={unit.race} affectedData={unit.affectedData}/>
</Grid2>} </Grid2>}
{unit.abilities.length > 0 && <Grid2 size={12}> {unit.abilities.length > 0 && <Grid2 size={12}>
<SectionTitle>Abilities</SectionTitle> <h3>Abilities</h3>
{unit.abilities.map(a => {unit.abilities.map(a =>
<Ability mod={mod} ability={a} race={unit.race}/> <Ability mod={mod} ability={a} race={unit.race}/>
)} )}
@ -351,14 +338,10 @@ function Unit(unit: IUnit, mod: IMod) {
{renderAffectedResearches(unit.affectedResearches, mod.id, unit.race.id)} {renderAffectedResearches(unit.affectedResearches, mod.id, unit.race.id)}
</Grid2> </Grid2>
</Grid2> </Grid2>
<b className="hotkey">Hotkey: {unit.hotkey}</b>
<Box sx={{ mt: 3, mb: 2, fontWeight: 600, fontSize: '0.85rem' }}> <hr/>
Hotkey: {unit.hotkey}
</Box>
<Divider sx={{ my: 3, borderColor: 'rgba(255, 255, 255, 0.1)' }} />
<UnitsTable modId={mod.id}/> <UnitsTable modId={mod.id}/>
</Box> </div>)
)
} }
@ -388,24 +371,12 @@ class UnitPage extends React.Component<any, UintPageState> {
if (this.state != null && this.state.unit != null && this.state.mod != null) { if (this.state != null && this.state.unit != null && this.state.mod != null) {
const backRef = "/mod/" + this.props.match.params.modId + "/race/" + this.props.match.params.raceId const backRef = "/mod/" + this.props.match.params.modId + "/race/" + this.props.match.params.raceId
return ( return <div key={this.state.unit.id}><a href={backRef}><Button id="back-button" variant="contained"
<Container maxWidth="lg" sx={{ py: 4 }}> startIcon={<ArrowBack/>}> Back</Button></a>
<BackButton {Unit(this.state.unit, this.state.mod)}
variant="outlined" </div>;
startIcon={<ArrowBack/>}
href={backRef}
>
Back to race
</BackButton>
{Unit(this.state.unit, this.state.mod)}
</Container>
);
} else { } else {
return ( return <div key = {'loading'}>loading...</div>;
<Box sx={{ display: 'flex', justifyContent: 'center', py: 8 }}>
<LinearProgress sx={{ width: '200px' }} />
</Box>
);
} }
} }
} }

View File

@ -1,7 +1,6 @@
export interface IMod { export interface IMod {
id: number; id: number;
name: string; name: string;
order: number;
version: string; version: string;
technicalName: string; technicalName: string;
isBeta: boolean; isBeta: boolean;