Sergeants frontend
This commit is contained in:
+3
-2
@@ -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
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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> <img style={{verticalAlign: "top"}}
|
||||
src="/images/DETECT_YES.webp"/> {sergeant.detectRadius}</span> :
|
||||
<span> <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> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_requisition.gif"/>
|
||||
{sergeant.buildCostRequisition}</span>}
|
||||
{sergeant.buildCostPower > 0 && <span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_power.gif"/>
|
||||
{sergeant.buildCostPower}</span>}
|
||||
{(sergeant.buildCostPopulation !== undefined && sergeant.buildCostPopulation > 0) && <span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_orksquadcap.gif"/>
|
||||
{sergeant.buildCostPopulation}</span>}
|
||||
|
||||
{(sergeant.buildCostTime !== undefined && sergeant.buildCostTime > 0) && <span> <AvTimerOutlinedIcon style={{verticalAlign: "top", fontSize:"18px"}} />
|
||||
{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"/>
|
||||
{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;
|
||||
|
||||
@@ -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
@@ -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> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_requisition.gif"/>
|
||||
{weapon.costRequisition}</span>}
|
||||
{weapon.costPower > 0 && <span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_power.gif"/>
|
||||
{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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"/> {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> <img style={{verticalAlign: "top"}}
|
||||
src="/images/DETECT_YES.webp"/> {unit.detectRadius}</span> :
|
||||
<span> <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> <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> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_requisition.gif"/>
|
||||
{unit.buildCostRequisition}</span>}
|
||||
{unit.buildCostPower > 0 && <span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_power.gif"/>
|
||||
{unit.buildCostPower}</span>}
|
||||
{unit.capInfantry > 0 && <span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_cap_infantry.gif"/>
|
||||
{unit.capInfantry}</span>}
|
||||
{unit.capSupport > 0 && <span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_cap_vehicle.gif"/>
|
||||
{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"/>
|
||||
{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);
|
||||
@@ -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> <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);
|
||||
@@ -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"/> {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> <img style={{verticalAlign: "top"}}
|
||||
src="/images/DETECT_YES.webp"/> {unit.detectRadius}</span> :
|
||||
<span> <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('\\', '/')}/> }
|
||||
{props.name}
|
||||
{props.canDetect && <span> <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> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_requisition.gif"/>
|
||||
{unit.buildCostRequisition}</span>}
|
||||
{unit.buildCostPower > 0 && <span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_power.gif"/>
|
||||
{unit.buildCostPower}</span>}
|
||||
{(unit.buildCostPopulation !== undefined && unit.buildCostPopulation > 0) &&
|
||||
<span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_orksquadcap.gif"/>
|
||||
{unit.buildCostPopulation}</span>}
|
||||
|
||||
{unit.capInfantry > 0 && <span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_cap_infantry.gif"/>
|
||||
{unit.capInfantry}</span>}
|
||||
{unit.capSupport > 0 && <span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_cap_vehicle.gif"/>
|
||||
{unit.capSupport}</span>}
|
||||
{(unit.buildCostTime !== undefined && unit.buildCostTime > 0) &&
|
||||
<span> <AvTimerOutlinedIcon
|
||||
style={{verticalAlign: "top", fontSize: "18px"}}/>
|
||||
{unit.buildCostTime}s</span>}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{(unit?.reinforceTime !== 0 && unit.reinforceTime !== null) &&
|
||||
<TableRow>
|
||||
<TableCell>reinforce cost</TableCell>
|
||||
<TableCell>
|
||||
{unit.reinforceCostRequisition && unit.reinforceCostRequisition > 0 &&
|
||||
<span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_requisition.gif"/>
|
||||
{unit.reinforceCostRequisition}</span>}
|
||||
{unit.reinforceCostPower !== undefined && unit.reinforceCostPower > 0 &&
|
||||
<span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_power.gif"/>
|
||||
{unit.reinforceCostPower}</span>}
|
||||
{(unit.reinforceCostPopulation !== undefined && unit.reinforceCostPopulation > 0) &&
|
||||
<span> <img style={{verticalAlign: "top"}}
|
||||
src="/images/Resource_orksquadcap.gif"/>
|
||||
{unit.reinforceCostPopulation}</span>}
|
||||
{(unit.reinforceTime !== undefined && unit.reinforceTime > 0) &&
|
||||
<span> <AvTimerOutlinedIcon
|
||||
style={{verticalAlign: "top", fontSize: "18px"}}/>
|
||||
{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"/>
|
||||
{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
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user