Add teleport feature

This commit is contained in:
anibus 2026-08-01 21:43:14 +03:00
parent 57779cacbd
commit 405b562f8f
20 changed files with 351 additions and 32 deletions

BIN
public/images/Jump.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
public/images/NoImg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/images/Teleport.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
public/images/dowde.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View File

@ -29,7 +29,7 @@ function Ability(props: IAbilityProps){
aria-controls="panel1-content" aria-controls="panel1-content"
> >
<span style={{fontSize: 20, color: textColor, display: 'flex', alignItems: 'center'}}> <span style={{fontSize: 20, color: textColor, display: 'flex', alignItems: 'center'}}>
{ability.activationType === "Passive ability" ? <img className="abilityIcon" src="/images/PassiveIcon.gif"/> : <img className="abilityIcon" src={getIcon(ability.icon)}/>} {ability.activationType === "Passive ability" ? <img className="abilityIcon" src="/images/PassiveIcon.png"/> : <img className="abilityIcon" src={getIcon(ability.icon)}/>}
&nbsp; {ability.name ? ability.name : ability.fileName.charAt(0).toUpperCase() + ability.fileName.slice(1).replaceAll('_', ' ').replace('.rgd', '')} &nbsp; {ability.name ? ability.name : ability.fileName.charAt(0).toUpperCase() + ability.fileName.slice(1).replaceAll('_', ' ').replace('.rgd', '')}
<i style={{fontSize: 12, color: 'rgba(255,255,255,0.5)'}}> ({ability.activationType})</i> <i style={{fontSize: 12, color: 'rgba(255,255,255,0.5)'}}> ({ability.activationType})</i>
</span> </span>

View File

@ -1,5 +1,6 @@
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import {useTheme} from "@mui/material/styles"; import {useTheme} from "@mui/material/styles";
import useMediaQuery from "@mui/material/useMediaQuery";
import {AbilityUrl, AvailableBuildings, AvailableUnits, UserUrl, WeaponUrl} from "../core/api"; import {AbilityUrl, AvailableBuildings, AvailableUnits, UserUrl, WeaponUrl} from "../core/api";
import {IWeapon} from "../types/IUnit"; import {IWeapon} from "../types/IUnit";
import ArmorTypeNames from "../types/ArmorTypeValues"; import ArmorTypeNames from "../types/ArmorTypeValues";
@ -24,7 +25,6 @@ import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
import ability from "./Ability"; import ability from "./Ability";
import AbilitySpawnedObject from "./AbilitySpawnedObject"; import AbilitySpawnedObject from "./AbilitySpawnedObject";
import DpsTable from "./DpsTable"; import DpsTable from "./DpsTable";
import {DescriptionBox} from "../commons/DescriptionBox";
import {StyledPaper} from "../commons/StyledPaper"; import {StyledPaper} from "../commons/StyledPaper";
import {StyledTableCell} from "../commons/StyledTableCell"; import {StyledTableCell} from "../commons/StyledTableCell";
@ -34,6 +34,7 @@ interface IAbilityFullState {
export default function AbilityFull(props: { abilityId?: number, mod: IMod, race: Irace, isChild?: boolean, abilityFull?: IAbilityFill }) { export default function AbilityFull(props: { abilityId?: number, mod: IMod, race: Irace, isChild?: boolean, abilityFull?: IAbilityFill }) {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const isDark = theme.palette.mode === 'dark'; const isDark = theme.palette.mode === 'dark';
const textColor = isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)'; const textColor = isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)';
@ -181,7 +182,7 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
</TableContainer><br/> </TableContainer><br/>
</Grid2> </Grid2>
<Grid2 size={8}> <Grid2 size={8}>
{props.isChild !== true && <DescriptionBox>{ability.description}</DescriptionBox>} {props.isChild !== true && <span style={{ whiteSpace: 'pre-wrap' }}>{ability.description}</span>}
</Grid2> </Grid2>
{showDpsTable ? {showDpsTable ?
<Grid2 size={12}> <Grid2 size={12}>
@ -191,8 +192,71 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
</Grid2> : showTargetTable ? </Grid2> : showTargetTable ?
<Grid2 size={12}> <Grid2 size={12}>
<TableContainer> <TableContainer>
{ props.mod !== undefined && props.mod.technicalName === 'UltimateApocalypse' ? {isMobile ? (() => {
<Table sx={{ textAlign: "center"}} size="small" aria-label="a dense table"> const items = props.mod !== undefined && props.mod.technicalName === 'UltimateApocalypse' ? [
{ label: <ArmorType name={ArmorTypeNames.InfantryLow}/>, value: () => isAbilityApplyTo(ArmorTypeNames.InfantryLow) },
{ label: <ArmorType name={ArmorTypeNames.InfantryMedium}/>, value: () => isAbilityApplyTo(ArmorTypeNames.InfantryMedium) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHigh}/>, value: () => isAbilityApplyTo(ArmorTypeNames.InfantryHigh) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHeavyMedium}/>, value: () => isAbilityApplyTo(ArmorTypeNames.InfantryHeavyMedium) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHeavyHigh}/>, value: () => isAbilityApplyTo(ArmorTypeNames.InfantryHeavyHigh) },
{ label: <ArmorType name={ArmorTypeNames.Commander}/>, value: () => isAbilityApplyTo(ArmorTypeNames.Commander) },
{ label: <ArmorType name={ArmorTypeNames.DemonLow}/>, value: () => isAbilityApplyTo(ArmorTypeNames.DemonLow) },
{ label: <ArmorType name={ArmorTypeNames.DemonMedium}/>, value: () => isAbilityApplyTo(ArmorTypeNames.DemonMedium) },
{ label: <ArmorType name={ArmorTypeNames.DemonHigh}/>, value: () => isAbilityApplyTo(ArmorTypeNames.DemonHigh) },
{ label: <ArmorType name={ArmorTypeNames.Air}/>, value: () => isAbilityApplyTo(ArmorTypeNames.Air) },
{ label: <ArmorType name={ArmorTypeNames.VehicleLow}/>, value: () => isAbilityApplyTo(ArmorTypeNames.VehicleLow) },
{ label: <ArmorType name={ArmorTypeNames.VehicleMedium}/>, value: () => isAbilityApplyTo(ArmorTypeNames.VehicleMedium) },
{ label: <ArmorType name={ArmorTypeNames.VehicleHigh}/>, value: () => isAbilityApplyTo(ArmorTypeNames.VehicleHigh) },
{ label: <ArmorType name={ArmorTypeNames.LivingMetal}/>, value: () => isAbilityApplyTo(ArmorTypeNames.LivingMetal) },
{ label: <ArmorType name={ArmorTypeNames.Titan}/>, value: () => isAbilityApplyTo(ArmorTypeNames.Titan) },
{ label: <ArmorType name={ArmorTypeNames.BuildingLow}/>, value: () => isAbilityApplyTo(ArmorTypeNames.BuildingLow) },
{ label: <ArmorType name={ArmorTypeNames.BuildingMedium}/>, value: () => isAbilityApplyTo(ArmorTypeNames.BuildingMedium) },
{ label: <ArmorType name={ArmorTypeNames.BuildingHigh}/>, value: () => isAbilityApplyTo(ArmorTypeNames.BuildingHigh) },
{ label: <ArmorType name={ArmorTypeNames.BuildingSuper}/>, value: () => isAbilityApplyTo(ArmorTypeNames.BuildingSuper) },
{ label: null, value: () => null },
] : [
{ label: <ArmorType name={ArmorTypeNames.InfantryLow}/>, value: () => isAbilityApplyTo(ArmorTypeNames.InfantryLow) },
{ label: <ArmorType name={ArmorTypeNames.InfantryMedium}/>, value: () => isAbilityApplyTo(ArmorTypeNames.InfantryMedium) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHigh}/>, value: () => isAbilityApplyTo(ArmorTypeNames.InfantryHigh) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHeavyMedium}/>, value: () => isAbilityApplyTo(ArmorTypeNames.InfantryHeavyMedium) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHeavyHigh}/>, value: () => isAbilityApplyTo(ArmorTypeNames.InfantryHeavyHigh) },
{ label: <ArmorType name={ArmorTypeNames.Commander}/>, value: () => isAbilityApplyTo(ArmorTypeNames.Commander) },
{ label: <ArmorType name={ArmorTypeNames.DemonMedium}/>, value: () => isAbilityApplyTo(ArmorTypeNames.DemonMedium) },
{ label: <ArmorType name={ArmorTypeNames.DemonHigh}/>, value: () => isAbilityApplyTo(ArmorTypeNames.DemonHigh) },
{ label: <ArmorType name={ArmorTypeNames.Air}/>, value: () => isAbilityApplyTo(ArmorTypeNames.Air) },
{ label: <ArmorType name={ArmorTypeNames.VehicleLow}/>, value: () => isAbilityApplyTo(ArmorTypeNames.VehicleLow) },
{ label: <ArmorType name={ArmorTypeNames.VehicleMedium}/>, value: () => isAbilityApplyTo(ArmorTypeNames.VehicleMedium) },
{ label: <ArmorType name={ArmorTypeNames.VehicleHigh}/>, value: () => isAbilityApplyTo(ArmorTypeNames.VehicleHigh) },
{ label: <ArmorType name={ArmorTypeNames.BuildingLow}/>, value: () => isAbilityApplyTo(ArmorTypeNames.BuildingLow) },
{ label: <ArmorType name={ArmorTypeNames.BuildingMedium}/>, value: () => isAbilityApplyTo(ArmorTypeNames.BuildingMedium) },
{ label: <ArmorType name={ArmorTypeNames.BuildingHigh}/>, value: () => isAbilityApplyTo(ArmorTypeNames.BuildingHigh) },
];
const chunks = [];
for (let i = 0; i < items.length; i += 5) {
chunks.push(items.slice(i, i + 5));
}
return (<>{chunks.map((chunk, chunkIndex) => (
<Table key={chunkIndex} sx={{marginTop: "10px", textAlign: "center"}} size="small" aria-label="a dense table">
<TableHead>
<TableRow>
{chunk.map((item, i) => (
<StyledTableCell key={i}>{item.label}</StyledTableCell>
))}
</TableRow>
</TableHead>
<TableBody>
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
{chunk.map((item, i) => (
<StyledTableCell key={i}>{item.value()}</StyledTableCell>
))}
</TableRow>
</TableBody>
</Table>
))}</>)
})() : props.mod !== undefined && props.mod.technicalName === 'UltimateApocalypse' ?
<Table sx={{ textAlign: "center", minWidth: 600}} size="small" aria-label="a dense table">
<TableHead> <TableHead>
<TableRow> <TableRow>
<StyledTableCell><ArmorType <StyledTableCell><ArmorType
@ -268,7 +332,7 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
</TableRow> </TableRow>
</TableBody> </TableBody>
</Table> : </Table> :
<Table size="small" aria-label="a dense table"> <Table sx={{ minWidth: 800 }} size="small" aria-label="a dense table">
<TableHead> <TableHead>
<TableRow> <TableRow>
<StyledTableCell><ArmorType <StyledTableCell><ArmorType
@ -324,7 +388,8 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
</Table> </Table>
} }
</TableContainer> </TableContainer>
</Grid2> : <span></span>} </Grid2>
: <span></span>}
<ModifiersProvidesTable modifiers={ability.modifiers} affectedData={ability.affectedData} modId={props.mod.id} race={props.race}/> <ModifiersProvidesTable modifiers={ability.modifiers} affectedData={ability.affectedData} modId={props.mod.id} race={props.race}/>
</Grid2> </Grid2>
{(ability.abilityEnvironment != null) && <AbilitySpawnedObject abilityEnvironment={ability.abilityEnvironment} race={props.race} mod={props.mod}/>} {(ability.abilityEnvironment != null) && <AbilitySpawnedObject abilityEnvironment={ability.abilityEnvironment} race={props.race} mod={props.mod}/>}

