Sergeants frontend

This commit is contained in:
Anibus
2025-03-16 12:35:01 +03:00
parent 2b4352ff21
commit 98b5a47c0f
20 changed files with 938 additions and 479 deletions
+3 -2
View File
@@ -24,8 +24,9 @@ function App() {
cssVariables: true,
});
return (
<div>
<div >
<AppBar position="static">
<Toolbar>
<IconButton
@@ -37,7 +38,7 @@ function App() {
>
</IconButton>
<Typography variant="h6" component="div" sx={{flexGrow: 1}}>
Soulstorm wiki
Soulstorm autogeneration wiki
</Typography>
<Button color="inherit" onClick={steamLogin}>
Log in through Steam
+5 -3
View File
@@ -1,7 +1,8 @@
import {Routes, Route, BrowserRouter} from "react-router-dom";
import RacePage from "./pages/RacePage";
import ModsPage from "./pages/ModsPage";
import ModPage from "./pages/ModPage";
import RacePageFast from "./pages/RacePageFast";
import UnitPage from "./pages/UnitPage";
export const MyRoutes = () => {
@@ -9,8 +10,9 @@ export const MyRoutes = () => {
<BrowserRouter>
<Routes>
<Route path="/" element={<ModsPage/>}/>
<Route path="/mod/:id" element={<ModPage/>}/>
<Route path="/mod/:modId/race/:raceId" element={<RacePage/>}/>
<Route path="/mod/:modId" element={<ModPage/>}/>
<Route path="/mod/:modId/race/:raceId" element={<RacePageFast/>}/>
<Route path="/mod/:modId/race/:raceId/unit/:unitId" element={<UnitPage/>}/>
</Routes>
</BrowserRouter>
)
+6 -4
View File
@@ -1,4 +1,5 @@
import React from "react";
import {Tooltip} from "@mui/material";
interface IArmorType{
name: string,
@@ -11,8 +12,8 @@ class ArmorType extends React.Component<IArmorType, any> {
withName: false
};
renderArmorImage(armorType: String): string {
switch(armorType) {
renderArmorImage(armorTypeId: string): string {
switch(armorTypeId) {
case 'Infantry Low':
return '/images/ARM_Inf_Lo.webp';
case 'Infantry Medium':
@@ -44,13 +45,14 @@ class ArmorType extends React.Component<IArmorType, any> {
case 'Demon High':
return '/images/ARM_Dmn_Hi.webp';
default:
return 'Unknown armor';
return armorTypeId;
}
}
render() {
return (<span> <img style={{verticalAlign: "top"}} src={this.renderArmorImage(this.props.name)}/> {this.props.withName && this.props.name} </span>);
return (<Tooltip title={this.props.name}><span> <img style={{verticalAlign: "top"}}
src={this.renderArmorImage(this.props.name)}/> {this.props.withName && this.props.name}</span></Tooltip>);
}
}
-16
View File
@@ -1,16 +0,0 @@
import React from 'react';
export default class Item extends React.Component {
render() {
return (
<div className="shopping-list">
<h1>Shopping List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Oculus</li>
</ul>
</div>
);
}
}
+132
View File
@@ -0,0 +1,132 @@
import React, {useState} from "react";
import {IconUrl, UserUrl} from "../core/api";
import {ISergeant, IWeapon} from "../types/IUnit";
import {
Accordion, AccordionDetails,
AccordionSummary,
Grid2,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableRow
} from "@mui/material";
import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
import ArmorType from "./ArmorType";
import {ExpandMore} from "@mui/icons-material";
import WeaponSlot from "./WeaponSlot";
export interface SergeantProps {
sergeant: ISergeant
}
const Sergeant = (props: SergeantProps) => {
const sergeant = props.sergeant
const detect = sergeant.detectRadius > 0 ? <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/DETECT_YES.webp"/> {sergeant.detectRadius}</span> :
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/DETECT_NO.webp"/></span>
let mapWithUnitWeapons: Map<number, Map<number, IWeapon>> = new Map();
sergeant.weapons.forEach(weapon => {
const weaponMap = mapWithUnitWeapons.get(weapon.hardpoint)
if (weaponMap == null) {
const weaponMap = new Map()
weaponMap.set(weapon.hardpointOrder, weapon.weapon)
mapWithUnitWeapons.set(weapon.hardpoint, weaponMap)
} else {
weaponMap.set(weapon.hardpointOrder, weapon.weapon)
}
})
return (
<div>
<Grid2 container spacing={2}>
<Grid2 size= {{xs: 12, md: 4}}>
<TableContainer component={Paper}>
<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">
{sergeant.buildCostRequisition > 0 &&
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_requisition.gif"/>&nbsp;
{sergeant.buildCostRequisition}</span>}
{sergeant.buildCostPower > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_power.gif"/>&nbsp;
{sergeant.buildCostPower}</span>}
{(sergeant.buildCostPopulation !== undefined && sergeant.buildCostPopulation > 0) && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_orksquadcap.gif"/>&nbsp;
{sergeant.buildCostPopulation}</span>}
{(sergeant.buildCostTime !== undefined && sergeant.buildCostTime > 0) && <span>&nbsp;<AvTimerOutlinedIcon style={{verticalAlign: "top", fontSize:"18px"}} />&nbsp;
{sergeant.buildCostTime}s</span>}
</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">
<ArmorType name={sergeant.armorType.name}/>
</TableCell>
</TableRow>
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">health</TableCell>
<TableCell component="th" scope="row">
<span><img style={{verticalAlign: "top"}}
src="/images/Health_icon.webp"/>&nbsp;
{sergeant.health} {sergeant.healthRegeneration > 0 &&
<span>+{sergeant.healthRegeneration}/s</span>} </span>
</TableCell>
</TableRow>
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">morale</TableCell>
<TableCell component="th" scope="row">
<img style={{verticalAlign: "top"}}
src="/images/Kills_icon.webp"/> -{sergeant.moraleDeathPenalty}
</TableCell>
</TableRow>
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">detect</TableCell>
<TableCell component="th" scope="row">
{detect}
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid2>
<Grid2 size={8}>
<div style={{whiteSpace: "pre-wrap"}}>
{sergeant.description}
</div>
</Grid2>
<Grid2 size={12}>
{[...mapWithUnitWeapons.keys()].sort(function (a, b) {
return a - b;
}).map(h => <WeaponSlot unitWeapons={mapWithUnitWeapons.get(h)} hardpoint={h}/>)}
</Grid2>
</Grid2>
<i className="rgdFrom">{sergeant.filename}</i>
</div>)
};
export default Sergeant;
+140
View File
@@ -0,0 +1,140 @@
import React, {useEffect, useState} from "react";
import {AvailableUnits} from "../core/api";
import {Grid2, Table, TableBody, TableCell, TableContainer, TableHead, TableRow} from "@mui/material";
import {NavLink} from "react-router-dom";
import {IRaceUnits, IUnitShort} from "../types/IUnitShort";
interface IUnitsTable {
racesUnits: IRaceUnits[];
}
export default function UnitsTable(prop: {modId: number}) {
const [unitsTable, setUnitsTable] = useState<IUnitsTable>({
racesUnits: []
});
function getUnitRef(modId: number, raceId: string, unit: IUnitShort) {
return <span style={{fontSize: 11}}><a href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id}>
{unit.name}
</a><br/></span>
}
useEffect(() => {
fetch(AvailableUnits + "/mod/" + prop.modId)
.then(res => res.json())
.then((res: IRaceUnits[]) => {
setUnitsTable({
racesUnits : res
});
})
}, []);
function armourTypePriority(armorTypeId: string): number {
switch(armorTypeId) {
case 'Infantry Low':
return 1;
case 'Infantry Medium':
return 2;
case 'Infantry High':
return 3;
case 'Demon Medium':
return 4;
case 'Infantry Heavy Medium':
return 5;
case 'Infantry Heavy High':
return 6;
case 'Commander':
return 7;
case 'Vehicle Low':
return 8;
case 'Vehicle Medium':
return 9;
case 'Vehicle High':
return 10;
case 'Demon High':
return 11;
case 'Air':
return 12;
case 'Building Low':
return 13;
case 'Building Medium':
return 14;
case 'Building High':
return 15;
default:
return 0;
}
}
function getUnitsRef(units: IUnitShort[], raceId: string): JSX.Element[] {
return units
.sort((a, b) => armourTypePriority(a.armourTypeName) - armourTypePriority(b.armourTypeName))
.map(unit =>
getUnitRef(prop.modId, raceId, unit)
)
}
function generateRaceUnitTable(racesUnitsPart: IRaceUnits[]){
return (<TableContainer>
{<Table sx={{minWidth: 650}} size="small" aria-label="a dense table">
<TableHead>
<TableRow>
{racesUnitsPart.map(raceUnits => <TableCell><h2><NavLink state={raceUnits.race.id}
to={"/mod/" + prop.modId + "/race/" + raceUnits.race.id}>{raceUnits.race.name}</NavLink>
</h2></TableCell>)}
</TableRow>
</TableHead>
<TableBody>
<TableRow style={{verticalAlign: 'top'}}>
{racesUnitsPart.map(raceUnits =>
<TableCell>
{getUnitsRef(raceUnits.infantry, raceUnits.race.id)}
</TableCell>)
}
</TableRow>
<TableRow style={{verticalAlign: 'top'}}>
{racesUnitsPart.map(raceUnits =>
<TableCell>
{getUnitsRef(raceUnits.tech, raceUnits.race.id)}
</TableCell>)
}
</TableRow>
<TableRow style={{verticalAlign: 'top'}}>
{racesUnitsPart.map(raceUnits =>
<TableCell>
{getUnitsRef(raceUnits.support, raceUnits.race.id)}
</TableCell>)
}
</TableRow>
</TableBody>
</Table>}
</TableContainer>)
}
function generateRaceUnitTables(racesUnits: IRaceUnits[], raceCount: number){
var elements: JSX.Element[] = []
for(let i = 0; i < racesUnits.length;i = i + raceCount){
elements.push(generateRaceUnitTable(racesUnits.slice(i, i + raceCount)))
}
return elements
}
return (
<Grid2>
<Grid2 display={{ lg: 'block', xs: 'none' }} >
{generateRaceUnitTables(unitsTable.racesUnits, 9)}
</Grid2>
<Grid2 display={{ lg: 'none', xs: 'block' }}>
{unitsTable.racesUnits.map(raceUnits => <h4><NavLink state={raceUnits.race.id}
to={"/mod/" + prop.modId + "/race/" + raceUnits.race.id}>{raceUnits.race.name}</NavLink>
</h4>)}
</Grid2>
</Grid2>
)
}
+97 -55
View File
@@ -1,5 +1,5 @@
import React from "react";
import {IWeapon, WeaponPiercing} from "../types/IUnit";
import {IWeapon, IWeaponPiercing} from "../types/IUnit";
import {
Grid2,
Paper,
@@ -14,9 +14,11 @@ import {
} from "@mui/material";
import ArmorType from "./ArmorType";
import ArmorTypeNames from "../types/ArmorTypeValues";
import {IconUrl} from "../core/api";
interface IWeaponProps{
weapon: IWeapon,
isDefault: Boolean,
}
interface IWeaponState{
@@ -25,6 +27,10 @@ interface IWeaponState{
class Weapon extends React.Component<IWeaponProps, any> {
public static defaultProps = {
isDefault: false
};
humanReadableName(unit: string) {
const firstUpper = String(unit).charAt(0).toUpperCase() + String(unit).slice(1);
return firstUpper.replaceAll('_', ' ').replace('.rgd', '');
@@ -56,7 +62,7 @@ class Weapon extends React.Component<IWeaponProps, any> {
const weapon = this.props.weapon
const dpsK = (this.state.currentTable == "dps") ? weapon.accuracy * (1/(weapon.reloadTime - weapon.reloadTime % 0.125)) : 1
const dpsK = (this.state.currentTable == "dps") ? weapon.accuracy * (1/(weapon.reloadTime - (weapon.reloadTime % 0.125))) : 1
const infLowPiercing = this.getPiercingK(ArmorTypeNames.InfantryLow)
const infMedPiercing = this.getPiercingK(ArmorTypeNames.InfantryMedium)
@@ -77,6 +83,9 @@ class Weapon extends React.Component<IWeaponProps, any> {
const getTotalDamage = (damagePiercing: number, isAir: boolean = false) => {
if(!isAir && !weapon.canAttackGround && !weapon.isMeleeWeapon) return ""
if(isAir && !weapon.canAttackAir) return ""
var minDamage = damagePiercing * weapon.minDamage
var maxDamage = damagePiercing * weapon.maxDamage
@@ -101,18 +110,20 @@ class Weapon extends React.Component<IWeaponProps, any> {
[`&.${tableCellClasses.head}`]: {
backgroundColor: "rgb(234, 234, 234)",
color: theme.palette.common.white,
paddingLeft: 10
},
[`&.${tableCellClasses.body}`]: {
fontSize: 14,
fontSize: 13,
paddingLeft: 10,
},
}));
return (
<div>
<h4>{this.humanReadableName(weapon.name)}</h4>
<h4>{ weapon.name ? weapon.name : this.humanReadableName(weapon.filename)}</h4>
<Grid2 container spacing={2}>
<Grid2 size={3}>
<Grid2 size= {{xs: 12, md: 3}}>
<TableContainer component={Paper}>
<Table size="small" aria-label="a dense table">
<TableBody>
@@ -163,11 +174,37 @@ class Weapon extends React.Component<IWeaponProps, any> {
scope="row">{weapon.setupTime != 0 ? (weapon.setupTime).toFixed(2) : "-"}
</TableCell>
</TableRow>
{(weapon.costRequisition > 0 || weapon.costPower > 0) &&
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">Cost</TableCell>
<TableCell component="th"
scope="row">
{weapon.costRequisition > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_requisition.gif"/>&nbsp;
{weapon.costRequisition}</span>}
{weapon.costPower > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_power.gif"/>&nbsp;
{weapon.costPower}</span>}
</TableCell>
</TableRow>
}
</TableBody>
</Table>
</TableContainer>
</Grid2>
<Grid2 size={9}>
<div style={{whiteSpace: "pre-wrap"}}>
{!this.props.isDefault && weapon.icon ? <img className="unitIcon" src={IconUrl + weapon.icon.replaceAll('\\', '/')}/> : (
weapon.isMeleeWeapon ?
<img src="/images/MeleeStance_icon_bw.jpg"/> :
<img src="/images/RangedStance_icon_bw.jpg"/>
) } <br/>
{this.props.isDefault ? "Default weapon" : weapon.description}
</div>
</Grid2>
<Grid2 size={12}>
<ToggleButtonGroup
color="primary"
exclusive
@@ -179,57 +216,62 @@ class Weapon extends React.Component<IWeaponProps, any> {
<ToggleButton size="small" value="one hit">One hit average damage</ToggleButton>
</ToggleButtonGroup>
<TableContainer>
<Table sx={{minWidth: 600, marginTop: "10px"}} size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<StyledTableCell ><ArmorType name={ArmorTypeNames.InfantryLow}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.InfantryMedium}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.InfantryHigh}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.InfantryHeavyMedium}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.InfantryHeavyHigh}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.DemonMedium}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.DemonHigh}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.Commander}/></StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<StyledTableCell>{getTotalDamage(infLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infHeavyMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infHeavyHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(demonPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(demonHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(commanderPiercing)}</StyledTableCell>
</TableRow>
</TableBody>
<TableHead>
<TableRow >
<StyledTableCell><ArmorType name={ArmorTypeNames.Air}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.VehicleLow}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.VehicleMedium}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.VehicleHigh}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.BuildingLow}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.BuildingMedium}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.BuildingHigh}/></StyledTableCell>
<StyledTableCell><img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/></StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<StyledTableCell>{getTotalDamage(airPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(vehLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(vehMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(vehHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(buildingLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(buildingMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(buildingHighPiercing)}</StyledTableCell>
<StyledTableCell>{getMoraleDamage()}</StyledTableCell>
</TableRow>
</TableBody>
</Table>
<Table sx={{marginTop: "10px"}} size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<StyledTableCell><ArmorType
name={ArmorTypeNames.InfantryLow}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.InfantryMedium}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.InfantryHigh}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.InfantryHeavyMedium}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.InfantryHeavyHigh}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.Commander}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.DemonMedium}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.DemonHigh}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.Air}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.VehicleLow}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.VehicleMedium}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.VehicleHigh}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.BuildingLow}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.BuildingMedium}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.BuildingHigh}/></StyledTableCell>
<StyledTableCell><img style={{verticalAlign: "top"}}
src="/images/ARM_Morale.webp"/></StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<StyledTableCell>{getTotalDamage(infLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infHeavyMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infHeavyHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(commanderPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(demonPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(demonHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(airPiercing, true)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(vehLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(vehMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(vehHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(buildingLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(buildingMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(buildingHighPiercing)}</StyledTableCell>
<StyledTableCell>{getMoraleDamage()}</StyledTableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
<i className="rgdFrom">{weapon.filename}</i>
</Grid2>
</Grid2>
</div>
+37
View File
@@ -0,0 +1,37 @@
import Weapon from "./Weapon";
import React from "react";
import {IWeapon} from "../types/IUnit";
export interface WeaponSlotProps {
hardpoint: number,
unitWeapons: Map<number, IWeapon> | undefined
}
const WeaponSlot= (props: WeaponSlotProps) => {
let weaponThisSlotMap = props.unitWeapons ?? []
let weaponsSorted = [...weaponThisSlotMap].sort()
let header = "Weapon slot " + props.hardpoint
let haveDummy = weaponsSorted.find(wp => !wp[1].filename.includes("dummy"))
let firstWeapon = weaponsSorted[0][1]
let onlyDummy = weaponsSorted.filter(wp => !wp[1].filename.includes("dummy")).length === 0
if(onlyDummy) return (<div></div>)
return (
<div>
<h3>{header}</h3>
{weaponsSorted.filter(wp => !wp[1].filename.includes("dummy")).map(wp =>
<div style={{marginLeft: 20}}><Weapon isDefault={!haveDummy || wp[1].filename === firstWeapon.filename} weapon={wp[1]}/></div>
)}
</div>
)
}
export default WeaponSlot;
+8 -3
View File
@@ -1,5 +1,10 @@
.unitIcon{
width: 64px;
width: 50px;
}
.sergeantIcon{
padding: 0px;
width: 40px;
}
.unitHeader{
@@ -16,6 +21,6 @@
}
#filtered-units{
margin-top: 10px;
.rgdFrom{
font-size: 11px;
}
+19 -29
View File
@@ -1,57 +1,45 @@
import {AvailableMods, AvailableRacesPart} from "../core/api";
import {AvailableMods, AvailableRacesPart, AvailableUnits, IconUrl} from "../core/api";
import React, {useEffect, useRef, useState} from "react";
import {NavLink, useLocation, useParams} from "react-router-dom";
import {withRouter} from "../core/withrouter";
import {IMod} from "../types/Imod";
import {Irace} from "../types/Irace";
import {Button} from "@mui/material";
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 UnitsTable from "../classes/UnitsTable";
interface ModPageState {
mod: IMod,
races: Irace[],
modId: Number,
}
class ModPage extends React.Component<any, ModPageState> {
constructor(props: any) {
super(props);
console.log(this.props.match.params.id);
this.setState({
modId : this.props.match.params.id
});
fetch(AvailableMods + "/" + this.props.match.params.modId)
.then(res => res.json())
.then((res: IMod) => {
this.setState({
mod : res
});
});
}
async componentDidMount() {
const responseMod = await fetch(AvailableMods + "/" + this.props.match.params.id);
const modData: IMod = await responseMod.json();
console.log(modData);
this.setState({
mod : modData
});
const response = await fetch(AvailableRacesPart + "/mod/" + this.props.match.params.id);
const racesData: Irace[] = await response.json();
this.setState({
races : racesData
});
}
render() {
if(this.state != null && this.state.races != null ){
if(this.state != null && this.state.mod != null ){
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>
{this.state.races.map(race => <ul><NavLink state={race.id}
to={"/mod/" + this.state.mod.id + "/race/" + race.id}>{race.name}</NavLink>
</ul>)}
<UnitsTable modId={this.state.mod.id}/>
</div>;
} else {
return "";
@@ -59,4 +47,6 @@ class ModPage extends React.Component<any, ModPageState> {
}
}
export default withRouter(ModPage);
+7 -1
View File
@@ -24,7 +24,13 @@ function Mods (mods: IMod[]) {
return(
<div>
<h1>{modName}</h1>
{sameMods.map(mod => <ul><NavLink state={mod.id} to= {"/mod/" + mod.id} >{mod.version}</NavLink></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>}
{sameMods.find((m) => m.isBeta) != null &&
sameMods.filter((m) => m.isBeta).map(mod =>
<ul><NavLink state={mod.id} to= {"/mod/" + mod.id} >{mod.version}</NavLink></ul>)}
</div>
)
}
-296
View File
@@ -1,296 +0,0 @@
import {AvailableMods, AvailableRacesPart, AvailableUnits, IconUrl} from "../core/api";
import React, {useState} from "react";
import {withRouter} from "../core/withrouter";
import {IMod} from "../types/Imod";
import {Irace} from "../types/Irace";
import {IUnit, IUnitResponse, IWeapon} from "../types/IUnit";
import '../css/Unit.css'
import {
Accordion,
AccordionDetails,
AccordionSummary,
Button,
ButtonGroup,
Grid2,
List,
ListItem,
ListSubheader,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
ToggleButton,
ToggleButtonClassKey,
ToggleButtonGroup,
Typography
} from "@mui/material";
import {ArrowBack, ArrowDropDown} from "@mui/icons-material";
import ArmorType from "../classes/ArmorType";
import Weapon from "../classes/Weapon";
interface RacePageState {
mod: IMod,
modId: Number,
race: Irace,
units: IUnitResponse,
}
function Unit (unit: IUnit) {
const morale = (unit.moraleMax !== null) ? <span>
<img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/>&nbsp;{unit.moraleMax}
+{unit.moraleRegeneration}/s
{unit.moraleDeathPenalty > 0 && <span> <img style={{verticalAlign: "top"}} src="/images/Kills_icon.webp"/> -{unit.moraleDeathPenalty}</span>}
</span> : "-"
const detect = unit.detectRadius > 0 ? <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/DETECT_YES.webp"/> {unit.detectRadius}</span> :
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/DETECT_NO.webp"/></span>
let mapWithUnitWeapons: Map<number, IWeapon[]> = new Map();
unit.weapons.forEach( weapon => {
const weaponList = mapWithUnitWeapons.get(weapon.hardpoint)
if(weaponList == null){
mapWithUnitWeapons.set(weapon.hardpoint, new Array(weapon.weapon))
} else {
weaponList.push(weapon.weapon)
}
})
function WeaponSlot (hardpoint: number) {
let weaponThisSlot = mapWithUnitWeapons.get(hardpoint) ?? []
let header = "Weapon slot " + hardpoint
return(
<div>
<h3>{header}</h3>
{weaponThisSlot.map(weapon => <div><Weapon weapon={weapon}/>
</div>)}
</div>
)
}
return (
<Accordion>
<AccordionSummary
expandIcon={<ArrowDropDown/>}
aria-controls="panel1-content"
id={unit.filename}
>
<img className="unitIcon" src={IconUrl + unit.icon.replaceAll('\\', '/')}/>
<h2> {unit.name}</h2>
{unit.detectRadius > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/DETECT_YES.webp"/></span>}
</AccordionSummary>
<AccordionDetails>
<Grid2 container spacing={2}>
<Grid2 size={4}>
<TableContainer component={Paper}>
<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">
{unit.buildCostRequisition > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_requisition.gif"/>&nbsp;
{unit.buildCostRequisition}</span>}
{unit.buildCostPower > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_power.gif"/>&nbsp;
{unit.buildCostPower}</span>}
{unit.capInfantry > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_cap_infantry.gif"/>&nbsp;
{unit.capInfantry}</span>}
{unit.capSupport > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_cap_vehicle.gif"/>&nbsp;
{unit.capSupport}</span>}
</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">
<ArmorType name={unit.armorType.name} withName={true}/>
</TableCell>
</TableRow>
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">health</TableCell>
<TableCell component="th" scope="row">
<span><img style={{verticalAlign: "top"}}
src="/images/Health_icon.webp"/>&nbsp;
{unit.health} {unit.healthRegeneration > 0 &&
<span>+{unit.healthRegeneration}/s</span>} </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>
<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>
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">detect</TableCell>
<TableCell component="th" scope="row">
{detect}
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid2>
<Grid2 size={8}>
<div style={{whiteSpace: "pre-wrap"}} >
{unit.description}
</div>
</Grid2>
<Grid2 size={12}>
{[...mapWithUnitWeapons.keys()].sort(function(a, b) {
return a - b;
}).map(h => WeaponSlot(h))}
</Grid2>
</Grid2>
<i>{unit.filename}</i>
</AccordionDetails>
</Accordion>)
}
interface UnitsState {
selectedUnits: String | null,
}
class Units extends React.Component<IUnitResponse, UnitsState> {
constructor(props: any) {
super(props);
this.state = ({
selectedUnits: 'infantry'
});
}
handleChange = (e: React.MouseEvent, newUnitType: String | null) => {
this.setState({
selectedUnits: newUnitType
});
}
render () {
let unitsToRender: IUnit[] = [];
if(this.state.selectedUnits === 'infantry'){
unitsToRender = this.props.units.filter(unit => unit.capInfantry > 0)
} else if (this.state.selectedUnits === 'tech'){
unitsToRender = this.props.units.filter(unit => unit.capSupport > 0)
} else if (this.state.selectedUnits === 'support'){
unitsToRender = this.props.units.filter(unit => unit.capSupport == 0 && unit.capInfantry == 0)
} else {
unitsToRender = this.props.units
}
if (this.state) {
return (<div>
<ToggleButtonGroup
color="primary"
exclusive
onChange={this.handleChange}
value={this.state.selectedUnits}
aria-label="Platform"
>
<ToggleButton value="infantry">Infantry</ToggleButton>
<ToggleButton value="tech">Tech</ToggleButton>
<ToggleButton value="support">Support</ToggleButton>
</ToggleButtonGroup>
<div id = 'filtered-units'>
{unitsToRender.map(unit => Unit(unit))}
</div>
</div>)
} else {
return "";
}
}
}
class RacePage extends React.Component<any, RacePageState> {
constructor(props: any) {
super(props);
console.log(this.props.match.params.id);
this.setState({
modId: this.props.match.params.id
});
}
async componentDidMount() {
const responseMod = await fetch(AvailableMods + "/" + this.props.match.params.modId);
const modData: IMod = await responseMod.json();
this.setState({
mod: modData
});
const response = await fetch(AvailableRacesPart + "/" + this.props.match.params.raceId);
const racesData: Irace = await response.json();
this.setState({
race : racesData
});
const unitsResponse = await fetch(AvailableUnits + "/" + this.state.mod.id + "/" + this.props.match.params.raceId);
const unitsData: IUnitResponse = await unitsResponse.json();
console.log(unitsData);
this.setState({
units : unitsData
});
}
render() {
if(this.state != null && this.state.units != null ){
const backRef = "/mod/" + this.state.mod.id
return <div><a href={backRef}><Button id="back-button" variant="contained" startIcon={<ArrowBack/>}> Back</Button></a>
<h1>{this.state.mod.name} ({this.state.mod.version}) - {this.state.race.name}</h1>
<Units race={this.state.race.name} units={this.state.units.units}/>
</div>;
} else {
return "loading...";
}
}
}
export default withRouter(RacePage);
+163
View File
@@ -0,0 +1,163 @@
import {AvailableMods, AvailableRacesPart, AvailableUnits, IconUrl} from "../core/api";
import React, {useState} from "react";
import {withRouter} from "../core/withrouter";
import {IMod} from "../types/Imod";
import {Irace} from "../types/Irace";
import '../css/Unit.css'
import {
Accordion,
AccordionDetails,
AccordionSummary,
Button,
ButtonGroup,
Grid2,
List,
ListItem,
ListSubheader,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
ToggleButton,
ToggleButtonClassKey,
ToggleButtonGroup,
Typography
} from "@mui/material";
import {ArrowBack, ArrowDropDown} from "@mui/icons-material";
import ArmorType from "../classes/ArmorType";
import Weapon from "../classes/Weapon";
import {IRaceUnits, IUnitShort} from "../types/IUnitShort";
interface RacePageState {
mod: IMod,
race: Irace,
units: IUnitShort[],
}
function Unit (unit: IUnitShort, modId: number, raceId: String) {
return (<a href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id}><ListItem>
{unit.icon && <img className="unitIcon" src={IconUrl + unit.icon.replaceAll('\\', '/')}/> }
{unit.name}
{unit.canDetect && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/DETECT_YES.webp"/></span>}
</ListItem></a>)
}
interface UnitsProps{
raceId: string,
modId: number,
}
interface UnitsState {
selectedUnits: String | null,
units: IRaceUnits | null,
}
class Units extends React.Component<UnitsProps, UnitsState> {
constructor(props: any) {
super(props);
let url = AvailableUnits + "/" + this.props.modId + "/" + this.props.raceId;
fetch(url)
.then(res => res.json())
.then((res: IRaceUnits) => {
this.setState({
units: res,
})
})
}
handleChange = (e: React.MouseEvent, newUnitType: String | null) => {
this.setState({
selectedUnits: newUnitType
});
}
render () {
if (this.state && this.state.units) {
return (<div>
{this.state.units != null ?
<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>
</Grid2>
: "Loading"}
</div>)
} else {
return "Loading...";
}
}
}
class RacePageFast extends React.Component<any, RacePageState> {
constructor(props: any) {
super(props);
console.log(this.props.match.params.id);
}
async componentDidMount() {
const responseMod = await fetch(AvailableMods + "/" + this.props.match.params.modId);
const modData: IMod = await responseMod.json();
this.setState({
mod: modData
});
const response = await fetch(AvailableRacesPart + "/" + this.props.match.params.raceId);
const racesData: Irace = await response.json();
this.setState({
race : racesData
});
}
render() {
if(this.state != null && this.state.mod != null && this.state.race != null ){
const backRef = "/mod/" + this.state.mod.id
return <div><a href={backRef}><Button id="back-button" variant="contained" startIcon={<ArrowBack/>}> Back</Button></a>
<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>;
} else {
return "loading...";
}
}
}
export default withRouter(RacePageFast);
+263
View File
@@ -0,0 +1,263 @@
import {AvailableMods, AvailableUnits, IconUrl} from "../core/api";
import React from "react";
import {withRouter} from "../core/withrouter";
import {IUnit, IWeapon} from "../types/IUnit";
import '../css/Unit.css'
import {
Accordion,
AccordionDetails,
AccordionSummary,
Button,
Grid2,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableRow
} from "@mui/material";
import {ArrowBack, ExpandMore} from "@mui/icons-material";
import ArmorType from "../classes/ArmorType";
import AvTimerOutlinedIcon from '@mui/icons-material/AvTimer';
import Sergeant from "../classes/Sergeant";
import WeaponSlot from "../classes/WeaponSlot";
import UnitsTable from "../classes/UnitsTable";
import {IMod} from "../types/Imod";
interface UintPageState {
unit: IUnit,
mod: IMod,
}
function Unit(unit: IUnit, mod: IMod) {
const morale = (unit.moraleMax !== null) ? <span>
<img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/>&nbsp;{unit.moraleMax}
+{unit.moraleRegeneration}/s
{unit.moraleDeathPenalty > 0 && <span> <img style={{verticalAlign: "top"}}
src="/images/Kills_icon.webp"/> -{unit.moraleDeathPenalty}</span>}
</span> : "-"
const detect = unit.detectRadius > 0 ? <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/DETECT_YES.webp"/> {unit.detectRadius}</span> :
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/DETECT_NO.webp"/></span>
let mapWithUnitWeapons: Map<number, Map<number, IWeapon>> = new Map();
unit.weapons.forEach(weapon => {
const weaponMap = mapWithUnitWeapons.get(weapon.hardpoint)
if (weaponMap == null) {
const weaponMap = new Map()
weaponMap.set(weapon.hardpointOrder, weapon.weapon)
mapWithUnitWeapons.set(weapon.hardpoint, weaponMap)
} else {
weaponMap.set(weapon.hardpointOrder, weapon.weapon)
}
})
type sergeantProps = {
name: string
icon: String
canDetect: Boolean
}
const SergeantShort = (props: sergeantProps) => {
return (
<span style={{fontSize: 20}}>
{props.icon && <img className="sergeantIcon" src={IconUrl + props.icon.replaceAll('\\', '/')}/> }
&nbsp; {props.name}
{props.canDetect && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/DETECT_YES.webp"/></span>}
</span>
)
}
console.log(unit);
return (
<div>
<h1>{mod.name} ({mod.version})</h1>
<h2>{unit.icon &&
<img className="unitIcon" src={IconUrl + unit.icon.replaceAll('\\', '/')}/>} {unit.name} </h2>
<Grid2 container spacing={2}>
<Grid2 size={{xs: 12, md: 4}}>
<TableContainer component={Paper}>
<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">
{unit.buildCostRequisition > 0 &&
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_requisition.gif"/>&nbsp;
{unit.buildCostRequisition}</span>}
{unit.buildCostPower > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_power.gif"/>&nbsp;
{unit.buildCostPower}</span>}
{(unit.buildCostPopulation !== undefined && unit.buildCostPopulation > 0) &&
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_orksquadcap.gif"/>&nbsp;
{unit.buildCostPopulation}</span>}
{unit.capInfantry > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_cap_infantry.gif"/>&nbsp;
{unit.capInfantry}</span>}
{unit.capSupport > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_cap_vehicle.gif"/>&nbsp;
{unit.capSupport}</span>}
{(unit.buildCostTime !== undefined && unit.buildCostTime > 0) &&
<span>&nbsp;<AvTimerOutlinedIcon
style={{verticalAlign: "top", fontSize: "18px"}}/>&nbsp;
{unit.buildCostTime}s</span>}
</TableCell>
</TableRow>
{(unit?.reinforceTime !== 0 && unit.reinforceTime !== null) &&
<TableRow>
<TableCell>reinforce cost</TableCell>
<TableCell>
{unit.reinforceCostRequisition && unit.reinforceCostRequisition > 0 &&
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_requisition.gif"/>&nbsp;
{unit.reinforceCostRequisition}</span>}
{unit.reinforceCostPower !== undefined && unit.reinforceCostPower > 0 &&
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_power.gif"/>&nbsp;
{unit.reinforceCostPower}</span>}
{(unit.reinforceCostPopulation !== undefined && unit.reinforceCostPopulation > 0) &&
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_orksquadcap.gif"/>&nbsp;
{unit.reinforceCostPopulation}</span>}
{(unit.reinforceTime !== undefined && unit.reinforceTime > 0) &&
<span>&nbsp;<AvTimerOutlinedIcon
style={{verticalAlign: "top", fontSize: "18px"}}/>&nbsp;
{unit.reinforceTime}s</span>}
</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">
<ArmorType name={unit.armorType.name}/>
</TableCell>
</TableRow>
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">health</TableCell>
<TableCell component="th" scope="row">
<span><img style={{verticalAlign: "top"}}
src="/images/Health_icon.webp"/>&nbsp;
{unit.health} {unit.healthRegeneration > 0 &&
<span>+{unit.healthRegeneration}/s</span>} </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>
<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>
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">detect</TableCell>
<TableCell component="th" scope="row">
{detect}
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid2>
<Grid2 size={8}>
<div style={{whiteSpace: "pre-wrap"}}>
{unit.description}
</div>
</Grid2>
<Grid2 size={12}>
{unit.sergeants.map(s =>
<Accordion>
<AccordionSummary
expandIcon={<ExpandMore/>}
aria-controls="panel1-content"
id="panel1-header"
>
<SergeantShort name={s.name} icon={s.icon} canDetect={s.detectRadius > 0}/>
</AccordionSummary>
<AccordionDetails>
<Sergeant sergeant={s}/>
<hr/>
</AccordionDetails>
</Accordion>)}
</Grid2>
<Grid2 size={12}>
{[...mapWithUnitWeapons.keys()].sort(function (a, b) {
return a - b;
}).map(h => <WeaponSlot unitWeapons={mapWithUnitWeapons.get(h)} hardpoint={h}/>)}
</Grid2>
</Grid2>
<i className="rgdFrom">{unit.filename}</i>
<hr/>
<UnitsTable modId={mod.id}/>
</div>)
}
class UnitPage extends React.Component<any, UintPageState> {
async componentDidMount() {
const unitsResponse = await fetch(AvailableUnits + "/" + this.props.match.params.unitId);
const unitsData: IUnit = await unitsResponse.json();
this.setState({
unit: unitsData
});
const responseMod = await fetch(AvailableMods + "/" + this.props.match.params.modId);
const modData: IMod = await responseMod.json();
this.setState({
mod: modData
});
}
render() {
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><a href={backRef}><Button id="back-button" variant="contained"
startIcon={<ArrowBack/>}> Back</Button></a>
{Unit(this.state.unit, this.state.mod)}
</div>;
} else {
return "loading...";
}
}
}
export default withRouter(UnitPage);
+38 -2
View File
@@ -16,6 +16,10 @@ export interface IUnit {
description: string
buildCostRequisition: number
buildCostPower: number
buildCostPopulation: number
buildCostFaith: number
buildCostSouls: number
buildCostTime: number
capInfantry: number
capSupport: number
squadStartSize: number
@@ -34,9 +38,36 @@ export interface IUnit {
detectRadius: number
reinforceCostRequisition?: number
reinforceCostPower?: number
reinforceCostPopulation ?: number
reinforceCostFaith ?: number
reinforceTime?: number
icon: string
modId: number
sergeants: ISergeant[]
weapons: WeaponHardpoint[]
}
export interface ISergeant {
id: number
armorType: IArmorType
armorType2?: IArmorType
name: string
filename: string
description: string
buildCostRequisition: number
buildCostPower: number
buildCostPopulation: number
buildCostFaith: number
buildCostSouls: number
buildCostTime: number
health: number
healthRegeneration: number
moraleDeathPenalty: number
mass: number
upTime: number
sightRadius: number
detectRadius: number
icon: string
weapons: WeaponHardpoint[]
}
@@ -44,11 +75,14 @@ export interface IUnit {
export interface WeaponHardpoint {
weapon: IWeapon
hardpoint: number
hardpointOrder: number
}
export interface IWeapon {
id: number
name: string
filename: string
description: string
costRequisition: number
costPower: number
costTimeSeconds: number
@@ -64,11 +98,13 @@ export interface IWeapon {
isMeleeWeapon: boolean
canAttackAir: boolean
canAttackGround: boolean
haveEquipButton: boolean
icon: string
modId: number
weaponPiercings: WeaponPiercing[]
weaponPiercings: IWeaponPiercing[]
}
export interface WeaponPiercing {
export interface IWeaponPiercing {
id: number
armorType: IArmorType
piercingValue: number
+16
View File
@@ -0,0 +1,16 @@
import {Irace} from "./Irace";
export interface IRaceUnits {
race: Irace
infantry: IUnitShort[]
tech: IUnitShort[]
support: IUnitShort[]
}
export interface IUnitShort {
name: string
icon: string
id: number
canDetect: boolean
armourTypeName: string
}