Compare commits
2 Commits
9d4bb49713
...
5669a70156
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5669a70156 | ||
|
|
bc36f1e56d |
@ -1,6 +1,6 @@
|
||||
# Soulstorm wiki frontend
|
||||
|
||||
## Стек технологий:
|
||||
## Tech stack:
|
||||
|
||||
- react (вперемешку классы и функции)
|
||||
- react (mix of classes and functions)
|
||||
- typescript
|
||||
140
src/App.tsx
140
src/App.tsx
@ -1,66 +1,106 @@
|
||||
import './App.css';
|
||||
import React from "react";
|
||||
import {AppBar, Container, createTheme, IconButton, ThemeProvider, Toolbar, Typography} from "@mui/material";
|
||||
import {MyRoutes} from "./Routes";
|
||||
import {
|
||||
AppBar,
|
||||
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() {
|
||||
console.log(process.env);
|
||||
const theme = useTheme();
|
||||
|
||||
const steamLogin = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
(document.getElementById('submit-steam-login') as HTMLFormElement).click()
|
||||
};
|
||||
|
||||
const theme = createTheme({
|
||||
const themeConfig = createTheme({
|
||||
breakpoints: {
|
||||
values: {
|
||||
xs: 0,
|
||||
sm: 600,
|
||||
md: 900,
|
||||
lg: 1320,
|
||||
xl: 1536,
|
||||
},
|
||||
},
|
||||
palette: {
|
||||
primary: {
|
||||
main: "#191919",
|
||||
main: "#1a1a2e",
|
||||
},
|
||||
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>
|
||||
|
||||
return (
|
||||
<div >
|
||||
<ThemeProvider theme={theme}>
|
||||
<AppBar position="static">
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
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>
|
||||
|
||||
{/* Right side - Navigation */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</StyledAppBar>
|
||||
</a>
|
||||
|
||||
<form action="https://steamcommunity.com/openid/login" method="post">
|
||||
<input type="hidden" name="openid.identity"
|
||||
value="http://specs.openid.net/auth/2.0/identifier_select"/>
|
||||
<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>
|
||||
);
|
||||
<Container maxWidth="lg" sx={{ py: 4 }}>
|
||||
<MyRoutes />
|
||||
</Container>
|
||||
</ThemeProvider>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@ -6,7 +6,15 @@ import {getIcon} from "./ModifiersProvideTable";
|
||||
import {IMod} from "../types/Imod";
|
||||
import AbilityFull from "./AbilityFull";
|
||||
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 {
|
||||
ability: IAbilityShort,
|
||||
@ -18,22 +26,23 @@ function Ability(props: IAbilityProps){
|
||||
|
||||
const ability = props.ability
|
||||
|
||||
return <div className='addon-research-accordion' id={"research-" + ability.id}><Accordion TransitionProps={{ unmountOnExit: true, timeout: 100 }}>
|
||||
return <div className='addon-research-accordion' id={"research-" + ability.id}><StyledAccordion TransitionProps={{ unmountOnExit: true, timeout: 100 }}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMore/>}
|
||||
expandIcon={<ExpandMore sx={{color: '#ffffff'}}/>}
|
||||
aria-controls="panel1-content"
|
||||
sx={{color: '#ffffff'}}
|
||||
>
|
||||
<span style={{fontSize: 20}}>
|
||||
<span style={{fontSize: 20, color: '#ffffff'}}>
|
||||
{ability.activationType === "Passive ability" ? <img className="abilityIcon" src="/images/PassiveIcon.gif"/> : <img className="abilityIcon" src={getIcon(ability.icon)}/>}
|
||||
{ability.name ? ability.name : ability.fileName.charAt(0).toUpperCase() + ability.fileName.slice(1).replaceAll('_', ' ').replace('.rgd', '')}
|
||||
<i style={{fontSize: 12}}> ({ability.activationType})</i>
|
||||
<i style={{fontSize: 12, color: 'rgba(255,255,255,0.5)'}}> ({ability.activationType})</i>
|
||||
</span>
|
||||
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<AccordionDetails sx={{color: 'rgba(255,255,255,0.85)'}}>
|
||||
<AbilityFull abilityId={ability.id} mod={props.mod} race={props.race} />
|
||||
</AccordionDetails>
|
||||
</Accordion></div>
|
||||
</StyledAccordion></div>
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -27,6 +27,7 @@ import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
|
||||
import ability from "./Ability";
|
||||
import AbilitySpawnedObject from "./AbilitySpawnedObject";
|
||||
import DpsTable from "./DpsTable";
|
||||
import {DescriptionBox} from "../commons/DescriptionBox";
|
||||
|
||||
interface IAbilityFullState {
|
||||
ability?: IAbilityFill;
|
||||
@ -41,16 +42,33 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
|
||||
|
||||
const StyledTableCell = styled(TableCell)(({theme}) => ({
|
||||
[`&.${tableCellClasses.head}`]: {
|
||||
backgroundColor: "rgb(244,244,244)",
|
||||
backgroundColor: 'rgba(233, 69, 96, 0.15)',
|
||||
fontWeight: 700,
|
||||
marginRight: 'auto',
|
||||
marginLeft: 'auto',
|
||||
paddingLeft: 10
|
||||
paddingLeft: 10,
|
||||
borderBottom: '2px solid rgba(233, 69, 96, 0.3)',
|
||||
},
|
||||
[`&.${tableCellClasses.body}`]: {
|
||||
fontSize: 12,
|
||||
textAlign: 'center',
|
||||
color: 'rgba(255, 255, 255, 0.85)',
|
||||
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,
|
||||
},
|
||||
}));
|
||||
|
||||
@ -90,21 +108,21 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
|
||||
const showTargetTable = !showDpsTable && ability.abilityEnvironment == null && (ability.targetFilter.length > 0 || ability.radius !== 0)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>
|
||||
<Grid2 container spacing={2}>
|
||||
<Grid2 size={{xs: 12}}>
|
||||
{ability.initialDelayTime !== undefined && props.isChild === true &&
|
||||
<b><br/><center> After {ability.initialDelayTime.toFixed(1)}s
|
||||
<b><br/><center style={{color: 'rgba(255,255,255,0.7)'}}> After {ability.initialDelayTime.toFixed(1)}s
|
||||
<img style={{verticalAlign: "top"}}
|
||||
src="/images/Time_icon.webp"/></center></b>}
|
||||
</Grid2>
|
||||
<Grid2 size={{xs: 12, md: 4}}>
|
||||
<TableContainer component={Paper}>
|
||||
<TableContainer component={StyledPaper} elevation={0}>
|
||||
<Table size="small" aria-label="a dense table">
|
||||
<TableBody id="unit-stats-table">
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row">Affected on</TableCell>
|
||||
<TableCell component="th" scope="row">{ability.areaFilter}</TableCell>
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<TableCell component="th" scope="row" >Affected on</TableCell>
|
||||
<TableCell>{ability.areaFilter}</TableCell>
|
||||
</TableRow>
|
||||
{props.isChild !== true && (ability.costPower !== undefined && ability.costPower > 0 ||
|
||||
ability.costRequisition !== undefined && ability.costRequisition > 0 ||
|
||||
@ -113,8 +131,8 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Cost</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableCell component="th" scope="row" >Cost</TableCell>
|
||||
<TableCell>
|
||||
{ability.costRequisition !== undefined && ability.costRequisition > 0 &&
|
||||
<span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_requisition.gif"/>
|
||||
@ -135,19 +153,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" &&
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<TableCell component="th" scope="row">Recharge time</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableCell component="th" scope="row" >Recharge time</TableCell>
|
||||
<TableCell>
|
||||
<img style={{verticalAlign: "top"}}
|
||||
src="/images/Time_icon.webp"/>
|
||||
{ability.rechargeTime}s
|
||||
{ability.rechargeTimerGlobal && <i style={{fontSize: 10}}>global cooldown</i>}
|
||||
{ability.rechargeTimerGlobal && <i style={{fontSize: 10, color: 'rgba(255,255,255,0.5)'}}>global cooldown</i>}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
{props.isChild !== true && ability.initialDelayTime !== undefined && ability.initialDelayTime > 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">
|
||||
<TableCell component="th" scope="row" >Delay after cast</TableCell>
|
||||
<TableCell>
|
||||
<img style={{verticalAlign: "top"}}
|
||||
src="/images/Time_icon.webp"/>
|
||||
{ability.initialDelayTime.toFixed(1)}s
|
||||
@ -156,8 +174,8 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
|
||||
}
|
||||
{ability.durationTime !== undefined && ability.durationTime > 1.5 &&
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<TableCell component="th" scope="row">Duration time</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableCell component="th" scope="row" >Duration time</TableCell>
|
||||
<TableCell>
|
||||
<img style={{verticalAlign: "top"}}
|
||||
src="/images/Time_icon.webp"/>
|
||||
{ability.durationTime.toFixed(1)}s
|
||||
@ -166,16 +184,16 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
|
||||
}
|
||||
{ability.areaEffect !== null && ability.radius !== undefined && ability.radius > 0 &&
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<TableCell component="th" scope="row">Radius</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{ability.radius.toFixed(0)} <i style={{fontSize: 10}}>{ability.areaEffect}</i>
|
||||
<TableCell component="th" scope="row" >Radius</TableCell>
|
||||
<TableCell>
|
||||
{ability.radius.toFixed(0)} <i style={{fontSize: 10, color: 'rgba(255,255,255,0.5)'}}>{ability.areaEffect}</i>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
{props.isChild !== true && ability.range !== undefined && ability.areaFilter !== "Own" && ability.range > 0 &&
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<TableCell component="th" scope="row">Range</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableCell component="th" scope="row" >Range</TableCell>
|
||||
<TableCell>
|
||||
{ability.range.toFixed(0)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@ -184,26 +202,20 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Throw force</TableCell>
|
||||
<TableCell component="th"
|
||||
scope="row">{ability.throwForceMin != 0 ? ability.throwForceMin + " - " + ability.throwForceMax : "-"}
|
||||
<TableCell component="th" scope="row" >Throw force</TableCell>
|
||||
<TableCell>{ability.throwForceMin != 0 ? ability.throwForceMin + " - " + ability.throwForceMax : "-"}
|
||||
</TableCell>
|
||||
</TableRow>}
|
||||
<TableRow>
|
||||
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer><br/>
|
||||
</Grid2>
|
||||
<Grid2 size={8}>
|
||||
<div style={{whiteSpace: "pre-wrap"}}>
|
||||
{props.isChild !== true && ability.description}
|
||||
</div>
|
||||
{props.isChild !== true && <DescriptionBox>{ability.description}</DescriptionBox>}
|
||||
</Grid2>
|
||||
{showDpsTable ?
|
||||
<Grid2 size={12}>
|
||||
{(ability.refreshTime !== undefined && ability.durationTime !== undefined && ability.refreshTime * 2 < ability.durationTime) && <i>Every <b>{ability.refreshTime.toFixed(1)}s</b> deal damege:</i>}
|
||||
{(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>}
|
||||
<DpsTable mod={props.mod} minDamageValue={ability.minDamageValue} minDamage={ability.minDamage} maxDamage={ability.maxDamage} piercings={ability.piercings} targetFilter={ability.targetFilter} moraleDamage={ability.moraleDamage}
|
||||
/>
|
||||
</Grid2> : showTargetTable ?
|
||||
@ -235,7 +247,7 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryLow)}</StyledTableCell>
|
||||
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryMedium)}</StyledTableCell>
|
||||
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryHigh)}</StyledTableCell>
|
||||
@ -272,7 +284,7 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.VehicleLow)}</StyledTableCell>
|
||||
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.VehicleMedium)}</StyledTableCell>
|
||||
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.VehicleHigh)}</StyledTableCell>
|
||||
@ -321,7 +333,7 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryLow)}</StyledTableCell>
|
||||
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryMedium)}</StyledTableCell>
|
||||
<StyledTableCell>{isAbilityApplyTo(ArmorTypeNames.InfantryHigh)}</StyledTableCell>
|
||||
@ -352,10 +364,10 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
|
||||
<Grid2 size={{xs: 12, md: 12}}>
|
||||
<Required requirement={ability.requirements} modId={props.mod.id} raceId={props.race.id}/>
|
||||
</Grid2>}
|
||||
{ability.activation !== "Passive ability" && props.isChild !== true && <b className="hotkey">Hotkey: {ability.hotkey}</b>}
|
||||
{props.isChild !== true && ability.activation !== "Passive ability" && <b className="hotkey" >Hotkey: {ability.hotkey}</b>}
|
||||
</div>
|
||||
)
|
||||
} else return <div>loading...</div>;
|
||||
} else return <div style={{color: 'rgba(255, 255, 255, 0.5)'}}>loading...</div>;
|
||||
|
||||
|
||||
|
||||
|
||||
@ -6,7 +6,8 @@ import {
|
||||
TableBody,
|
||||
TableCell, TableContainer,
|
||||
TableRow,
|
||||
Tooltip
|
||||
Tooltip,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import {ExpandMore} from "@mui/icons-material";
|
||||
import React from "react";
|
||||
@ -21,6 +22,28 @@ import ArmorType from "./ArmorType";
|
||||
import Vision from "./Vision";
|
||||
import Ability from "./Ability";
|
||||
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 {
|
||||
@ -33,21 +56,22 @@ function AbilitySpawnedObject(props: AbilityEnvironmentProps) {
|
||||
|
||||
const abilityEnvironment = props.abilityEnvironment
|
||||
|
||||
return <div>
|
||||
<h3> {props.abilityEnvironment.name ?? props.abilityEnvironment.filename.charAt(0).toUpperCase() + props.abilityEnvironment.filename.slice(1).replaceAll('_', ' ').replace('.rgd', '')}
|
||||
<i style={{fontSize: 11}}> (Spawned object)</i>
|
||||
</h3>
|
||||
return <div style={{color: 'rgba(255, 255, 255, 0.85)'}}>
|
||||
<SectionTitle>
|
||||
{props.abilityEnvironment.name ?? props.abilityEnvironment.filename.charAt(0).toUpperCase() + props.abilityEnvironment.filename.slice(1).replaceAll('_', ' ').replace('.rgd', '')}
|
||||
<i style={{fontSize: 11, color: 'rgba(255,255,255,0.5)'}}> (Spawned object)</i>
|
||||
</SectionTitle>
|
||||
<Grid2 container spacing={2}>
|
||||
<Grid2 size={{xs: 12, md: 4}}>
|
||||
<TableContainer component={Paper}>
|
||||
<TableContainer component={StyledPaper} elevation={0}>
|
||||
<Table size="small" aria-label="a dense table">
|
||||
<TableBody id="unit-stats-table">
|
||||
{abilityEnvironment.armorType !== null &&
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Armor type</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableCell component="th" scope="row" >Armor type</TableCell>
|
||||
<TableCell>
|
||||
{abilityEnvironment.armorType2 == null ?
|
||||
<ArmorType name={abilityEnvironment.armorType.name} compact={false}/> :
|
||||
<Tooltip
|
||||
@ -61,8 +85,8 @@ function AbilitySpawnedObject(props: AbilityEnvironmentProps) {
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Health</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableCell component="th" scope="row" >Health</TableCell>
|
||||
<TableCell>
|
||||
<img style={{verticalAlign: "top"}}
|
||||
src="/images/Health_icon.webp"/>
|
||||
{abilityEnvironment.health} {abilityEnvironment.healthRegeneration != null && abilityEnvironment.healthRegeneration > 0 &&
|
||||
@ -77,18 +101,16 @@ function AbilitySpawnedObject(props: AbilityEnvironmentProps) {
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Move speed</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{abilityEnvironment.moveSpeed}
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" >Move speed</TableCell>
|
||||
<TableCell>{abilityEnvironment.moveSpeed}</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
{abilityEnvironment.sightRadius != null &&
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Vision</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableCell component="th" scope="row" >Vision</TableCell>
|
||||
<TableCell>
|
||||
<Vision sight={abilityEnvironment.sightRadius ?? 0}
|
||||
detect={abilityEnvironment.detectRadius ?? 0}/>
|
||||
</TableCell>
|
||||
@ -96,8 +118,8 @@ function AbilitySpawnedObject(props: AbilityEnvironmentProps) {
|
||||
}
|
||||
{abilityEnvironment.lifetime != null &&
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<TableCell component="th" scope="row">Lifetime</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableCell component="th" scope="row" >Lifetime</TableCell>
|
||||
<TableCell>
|
||||
<img style={{verticalAlign: "top"}}
|
||||
src="/images/Time_icon.webp"/>
|
||||
{abilityEnvironment.lifetime.toFixed(0)}s
|
||||
@ -109,9 +131,9 @@ function AbilitySpawnedObject(props: AbilityEnvironmentProps) {
|
||||
</TableContainer>
|
||||
</Grid2>
|
||||
<Grid2 size={{xs: 12, md: 8}}>
|
||||
<div style={{whiteSpace: "pre-wrap"}}>
|
||||
<DescriptionBox>
|
||||
{abilityEnvironment.description}
|
||||
</div>
|
||||
</DescriptionBox>
|
||||
</Grid2>
|
||||
{abilityEnvironment.abilities.length > 0 && <Grid2 size={12}>
|
||||
{abilityEnvironment.abilities.map(a =>
|
||||
|
||||
@ -24,6 +24,29 @@ import {IDeathExplosion} from "../types/IDeathExplosion";
|
||||
import Required from "./Required";
|
||||
import ArmorTypeNames from "../types/ArmorTypeValues";
|
||||
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 {
|
||||
@ -37,44 +60,37 @@ function DeathExplosion(props: DeathExpProps) {
|
||||
const chanceTotalNotZero = deathExplosion.globalChance * deathExplosion.chance != 0
|
||||
|
||||
return chanceTotalNotZero ?
|
||||
<div className='addon-research-accordion' id='death-explosion'><Accordion>
|
||||
<div className='addon-research-accordion' id='death-explosion'><StyledAccordion>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMore/>}
|
||||
expandIcon={<ExpandMore sx={{color: '#ffffff'}}/>}
|
||||
aria-controls="panel1-content"
|
||||
sx={{color: '#ffffff'}}
|
||||
>
|
||||
<span style={{fontSize: 20}}>
|
||||
<span style={{fontSize: 20, color: '#ffffff'}}>
|
||||
Death Explosion
|
||||
</span>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<AccordionDetails sx={{color: 'rgba(255,255,255,0.85)'}}>
|
||||
<Grid2 container spacing={2}>
|
||||
<Grid2 size={{xs: 12, md: 4}}>
|
||||
<TableContainer component={Paper}>
|
||||
<TableContainer component={StyledPaper} elevation={0}>
|
||||
<Table size="small" aria-label="a dense table">
|
||||
<TableBody id="unit-stats-table">
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Chance</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{deathExplosion.globalChance * deathExplosion.chance}%
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" >Chance</TableCell>
|
||||
<TableCell>{deathExplosion.globalChance * deathExplosion.chance}%</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Damage radius</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{deathExplosion.damageRadius}
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" >Damage radius</TableCell>
|
||||
<TableCell>{deathExplosion.damageRadius}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Throw force</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{deathExplosion.throwForceMin} - {deathExplosion.throwForceMax}
|
||||
</TableCell>
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<TableCell component="th" scope="row" >Throw force</TableCell>
|
||||
<TableCell>{deathExplosion.throwForceMin} - {deathExplosion.throwForceMax}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
@ -88,7 +104,7 @@ function DeathExplosion(props: DeathExpProps) {
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
</AccordionDetails>
|
||||
</Accordion></div> : <span></span>
|
||||
</StyledAccordion></div> : <span></span>
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -19,16 +19,19 @@ export default function DpsTable(props: { mod: IMod, minDamageValue?: number, mi
|
||||
|
||||
const StyledTableCell = styled(TableCell)(({theme}) => ({
|
||||
[`&.${tableCellClasses.head}`]: {
|
||||
backgroundColor: "rgb(244,244,244)",
|
||||
backgroundColor: 'rgba(233, 69, 96, 0.15)',
|
||||
fontWeight: 700,
|
||||
marginRight: 'auto',
|
||||
marginLeft: 'auto',
|
||||
paddingLeft: 10
|
||||
paddingLeft: 10,
|
||||
borderBottom: '2px solid rgba(233, 69, 96, 0.3)',
|
||||
},
|
||||
[`&.${tableCellClasses.body}`]: {
|
||||
fontSize: 12,
|
||||
textAlign: 'center',
|
||||
color: 'rgba(255, 255, 255, 0.85)',
|
||||
paddingRight: 18,
|
||||
paddingLeft: 10
|
||||
paddingLeft: 10,
|
||||
},
|
||||
}));
|
||||
|
||||
@ -158,7 +161,7 @@ export default function DpsTable(props: { mod: IMod, minDamageValue?: number, mi
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<StyledTableCell>{getTotalDamage(vehLowPiercing)}</StyledTableCell>
|
||||
<StyledTableCell>{getTotalDamage(vehMedPiercing)}</StyledTableCell>
|
||||
<StyledTableCell>{getTotalDamage(vehHighPiercing)}</StyledTableCell>
|
||||
@ -212,7 +215,7 @@ export default function DpsTable(props: { mod: IMod, minDamageValue?: number, mi
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<StyledTableCell>{getTotalDamage(infLowPiercing)}</StyledTableCell>
|
||||
<StyledTableCell>{getTotalDamage(infMedPiercing)}</StyledTableCell>
|
||||
<StyledTableCell>{getTotalDamage(infHighPiercing)}</StyledTableCell>
|
||||
|
||||
@ -6,6 +6,20 @@ import {Irace} from "../types/Irace";
|
||||
import {IAffectedData} from "../types/IAffectedData";
|
||||
import {IMod} from "../types/Imod";
|
||||
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 {
|
||||
modifiers: IModifier[],
|
||||
@ -142,15 +156,14 @@ export function ModifiersProvidesAddonTable(props: IModifiersProvidesTable) {
|
||||
let noWeaponMods = props.modifiers.filter(m => !m.reference.includes("default_weapon_modifier_hardpoint"))
|
||||
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<TableContainer component={StyledPaper} elevation={0}>
|
||||
<Table size="small" aria-label="a dense table">
|
||||
{noWeaponMods.map(m =>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">{getModName(m.reference)}</TableCell>
|
||||
<TableCell component="th"
|
||||
scope="row">{getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)}</TableCell>
|
||||
<TableCell component="th" scope="row" sx={{color: 'rgba(255,255,255,0.85)'}}>{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>
|
||||
</TableRow>
|
||||
)}
|
||||
</Table>
|
||||
@ -168,37 +181,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);
|
||||
if (affectedUnit != null) {
|
||||
return <span><img style={{verticalAlign: "top", height: 25}}
|
||||
src={getIcon(affectedUnit.icon)}/><a
|
||||
src={getIcon(affectedUnit.icon)}/><StyledLink
|
||||
href={`/mod/${props.modId}/race/${props.race.id}/unit/${affectedUnit.id}`}>
|
||||
{affectedUnit.name}
|
||||
</a></span>
|
||||
</StyledLink></span>
|
||||
}
|
||||
var affectedSergeant = props.affectedData.affectedSergeants.find(as => as.filename.replaceAll('.rgd', '') === target);
|
||||
if (affectedSergeant != null) {
|
||||
return <span><img style={{verticalAlign: "top", height: 25}}
|
||||
src={getIcon(affectedSergeant.icon)}/>{affectedSergeant.name} at <img
|
||||
style={{verticalAlign: "top", height: 25}}
|
||||
src={getIcon(affectedSergeant.unit.icon)}/><a
|
||||
src={getIcon(affectedSergeant.unit.icon)}/><StyledLink
|
||||
href={`/mod/${props.modId}/race/${props.race.id}/unit/${affectedSergeant.unit.id}`}>
|
||||
{affectedSergeant.unit.name}
|
||||
</a></span>
|
||||
</StyledLink></span>
|
||||
}
|
||||
var affectedBuilding = props.affectedData.affectedBuildings.find(ab => ab.filename.replaceAll('.rgd', '') === target);
|
||||
if (affectedBuilding != null) {
|
||||
return <span><img style={{verticalAlign: "top", height: 25}} src={getIcon(affectedBuilding.icon)}/><a
|
||||
return <span><img style={{verticalAlign: "top", height: 25}} src={getIcon(affectedBuilding.icon)}/><StyledLink
|
||||
href={`/mod/${props.modId}/race/${props.race.id}/building/${affectedBuilding.id}`}>
|
||||
{affectedBuilding.name}
|
||||
</a> </span>
|
||||
</StyledLink> </span>
|
||||
}
|
||||
|
||||
var affectedWeapon = props.affectedData.affectedWeapons.find(aw => aw.filename.replaceAll('.rgd', '') === target);
|
||||
if (affectedWeapon != null) {
|
||||
return <span>{affectedWeapon.name ? affectedWeapon.name : affectedWeapon.filename.replaceAll('_', ' ').replace('.rgd', '')} at {
|
||||
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)}/><a
|
||||
<span>{affectedWeapon != null && affectedWeapon.units.length + affectedWeapon.sergeants.length > 1 ? <br/> : <span></span>}<img style={{verticalAlign: "top", height: 25}} src={getIcon(unit.icon)}/><StyledLink
|
||||
href={`/mod/${props.modId}/race/${props.race.id}/unit/${unit.id}`}>
|
||||
{unit.name}
|
||||
</a> </span>
|
||||
</StyledLink> </span>
|
||||
)
|
||||
}
|
||||
{
|
||||
@ -206,18 +219,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}}
|
||||
src={getIcon(affectedSergeant.icon)}/>{affectedSergeant.name} at <img
|
||||
style={{verticalAlign: "top", height: 25}}
|
||||
src={getIcon(affectedSergeant.unit.icon)}/><a
|
||||
src={getIcon(affectedSergeant.unit.icon)}/><StyledLink
|
||||
href={`/mod/${props.modId}/race/${props.race.id}/unit/${affectedSergeant.unit.id}`}>
|
||||
{affectedSergeant.unit.name}
|
||||
</a> </span>
|
||||
</StyledLink> </span>
|
||||
)
|
||||
}
|
||||
{
|
||||
affectedWeapon.buildings.map(affectedBuilding =>
|
||||
<span><img style={{verticalAlign: "top", height: 25}} src={getIcon(affectedBuilding.icon)}/><a
|
||||
<span><img style={{verticalAlign: "top", height: 25}} src={getIcon(affectedBuilding.icon)}/><StyledLink
|
||||
href={`/mod/${props.modId}/race/${props.race.id}/building/${affectedBuilding.id}`}>
|
||||
{affectedBuilding.name}
|
||||
</a> </span>
|
||||
</StyledLink> </span>
|
||||
)
|
||||
}
|
||||
</span>
|
||||
@ -228,23 +241,21 @@ export function ModifiersProvidesTable(props: IModifiersProvidesResearchTable) {
|
||||
}
|
||||
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<TableContainer component={StyledPaper} elevation={0}>
|
||||
<Table size="small" aria-label="a dense table">
|
||||
{mods.map(m =>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">{getTarget(m.target)}</TableCell>
|
||||
<TableCell style={{minWidth: 120}} component="th"
|
||||
scope="row">{getModName(m.reference)}</TableCell>
|
||||
<TableCell component="th"
|
||||
scope="row">{getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)}
|
||||
<TableCell component="th" scope="row" sx={{color: 'rgba(255,255,255,0.85)'}}>{getTarget(m.target)}</TableCell>
|
||||
<TableCell component="th" scope="row" sx={{color: 'rgba(255,255,255,0.85)', minWidth: 120}}>{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)}
|
||||
{m.maxLifeTime !== undefined && m.maxLifeTime > 0 &&
|
||||
<span><img style={{verticalAlign: "top"}} src='/images/Time_icon.webp'/>{m.maxLifeTime}s
|
||||
</span>}
|
||||
<i style={{fontSize: 11}}>{m.exclusive ? " does not stuck" : " does stuck"}</i>
|
||||
<i style={{fontSize: 11}}>{m.exclusive ? " does not stack" : " does stack"}</i>
|
||||
{m.probabilityOfApplying !== undefined && m.probabilityOfApplying !== 1.0 &&
|
||||
<span><i style={{fontSize: 11}}> ({m.probabilityOfApplying * 100}% chance)</i>
|
||||
<span><i style={{fontSize: 11, color: 'rgba(255,255,255,0.5)'}}> ({m.probabilityOfApplying * 100}% chance)</i>
|
||||
</span>}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@ -5,6 +5,7 @@ import {IconUrl} from "../core/api";
|
||||
import {IResearchRequirements} from "../types/IResearchRequirements";
|
||||
import {IRequirement, IRequirementLimitByBuildingDto, IRequirementSquadsDto} from "../types/IRequirement";
|
||||
import {IUnitShort} from "../types/IUnitShort";
|
||||
import {StyledLink} from "../commons/StyledLink";
|
||||
|
||||
interface IRequiredProps{
|
||||
requirement: IRequirement,
|
||||
@ -20,9 +21,9 @@ function Required (props: IRequiredProps) {
|
||||
|
||||
<span> Global addon: <img style={{verticalAlign: "top", height: 25}}
|
||||
src={getIcon(rga.icon)}/>
|
||||
<a href={`/mod/${props.modId}/race/${props.raceId}/building/${rga.buildingId}#addon-${rga.id}`}>
|
||||
<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${rga.buildingId}#addon-${rga.id}`}>
|
||||
{rga.name}
|
||||
</a></span>
|
||||
</StyledLink></span>
|
||||
)}</div>
|
||||
}
|
||||
|
||||
@ -30,29 +31,29 @@ function Required (props: IRequiredProps) {
|
||||
return requirementBuildings.length > 0 && (
|
||||
<div> Buildings: <img style={{verticalAlign: "top", height: 25}}
|
||||
src={IconUrl + requirementBuildings[0].icon.replaceAll('\\', '/')}/>
|
||||
{" "}<a href={`/mod/${props.modId}/race/${props.raceId}/building/${requirementBuildings[0].id}`}>
|
||||
{" "}<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${requirementBuildings[0].id}`}>
|
||||
{requirementBuildings[0].name}
|
||||
</a> {requirementBuildings[1] !== undefined && <span>or <img style={{verticalAlign: "top", height: 25}}
|
||||
</StyledLink> {requirementBuildings[1] !== undefined && <span>or <img style={{verticalAlign: "top", height: 25}}
|
||||
src={IconUrl + requirementBuildings[1].icon.replaceAll('\\', '/')}/>
|
||||
{" "}<a href={`/mod/${props.modId}/race/${props.raceId}/building/${requirementBuildings[1].id}`}>
|
||||
{requirementBuildings[1].name}</a></span> } </div>
|
||||
{" "}<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${requirementBuildings[1].id}`}>
|
||||
{requirementBuildings[1].name}</StyledLink></span> } </div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderRequiremenStructureExclusive(buildingExclusive: IBuildingShort) {
|
||||
return <div> Can't be build with: <img style={{verticalAlign: "top", height: 25}}
|
||||
src={IconUrl + buildingExclusive.icon.replaceAll('\\', '/')}/>
|
||||
{" "}<a href={`/mod/${props.modId}/race/${props.raceId}/building/${buildingExclusive.id}`}>
|
||||
{" "}<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${buildingExclusive.id}`}>
|
||||
{buildingExclusive.name}
|
||||
</a> </div>
|
||||
</StyledLink> </div>
|
||||
}
|
||||
|
||||
function renderRequiremenAddonExclusive(addonShort: IBuildingAddonShort) {
|
||||
return <div> Can't be build with: <img style={{verticalAlign: "top", height: 25}}
|
||||
src={getIcon(addonShort.icon)}/>
|
||||
<a href={`/mod/${props.modId}/race/${props.raceId}/building/${addonShort.buildingId}#addon-${addonShort.id}`}>
|
||||
<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${addonShort.buildingId}#addon-${addonShort.id}`}>
|
||||
{addonShort.name}
|
||||
</a></div>
|
||||
</StyledLink></div>
|
||||
}
|
||||
|
||||
function renderRequirementOwnership(requirementOwnership: string[],) {
|
||||
@ -70,8 +71,8 @@ function Required (props: IRequiredProps) {
|
||||
function renderLimitPerBuilding(limitPerBuilding: IRequirementLimitByBuildingDto,) {
|
||||
return <span> One <img style={{verticalAlign: "top", height: 25}}
|
||||
src={IconUrl + limitPerBuilding.building.icon.replaceAll('\\', '/')}/>
|
||||
{" "}<a href={`/mod/${props.modId}/race/${props.raceId}/building/${limitPerBuilding.building.id}`}>
|
||||
{limitPerBuilding.building.name} </a> allow produce max {limitPerBuilding.limit}</span>
|
||||
{" "}<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${limitPerBuilding.building.id}`}>
|
||||
{limitPerBuilding.building.name} </StyledLink> allow produce max {limitPerBuilding.limit}</span>
|
||||
}
|
||||
|
||||
function renderRequirementResearches(requirementResearches: IResearchRequirements[],) {
|
||||
@ -79,9 +80,9 @@ function Required (props: IRequiredProps) {
|
||||
|
||||
<span> Research {rs.researchMustNotBeComplete ? "not" : ""}: <img style={{verticalAlign: "top", height: 25}}
|
||||
src={getIcon(rs.researchShortDto.icon)}/>
|
||||
<a href={`/mod/${props.modId}/race/${props.raceId}/building/${rs.researchShortDto.buildingId}#research-${rs.researchShortDto.id}`}>
|
||||
<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${rs.researchShortDto.buildingId}#research-${rs.researchShortDto.id}`}>
|
||||
{rs.researchShortDto.name}
|
||||
</a></span>
|
||||
</StyledLink></span>
|
||||
)}</div>
|
||||
}
|
||||
|
||||
@ -90,9 +91,9 @@ function Required (props: IRequiredProps) {
|
||||
|
||||
<span> Squad: {rs.count} <img style={{verticalAlign: "top", height: 25}}
|
||||
src={getIcon(rs.squad.icon)}/>
|
||||
<a href={`/mod/${props.modId}/race/${props.raceId}/unit/${rs.squad.id}`}>
|
||||
<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/unit/${rs.squad.id}`}>
|
||||
{rs.squad.name}
|
||||
</a></span>
|
||||
</StyledLink></span>
|
||||
)}</div>
|
||||
}
|
||||
|
||||
@ -101,12 +102,12 @@ function Required (props: IRequiredProps) {
|
||||
return requirementResearches.length > 0 && (
|
||||
<div> Researches: <span> <img style={{verticalAlign: "top", height: 25}}
|
||||
src={getIcon(requirementResearches[0].researchShortDto.icon)}/>
|
||||
<a href={`/mod/${props.modId}/race/${props.raceId}/building/${requirementResearches[0].researchShortDto.buildingId}#research-${requirementResearches[0].researchShortDto.id}`}>
|
||||
<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${requirementResearches[0].researchShortDto.buildingId}#research-${requirementResearches[0].researchShortDto.id}`}>
|
||||
{requirementResearches[0].researchShortDto.name}
|
||||
</a></span> {requirementResearches[1] != null && <span> or <img style={{verticalAlign: "top", height: 25}} src={getIcon(requirementResearches[1].researchShortDto.icon)}/>
|
||||
<a href={`/mod/${props.modId}/race/${props.raceId}/building/${requirementResearches[1].researchShortDto.buildingId}#research-${requirementResearches[1].researchShortDto.id}`}>
|
||||
</StyledLink></span> {requirementResearches[1] != null && <span> or <img style={{verticalAlign: "top", height: 25}} src={getIcon(requirementResearches[1].researchShortDto.icon)}/>
|
||||
<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${requirementResearches[1].researchShortDto.buildingId}#research-${requirementResearches[1].researchShortDto.id}`}>
|
||||
{requirementResearches[1].researchShortDto.name}
|
||||
</a></span>} </div>
|
||||
</StyledLink></span>} </div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -114,48 +115,48 @@ function Required (props: IRequiredProps) {
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4>Required: </h4>
|
||||
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>
|
||||
<h4 style={{color: '#ffffff', fontWeight: 700}}>Required: </h4>
|
||||
{requirement.requiredTotalPop !== undefined && requirement.requiredTotalPop !== null &&
|
||||
<div> Population: <img style={{verticalAlign: "top", height: 25}}
|
||||
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}> Population: <img style={{verticalAlign: "top", height: 25}}
|
||||
src="/images/Resource_orksquadcap.gif"/>
|
||||
{requirement.requiredTotalPop}</div>
|
||||
}
|
||||
{requirement.mobBonus !== null && requirement.mobBonus !== undefined &&
|
||||
<div> Mob requirement: <img style={{verticalAlign: "top", height: 25}}
|
||||
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}> Mob requirement: <img style={{verticalAlign: "top", height: 25}}
|
||||
src="/images/Mob_bonus.gif"/>
|
||||
{requirement.mobBonus.mobvalueRequired} in radius {requirement.mobBonus.proximityRequired}</div>
|
||||
}
|
||||
{requirement.requireAddon !== null &&
|
||||
<div> Addon: <img style={{verticalAlign: "top", height: 25}}
|
||||
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}> Addon: <img style={{verticalAlign: "top", height: 25}}
|
||||
src={IconUrl + requirement.requireAddon.icon.replaceAll('\\', '/')}/>
|
||||
<a href={`/mod/${props.modId}/race/${props.raceId}/building/${requirement.requireAddon.buildingId}#addon-${requirement.requireAddon.id}`}>
|
||||
<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${requirement.requireAddon.buildingId}#addon-${requirement.requireAddon.id}`}>
|
||||
{requirement.requireAddon.name}
|
||||
</a>
|
||||
</StyledLink>
|
||||
{requirement.replaceWhenDone && <i> (old provides replace)</i>}
|
||||
</div>
|
||||
}
|
||||
{requirement.requirementAddonExclusive != null &&
|
||||
renderRequiremenAddonExclusive(requirement.requirementAddonExclusive,)}
|
||||
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderRequiremenAddonExclusive(requirement.requirementAddonExclusive,)}</div>}
|
||||
{requirement.requirementBuildings.map(b =>
|
||||
<div key={b.id}> Building: <img style={{verticalAlign: "top", height: 25}}
|
||||
<div key={b.id} style={{color: 'rgba(255, 255, 255, 0.85)'}}> Building: <img style={{verticalAlign: "top", height: 25}}
|
||||
src={IconUrl + b.icon.replaceAll('\\', '/')}/>
|
||||
<a href= {'/mod/'+ props.modId +'/race/'+ props.raceId +'/building/' + b.id + "/"}>{b.name}</a></div>)
|
||||
<StyledLink href= {'/mod/'+ props.modId +'/race/'+ props.raceId +'/building/' + b.id + "/"}>{b.name}</StyledLink></div>)
|
||||
}
|
||||
{requirement.requirementResearches.length !== 0 &&
|
||||
renderRequirementResearches(requirement.requirementResearches,)}
|
||||
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderRequirementResearches(requirement.requirementResearches,)}</div>}
|
||||
{requirement.requirementSquads.length !== 0 &&
|
||||
renderRequirementSquads(requirement.requirementSquads,)}
|
||||
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderRequirementSquads(requirement.requirementSquads,)}</div>}
|
||||
{requirement.requirementResearchesEither.length !== 0 &&
|
||||
renderRequirementResearchesEither(requirement.requirementResearchesEither,)}
|
||||
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderRequirementResearchesEither(requirement.requirementResearchesEither,)}</div>}
|
||||
{requirement.requirementBuildingsEither.length !== 0 &&
|
||||
renderRequirementBuildingEither(requirement.requirementBuildingsEither,)}
|
||||
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderRequirementBuildingEither(requirement.requirementBuildingsEither,)}</div>}
|
||||
{requirement.requirementStructureExclusive != null &&
|
||||
renderRequiremenStructureExclusive(requirement.requirementStructureExclusive,)}
|
||||
{requirement.limitByBuilding !== undefined && requirement.limitByBuilding !== null && renderLimitPerBuilding(requirement.limitByBuilding)}
|
||||
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderRequiremenStructureExclusive(requirement.requirementStructureExclusive,)}</div>}
|
||||
{requirement.limitByBuilding !== undefined && requirement.limitByBuilding !== null && <div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderLimitPerBuilding(requirement.limitByBuilding)}</div>}
|
||||
{requirement.requirementsGlobalAddons.length !== 0 &&
|
||||
renderRequirementGlobalAddons(requirement.requirementsGlobalAddons,)}
|
||||
{renderRequirementOwnership(requirement.requiredOwnership)}
|
||||
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderRequirementGlobalAddons(requirement.requirementsGlobalAddons,)}</div>}
|
||||
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>{renderRequirementOwnership(requirement.requiredOwnership)}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import {ISergeant, IShortWeapon} from "../types/IUnit";
|
||||
import {Grid2, Paper, Table, TableBody, TableCell, TableContainer, TableRow, Tooltip} from "@mui/material";
|
||||
import {Grid2, Paper, Table, TableBody, TableCell, TableContainer, TableRow, Tooltip, Typography} from "@mui/material";
|
||||
import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
|
||||
import ArmorType from "./ArmorType";
|
||||
import WeaponSlot from "./WeaponSlot";
|
||||
@ -12,6 +12,28 @@ import Required from "./Required";
|
||||
import {ModifiersProvidesTable} from "./ModifiersProvideTable";
|
||||
import Ability from "./Ability";
|
||||
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 {
|
||||
@ -38,17 +60,17 @@ const Sergeant = (props: SergeantProps) => {
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>
|
||||
<Grid2 container spacing={2}>
|
||||
<Grid2 size= {{xs: 12, md: 4}}>
|
||||
<TableContainer component={Paper}>
|
||||
<TableContainer component={StyledPaper} elevation={0}>
|
||||
<Table size="small" aria-label="a dense table">
|
||||
<TableBody id="unit-stats-table">
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Cost</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableCell component="th" scope="row" >Cost</TableCell>
|
||||
<TableCell>
|
||||
{sergeant.buildCostRequisition > 0 &&
|
||||
<span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_requisition.gif"/>
|
||||
@ -72,7 +94,7 @@ const Sergeant = (props: SergeantProps) => {
|
||||
</TableRow>
|
||||
{(sergeant.requisitionIncome !== undefined && sergeant.requisitionIncome !== null || sergeant.powerIncome !== undefined && sergeant.powerIncome !== null || sergeant.faithIncome !== undefined && sergeant.faithIncome !== null) &&
|
||||
<TableRow>
|
||||
<TableCell>Resource income</TableCell>
|
||||
<TableCell >Resource income</TableCell>
|
||||
<TableCell>
|
||||
{sergeant.requisitionIncome !== undefined && sergeant.requisitionIncome > 0 &&
|
||||
<span> <img style={{verticalAlign: "top"}}
|
||||
@ -92,8 +114,8 @@ const Sergeant = (props: SergeantProps) => {
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Armor type</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableCell component="th" scope="row" >Armor type</TableCell>
|
||||
<TableCell>
|
||||
{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> }
|
||||
</TableCell>
|
||||
@ -101,8 +123,8 @@ const Sergeant = (props: SergeantProps) => {
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Health</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableCell component="th" scope="row" >Health</TableCell>
|
||||
<TableCell>
|
||||
<span><img style={{verticalAlign: "top"}}
|
||||
src="/images/Health_icon.webp"/>
|
||||
{sergeant.health} {sergeant.healthRegeneration > 0 &&
|
||||
@ -112,8 +134,8 @@ const Sergeant = (props: SergeantProps) => {
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Morale</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableCell component="th" scope="row" >Morale</TableCell>
|
||||
<TableCell>
|
||||
{sergeant.moraleIncomeMax && <span><img style={{verticalAlign: "top"}}
|
||||
src="/images/ARM_Morale.webp"/> +{sergeant.moraleIncomeMax} </span>}
|
||||
{sergeant.moraleRate && <span>+ {sergeant.moraleRate}/s</span> }
|
||||
@ -124,22 +146,20 @@ const Sergeant = (props: SergeantProps) => {
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Mass</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{sergeant.mass}
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" >Mass</TableCell>
|
||||
<TableCell>{sergeant.mass}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Vision</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableCell component="th" scope="row" >Vision</TableCell>
|
||||
<TableCell>
|
||||
<Vision sight={sergeant.sightRadius} detect={sergeant.detectRadius}/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{sergeant.mobValue != null &&
|
||||
<TableRow>
|
||||
<TableCell>Mob value</TableCell>
|
||||
<TableCell component="th" scope="row" >Mob value</TableCell>
|
||||
<TableCell>
|
||||
<span> <img style={{height: 20, verticalAlign: "top"}} src="/images/Mob_bonus.gif"/> {sergeant.mobValue} </span>
|
||||
</TableCell>
|
||||
@ -150,20 +170,20 @@ const Sergeant = (props: SergeantProps) => {
|
||||
</TableContainer>
|
||||
</Grid2>
|
||||
<Grid2 size={8}>
|
||||
<div style={{whiteSpace: "pre-wrap"}}>
|
||||
<DescriptionBox>
|
||||
{sergeant.description}
|
||||
</div>
|
||||
</DescriptionBox>
|
||||
</Grid2>
|
||||
{sergeant.requirements !== null &&
|
||||
<Grid2 size={{xs: 12, md: 12}}>
|
||||
<Required requirement={sergeant.requirements} modId={props.mod.id} raceId={props.race.id}/>
|
||||
</Grid2>}
|
||||
{sergeant.modifiers.length > 0 && <Grid2 size={12}>
|
||||
<h3>Affected on</h3>
|
||||
<SectionTitle>Affected on</SectionTitle>
|
||||
<ModifiersProvidesTable modifiers={sergeant.modifiers} modId={props.mod.id} race={props.race} affectedData={null}/>
|
||||
</Grid2>}
|
||||
{sergeant.abilities.length > 0 && <Grid2 size={12}>
|
||||
<h3>Abilities</h3>
|
||||
<SectionTitle>Abilities</SectionTitle>
|
||||
{sergeant.abilities.map(a =>
|
||||
<Ability mod={props.mod} ability={a} race={props.race}/>
|
||||
)}
|
||||
@ -185,7 +205,7 @@ const Sergeant = (props: SergeantProps) => {
|
||||
{renderAffectedResearches(sergeant.affectedResearches, props.mod.id, props.race.id)}
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
<b className="hotkey">Hotkey: {sergeant.hotkey}</b>
|
||||
<b className="hotkey" >Hotkey: {sergeant.hotkey}</b>
|
||||
</div>)
|
||||
};
|
||||
|
||||
|
||||
@ -1,10 +1,45 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {AvailableBuildings, AvailableUnits} from "../core/api";
|
||||
import {Grid2, Link, Table, TableBody, TableCell, TableContainer, TableHead, TableRow} from "@mui/material";
|
||||
import {Grid2, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper} from "@mui/material";
|
||||
import {styled} from '@mui/material/styles';
|
||||
import {IRaceUnits, IUnitShort} from "../types/IUnitShort";
|
||||
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 {
|
||||
racesUnits: IRaceUnits[]
|
||||
racesBuildings: IRaceBuildings[]
|
||||
@ -12,6 +47,7 @@ interface IUnitsTable {
|
||||
|
||||
export default function UnitsTable(prop: {modId: number}) {
|
||||
|
||||
|
||||
const [unitsTable, setUnitsTable] = useState<IUnitsTable>({
|
||||
racesUnits: [],
|
||||
racesBuildings: []
|
||||
@ -19,15 +55,19 @@ export default function UnitsTable(prop: {modId: number}) {
|
||||
|
||||
|
||||
function getUnitRef(modId: number, raceId: string, unit: IUnitShort) {
|
||||
return <span style={{fontSize: 11}}><Link href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id}>
|
||||
{unit.name}
|
||||
</Link><br/></span>
|
||||
return <span style={{fontSize: 11}}>
|
||||
<StyledLink href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id}>
|
||||
{unit.name}
|
||||
</StyledLink><br/>
|
||||
</span>
|
||||
}
|
||||
|
||||
function getBuildingRef(modId: number, raceId: string, building: IBuildingShort) {
|
||||
return <span style={{fontSize: 11}}><Link href={"/mod/" + modId + "/race/" + raceId + "/building/" + building.id}>
|
||||
{building.name}
|
||||
<br/></Link></span>
|
||||
return <span style={{fontSize: 11}}>
|
||||
<StyledLink href={"/mod/" + modId + "/race/" + raceId + "/building/" + building.id}>
|
||||
{building.name}
|
||||
</StyledLink><br/>
|
||||
</span>
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@ -99,31 +139,31 @@ export default function UnitsTable(prop: {modId: number}) {
|
||||
}
|
||||
|
||||
function generateRaceUnitTable(racesUnitsPart: IRaceUnits[], racesBuildings: IRaceBuildings[]){
|
||||
return (<TableContainer>
|
||||
{<Table sx={{minWidth: 650}} size="small" aria-label="a dense table">
|
||||
return (<StyledPaper elevation={0}>
|
||||
{<StyledTable sx={{minWidth: 650}} size="small" aria-label="a dense table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{racesUnitsPart.map(raceUnits => <TableCell><h2><Link color="inherit"
|
||||
href={"/mod/" + prop.modId + "/race/" + raceUnits.race.id}>{raceUnits.race.name}</Link>
|
||||
{racesUnitsPart.map(raceUnits => <TableCell><h2><StyledHeaderLink
|
||||
href={"/mod/" + prop.modId + "/race/" + raceUnits.race.id}>{raceUnits.race.name}</StyledHeaderLink>
|
||||
</h2></TableCell>)}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow style={{verticalAlign: 'top'}}>
|
||||
<TableRow>
|
||||
{racesUnitsPart.map(raceUnits =>
|
||||
<TableCell>
|
||||
{getUnitsRef(raceUnits.infantry.concat(raceUnits.support), raceUnits.race.id)}
|
||||
</TableCell>)
|
||||
}
|
||||
</TableRow>
|
||||
<TableRow style={{verticalAlign: 'top'}}>
|
||||
<TableRow>
|
||||
{racesUnitsPart.map(raceUnits =>
|
||||
<TableCell>
|
||||
{getUnitsRef(raceUnits.tech, raceUnits.race.id)}
|
||||
</TableCell>)
|
||||
}
|
||||
</TableRow>
|
||||
<TableRow style={{verticalAlign: 'top'}}>
|
||||
<TableRow>
|
||||
{racesBuildings.map(raceBuilding =>
|
||||
<TableCell>
|
||||
{raceBuilding.buildings.map(building => getBuildingRef(prop.modId, raceBuilding.race.id, building))}
|
||||
@ -131,8 +171,8 @@ export default function UnitsTable(prop: {modId: number}) {
|
||||
}
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>}
|
||||
</TableContainer>)
|
||||
</StyledTable>}
|
||||
</StyledPaper>)
|
||||
}
|
||||
|
||||
function generateRaceUnitTables(racesUnits: IRaceUnits[], racesBuildings: IRaceBuildings[], raceCount: number){
|
||||
@ -156,8 +196,8 @@ export default function UnitsTable(prop: {modId: number}) {
|
||||
function generateRaceList(unitsTable: IUnitsTable){
|
||||
return unitsTable.racesUnits.sort(function (a, b) {
|
||||
return a.race.name.localeCompare(b.race.name);
|
||||
}).map(raceUnits => <h5 key={raceUnits.race.id}><Link color="inherit"
|
||||
href={"/mod/" + prop.modId + "/race/" + raceUnits.race.id}>{raceUnits.race.name}</Link><br/>
|
||||
}).map(raceUnits => <h5 key={raceUnits.race.id}><StyledLink
|
||||
href={"/mod/" + prop.modId + "/race/" + raceUnits.race.id}>{raceUnits.race.name}</StyledLink><br/>
|
||||
</h5>)
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,15 @@ import {ExpandMore} from "@mui/icons-material";
|
||||
import {IMod} from "../types/Imod";
|
||||
import {Irace} from "../types/Irace";
|
||||
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 {
|
||||
weapon: IShortWeapon,
|
||||
@ -34,23 +43,26 @@ class Weapon extends React.Component<IWeaponProps, any> {
|
||||
const weapon = this.props.weapon
|
||||
|
||||
return (
|
||||
<div style={{marginBottom: 10}}><Accordion TransitionProps={{ unmountOnExit: true, timeout: 100 }}>
|
||||
<div style={{marginBottom: 10}}><StyledAccordion TransitionProps={{ unmountOnExit: true, timeout: 100 }}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMore/>}
|
||||
expandIcon={<ExpandMore sx={{color: '#ffffff'}}/>}
|
||||
aria-controls="panel1-content"
|
||||
id="panel1-header"
|
||||
sx={{color: '#ffffff'}}
|
||||
>
|
||||
<span style={{fontSize: 18}}> {!this.props.isDefault && weapon.icon && !weapon.icon.endsWith("upgrade.png") ?
|
||||
<span style={{fontSize: 18, color: '#ffffff'}}> {!this.props.isDefault && weapon.icon && !weapon.icon.endsWith("upgrade.png") ?
|
||||
<img className="weaponIcon" src={IconUrl + weapon.icon.replaceAll('\\', '/')}/> : (
|
||||
weapon.isMeleeWeapon ?
|
||||
<img className="weaponIcon" src="/images/MeleeStance_icon_bw.jpg"/> :
|
||||
<img className="weaponIcon" src="/images/RangedStance_icon_bw.jpg"/>
|
||||
)} {weapon.name ? weapon.name : this.humanReadableName(weapon.filename)} </span>
|
||||
)} {weapon.name ? weapon.name : this.humanReadableName(weapon.filename)}
|
||||
{this.props.isDefault && <i style={{fontSize: 12, color: 'rgba(255,255,255,0.5)'}}> (default)</i>}
|
||||
</span>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<AccordionDetails sx={{color: 'rgba(255,255,255,0.85)'}}>
|
||||
<WeaponFull weaponId={weapon.id} isDefault={this.props.isDefault} mod={this.props.mod} race={this.props.race} haveReinforceMenu={this.props.haveReinforceMenu}/>
|
||||
</AccordionDetails>
|
||||
</Accordion></div>
|
||||
</StyledAccordion></div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,8 @@ import {Irace} from "../types/Irace";
|
||||
import {IRaceUnits} from "../types/IUnitShort";
|
||||
import {IRaceBuildings} from "../types/IBuildingShort";
|
||||
import {ModifiersProvidesTable} from "./ModifiersProvideTable";
|
||||
import {DescriptionBox} from "../commons/DescriptionBox";
|
||||
import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
|
||||
|
||||
interface IWeaponFull {
|
||||
currentTable: string;
|
||||
@ -37,16 +39,32 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
||||
|
||||
const StyledTableCell = styled(TableCell)(({theme}) => ({
|
||||
[`&.${tableCellClasses.head}`]: {
|
||||
backgroundColor: "rgb(244,244,244)",
|
||||
backgroundColor: 'rgba(233, 69, 96, 0.15)',
|
||||
marginRight: 'auto',
|
||||
marginLeft: 'auto',
|
||||
paddingLeft: 10
|
||||
paddingLeft: 10,
|
||||
borderBottom: '2px solid rgba(233, 69, 96, 0.3)',
|
||||
},
|
||||
[`&.${tableCellClasses.body}`]: {
|
||||
fontSize: 12,
|
||||
textAlign: 'center',
|
||||
color: 'rgba(255, 255, 255, 0.85)',
|
||||
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,
|
||||
},
|
||||
}));
|
||||
|
||||
@ -133,93 +151,76 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{color: 'rgba(255, 255, 255, 0.85)'}}>
|
||||
<Grid2 container spacing={2}>
|
||||
<Grid2 size={12}>
|
||||
<div style={{whiteSpace: "pre-wrap"}}>
|
||||
<span
|
||||
className="weaponDescription">{props.isDefault ? "Default weapon" : weapon.description}</span>
|
||||
</div>
|
||||
</Grid2>
|
||||
{!props.isDefault && <Grid2 size={12}>
|
||||
<DescriptionBox>
|
||||
<span className="weaponDescription">{weapon.description}</span>
|
||||
</DescriptionBox>
|
||||
</Grid2>}
|
||||
|
||||
<Grid2 size={{xs: 12, md: 4}}>
|
||||
<TableContainer component={Paper}>
|
||||
<TableContainer component={StyledPaper} elevation={0}>
|
||||
<Table size="small" aria-label="a dense table">
|
||||
{/*<TableHead><TableRow><TableCell component="th" scope="row"><b>Damage</b></TableCell><TableCell/></TableRow></TableHead>*/}
|
||||
<TableBody>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Base damage</TableCell>
|
||||
<TableCell component="th"
|
||||
scope="row">{weapon.minDamage} {weapon.maxDamage !== weapon.minDamage && "- " + weapon.maxDamage}
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" >Base damage</TableCell>
|
||||
<TableCell>{weapon.minDamage} {weapon.maxDamage !== weapon.minDamage && "- " + weapon.maxDamage}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Accuracy</TableCell>
|
||||
<TableCell component="th"
|
||||
scope="row">{weapon.accuracy.toFixed(3).replace(/[,.]?0+$/, '')}
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" >Accuracy</TableCell>
|
||||
<TableCell>{weapon.accuracy.toFixed(3).replace(/[,.]?0+$/, '')}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Accuracy moving</TableCell>
|
||||
<TableCell component="th"
|
||||
scope="row">{weapon.setupTime === 0 && weapon.accuracy - weapon.accuracyReductionMoving > 0 ? (weapon.accuracy - weapon.accuracyReductionMoving).toFixed(3).replace(/[,.]?0+$/, '') : "-"}
|
||||
</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>
|
||||
</TableRow>
|
||||
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Grid2>
|
||||
<Grid2 size={{xs: 12, md: 4}}>
|
||||
<TableContainer component={Paper}>
|
||||
<TableContainer component={StyledPaper} elevation={0}>
|
||||
<Table size="small" aria-label="a dense table">
|
||||
<TableBody>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Reload time</TableCell>
|
||||
<TableCell component="th"
|
||||
scope="row">{weapon.reloadTime}
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" >Reload time</TableCell>
|
||||
<TableCell>{weapon.reloadTime}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Range</TableCell>
|
||||
<TableCell component="th"
|
||||
scope="row">{weapon.maxRange ? (weapon.maxRange) : "-"} {weapon.minRange ? "(min " + (weapon.minRange) + ")" : ""}
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" >Range</TableCell>
|
||||
<TableCell>{weapon.maxRange ? (weapon.maxRange) : "-"} {weapon.minRange ? "(min " + (weapon.minRange) + ")" : ""}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Damage radius</TableCell>
|
||||
<TableCell component="th"
|
||||
scope="row">{weapon.damageRadius ? (weapon.damageRadius) : "-"}
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" >Damage radius</TableCell>
|
||||
<TableCell>{weapon.damageRadius ? (weapon.damageRadius) : "-"}</TableCell>
|
||||
</TableRow>
|
||||
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Grid2>
|
||||
<Grid2 size={{xs: 12, md: 4}}>
|
||||
<TableContainer component={Paper}>
|
||||
<TableContainer component={StyledPaper} elevation={0}>
|
||||
<Table size="small" aria-label="a dense table">
|
||||
<TableBody>
|
||||
<TableRow
|
||||
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) ?
|
||||
<TableCell component="th"
|
||||
scope="row">
|
||||
<TableCell>
|
||||
{weapon.costRequisition > 0 &&
|
||||
<span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_requisition.gif"/>
|
||||
@ -228,24 +229,24 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
||||
<span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_power.gif"/>
|
||||
{weapon.costPower.toFixed(0)}</span>}
|
||||
</TableCell> : <TableCell component="th" scope="row"> - </TableCell>
|
||||
{(weapon.costTimeSeconds !== undefined && weapon.costTimeSeconds > 0) &&
|
||||
<span> <AvTimerOutlinedIcon
|
||||
style={{verticalAlign: "top", fontSize: "18px"}}/>
|
||||
{weapon.costTimeSeconds}s</span>}
|
||||
</TableCell> : <TableCell> - </TableCell>
|
||||
}
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Setup time</TableCell>
|
||||
<TableCell component="th"
|
||||
scope="row">{weapon.setupTime != 0 ? (weapon.setupTime).toFixed(3).replace(/[,.]?0+$/, '') : "-"}
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" >Setup time</TableCell>
|
||||
<TableCell>{weapon.setupTime != 0 ? (weapon.setupTime).toFixed(3).replace(/[,.]?0+$/, '') : "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Throw force</TableCell>
|
||||
<TableCell component="th"
|
||||
scope="row">{weapon.throwForceMin != 0 ? weapon.throwForceMin + " - " + weapon.throwForceMax : "-"}
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" >Throw force</TableCell>
|
||||
<TableCell>{weapon.throwForceMin != 0 ? weapon.throwForceMin + " - " + weapon.throwForceMax : "-"}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
@ -259,9 +260,10 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
||||
aria-label="Platform"
|
||||
value={weaoponFull.currentTable}
|
||||
onChange={handleChange}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
<ToggleButton size="small" value="dps">Dps</ToggleButton>
|
||||
<ToggleButton size="small" value="one hit">One hit average damage</ToggleButton>
|
||||
<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="one hit" sx={{ color: '#ffffff', '&.Mui-selected': {borderColor: '#e94560', color: '#e94560', backgroundColor: 'rgba(233, 69, 96, 0.15)' } }}>One hit average damage</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
<TableContainer>
|
||||
{ props.mod !== undefined && props.mod.technicalName === 'UltimateApocalypse' ?
|
||||
@ -290,7 +292,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<StyledTableCell>{getTotalDamage(infLowPiercing)}</StyledTableCell>
|
||||
<StyledTableCell>{getTotalDamage(infMedPiercing)}</StyledTableCell>
|
||||
<StyledTableCell>{getTotalDamage(infHighPiercing)}</StyledTableCell>
|
||||
@ -325,13 +327,13 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
||||
name={ArmorTypeNames.BuildingSuper}/></StyledTableCell>
|
||||
<StyledTableCell><img style={{verticalAlign: "top"}}
|
||||
src="/images/ARM_Morale.webp"/>
|
||||
<div style={{width: 20, fontSize: 12, height: 50}}>
|
||||
<div style={{width: 20, fontSize: 12, height: 50, color: 'rgba(255,255,255,0.7)'}}>
|
||||
<i>Morale</i></div>
|
||||
</StyledTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<StyledTableCell>{getTotalDamage(vehLowPiercing)}</StyledTableCell>
|
||||
<StyledTableCell>{getTotalDamage(vehMedPiercing)}</StyledTableCell>
|
||||
<StyledTableCell>{getTotalDamage(vehHighPiercing)}</StyledTableCell>
|
||||
@ -379,13 +381,13 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
||||
name={ArmorTypeNames.BuildingHigh}/></StyledTableCell>
|
||||
<StyledTableCell><img style={{verticalAlign: "top"}}
|
||||
src="/images/ARM_Morale.webp"/>
|
||||
<div style={{width: 20, fontSize: 12, height: 50}}>
|
||||
<div style={{width: 20, fontSize: 12, height: 50, color: 'rgba(255,255,255,0.7)'}}>
|
||||
<i>Morale</i></div>
|
||||
</StyledTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<StyledTableCell>{getTotalDamage(infLowPiercing)}</StyledTableCell>
|
||||
<StyledTableCell>{getTotalDamage(infMedPiercing)}</StyledTableCell>
|
||||
<StyledTableCell>{getTotalDamage(infHighPiercing)}</StyledTableCell>
|
||||
@ -411,7 +413,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
||||
|
||||
</Grid2>
|
||||
{weapon.modifiers.length > 0 && <Grid2 size={12}>
|
||||
<h3>Modifiers</h3>
|
||||
<h3 style={{color: '#ffffff', fontWeight: 700}}>Modifiers</h3>
|
||||
<ModifiersProvidesTable modifiers={weapon.modifiers} modId={props.mod.id} race={props.race} affectedData={null}/>
|
||||
</Grid2>}
|
||||
{weapon.requirements !== null && !props.isDefault && props.haveReinforceMenu &&
|
||||
@ -422,10 +424,10 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
||||
{renderAffectedResearches(weapon.affectedResearches, props.mod.id, props.race.id)}
|
||||
</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>
|
||||
)
|
||||
} else return <div>loading...</div>;
|
||||
} else return <div style={{color: 'rgba(255, 255, 255, 0.5)'}}>loading...</div>;
|
||||
|
||||
|
||||
|
||||
|
||||
@ -4,6 +4,14 @@ import {IShortWeapon, IWeapon} from "../types/IUnit";
|
||||
import {IMod} from "../types/Imod";
|
||||
import {Irace} from "../types/Irace";
|
||||
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 {
|
||||
hardpoint: number,
|
||||
@ -33,7 +41,7 @@ const WeaponSlot= (props: WeaponSlotProps) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>{header}</h3>
|
||||
<SectionTitle>{header}</SectionTitle>
|
||||
{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>
|
||||
|
||||
@ -9,7 +9,8 @@ import {
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableRow
|
||||
TableRow,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import {IBuilding, IBuildingAddon} from "../../types/IBuilding";
|
||||
import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
|
||||
@ -17,6 +18,30 @@ import {ExpandMore} from "@mui/icons-material";
|
||||
import {IMod} from "../../types/Imod";
|
||||
import {getIcon, ModifiersProvidesTable,} from "../ModifiersProvideTable";
|
||||
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 {
|
||||
addon: IBuildingAddon,
|
||||
@ -31,27 +56,28 @@ function BuildingAddon(props: IBuildingAddonProps){
|
||||
|
||||
|
||||
|
||||
return <div className='addon-research-accordion' id={"addon-" + addon.id} ><Accordion>
|
||||
return <div className='addon-research-accordion' id={"addon-" + addon.id} ><StyledAccordion>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMore/>}
|
||||
expandIcon={<ExpandMore sx={{color: '#ffffff'}}/>}
|
||||
aria-controls="panel1-content"
|
||||
sx={{color: '#ffffff'}}
|
||||
>
|
||||
<span style={{fontSize: 20}}>
|
||||
<span style={{fontSize: 20, color: '#ffffff'}}>
|
||||
<img className="sergeantIcon" src={getIcon(addon.icon)}/>
|
||||
{addon.name}
|
||||
</span>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<AccordionDetails sx={{color: 'rgba(255,255,255,0.85)'}}>
|
||||
<Grid2 container spacing={2}>
|
||||
<Grid2 size={{xs: 12, md: 4}}>
|
||||
<TableContainer component={Paper}>
|
||||
<TableContainer component={StyledPaper} elevation={0}>
|
||||
<Table size="small" aria-label="a dense table">
|
||||
<TableBody id="unit-stats-table">
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Cost</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableCell component="th" scope="row" >Cost</TableCell>
|
||||
<TableCell>
|
||||
{addon.addonCostRequisition > 0 &&
|
||||
<span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_requisition.gif"/>
|
||||
@ -83,9 +109,9 @@ function BuildingAddon(props: IBuildingAddonProps){
|
||||
</TableContainer><br/>
|
||||
</Grid2>
|
||||
<Grid2 size={{xs: 12, md: 8}}>
|
||||
<div style={{whiteSpace: "pre-wrap"}}>
|
||||
<DescriptionBox>
|
||||
{addon.description}
|
||||
</div>
|
||||
</DescriptionBox>
|
||||
</Grid2>
|
||||
<ModifiersProvidesTable modifiers={addon.addonModifiers} affectedData={addon.affectedData} modId={props.mod.id} race={building.race}/>
|
||||
{addon.addonRequirement !== null &&
|
||||
@ -93,9 +119,9 @@ function BuildingAddon(props: IBuildingAddonProps){
|
||||
<Required requirement={addon.addonRequirement} modId={building.modId} raceId={building.race.id}/>
|
||||
</Grid2>}
|
||||
</Grid2>
|
||||
<b className="hotkey">Hotkey: {addon.hotkey}</b>
|
||||
<b className="hotkey" >Hotkey: {addon.hotkey}</b>
|
||||
</AccordionDetails>
|
||||
</Accordion></div>
|
||||
</StyledAccordion></div>
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -20,6 +20,24 @@ import React from "react";
|
||||
import {IBuilding} from "../../types/IBuilding";
|
||||
import {IResearchShort} from "../../types/IResearchShort";
|
||||
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 {
|
||||
@ -33,20 +51,21 @@ function Research(props: IResearchProps){
|
||||
const research = props.research
|
||||
const building = props.building
|
||||
|
||||
return <div className='addon-research-accordion' id={"research-" + research.id}><Accordion TransitionProps={{ unmountOnExit: true, timeout: 100 }}>
|
||||
return <div className='addon-research-accordion' id={"research-" + research.id}><StyledAccordion TransitionProps={{ unmountOnExit: true, timeout: 100 }}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMore/>}
|
||||
expandIcon={<ExpandMore sx={{color: '#ffffff'}}/>}
|
||||
aria-controls="panel1-content"
|
||||
sx={{color: '#ffffff'}}
|
||||
>
|
||||
<span style={{fontSize: 20}}>
|
||||
<span style={{fontSize: 20, color: '#ffffff'}}>
|
||||
<img className="sergeantIcon" src={getIcon(research.icon)}/>
|
||||
{research.name}
|
||||
</span>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<AccordionDetails sx={{color: 'rgba(255,255,255,0.85)'}}>
|
||||
<ResearchFull id={research.id} building={building}/>
|
||||
</AccordionDetails>
|
||||
</Accordion></div>
|
||||
</StyledAccordion></div>
|
||||
|
||||
}
|
||||
|
||||
@ -55,12 +74,12 @@ export function renderAffectedResearches(researches: IResearchShort[], modId: nu
|
||||
function researchLink(rs: IResearchShort) {
|
||||
return <span><img style={{verticalAlign: "top", height: 25}}
|
||||
src={getIcon(rs.icon)}/>
|
||||
<a href={`/mod/${modId}/race/${raceId}/building/${rs.buildingId}#research-${rs.id}`}>
|
||||
<StyledLink href={`/mod/${modId}/race/${raceId}/building/${rs.buildingId}#research-${rs.id}`}>
|
||||
{rs.name}
|
||||
</a></span>
|
||||
</StyledLink></span>
|
||||
}
|
||||
|
||||
return <div> {researches.map(rs =>
|
||||
return <div style={{color: 'rgba(255, 255, 255, 0.85)'}}> {researches.map(rs =>
|
||||
rs.buildingId == null ? <span key={rs.id}></span> :
|
||||
<span key={rs.id}>Research affect: {researchLink(rs)}<br/></span>
|
||||
)}</div>
|
||||
|
||||
@ -10,7 +10,8 @@ import {
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableRow
|
||||
TableRow,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import {ExpandMore} from "@mui/icons-material";
|
||||
import {getIcon, ModifiersProvidesTable} from "../ModifiersProvideTable";
|
||||
@ -21,6 +22,22 @@ import {IBuilding} from "../../types/IBuilding";
|
||||
import {IResearchShort} from "../../types/IResearchShort";
|
||||
import {ResearchUrl, WeaponUrl} from "../../core/api";
|
||||
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 {
|
||||
@ -54,14 +71,14 @@ function ResearchFull(props: { id: number, building: IBuilding }){
|
||||
|
||||
<Grid2 container spacing={2}>
|
||||
<Grid2 size={{xs: 12, md: 4}}>
|
||||
<TableContainer component={Paper}>
|
||||
<TableContainer component={StyledPaper} elevation={0}>
|
||||
<Table size="small" aria-label="a dense table">
|
||||
<TableBody id="unit-stats-table">
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Cost</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableCell component="th" scope="row" >Cost</TableCell>
|
||||
<TableCell>
|
||||
{research.costRequisition > 0 &&
|
||||
<span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_requisition.gif"/>
|
||||
@ -93,9 +110,9 @@ function ResearchFull(props: { id: number, building: IBuilding }){
|
||||
</TableContainer><br/>
|
||||
</Grid2>
|
||||
<Grid2 size={{xs: 12, md: 8}}>
|
||||
<div style={{whiteSpace: "pre-wrap"}}>
|
||||
<DescriptionBox>
|
||||
{research.description}
|
||||
</div>
|
||||
</DescriptionBox>
|
||||
|
||||
</Grid2>
|
||||
<Grid2 size={{xs: 12, md: 12}}>
|
||||
@ -107,9 +124,9 @@ function ResearchFull(props: { id: number, building: IBuilding }){
|
||||
<Required requirement={research.requirements} modId={building.modId} raceId={building.race.id}/>
|
||||
</Grid2>}
|
||||
</Grid2>
|
||||
<b className="hotkey">Hotkey: {research.hotkey}</b>
|
||||
<b className="hotkey" >Hotkey: {research.hotkey}</b>
|
||||
</div>
|
||||
} else return <div>loading...</div>
|
||||
} else return <div style={{color: 'rgba(255, 255, 255, 0.5)'}}>loading...</div>
|
||||
}
|
||||
|
||||
export default ResearchFull;
|
||||
|
||||
14
src/commons/BackButton.tsx
Normal file
14
src/commons/BackButton.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
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)',
|
||||
},
|
||||
}));
|
||||
12
src/commons/DescriptionBox.tsx
Normal file
12
src/commons/DescriptionBox.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
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',
|
||||
}));
|
||||
12
src/commons/StyledLink.tsx
Normal file
12
src/commons/StyledLink.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
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',
|
||||
},
|
||||
}));
|
||||
@ -1,13 +1,43 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
background-color: #0a0a0a;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
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;
|
||||
}
|
||||
|
||||
@ -5,6 +5,9 @@ import {IShortWeapon} from "../types/IUnit";
|
||||
import '../css/Building.css'
|
||||
import {
|
||||
Button,
|
||||
Box,
|
||||
Container,
|
||||
Divider,
|
||||
Grid2,
|
||||
Link,
|
||||
ListItem,
|
||||
@ -12,11 +15,10 @@ import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableRow,
|
||||
Tooltip
|
||||
Tooltip,
|
||||
Typography, LinearProgress
|
||||
} from "@mui/material";
|
||||
import {ArrowBack} from "@mui/icons-material";
|
||||
import ArmorType from "../classes/ArmorType";
|
||||
import AvTimerOutlinedIcon from '@mui/icons-material/AvTimer';
|
||||
import WeaponSlot from "../classes/WeaponSlot";
|
||||
@ -31,6 +33,31 @@ import Required from "../classes/Required";
|
||||
import {ModifiersProvidesTable} from "../classes/ModifiersProvideTable";
|
||||
import Ability from "../classes/Ability";
|
||||
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 {
|
||||
building: IBuilding,
|
||||
@ -39,18 +66,30 @@ interface UintPageState {
|
||||
|
||||
function Unit (unit: IUnitShort, modId: number, raceId: String) {
|
||||
|
||||
return (<Grid2 key={unit.id} size={{xs: 12, md: 3}}><Link href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id}><ListItem>
|
||||
{unit.icon && <img className="unitIcon" src={IconUrl + unit.icon.replaceAll('\\', '/')}/> }
|
||||
{unit.name}
|
||||
{unit.canDetect && <span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/DETECT_YES.webp"/></span>}
|
||||
</ListItem></Link></Grid2>)
|
||||
return (<Grid2 key={unit.id} size={{xs: 12, md: 3}}>
|
||||
<Link href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id} sx={{color: 'rgba(255,255,255,0.85)', textDecoration: 'none', '&:hover': {color: '#e94560'}}}>
|
||||
<ListItem sx={{
|
||||
color: 'rgba(255,255,255,0.85)',
|
||||
padding: '8px 12px',
|
||||
background: 'rgba(255,255,255,0.03)',
|
||||
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> <img style={{verticalAlign: "top"}}
|
||||
src="/images/DETECT_YES.webp"/></span>}
|
||||
</ListItem>
|
||||
</Link>
|
||||
</Grid2>)
|
||||
}
|
||||
|
||||
|
||||
function Building(building: IBuilding, mod: IMod) {
|
||||
|
||||
document.title = building.name
|
||||
document.title = building.name + " — " + mod.name
|
||||
|
||||
|
||||
let mapBuildingWeapons: Map<number, Map<number, IShortWeapon>> = new Map();
|
||||
@ -72,21 +111,35 @@ function Building(building: IBuilding, mod: IMod) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{mod.name} ({mod.version})</h1>
|
||||
<h2>{building.icon &&
|
||||
<img className="unitIcon" src={IconUrl + building.icon.replaceAll('\\', '/')}/>} {buildingName} </h2>
|
||||
<Box>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="h4" component="h2" sx={{
|
||||
fontWeight: 800,
|
||||
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={2}>
|
||||
<Grid2 container spacing={3}>
|
||||
<Grid2 size={{xs: 12, md: 4}}>
|
||||
<TableContainer component={Paper}>
|
||||
<StatsPaper elevation={0}>
|
||||
<Table size="small" aria-label="a dense table">
|
||||
<TableBody id="unit-stats-table">
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Cost</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" >Cost</TableCell>
|
||||
<TableCell>
|
||||
{building.buildCostRequisition > 0 &&
|
||||
<span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_requisition.gif"/>
|
||||
@ -114,7 +167,7 @@ function Building(building: IBuilding, mod: IMod) {
|
||||
</TableRow>
|
||||
{(building.requisitionIncome !== undefined && building.requisitionIncome > 0 || building.powerIncome !== undefined && building.powerIncome !== null || building.faithIncome !== undefined && building.faithIncome !== null) &&
|
||||
<TableRow>
|
||||
<TableCell>Resource income</TableCell>
|
||||
<TableCell >Resource income</TableCell>
|
||||
<TableCell>
|
||||
{building.requisitionIncome !== undefined && building.requisitionIncome != null &&
|
||||
<span> <img style={{verticalAlign: "top"}}
|
||||
@ -131,62 +184,52 @@ function Building(building: IBuilding, mod: IMod) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Armor type</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" >Armor type</TableCell>
|
||||
<TableCell>
|
||||
{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> }
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Health</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" >Health</TableCell>
|
||||
<TableCell>
|
||||
<span><img style={{verticalAlign: "top"}}
|
||||
src="/images/Health_icon.webp"/>
|
||||
{building.health} {building.healthRegeneration > 0 &&
|
||||
<span>+{building.healthRegeneration}/s</span>} </span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Sight</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" >Sight</TableCell>
|
||||
<TableCell>
|
||||
<Vision sight={building.sightRadius} detect={building.detectRadius}/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Repair max</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{building.repairMax}
|
||||
</TableCell>
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<TableCell component="th" scope="row" >Repair max</TableCell>
|
||||
<TableCell>{building.repairMax}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</StatsPaper>
|
||||
</Grid2>
|
||||
<Grid2 size={8}>
|
||||
<div style={{whiteSpace: "pre-wrap"}}>
|
||||
<Grid2 size={{xs: 12, md: 8}}>
|
||||
<DescriptionBox>
|
||||
{building.description}
|
||||
</div>
|
||||
</DescriptionBox>
|
||||
</Grid2>
|
||||
{building.requirements !== null &&
|
||||
<Grid2 size={{xs: 12, md: 12}}>
|
||||
<Required requirement={building.requirements} modId={building.modId} raceId={building.race.id}/>
|
||||
</Grid2>}
|
||||
{building.modifiers.length > 0 &&
|
||||
<Grid2 size={{xs: 12, md: 12}} >
|
||||
<h3>Affected on</h3>
|
||||
<Grid2 size={{xs: 12, md: 12}}>
|
||||
<SectionTitle>Affected on</SectionTitle>
|
||||
<ModifiersProvidesTable modifiers={building.modifiers} race={building.race} modId={building.modId} affectedData={building.affectedData} />
|
||||
</Grid2>}
|
||||
{building.units.length > 0 && <Grid2 size={12}>
|
||||
<h3>Unit production</h3>
|
||||
<SectionTitle>Unit production</SectionTitle>
|
||||
<Grid2 container spacing={2}>
|
||||
{building.units.map(unit =>
|
||||
Unit(unit, mod.id, building.race.id)
|
||||
@ -194,7 +237,7 @@ function Building(building: IBuilding, mod: IMod) {
|
||||
</Grid2>
|
||||
</Grid2>}
|
||||
{building.abilities.length > 0 && <Grid2 size={12}>
|
||||
<h3>Abilities</h3>
|
||||
<SectionTitle>Abilities</SectionTitle>
|
||||
{building.abilities.map(a =>
|
||||
<Ability mod={mod} ability={a} race={building.race}/>
|
||||
)}
|
||||
@ -207,13 +250,13 @@ function Building(building: IBuilding, mod: IMod) {
|
||||
</Grid2>
|
||||
}
|
||||
{building.addons.length > 0 && <Grid2 size={12}>
|
||||
<h3>Addons</h3>
|
||||
<SectionTitle>Addons</SectionTitle>
|
||||
{building.addons.map(b =>
|
||||
<BuildingAddon mod={mod} addon={b} building={building}/>
|
||||
)}
|
||||
</Grid2> }
|
||||
{building.researches.length > 0 && <Grid2 size={12}>
|
||||
<h3>Researches</h3>
|
||||
<SectionTitle>Researches</SectionTitle>
|
||||
{building.researches.map(r =>
|
||||
<Research key={r.id} research={r} building={building} mod={mod}/>
|
||||
)}
|
||||
@ -227,10 +270,12 @@ function Building(building: IBuilding, mod: IMod) {
|
||||
{renderAffectedResearches(building.affectedResearches, mod.id, building.race.id)}
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
<b className="hotkey">Hotkey: {building.hotkey}</b>
|
||||
<hr/>
|
||||
<Box sx={{ mt: 3, mb: 2, color: '#e94560', fontWeight: 600, fontSize: '0.85rem' }}>
|
||||
Hotkey: {building.hotkey}
|
||||
</Box>
|
||||
<Divider sx={{ my: 3, borderColor: 'rgba(255, 255, 255, 0.1)' }} />
|
||||
<UnitsTable modId={mod.id}/>
|
||||
</div>)
|
||||
</Box>)
|
||||
}
|
||||
|
||||
class BuildingPage extends React.Component<any, UintPageState> {
|
||||
@ -256,12 +301,24 @@ class BuildingPage extends React.Component<any, UintPageState> {
|
||||
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
|
||||
|
||||
return <div><a href={backRef}><Button id="back-button" variant="contained"
|
||||
startIcon={<ArrowBack/>}> Back</Button></a>
|
||||
{Building(this.state.building, this.state.mod)}
|
||||
</div>;
|
||||
return (
|
||||
<Container maxWidth="lg" sx={{ py: 4 }}>
|
||||
<BackButton
|
||||
variant="outlined"
|
||||
startIcon={<ArrowBack/>}
|
||||
href={backRef}
|
||||
>
|
||||
Back to race
|
||||
</BackButton>
|
||||
{Building(this.state.building, this.state.mod)}
|
||||
</Container>
|
||||
);
|
||||
} else {
|
||||
return "loading...";
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 8 }}>
|
||||
<LinearProgress sx={{ width: '200px' }} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,48 @@
|
||||
import {AvailableMods, AvailableRacesPart, AvailableUnits, IconUrl} from "../core/api";
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import {NavLink, useLocation, useParams} from "react-router-dom";
|
||||
import React, { useState } from "react";
|
||||
import {withRouter} from "../core/withrouter";
|
||||
import {IMod} from "../types/Imod";
|
||||
import {Irace} from "../types/Irace";
|
||||
import {Button, ListItem, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow} from "@mui/material";
|
||||
import {ArrowBack} from "@mui/icons-material";
|
||||
import {IRaceUnits, IUnitShort} from "../types/IUnitShort";
|
||||
import {
|
||||
ArrowBack,
|
||||
} from "@mui/icons-material";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Container,
|
||||
Paper,
|
||||
Typography,
|
||||
Divider,
|
||||
LinearProgress
|
||||
} from "@mui/material";
|
||||
import {styled} from '@mui/material/styles';
|
||||
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 {
|
||||
mod: IMod,
|
||||
mod: IMod | null,
|
||||
loading: boolean,
|
||||
}
|
||||
|
||||
|
||||
@ -20,31 +51,80 @@ class ModPage extends React.Component<any, ModPageState> {
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.state = {
|
||||
mod: null,
|
||||
loading: true
|
||||
};
|
||||
|
||||
fetch(AvailableMods + "/" + this.props.match.params.modId)
|
||||
.then(res => res.json())
|
||||
.then((res: IMod) => {
|
||||
this.setState({
|
||||
mod : res
|
||||
mod: res,
|
||||
loading: false
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({ loading: false });
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
if(this.state != null && this.state.mod != null ){
|
||||
if (this.state.loading) {
|
||||
return (
|
||||
<Container maxWidth="lg" sx={{ py: 4 }}>
|
||||
<LinearProgress sx={{ width: '200px', mx: 'auto', display: 'block' }} />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
document.title = this.state.mod.name
|
||||
if (this.state != null && this.state.mod != null) {
|
||||
document.title = this.state.mod.name + " — ModWiki";
|
||||
|
||||
return <div>
|
||||
<a href="/"><Button id="back-button" variant="contained"
|
||||
startIcon={<ArrowBack/>}> Back</Button></a>
|
||||
<h1>{this.state.mod.name} ({this.state.mod.version})</h1>
|
||||
<UnitsTable modId={this.state.mod.id}/>
|
||||
</div>;
|
||||
return (
|
||||
<Container maxWidth="lg" sx={{ py: 4 }}>
|
||||
<BackButton
|
||||
variant="outlined"
|
||||
startIcon={<ArrowBack/>}
|
||||
href="/"
|
||||
>
|
||||
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 {
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,49 +1,272 @@
|
||||
import {AvailableMods} from "../core/api";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {NavLink} from "react-router-dom";
|
||||
import {IMod} from "../types/Imod";
|
||||
import React from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
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';
|
||||
|
||||
function Mods (mods: IMod[]) {
|
||||
// Styled components
|
||||
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',
|
||||
},
|
||||
}));
|
||||
|
||||
let mapWithModVersions: Map<String, IMod[]> = new Map();
|
||||
const ModCard = styled(Card)(({ theme }) => ({
|
||||
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)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
mods.forEach( mod => {
|
||||
const versionList = mapWithModVersions.get(mod.name)
|
||||
if(versionList == null){
|
||||
mapWithModVersions.set(mod.name, new Array(mod))
|
||||
} else {
|
||||
versionList.push(mod)
|
||||
}
|
||||
})
|
||||
const VersionLink = styled(NavLink)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: theme.spacing(1.5, 2),
|
||||
marginBottom: theme.spacing(1),
|
||||
background: 'rgba(255, 255, 255, 0.05)',
|
||||
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);
|
||||
|
||||
function Mod (modName: String) {
|
||||
let sameMods = mapWithModVersions.get(modName) ?? []
|
||||
return (
|
||||
<ModCard sx={{ animationDelay: `${index * 100}ms` }}>
|
||||
<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>
|
||||
|
||||
let lastBeta = sameMods.filter((m) => m.isBeta).reverse()[0]
|
||||
<Box sx={{ display: 'flex', gap: 1, mb: 2 }}>
|
||||
<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>
|
||||
|
||||
return(
|
||||
<div>
|
||||
<h1>{modName}</h1>
|
||||
{modName == "Unification new races" && <ul><a href="https://dawn-of-war-unification-mod.fandom.com/">Official unification wiki</a></ul>}
|
||||
{sameMods.filter((m) => !m.isBeta).map(mod =>
|
||||
<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>}
|
||||
{lastBeta != null &&
|
||||
<ul><NavLink state={lastBeta.id} to={"/mod/" + lastBeta.id}>{lastBeta.version}</NavLink></ul>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
<Divider sx={{ my: 2, borderColor: 'rgba(255, 255, 255, 0.1)' }} />
|
||||
|
||||
<Typography variant="subtitle2" sx={{
|
||||
color: 'rgba(255, 255, 255, 0.9)',
|
||||
fontWeight: 600,
|
||||
mb: 1.5,
|
||||
}}>
|
||||
Main versions:
|
||||
</Typography>
|
||||
|
||||
{sameMods.filter(m => !m.isBeta).map(mod => (
|
||||
<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(
|
||||
[...mapWithModVersions.keys()].map(m => Mod(m))
|
||||
)
|
||||
return (
|
||||
<Box>
|
||||
|
||||
|
||||
<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 {
|
||||
mods: IMod[]
|
||||
mods: IMod[];
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
class ModsPage extends React.Component<any, ModsPageState> {
|
||||
@ -51,22 +274,36 @@ class ModsPage extends React.Component<any, ModsPageState> {
|
||||
constructor({props}: { props: any }) {
|
||||
super(props);
|
||||
this.state = {
|
||||
mods: []
|
||||
}
|
||||
mods: [],
|
||||
loading: true
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const response = await fetch(AvailableMods);
|
||||
const data: IMod[] = await response.json();
|
||||
console.log(data);
|
||||
this.setState({
|
||||
mods : data
|
||||
});
|
||||
try {
|
||||
const response = await fetch(AvailableMods);
|
||||
const data: IMod[] = await response.json();
|
||||
this.setState({
|
||||
mods: data,
|
||||
loading: false
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error loading mods:', error);
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>{Mods(this.state.mods)}</div>;
|
||||
if (this.state.loading) {
|
||||
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;
|
||||
|
||||
@ -8,29 +8,43 @@ import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Box,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Grid2, Link,
|
||||
Container,
|
||||
Divider,
|
||||
Grid2, LinearProgress,
|
||||
Link,
|
||||
List,
|
||||
ListItem,
|
||||
ListSubheader,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
ToggleButton,
|
||||
ToggleButtonClassKey,
|
||||
ToggleButtonGroup,
|
||||
Typography
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import {ArrowBack, ArrowDropDown, ExpandMore} from "@mui/icons-material";
|
||||
import ArmorType from "../classes/ArmorType";
|
||||
import Weapon from "../classes/Weapon";
|
||||
import {ArrowBack, ExpandMore} from "@mui/icons-material";
|
||||
import {IRaceUnits, IUnitShort} from "../types/IUnitShort";
|
||||
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 {
|
||||
mod: IMod,
|
||||
@ -41,13 +55,27 @@ interface RacePageState {
|
||||
|
||||
function Unit(unit: IUnitShort, modId: number, raceId: String) {
|
||||
|
||||
return (<Link href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id}><ListItem>
|
||||
{unit.icon && <img className="unitIcon" src={IconUrl + unit.icon.replaceAll('\\', '/')}/>}
|
||||
{unit.name}
|
||||
{unit.canDetect && <span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/DETECT_YES.webp"/></span>}
|
||||
|
||||
</ListItem></Link>)
|
||||
return (<Link href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id} sx={{
|
||||
color: 'rgba(255,255,255,0.85)',
|
||||
textDecoration: 'none',
|
||||
display: 'block',
|
||||
'&:hover': { color: '#e94560' }
|
||||
}}>
|
||||
<ListItem sx={{
|
||||
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> <img style={{verticalAlign: "top"}}
|
||||
src="/images/DETECT_YES.webp"/></span>}
|
||||
</ListItem>
|
||||
</Link>)
|
||||
}
|
||||
|
||||
function UnitSmall(unit: IUnitShort, modId: number, raceId: String) {
|
||||
@ -59,23 +87,33 @@ function UnitSmall(unit: IUnitShort, modId: number, raceId: String) {
|
||||
unitName = unit.name;
|
||||
}
|
||||
|
||||
return (<Link href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id}>
|
||||
return (<StyledLink href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id} >
|
||||
{unit.icon && <img className="unitIconSmall" src={IconUrl + unit.icon.replaceAll('\\', '/')}/>}
|
||||
<span style={{fontSize: 12}}>{unitName}</span>
|
||||
<span style={{fontSize: 14}}>{unitName}</span>
|
||||
{unit.canDetect && <span> <img
|
||||
src="/images/DETECT_YES.webp"/></span>}<br/></Link>)
|
||||
src="/images/DETECT_YES.webp"/></span>}<br/></StyledLink>)
|
||||
}
|
||||
|
||||
function Building(building: IBuildingShort, modId: number, raceId: String) {
|
||||
|
||||
return (<Grid2 size={{xs: 12, md: 3}}><Link href={"/mod/" + modId + "/race/" + raceId + "/building/" + building.id}><ListItem>
|
||||
{building.icon && <img className="unitIcon" src={IconUrl + building.icon.replaceAll('\\', '/')}/>}
|
||||
{building.name}
|
||||
{building.canDetect && <span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/DETECT_YES.webp"/></span>}
|
||||
|
||||
</ListItem></Link>
|
||||
{building.units.map(unit => UnitSmall(unit, modId, raceId))}
|
||||
return (<Grid2 size={{xs: 12, md: 3}}>
|
||||
<Link href={"/mod/" + modId + "/race/" + raceId + "/building/" + building.id} sx={{
|
||||
textDecoration: 'none',
|
||||
display: 'block',
|
||||
'&:hover': { color: '#e94560' }
|
||||
}}>
|
||||
<BuildingCard elevation={0}>
|
||||
<ListItem sx={{
|
||||
padding: '8px 12px',
|
||||
}}>
|
||||
{building.icon && <img className="unitIcon" src={IconUrl + building.icon.replaceAll('\\', '/')}/>}
|
||||
<StyledLink>{building.name}</StyledLink>
|
||||
{building.canDetect && <span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/DETECT_YES.webp"/></span>}
|
||||
</ListItem>
|
||||
{building.units.map(unit => UnitSmall(unit, modId, raceId))}
|
||||
</BuildingCard>
|
||||
</Link>
|
||||
</Grid2>)
|
||||
}
|
||||
|
||||
@ -127,59 +165,61 @@ class Units extends React.Component<UnitsProps, UnitsState> {
|
||||
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 ?
|
||||
<div>
|
||||
<Box>
|
||||
<Grid2 container spacing={2}>
|
||||
{this.state.buildings.buildings.map(building => Building(building, this.props.modId, this.props.raceId))}
|
||||
</Grid2>
|
||||
{this.state.buildings.buildingsAdvanced.length > 0 && <div><hr/><Grid2 container spacing={2}>
|
||||
{this.state.buildings.buildingsAdvanced.map(building => Building(building, this.props.modId, this.props.raceId))}
|
||||
</Grid2><br/></div>}
|
||||
<Grid2>
|
||||
<Grid2 size={{xs: 12, md: 12}}>
|
||||
<Accordion>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMore/>}
|
||||
aria-controls="units-accordion"
|
||||
id="units-accordion"
|
||||
>
|
||||
<h3>All units</h3>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Grid2 container spacing={2}>
|
||||
<Grid2 size={{xs: 12, md: 4}}>
|
||||
<h3>Infantry</h3>
|
||||
{this.state.units.infantry.map(unit => Unit(unit, this.props.modId, this.props.raceId))}
|
||||
</Grid2>
|
||||
<Grid2 size={{xs: 12, md: 4}}>
|
||||
<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>
|
||||
{this.state.buildings.buildingsAdvanced.length > 0 && <Box sx={{ mt: 3 }}>
|
||||
<Divider sx={{ my: 2, borderColor: 'rgba(255, 255, 255, 0.1)' }} />
|
||||
<SectionTitle>Advanced buildings</SectionTitle>
|
||||
<Grid2 container spacing={2}>
|
||||
{this.state.buildings.buildingsAdvanced.map(building => Building(building, this.props.modId, this.props.raceId))}
|
||||
</Grid2><br/></Box>}
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Accordion sx={accordionSx}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMore sx={{color: '#ffffff'}}/>}
|
||||
aria-controls="units-accordion"
|
||||
id="units-accordion"
|
||||
sx={{color: '#ffffff'}}
|
||||
>
|
||||
<SectionTitle sx={{ mb: 0 }}>All units</SectionTitle>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Grid2 container spacing={2}>
|
||||
<Grid2 size={{xs: 12, md: 4}}>
|
||||
<Typography variant="h6" sx={{ color: '#ffffff', fontWeight: 600, mb: 2 }}>Infantry</Typography>
|
||||
{this.state.units.infantry.map(unit => Unit(unit, this.props.modId, this.props.raceId))}
|
||||
</Grid2>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</Grid2><br/>
|
||||
</Grid2>
|
||||
</div> : "Loading"
|
||||
<Grid2 size={{xs: 12, md: 4}}>
|
||||
<Typography variant="h6" sx={{ color: '#ffffff', fontWeight: 600, mb: 2 }}>Tech</Typography>
|
||||
<List>
|
||||
{this.state.units.tech.map(unit => Unit(unit, this.props.modId, this.props.raceId))}
|
||||
</List>
|
||||
</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 {
|
||||
return "Loading...";
|
||||
return <Box sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>Loading...</Box>;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -188,7 +228,6 @@ class RacePageFast extends React.Component<any, RacePageState> {
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
console.log(this.props.match.params.id);
|
||||
}
|
||||
|
||||
|
||||
@ -214,13 +253,43 @@ class RacePageFast extends React.Component<any, RacePageState> {
|
||||
if (this.state != null && this.state.mod != null && this.state.race != null) {
|
||||
const backRef = "/mod/" + this.state.mod.id
|
||||
|
||||
return <div><Link href={backRef}><Button id="back-button" variant="contained"
|
||||
startIcon={<ArrowBack/>}> Back</Button></Link>
|
||||
<h1>{this.state.mod.name} ({this.state.mod.version}) - {this.state.race.name}</h1>
|
||||
<Units raceId={this.state.race.id} modId={this.state.mod.id}/>
|
||||
</div>;
|
||||
return (
|
||||
<Container maxWidth="lg" sx={{ py: 4 }}>
|
||||
<BackButton
|
||||
variant="outlined"
|
||||
startIcon={<ArrowBack/>}
|
||||
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 {
|
||||
return "loading...";
|
||||
return (
|
||||
<Container maxWidth="lg" sx={{ py: 4 }}>
|
||||
<LinearProgress sx={{ width: '200px', mx: 'auto', display: 'block' }} />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,15 +7,18 @@ import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Box,
|
||||
Button,
|
||||
Grid2,
|
||||
Container,
|
||||
Divider,
|
||||
Grid2, LinearProgress,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableRow,
|
||||
Tooltip
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import {ArrowBack, ExpandMore} from "@mui/icons-material";
|
||||
import ArmorType from "../classes/ArmorType";
|
||||
@ -30,6 +33,30 @@ import {renderAffectedResearches} from "../classes/building/Research";
|
||||
import {ModifiersProvidesTable} from "../classes/ModifiersProvideTable";
|
||||
import Ability from "../classes/Ability";
|
||||
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 {
|
||||
unit: IUnit,
|
||||
@ -39,7 +66,7 @@ interface UintPageState {
|
||||
|
||||
function Unit(unit: IUnit, mod: IMod) {
|
||||
|
||||
document.title = unit.name
|
||||
document.title = unit.name + " — " + mod.name
|
||||
|
||||
const morale = (unit.moraleMax !== null) ? <span>
|
||||
<img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/> {unit.moraleMax}
|
||||
@ -71,7 +98,7 @@ function Unit(unit: IUnit, mod: IMod) {
|
||||
const SergeantShort = (props: sergeantProps) => {
|
||||
|
||||
return (
|
||||
<span style={{fontSize: 20}}>
|
||||
<span style={{fontSize: 20, color: '#ffffff'}}>
|
||||
{props.icon && <img className="sergeantIcon" src={IconUrl + props.icon.replaceAll('\\', '/')}/> }
|
||||
{props.name}
|
||||
{props.canDetect && <span> <img style={{verticalAlign: "top"}}
|
||||
@ -82,21 +109,35 @@ function Unit(unit: IUnit, mod: IMod) {
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{mod.name} ({mod.version})</h1>
|
||||
<h2>{unit.icon &&
|
||||
<img className="unitIcon" src={IconUrl + unit.icon.replaceAll('\\', '/')}/>} {unit.name} </h2>
|
||||
<Box>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="h4" component="h2" sx={{
|
||||
fontWeight: 800,
|
||||
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={2}>
|
||||
<Grid2 container spacing={3}>
|
||||
<Grid2 size={{xs: 12, md: 4}}>
|
||||
<TableContainer component={Paper}>
|
||||
<StatsPaper >
|
||||
<Table size="small" aria-label="a dense table">
|
||||
<TableBody id="unit-stats-table">
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Cost</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableBody id="unit-stats-table" >
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" >Cost</TableCell>
|
||||
<TableCell>
|
||||
{unit.buildCostRequisition > 0 &&
|
||||
<span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_requisition.gif"/>
|
||||
@ -131,7 +172,7 @@ function Unit(unit: IUnit, mod: IMod) {
|
||||
</TableRow>
|
||||
{(unit?.reinforceTime !== 0 && unit.reinforceTime !== null && unit.squadMaxSize > 1) &&
|
||||
<TableRow>
|
||||
<TableCell>Reinforce cost</TableCell>
|
||||
<TableCell >Reinforce cost</TableCell>
|
||||
<TableCell>
|
||||
{unit.reinforceCostRequisition && unit.reinforceCostRequisition > 0 ?
|
||||
<span> <img style={{verticalAlign: "top"}}
|
||||
@ -162,7 +203,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) &&
|
||||
<TableRow>
|
||||
<TableCell>Resource income</TableCell>
|
||||
<TableCell >Resource income</TableCell>
|
||||
<TableCell>
|
||||
{unit.requisitionIncome !== undefined && unit.requisitionIncome > 0 &&
|
||||
<span> <img style={{verticalAlign: "top"}}
|
||||
@ -181,25 +222,19 @@ function Unit(unit: IUnit, mod: IMod) {
|
||||
}
|
||||
{unit.squadMaxSize > 1 &&
|
||||
<TableRow>
|
||||
<TableCell>Squad size</TableCell>
|
||||
<TableCell>
|
||||
{unit.squadStartSize} / {unit.squadMaxSize}
|
||||
</TableCell>
|
||||
<TableCell >Squad size</TableCell>
|
||||
<TableCell>{unit.squadStartSize} / {unit.squadMaxSize}</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Armor type</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableRow>
|
||||
<TableCell >Armor type</TableCell>
|
||||
<TableCell>
|
||||
{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>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Health</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<TableRow>
|
||||
<TableCell >Health</TableCell>
|
||||
<TableCell>
|
||||
<img style={{verticalAlign: "top"}}
|
||||
src="/images/Health_icon.webp"/>
|
||||
{unit.health} {unit.healthRegeneration > 0 &&
|
||||
@ -207,78 +242,54 @@ 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> }
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Move speed</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{unit.moveSpeed}
|
||||
</TableCell>
|
||||
<TableRow>
|
||||
<TableCell >Move speed</TableCell>
|
||||
<TableCell>{unit.moveSpeed}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Morale</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{morale}
|
||||
</TableCell>
|
||||
<TableRow>
|
||||
<TableCell >Morale</TableCell>
|
||||
<TableCell>{morale}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Mass</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{unit.mass}
|
||||
</TableCell>
|
||||
<TableRow>
|
||||
<TableCell >Mass</TableCell>
|
||||
<TableCell>{unit.mass}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row">Vision</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
<Vision sight={unit.sightRadius} detect={unit.detectRadius}/>
|
||||
</TableCell>
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<TableCell >Vision</TableCell>
|
||||
<TableCell><Vision sight={unit.sightRadius} detect={unit.detectRadius}/></TableCell>
|
||||
</TableRow>
|
||||
{unit.repairMax !== undefined && unit.repairMax !== null &&
|
||||
<TableRow>
|
||||
<TableCell>Repair max</TableCell>
|
||||
<TableCell>
|
||||
{unit.repairMax}
|
||||
</TableCell>
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<TableCell >Repair max</TableCell>
|
||||
<TableCell>{unit.repairMax}</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
{unit.repairSpeed !== undefined && unit.repairSpeed !== null && unit.repairCostPercent !== null &&
|
||||
<TableRow>
|
||||
<TableCell>Repair</TableCell>
|
||||
<TableCell>
|
||||
{unit.repairSpeed} hp/s; {unit.repairCostPercent}% cost
|
||||
</TableCell>
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<TableCell >Repair</TableCell>
|
||||
<TableCell>{unit.repairSpeed} hp/s; {unit.repairCostPercent}% cost</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
{unit.mobValue != null &&
|
||||
<TableRow>
|
||||
<TableCell>Mob value</TableCell>
|
||||
<TableCell>
|
||||
<span> <img style={{height: 20, verticalAlign: "top"}} src="/images/Mob_bonus.gif"/> {unit.mobValue} </span>
|
||||
</TableCell>
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<TableCell >Mob value</TableCell>
|
||||
<TableCell><span> <img style={{height: 20, verticalAlign: "top"}} src="/images/Mob_bonus.gif"/> {unit.mobValue} </span></TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
{unit.squadLimit !== undefined && unit.squadLimit !== null &&
|
||||
<TableRow>
|
||||
<TableCell>Limit</TableCell>
|
||||
<TableCell>
|
||||
{unit.squadLimit}
|
||||
</TableCell>
|
||||
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
|
||||
<TableCell >Limit</TableCell>
|
||||
<TableCell>{unit.squadLimit}</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</StatsPaper>
|
||||
</Grid2>
|
||||
<Grid2 size={8}>
|
||||
<div style={{whiteSpace: "pre-wrap"}}>
|
||||
<Grid2 size={{xs: 12, md: 8}}>
|
||||
<DescriptionBox>
|
||||
{unit.description}
|
||||
</div>
|
||||
</DescriptionBox>
|
||||
</Grid2>
|
||||
{unit.requirements !== null &&
|
||||
<Grid2 size={{xs: 12, md: 12}}>
|
||||
@ -286,19 +297,21 @@ function Unit(unit: IUnit, mod: IMod) {
|
||||
</Grid2>}
|
||||
<Grid2 size={12}>
|
||||
{unit.sergeants.map(s =>
|
||||
<Accordion key={s.id}>
|
||||
<Accordion key={s.id} sx={{
|
||||
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
|
||||
expandIcon={<ExpandMore/>}
|
||||
aria-controls="panel1-content"
|
||||
id="panel1-header"
|
||||
>
|
||||
sx={{color: '#ffffff'}}>
|
||||
<SergeantShort name={s.name} icon={s.icon} canDetect={s.detectRadius > 0}/>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<AccordionDetails sx={{color: 'rgba(255, 255, 255, 0.85)'}}>
|
||||
<Sergeant mod={mod} sergeant={s} race={unit.race}/>
|
||||
<hr/>
|
||||
</AccordionDetails>
|
||||
|
||||
</Accordion>)}
|
||||
</Grid2>
|
||||
|
||||
@ -306,17 +319,17 @@ function Unit(unit: IUnit, mod: IMod) {
|
||||
unit.moraleBrakeModifiers.find(m => m.reference.includes("speed_maximum_modifier") && m.value === 1.2) !== undefined &&
|
||||
unit.moraleBrakeModifiers.length === 2)
|
||||
&& <Grid2 size={12}>
|
||||
<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}/>
|
||||
</Grid2>}
|
||||
<SectionTitle><img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/> Morale broken unusual behavior <img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/></SectionTitle>
|
||||
<ModifiersProvidesTable modifiers={unit.moraleBrakeModifiers} modId={unit.modId} race={unit.race} affectedData={unit.affectedData}/>
|
||||
</Grid2>}
|
||||
|
||||
{unit.modifiers.length > 0 && <Grid2 size={12}>
|
||||
<h3>Affected on</h3>
|
||||
<SectionTitle>Affected on</SectionTitle>
|
||||
<ModifiersProvidesTable modifiers={unit.modifiers} modId={unit.modId} race={unit.race} affectedData={unit.affectedData}/>
|
||||
</Grid2>}
|
||||
|
||||
{unit.abilities.length > 0 && <Grid2 size={12}>
|
||||
<h3>Abilities</h3>
|
||||
<SectionTitle>Abilities</SectionTitle>
|
||||
{unit.abilities.map(a =>
|
||||
<Ability mod={mod} ability={a} race={unit.race}/>
|
||||
)}
|
||||
@ -338,10 +351,14 @@ function Unit(unit: IUnit, mod: IMod) {
|
||||
{renderAffectedResearches(unit.affectedResearches, mod.id, unit.race.id)}
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
<b className="hotkey">Hotkey: {unit.hotkey}</b>
|
||||
<hr/>
|
||||
|
||||
<Box sx={{ mt: 3, mb: 2, fontWeight: 600, fontSize: '0.85rem' }}>
|
||||
Hotkey: {unit.hotkey}
|
||||
</Box>
|
||||
<Divider sx={{ my: 3, borderColor: 'rgba(255, 255, 255, 0.1)' }} />
|
||||
<UnitsTable modId={mod.id}/>
|
||||
</div>)
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -371,12 +388,24 @@ class UnitPage extends React.Component<any, UintPageState> {
|
||||
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
|
||||
|
||||
return <div key={this.state.unit.id}><a href={backRef}><Button id="back-button" variant="contained"
|
||||
startIcon={<ArrowBack/>}> Back</Button></a>
|
||||
{Unit(this.state.unit, this.state.mod)}
|
||||
</div>;
|
||||
return (
|
||||
<Container maxWidth="lg" sx={{ py: 4 }}>
|
||||
<BackButton
|
||||
variant="outlined"
|
||||
startIcon={<ArrowBack/>}
|
||||
href={backRef}
|
||||
>
|
||||
Back to race
|
||||
</BackButton>
|
||||
{Unit(this.state.unit, this.state.mod)}
|
||||
</Container>
|
||||
);
|
||||
} else {
|
||||
return <div key = {'loading'}>loading...</div>;
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 8 }}>
|
||||
<LinearProgress sx={{ width: '200px' }} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
export interface IMod {
|
||||
id: number;
|
||||
name: string;
|
||||
order: number;
|
||||
version: string;
|
||||
technicalName: string;
|
||||
isBeta: boolean;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user