View File

@ -1,4 +1,6 @@
import React from "react"; import React from "react";
import {useTheme} from "@mui/material/styles";
import useMediaQuery from "@mui/material/useMediaQuery";
import ArmorTypeNames from "../types/ArmorTypeValues"; import ArmorTypeNames from "../types/ArmorTypeValues";
import { import {
Table, Table,
@ -14,6 +16,8 @@ import {IArmorType} from "../types/IArmorType";
import {StyledTableCell} from "../commons/StyledTableCell"; import {StyledTableCell} from "../commons/StyledTableCell";
export default function DpsTable(props: { mod: IMod, minDamageValue?: number, minDamage?: number, maxDamage?: number, piercings: IPiercing[], targetFilter: IArmorType[], moraleDamage?: number }) { export default function DpsTable(props: { mod: IMod, minDamageValue?: number, minDamage?: number, maxDamage?: number, piercings: IPiercing[], targetFilter: IArmorType[], moraleDamage?: number }) {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
function getPiercingK(armorType: string): number|null { function getPiercingK(armorType: string): number|null {
if(props.targetFilter.length > 0 && props.targetFilter.find(tf => tf.name == armorType) === undefined) return null if(props.targetFilter.length > 0 && props.targetFilter.find(tf => tf.name == armorType) === undefined) return null
@ -72,6 +76,72 @@ export default function DpsTable(props: { mod: IMod, minDamageValue?: number, mi
if(props.moraleDamage !== undefined) return (props.moraleDamage / 2).toFixed(2) if(props.moraleDamage !== undefined) return (props.moraleDamage / 2).toFixed(2)
} }
if (isMobile) {
const items = props.mod !== undefined && props.mod.name.includes("Ultimate Apocalypse") ? [
{ label: <ArmorType name={ArmorTypeNames.InfantryLow}/>, value: () => getTotalDamage(infLowPiercing) },
{ label: <ArmorType name={ArmorTypeNames.InfantryMedium}/>, value: () => getTotalDamage(infMedPiercing) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHigh}/>, value: () => getTotalDamage(infHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHeavyMedium}/>, value: () => getTotalDamage(infHeavyMedPiercing) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHeavyHigh}/>, value: () => getTotalDamage(infHeavyHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.Commander}/>, value: () => getTotalDamage(commanderPiercing) },
{ label: <ArmorType name={ArmorTypeNames.DemonLow}/>, value: () => getTotalDamage(demonLowPiercing) },
{ label: <ArmorType name={ArmorTypeNames.DemonMedium}/>, value: () => getTotalDamage(demonPiercing) },
{ label: <ArmorType name={ArmorTypeNames.DemonHigh}/>, value: () => getTotalDamage(demonHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.Air}/>, value: () => getTotalDamage(airPiercing) },
{ label: <ArmorType name={ArmorTypeNames.VehicleLow}/>, value: () => getTotalDamage(vehLowPiercing) },
{ label: <ArmorType name={ArmorTypeNames.VehicleMedium}/>, value: () => getTotalDamage(vehMedPiercing) },
{ label: <ArmorType name={ArmorTypeNames.VehicleHigh}/>, value: () => getTotalDamage(vehHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.LivingMetal}/>, value: () => getTotalDamage(livingMetalPiercing) },
{ label: <ArmorType name={ArmorTypeNames.Titan}/>, value: () => getTotalDamage(titanPiercing) },
{ label: <ArmorType name={ArmorTypeNames.BuildingLow}/>, value: () => getTotalDamage(buildingLowPiercing) },
{ label: <ArmorType name={ArmorTypeNames.BuildingMedium}/>, value: () => getTotalDamage(buildingMedPiercing) },
{ label: <ArmorType name={ArmorTypeNames.BuildingHigh}/>, value: () => getTotalDamage(buildingHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.BuildingSuper}/>, value: () => getTotalDamage(buildingSuperPiercing) },
{ label: <ArmorType name="Morale damage"/>, value: () => getMoraleDamage() },
] : [
{ label: <ArmorType name={ArmorTypeNames.InfantryLow}/>, value: () => getTotalDamage(infLowPiercing) },
{ label: <ArmorType name={ArmorTypeNames.InfantryMedium}/>, value: () => getTotalDamage(infMedPiercing) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHigh}/>, value: () => getTotalDamage(infHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHeavyMedium}/>, value: () => getTotalDamage(infHeavyMedPiercing) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHeavyHigh}/>, value: () => getTotalDamage(infHeavyHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.Commander}/>, value: () => getTotalDamage(commanderPiercing) },
{ label: <ArmorType name={ArmorTypeNames.DemonMedium}/>, value: () => getTotalDamage(demonPiercing) },
{ label: <ArmorType name={ArmorTypeNames.DemonHigh}/>, value: () => getTotalDamage(demonHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.Air}/>, value: () => getTotalDamage(airPiercing) },
{ label: <ArmorType name={ArmorTypeNames.VehicleLow}/>, value: () => getTotalDamage(vehLowPiercing) },
{ label: <ArmorType name={ArmorTypeNames.VehicleMedium}/>, value: () => getTotalDamage(vehMedPiercing) },
{ label: <ArmorType name={ArmorTypeNames.VehicleHigh}/>, value: () => getTotalDamage(vehHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.BuildingLow}/>, value: () => getTotalDamage(buildingLowPiercing) },
{ label: <ArmorType name={ArmorTypeNames.BuildingMedium}/>, value: () => getTotalDamage(buildingMedPiercing) },
{ label: <ArmorType name={ArmorTypeNames.BuildingHigh}/>, value: () => getTotalDamage(buildingHighPiercing) },
{ label: <ArmorType name="Morale damage"/>, value: () => getMoraleDamage() },
];
const chunks = [];
for (let i = 0; i < items.length; i += 5) {
chunks.push(items.slice(i, i + 5));
}
return (<>{chunks.map((chunk, chunkIndex) => (
<Table key={chunkIndex} sx={{marginTop: "10px", textAlign: "center"}} size="small" aria-label="a dense table">
<TableHead>
<TableRow>
{chunk.map((item, i) => (
<StyledTableCell key={i}>{item.label}</StyledTableCell>
))}
</TableRow>
</TableHead>
<TableBody>
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
{chunk.map((item, i) => (
<StyledTableCell key={i}>{item.value()}</StyledTableCell>
))}
</TableRow>
</TableBody>
</Table>
))}</>)
}
return ( return (
<TableContainer> <TableContainer>
{ props.mod !== undefined && props.mod.name.includes("Ultimate Apocalypse") ? { props.mod !== undefined && props.mod.name.includes("Ultimate Apocalypse") ?
@ -153,7 +223,7 @@ export default function DpsTable(props: { mod: IMod, minDamageValue?: number, mi
</TableRow> </TableRow>
</TableBody> </TableBody>
</Table> : </Table> :
<Table size="small" aria-label="a dense table"> <Table sx={{ minWidth: 800 }} size="small" aria-label="a dense table">
<TableHead> <TableHead>
<TableRow> <TableRow>
<StyledTableCell><ArmorType <StyledTableCell><ArmorType

100
src/classes/Jump.tsx Normal file
View File

@ -0,0 +1,100 @@
import React from "react";
import {AccordionDetails, AccordionSummary, Grid2, Table, TableBody, TableCell, TableRow, TableContainer} from "@mui/material";
import {ExpandMore} from "@mui/icons-material";
import {useTheme} from "@mui/material/styles";
import {IJumps} from "../types/IUnit";
import {Irace} from "../types/Irace";
import {IMod} from "../types/Imod";
import {StyledAccordion} from "../commons/StyledAccordion";
import {StyledPaper} from "../commons/StyledPaper";
import Required from "./Required";
interface IJumpProps {
jumps: IJumps,
race: Irace,
mod: IMod,
}
function Jump(props: IJumpProps) {
const theme = useTheme();
const isDark = theme.palette.mode === 'dark';
const textColor = isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)';
const jumps = props.jumps;
const isTeleport = jumps.teleport;
const icon = isTeleport ? "/images/Teleport.png" : "/images/Jump.png";
const title = isTeleport ? "Teleport" : "Jumps";
return (
<div style={{marginBottom: 10}}>
<StyledAccordion TransitionProps={{unmountOnExit: true, timeout: 100}}>
<AccordionSummary
expandIcon={<ExpandMore sx={{color: textColor}}/>}
aria-controls="jump-content"
id="jump-header"
sx={{
color: textColor,
'& .MuiAccordionSummary-content': {margin: '12px 0'},
'& .MuiAccordionSummary-content.Mui-expanded': {margin: '12px 0'}
}}
>
<span style={{fontSize: 20, color: textColor, display: 'flex', alignItems: 'center'}}>
<img className="jumpIcon" src={icon}/>
&nbsp; {title}
</span>
</AccordionSummary>
<AccordionDetails sx={{color: textColor}}>
<Grid2 container spacing={2}>
{(() => {
const rows: { label: string; value: React.ReactNode }[] = [
{ label: "Charge cost", value: jumps.chargeJumpCostMin === jumps.chargeJumpCostMax
? jumps.chargeJumpCostMin.toFixed(0)
: `${jumps.chargeJumpCostMin.toFixed(0)} - ${jumps.chargeJumpCostMax.toFixed(0)}` },
{ label: "Charge max", value: jumps.chargeMax.toFixed(0) },
{ label: "Charge regeneration", value: `${jumps.chargeRegeneration}/s` },
{ label: "Distance", value: jumps.jumpDistanceMax.toFixed(0) },
{ label: "Setup time", value: <span><img style={{verticalAlign: "top"}} src="/images/Time_icon.webp"/>&nbsp;{jumps.setupTime}s</span> },
{ label: "Breakdown time", value: <span><img style={{verticalAlign: "top"}} src="/images/Time_icon.webp"/>&nbsp;{jumps.breakdownTime}s</span> },
];
if (!isTeleport) {
if (jumps.goUpTime !== undefined && jumps.goUpTime !== null) {
rows.push({ label: "Go up time", value: <span><img style={{verticalAlign: "top"}} src="/images/Time_icon.webp"/>&nbsp;{jumps.goUpTime}s</span> });
}
if (jumps.goDownTime !== undefined && jumps.goDownTime !== null) {
rows.push({ label: "Go down time", value: <span><img style={{verticalAlign: "top"}} src="/images/Time_icon.webp"/>&nbsp;{jumps.goDownTime}s</span> });
}
}
const chunks: { label: string; value: React.ReactNode }[][] = [];
for (let i = 0; i < rows.length; i += 3) {
chunks.push(rows.slice(i, i + 3));
}
return chunks.map((chunk, chunkIndex) => (
<Grid2 key={chunkIndex} size={{xs: 12, md: 4}}>
<TableContainer component={StyledPaper} elevation={0}>
<Table size="small" aria-label="jump stats table">
<TableBody>
{chunk.map((row, i) => (
<TableRow key={i} sx={{'&:last-child td, &:last-child th': {border: 0}}}>
<TableCell component="th" scope="row">{row.label}</TableCell>
<TableCell>{row.value}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Grid2>
));
})()}
</Grid2>
{jumps.requirements !== null &&
<Grid2 size={{xs: 12, md: 12}}>
<Required requirement={jumps.requirements} modId={props.mod.id}
raceId={props.race.id}/>
</Grid2>}
</AccordionDetails>
</StyledAccordion>
</div>
);
}
export default Jump;

View File

@ -265,7 +265,7 @@ export function ModifiersProvidesTable(props: IModifiersProvidesResearchTable) {
export function getIcon(icon?: string): string { export function getIcon(icon?: string): string {
if (icon !== null && icon !== undefined) { if (icon !== null && icon !== undefined) {
return IconUrl + icon.replaceAll('\\', '/') return IconUrl + icon.replaceAll('\\', '/')
} else return '/images/NoImg.gif' } else return '/images/NoImg.png'
} }

View File

@ -1,5 +1,6 @@
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import {useTheme} from "@mui/material/styles"; import {useTheme} from "@mui/material/styles";
import useMediaQuery from "@mui/material/useMediaQuery";
import {AvailableBuildings, AvailableUnits, UserUrl, WeaponUrl} from "../core/api"; import {AvailableBuildings, AvailableUnits, UserUrl, WeaponUrl} from "../core/api";
import {IWeapon} from "../types/IUnit"; import {IWeapon} from "../types/IUnit";
import ArmorTypeNames from "../types/ArmorTypeValues"; import ArmorTypeNames from "../types/ArmorTypeValues";
@ -20,7 +21,6 @@ import {Irace} from "../types/Irace";
import {IRaceUnits} from "../types/IUnitShort"; import {IRaceUnits} from "../types/IUnitShort";
import {IRaceBuildings} from "../types/IBuildingShort"; import {IRaceBuildings} from "../types/IBuildingShort";
import {ModifiersProvidesTable} from "./ModifiersProvideTable"; import {ModifiersProvidesTable} from "./ModifiersProvideTable";
import {DescriptionBox} from "../commons/DescriptionBox";
import {StyledPaper} from "../commons/StyledPaper"; import {StyledPaper} from "../commons/StyledPaper";
import {StyledTableCell} from "../commons/StyledTableCell"; import {StyledTableCell} from "../commons/StyledTableCell";
import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer"; import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
@ -32,6 +32,7 @@ interface IWeaponFull {
export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, mod: IMod, race: Irace, haveReinforceMenu: Boolean}) { export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, mod: IMod, race: Irace, haveReinforceMenu: Boolean}) {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const isDark = theme.palette.mode === 'dark'; const isDark = theme.palette.mode === 'dark';
const textColor = isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)'; const textColor = isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)';
@ -54,8 +55,6 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
}, []); }, []);
if(weaoponFull.weapon !== undefined){ if(weaoponFull.weapon !== undefined){
const weapon = weaoponFull.weapon const weapon = weaoponFull.weapon
@ -126,9 +125,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
<div style={{color: textColor}}> <div style={{color: textColor}}>
<Grid2 container spacing={2}> <Grid2 container spacing={2}>
{!props.isDefault && <Grid2 size={12}> {!props.isDefault && <Grid2 size={12}>
<DescriptionBox> <span className="weaponDescription" style={{ whiteSpace: 'pre-wrap' }}>{weapon.description}</span>
<span className="weaponDescription">{weapon.description}</span>
</DescriptionBox>
</Grid2>} </Grid2>}
<Grid2 size={{xs: 12, md: 4}}> <Grid2 size={{xs: 12, md: 4}}>
@ -238,7 +235,71 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
<ToggleButton size="small" value="one hit" sx={{ color: textColor, '&.Mui-selected': {borderColor: textColor, color: textColor, backgroundColor: isDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.15)' } }}>One hit average damage</ToggleButton> <ToggleButton size="small" value="one hit" sx={{ color: textColor, '&.Mui-selected': {borderColor: textColor, color: textColor, backgroundColor: isDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.15)' } }}>One hit average damage</ToggleButton>
</ToggleButtonGroup> </ToggleButtonGroup>
<TableContainer> <TableContainer>
{ props.mod !== undefined && props.mod.technicalName === 'UltimateApocalypse' ? {isMobile ? (() => {
const items = props.mod !== undefined && props.mod.technicalName === 'UltimateApocalypse' ? [
{ label: <ArmorType name={ArmorTypeNames.InfantryLow}/>, value: () => getTotalDamage(infLowPiercing) },
{ label: <ArmorType name={ArmorTypeNames.InfantryMedium}/>, value: () => getTotalDamage(infMedPiercing) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHigh}/>, value: () => getTotalDamage(infHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHeavyMedium}/>, value: () => getTotalDamage(infHeavyMedPiercing) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHeavyHigh}/>, value: () => getTotalDamage(infHeavyHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.Commander}/>, value: () => getTotalDamage(commanderPiercing) },
{ label: <ArmorType name={ArmorTypeNames.DemonLow}/>, value: () => getTotalDamage(demonLowPiercing) },
{ label: <ArmorType name={ArmorTypeNames.DemonMedium}/>, value: () => getTotalDamage(demonPiercing) },
{ label: <ArmorType name={ArmorTypeNames.DemonHigh}/>, value: () => getTotalDamage(demonHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.Air}/>, value: () => getTotalDamage(airPiercing, true) },
{ label: <ArmorType name={ArmorTypeNames.VehicleLow}/>, value: () => getTotalDamage(vehLowPiercing) },
{ label: <ArmorType name={ArmorTypeNames.VehicleMedium}/>, value: () => getTotalDamage(vehMedPiercing) },
{ label: <ArmorType name={ArmorTypeNames.VehicleHigh}/>, value: () => getTotalDamage(vehHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.LivingMetal}/>, value: () => getTotalDamage(livingMetalPiercing) },
{ label: <ArmorType name={ArmorTypeNames.Titan}/>, value: () => getTotalDamage(titanPiercing) },
{ label: <ArmorType name={ArmorTypeNames.BuildingLow}/>, value: () => getTotalDamage(buildingLowPiercing) },
{ label: <ArmorType name={ArmorTypeNames.BuildingMedium}/>, value: () => getTotalDamage(buildingMedPiercing) },
{ label: <ArmorType name={ArmorTypeNames.BuildingHigh}/>, value: () => getTotalDamage(buildingHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.BuildingSuper}/>, value: () => getTotalDamage(buildingSuperPiercing) },
{ label: (<><img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/><div style={{width: 20, fontSize: 12, height: 50, color: textColor}}><i>Morale</i></div></>), value: () => getMoraleDamage() },
] : [
{ label: <ArmorType name={ArmorTypeNames.InfantryLow}/>, value: () => getTotalDamage(infLowPiercing) },
{ label: <ArmorType name={ArmorTypeNames.InfantryMedium}/>, value: () => getTotalDamage(infMedPiercing) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHigh}/>, value: () => getTotalDamage(infHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHeavyMedium}/>, value: () => getTotalDamage(infHeavyMedPiercing) },
{ label: <ArmorType name={ArmorTypeNames.InfantryHeavyHigh}/>, value: () => getTotalDamage(infHeavyHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.Commander}/>, value: () => getTotalDamage(commanderPiercing) },
{ label: <ArmorType name={ArmorTypeNames.DemonMedium}/>, value: () => getTotalDamage(demonPiercing) },
{ label: <ArmorType name={ArmorTypeNames.DemonHigh}/>, value: () => getTotalDamage(demonHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.Air}/>, value: () => getTotalDamage(airPiercing, true) },
{ label: <ArmorType name={ArmorTypeNames.VehicleLow}/>, value: () => getTotalDamage(vehLowPiercing) },
{ label: <ArmorType name={ArmorTypeNames.VehicleMedium}/>, value: () => getTotalDamage(vehMedPiercing) },
{ label: <ArmorType name={ArmorTypeNames.VehicleHigh}/>, value: () => getTotalDamage(vehHighPiercing) },
{ label: <ArmorType name={ArmorTypeNames.BuildingLow}/>, value: () => getTotalDamage(buildingLowPiercing) },
{ label: <ArmorType name={ArmorTypeNames.BuildingMedium}/>, value: () => getTotalDamage(buildingMedPiercing) },
{ label: <ArmorType name={ArmorTypeNames.BuildingHigh}/>, value: () => getTotalDamage(buildingHighPiercing) },
{ label: <ArmorType name="Morale damage"/>, value: () => getMoraleDamage() },
];
const chunks = [];
for (let i = 0; i < items.length; i += 5) {
chunks.push(items.slice(i, i + 5));
}
return chunks.map((chunk, chunkIndex) => (
<Table key={chunkIndex} sx={{marginTop: "10px", textAlign: "center"}} size="small" aria-label="a dense table">
<TableHead>
<TableRow>
{chunk.map((item, i) => (
<StyledTableCell key={i}>{item.label}</StyledTableCell>
))}
</TableRow>
</TableHead>
<TableBody>
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
{chunk.map((item, i) => (
<StyledTableCell key={i}>{item.value()}</StyledTableCell>
))}
</TableRow>
</TableBody>
</Table>
));
})() : props.mod !== undefined && props.mod.technicalName === 'UltimateApocalypse' ?
<Table sx={{marginTop: "10px", textAlign: "center"}} size="small" aria-label="a dense table"> <Table sx={{marginTop: "10px", textAlign: "center"}} size="small" aria-label="a dense table">
<TableHead> <TableHead>
<TableRow> <TableRow>
@ -378,7 +439,6 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
</TableBody> </TableBody>
</Table> </Table>
} }
</TableContainer> </TableContainer>
</Grid2> </Grid2>

View File

@ -18,7 +18,6 @@ import {IMod} from "../../types/Imod";
import {getIcon, ModifiersProvidesTable,} from "../ModifiersProvideTable"; import {getIcon, ModifiersProvidesTable,} from "../ModifiersProvideTable";
import Required from "../Required"; import Required from "../Required";
import {StyledAccordion} from "../../commons/StyledAccordion"; import {StyledAccordion} from "../../commons/StyledAccordion";
import {DescriptionBox} from "../../commons/DescriptionBox";
import {StyledPaper} from "../../commons/StyledPaper"; import {StyledPaper} from "../../commons/StyledPaper";
import {styled} from "@mui/material/styles"; import {styled} from "@mui/material/styles";
@ -92,9 +91,7 @@ function BuildingAddon(props: IBuildingAddonProps){
</TableContainer><br/> </TableContainer><br/>
</Grid2> </Grid2>
<Grid2 size={{xs: 12, md: 8}}> <Grid2 size={{xs: 12, md: 8}}>
<DescriptionBox> <span style={{ whiteSpace: 'pre-wrap' }}>{addon.description}</span>
{addon.description}
</DescriptionBox>
</Grid2> </Grid2>
<ModifiersProvidesTable modifiers={addon.addonModifiers} affectedData={addon.affectedData} modId={props.mod.id} race={building.race}/> <ModifiersProvidesTable modifiers={addon.addonModifiers} affectedData={addon.affectedData} modId={props.mod.id} race={building.race}/>
{addon.addonRequirement !== null && {addon.addonRequirement !== null &&

View File

@ -22,7 +22,6 @@ import {IResearchShort} from "../../types/IResearchShort";
import {ResearchUrl, WeaponUrl} from "../../core/api"; import {ResearchUrl, WeaponUrl} from "../../core/api";
import {IWeapon} from "../../types/IUnit"; import {IWeapon} from "../../types/IUnit";
import {styled} from '@mui/material/styles'; import {styled} from '@mui/material/styles';
import {DescriptionBox} from "../../commons/DescriptionBox";
import {StyledPaper} from "../../commons/StyledPaper"; import {StyledPaper} from "../../commons/StyledPaper";
@ -96,10 +95,7 @@ function ResearchFull(props: { id: number, building: IBuilding }){
</TableContainer><br/> </TableContainer><br/>
</Grid2> </Grid2>
<Grid2 size={{xs: 12, md: 8}}> <Grid2 size={{xs: 12, md: 8}}>
<DescriptionBox> <span style={{ whiteSpace: 'pre-wrap' }}>{research.description}</span>
{research.description}
</DescriptionBox>
</Grid2> </Grid2>
<Grid2 size={{xs: 12, md: 12}}> <Grid2 size={{xs: 12, md: 12}}>
<ModifiersProvidesTable modifiers={research.modifiers} affectedData={research.affectedData} <ModifiersProvidesTable modifiers={research.modifiers} affectedData={research.affectedData}

View File

@ -17,6 +17,10 @@ export const StyledTableCell = styled(TableCell)(({theme}) => {
color: textColor, color: textColor,
paddingRight: 18, paddingRight: 18,
paddingLeft: 10, paddingLeft: 10,
[theme.breakpoints.down('md')]: {
paddingRight: 4,
paddingLeft: 2,
},
}, },
}; };
}); });

View File

@ -21,6 +21,11 @@
width: 40px; width: 40px;
} }
.jumpIcon{
padding: 0px;
width: 40px;
}
.unitHeader{ .unitHeader{
margin-left: 15px; margin-left: 15px;
} }

View File

@ -183,7 +183,6 @@ function Mods({ mods }: ModsProps) {
const sameMods = mapWithModVersions.get(modName) ?? []; const sameMods = mapWithModVersions.get(modName) ?? [];
// The latest version is the one with the maximum id // 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 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); const betaMods = sameMods.filter(m => m.isBeta);
return { return {
latest: latest, latest: latest,
@ -329,7 +328,7 @@ class ModsPage extends React.Component<any, ModsPageState> {
const response = await fetch(AvailableMods); const response = await fetch(AvailableMods);
const data: IMod[] = await response.json(); const data: IMod[] = await response.json();
this.setState({ this.setState({
mods: data, mods: data.filter(mod => !mod.isHide),
loading: false loading: false
}); });
} catch (error) { } catch (error) {

View File

@ -33,6 +33,7 @@ import {AffectedResearches} from "../classes/building/Research";
import {ModifiersProvidesTable} from "../classes/ModifiersProvideTable"; import {ModifiersProvidesTable} from "../classes/ModifiersProvideTable";
import Ability from "../classes/Ability"; import Ability from "../classes/Ability";
import DeathExplosion from "../classes/DeathExplosion"; import DeathExplosion from "../classes/DeathExplosion";
import Jump from "../classes/Jump";
import {styled} from '@mui/material/styles'; import {styled} from '@mui/material/styles';
import {withTheme} from "../core/api"; import {withTheme} from "../core/api";
import {DescriptionBox} from "../commons/DescriptionBox"; import {DescriptionBox} from "../commons/DescriptionBox";
@ -337,7 +338,7 @@ function Unit(unit: IUnit, mod: IMod, theme: Theme) {
unit.moraleBrakeModifiers.find(m => m.reference.includes("speed_maximum_modifier") && m.value === 1.2) !== undefined && unit.moraleBrakeModifiers.find(m => m.reference.includes("speed_maximum_modifier") && m.value === 1.2) !== undefined &&
unit.moraleBrakeModifiers.length === 2) unit.moraleBrakeModifiers.length === 2)
&& <Grid2 size={12}> && <Grid2 size={12}>
<SectionTitle><img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/> Morale broken unusual behavior <img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/></SectionTitle> <SectionTitle><img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/> Morale broken <img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/></SectionTitle>
<ModifiersProvidesTable modifiers={unit.moraleBrakeModifiers} modId={unit.modId} race={unit.race} affectedData={unit.affectedData}/> <ModifiersProvidesTable modifiers={unit.moraleBrakeModifiers} modId={unit.modId} race={unit.race} affectedData={unit.affectedData}/>
</Grid2>} </Grid2>}
@ -353,6 +354,11 @@ function Unit(unit: IUnit, mod: IMod, theme: Theme) {
)} )}
</Grid2>} </Grid2>}
{unit.jumps != null && <Grid2 size={12}>
<SectionTitle>Jumps</SectionTitle>
<Jump jumps={unit.jumps} race={unit.race} mod={mod}/>
</Grid2>}
<Grid2 size={12}> <Grid2 size={12}>
{[...mapWithUnitWeapons.keys()].sort(function (a, b) { {[...mapWithUnitWeapons.keys()].sort(function (a, b) {
return a - b; return a - b;

View File

@ -61,8 +61,8 @@ export const lightTheme = createTheme({
paper: '#ffffff', paper: '#ffffff',
}, },
text: { text: {
primary: 'rgba(0, 0, 0, 0.85)', primary: '#111111',
secondary: 'rgba(0, 0, 0, 0.6)', secondary: '#333333',
}, },
}, },
typography: { typography: {
@ -75,7 +75,7 @@ export const lightTheme = createTheme({
MuiTableCell: { MuiTableCell: {
styleOverrides: { styleOverrides: {
root: { root: {
color: 'rgba(0, 0, 0, 0.85)', color: '#111111',
}, },
}, },
}, },

View File

@ -13,6 +13,21 @@ export interface IUnitResponse {
units: IUnit[] units: IUnit[]
} }
export interface IJumps {
chargeRegeneration: number;
goUpTime?: number;
jumpDistanceMax: number;
breakdownTime: number;
chargeJumpCostMin: number;
chargeJumpCostMax: number;
jumpHeight: number;
chargeMax: number;
setupTime: number;
goDownTime?: number;
teleport: boolean;
requirements: IRequirement | null;
}
export interface IUnit { export interface IUnit {
id: number id: number
race: Irace race: Irace
@ -69,6 +84,7 @@ export interface IUnit {
affectedData: IAffectedData, affectedData: IAffectedData,
affectedResearches: IResearchShort[], affectedResearches: IResearchShort[],
deathExplosions: IDeathExplosion[], deathExplosions: IDeathExplosion[],
jumps?: IJumps | null,
hotkey?: string, hotkey?: string,
} }

View File

@ -5,4 +5,5 @@ export interface IMod {
version: string; version: string;
technicalName: string; technicalName: string;
isBeta: boolean; isBeta: boolean;
isHide: boolean;
} }