Sergeants frontend
This commit is contained in:
parent
2b4352ff21
commit
98b5a47c0f
72
README.md
72
README.md
@ -1,70 +1,6 @@
|
|||||||
# Getting Started with Create React App
|
# Soulstorm wiki frontend
|
||||||
|
|
||||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
## Стек технологий:
|
||||||
|
|
||||||
## Available Scripts
|
- react (вперемешку классы и функции)
|
||||||
|
- typescript
|
||||||
In the project directory, you can run:
|
|
||||||
|
|
||||||
### `npm start`
|
|
||||||
|
|
||||||
Runs the app in the development mode.\
|
|
||||||
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
|
||||||
|
|
||||||
The page will reload when you make changes.\
|
|
||||||
You may also see any lint errors in the console.
|
|
||||||
|
|
||||||
### `npm test`
|
|
||||||
|
|
||||||
Launches the test runner in the interactive watch mode.\
|
|
||||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
|
||||||
|
|
||||||
### `npm run build`
|
|
||||||
|
|
||||||
Builds the app for production to the `build` folder.\
|
|
||||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
|
||||||
|
|
||||||
The build is minified and the filenames include the hashes.\
|
|
||||||
Your app is ready to be deployed!
|
|
||||||
|
|
||||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
|
||||||
|
|
||||||
### `npm run eject`
|
|
||||||
|
|
||||||
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
|
|
||||||
|
|
||||||
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
|
||||||
|
|
||||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
|
|
||||||
|
|
||||||
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
|
||||||
|
|
||||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
|
||||||
|
|
||||||
### Code Splitting
|
|
||||||
|
|
||||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
|
||||||
|
|
||||||
### Analyzing the Bundle Size
|
|
||||||
|
|
||||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
|
||||||
|
|
||||||
### Making a Progressive Web App
|
|
||||||
|
|
||||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
|
||||||
|
|
||||||
### Advanced Configuration
|
|
||||||
|
|
||||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
|
||||||
|
|
||||||
### Deployment
|
|
||||||
|
|
||||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
|
||||||
|
|
||||||
### `npm run build` fails to minify
|
|
||||||
|
|
||||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
|
||||||
BIN
public/images/MeleeStance_icon_bw.jpg
Normal file
BIN
public/images/MeleeStance_icon_bw.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
BIN
public/images/RangedStance_icon_bw.jpg
Normal file
BIN
public/images/RangedStance_icon_bw.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/images/Resource_orksquadcap.gif
Normal file
BIN
public/images/Resource_orksquadcap.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 166 B |
@ -24,8 +24,9 @@ function App() {
|
|||||||
cssVariables: true,
|
cssVariables: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div >
|
||||||
<AppBar position="static">
|
<AppBar position="static">
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<IconButton
|
<IconButton
|
||||||
@ -37,7 +38,7 @@ function App() {
|
|||||||
>
|
>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography variant="h6" component="div" sx={{flexGrow: 1}}>
|
<Typography variant="h6" component="div" sx={{flexGrow: 1}}>
|
||||||
Soulstorm wiki
|
Soulstorm autogeneration wiki
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button color="inherit" onClick={steamLogin}>
|
<Button color="inherit" onClick={steamLogin}>
|
||||||
Log in through Steam
|
Log in through Steam
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import {Routes, Route, BrowserRouter} from "react-router-dom";
|
import {Routes, Route, BrowserRouter} from "react-router-dom";
|
||||||
import RacePage from "./pages/RacePage";
|
|
||||||
import ModsPage from "./pages/ModsPage";
|
import ModsPage from "./pages/ModsPage";
|
||||||
import ModPage from "./pages/ModPage";
|
import ModPage from "./pages/ModPage";
|
||||||
|
import RacePageFast from "./pages/RacePageFast";
|
||||||
|
import UnitPage from "./pages/UnitPage";
|
||||||
|
|
||||||
export const MyRoutes = () => {
|
export const MyRoutes = () => {
|
||||||
|
|
||||||
@ -9,8 +10,9 @@ export const MyRoutes = () => {
|
|||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<ModsPage/>}/>
|
<Route path="/" element={<ModsPage/>}/>
|
||||||
<Route path="/mod/:id" element={<ModPage/>}/>
|
<Route path="/mod/:modId" element={<ModPage/>}/>
|
||||||
<Route path="/mod/:modId/race/:raceId" element={<RacePage/>}/>
|
<Route path="/mod/:modId/race/:raceId" element={<RacePageFast/>}/>
|
||||||
|
<Route path="/mod/:modId/race/:raceId/unit/:unitId" element={<UnitPage/>}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import {Tooltip} from "@mui/material";
|
||||||
|
|
||||||
interface IArmorType{
|
interface IArmorType{
|
||||||
name: string,
|
name: string,
|
||||||
@ -11,8 +12,8 @@ class ArmorType extends React.Component<IArmorType, any> {
|
|||||||
withName: false
|
withName: false
|
||||||
};
|
};
|
||||||
|
|
||||||
renderArmorImage(armorType: String): string {
|
renderArmorImage(armorTypeId: string): string {
|
||||||
switch(armorType) {
|
switch(armorTypeId) {
|
||||||
case 'Infantry Low':
|
case 'Infantry Low':
|
||||||
return '/images/ARM_Inf_Lo.webp';
|
return '/images/ARM_Inf_Lo.webp';
|
||||||
case 'Infantry Medium':
|
case 'Infantry Medium':
|
||||||
@ -44,13 +45,14 @@ class ArmorType extends React.Component<IArmorType, any> {
|
|||||||
case 'Demon High':
|
case 'Demon High':
|
||||||
return '/images/ARM_Dmn_Hi.webp';
|
return '/images/ARM_Dmn_Hi.webp';
|
||||||
default:
|
default:
|
||||||
return 'Unknown armor';
|
return armorTypeId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
132
src/classes/Sergeant.tsx
Normal file
132
src/classes/Sergeant.tsx
Normal 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> <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;
|
||||||
|
|
||||||
140
src/classes/UnitsTable.tsx
Normal file
140
src/classes/UnitsTable.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {IWeapon, WeaponPiercing} from "../types/IUnit";
|
import {IWeapon, IWeaponPiercing} from "../types/IUnit";
|
||||||
import {
|
import {
|
||||||
Grid2,
|
Grid2,
|
||||||
Paper,
|
Paper,
|
||||||
@ -14,9 +14,11 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import ArmorType from "./ArmorType";
|
import ArmorType from "./ArmorType";
|
||||||
import ArmorTypeNames from "../types/ArmorTypeValues";
|
import ArmorTypeNames from "../types/ArmorTypeValues";
|
||||||
|
import {IconUrl} from "../core/api";
|
||||||
|
|
||||||
interface IWeaponProps{
|
interface IWeaponProps{
|
||||||
weapon: IWeapon,
|
weapon: IWeapon,
|
||||||
|
isDefault: Boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IWeaponState{
|
interface IWeaponState{
|
||||||
@ -25,6 +27,10 @@ interface IWeaponState{
|
|||||||
|
|
||||||
class Weapon extends React.Component<IWeaponProps, any> {
|
class Weapon extends React.Component<IWeaponProps, any> {
|
||||||
|
|
||||||
|
public static defaultProps = {
|
||||||
|
isDefault: false
|
||||||
|
};
|
||||||
|
|
||||||
humanReadableName(unit: string) {
|
humanReadableName(unit: string) {
|
||||||
const firstUpper = String(unit).charAt(0).toUpperCase() + String(unit).slice(1);
|
const firstUpper = String(unit).charAt(0).toUpperCase() + String(unit).slice(1);
|
||||||
return firstUpper.replaceAll('_', ' ').replace('.rgd', '');
|
return firstUpper.replaceAll('_', ' ').replace('.rgd', '');
|
||||||
@ -56,7 +62,7 @@ class Weapon extends React.Component<IWeaponProps, any> {
|
|||||||
const weapon = this.props.weapon
|
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 infLowPiercing = this.getPiercingK(ArmorTypeNames.InfantryLow)
|
||||||
const infMedPiercing = this.getPiercingK(ArmorTypeNames.InfantryMedium)
|
const infMedPiercing = this.getPiercingK(ArmorTypeNames.InfantryMedium)
|
||||||
@ -77,6 +83,9 @@ class Weapon extends React.Component<IWeaponProps, any> {
|
|||||||
|
|
||||||
const getTotalDamage = (damagePiercing: number, isAir: boolean = false) => {
|
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 minDamage = damagePiercing * weapon.minDamage
|
||||||
var maxDamage = damagePiercing * weapon.maxDamage
|
var maxDamage = damagePiercing * weapon.maxDamage
|
||||||
|
|
||||||
@ -101,18 +110,20 @@ class Weapon extends React.Component<IWeaponProps, any> {
|
|||||||
[`&.${tableCellClasses.head}`]: {
|
[`&.${tableCellClasses.head}`]: {
|
||||||
backgroundColor: "rgb(234, 234, 234)",
|
backgroundColor: "rgb(234, 234, 234)",
|
||||||
color: theme.palette.common.white,
|
color: theme.palette.common.white,
|
||||||
|
paddingLeft: 10
|
||||||
},
|
},
|
||||||
[`&.${tableCellClasses.body}`]: {
|
[`&.${tableCellClasses.body}`]: {
|
||||||
fontSize: 14,
|
fontSize: 13,
|
||||||
|
paddingLeft: 10,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h4>{this.humanReadableName(weapon.name)}</h4>
|
<h4>{ weapon.name ? weapon.name : this.humanReadableName(weapon.filename)}</h4>
|
||||||
<Grid2 container spacing={2}>
|
<Grid2 container spacing={2}>
|
||||||
|
|
||||||
<Grid2 size={3}>
|
<Grid2 size= {{xs: 12, md: 3}}>
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
<Table size="small" aria-label="a dense table">
|
<Table size="small" aria-label="a dense table">
|
||||||
<TableBody>
|
<TableBody>
|
||||||
@ -163,11 +174,37 @@ class Weapon extends React.Component<IWeaponProps, any> {
|
|||||||
scope="row">{weapon.setupTime != 0 ? (weapon.setupTime).toFixed(2) : "-"}
|
scope="row">{weapon.setupTime != 0 ? (weapon.setupTime).toFixed(2) : "-"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</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>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
<Grid2 size={9}>
|
<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
|
<ToggleButtonGroup
|
||||||
color="primary"
|
color="primary"
|
||||||
exclusive
|
exclusive
|
||||||
@ -179,57 +216,62 @@ class Weapon extends React.Component<IWeaponProps, any> {
|
|||||||
<ToggleButton size="small" value="one hit">One hit average damage</ToggleButton>
|
<ToggleButton size="small" value="one hit">One hit average damage</ToggleButton>
|
||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<Table sx={{minWidth: 600, marginTop: "10px"}} size="small" aria-label="a dense table">
|
<Table sx={{marginTop: "10px"}} size="small" aria-label="a dense table">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<StyledTableCell ><ArmorType name={ArmorTypeNames.InfantryLow}/></StyledTableCell>
|
<StyledTableCell><ArmorType
|
||||||
<StyledTableCell><ArmorType name={ArmorTypeNames.InfantryMedium}/></StyledTableCell>
|
name={ArmorTypeNames.InfantryLow}/></StyledTableCell>
|
||||||
<StyledTableCell><ArmorType name={ArmorTypeNames.InfantryHigh}/></StyledTableCell>
|
<StyledTableCell><ArmorType
|
||||||
<StyledTableCell><ArmorType name={ArmorTypeNames.InfantryHeavyMedium}/></StyledTableCell>
|
name={ArmorTypeNames.InfantryMedium}/></StyledTableCell>
|
||||||
<StyledTableCell><ArmorType name={ArmorTypeNames.InfantryHeavyHigh}/></StyledTableCell>
|
<StyledTableCell><ArmorType
|
||||||
<StyledTableCell><ArmorType name={ArmorTypeNames.DemonMedium}/></StyledTableCell>
|
name={ArmorTypeNames.InfantryHigh}/></StyledTableCell>
|
||||||
<StyledTableCell><ArmorType name={ArmorTypeNames.DemonHigh}/></StyledTableCell>
|
<StyledTableCell><ArmorType
|
||||||
<StyledTableCell><ArmorType name={ArmorTypeNames.Commander}/></StyledTableCell>
|
name={ArmorTypeNames.InfantryHeavyMedium}/></StyledTableCell>
|
||||||
</TableRow>
|
<StyledTableCell><ArmorType
|
||||||
</TableHead>
|
name={ArmorTypeNames.InfantryHeavyHigh}/></StyledTableCell>
|
||||||
<TableBody>
|
<StyledTableCell><ArmorType name={ArmorTypeNames.Commander}/></StyledTableCell>
|
||||||
<TableRow>
|
<StyledTableCell><ArmorType
|
||||||
<StyledTableCell>{getTotalDamage(infLowPiercing)}</StyledTableCell>
|
name={ArmorTypeNames.DemonMedium}/></StyledTableCell>
|
||||||
<StyledTableCell>{getTotalDamage(infMedPiercing)}</StyledTableCell>
|
<StyledTableCell><ArmorType name={ArmorTypeNames.DemonHigh}/></StyledTableCell>
|
||||||
<StyledTableCell>{getTotalDamage(infHighPiercing)}</StyledTableCell>
|
<StyledTableCell><ArmorType name={ArmorTypeNames.Air}/></StyledTableCell>
|
||||||
<StyledTableCell>{getTotalDamage(infHeavyMedPiercing)}</StyledTableCell>
|
<StyledTableCell><ArmorType name={ArmorTypeNames.VehicleLow}/></StyledTableCell>
|
||||||
<StyledTableCell>{getTotalDamage(infHeavyHighPiercing)}</StyledTableCell>
|
<StyledTableCell><ArmorType
|
||||||
<StyledTableCell>{getTotalDamage(demonPiercing)}</StyledTableCell>
|
name={ArmorTypeNames.VehicleMedium}/></StyledTableCell>
|
||||||
<StyledTableCell>{getTotalDamage(demonHighPiercing)}</StyledTableCell>
|
<StyledTableCell><ArmorType
|
||||||
<StyledTableCell>{getTotalDamage(commanderPiercing)}</StyledTableCell>
|
name={ArmorTypeNames.VehicleHigh}/></StyledTableCell>
|
||||||
</TableRow>
|
<StyledTableCell><ArmorType
|
||||||
</TableBody>
|
name={ArmorTypeNames.BuildingLow}/></StyledTableCell>
|
||||||
<TableHead>
|
<StyledTableCell><ArmorType
|
||||||
<TableRow >
|
name={ArmorTypeNames.BuildingMedium}/></StyledTableCell>
|
||||||
<StyledTableCell><ArmorType name={ArmorTypeNames.Air}/></StyledTableCell>
|
<StyledTableCell><ArmorType
|
||||||
<StyledTableCell><ArmorType name={ArmorTypeNames.VehicleLow}/></StyledTableCell>
|
name={ArmorTypeNames.BuildingHigh}/></StyledTableCell>
|
||||||
<StyledTableCell><ArmorType name={ArmorTypeNames.VehicleMedium}/></StyledTableCell>
|
<StyledTableCell><img style={{verticalAlign: "top"}}
|
||||||
<StyledTableCell><ArmorType name={ArmorTypeNames.VehicleHigh}/></StyledTableCell>
|
src="/images/ARM_Morale.webp"/></StyledTableCell>
|
||||||
<StyledTableCell><ArmorType name={ArmorTypeNames.BuildingLow}/></StyledTableCell>
|
</TableRow>
|
||||||
<StyledTableCell><ArmorType name={ArmorTypeNames.BuildingMedium}/></StyledTableCell>
|
</TableHead>
|
||||||
<StyledTableCell><ArmorType name={ArmorTypeNames.BuildingHigh}/></StyledTableCell>
|
<TableBody>
|
||||||
<StyledTableCell><img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/></StyledTableCell>
|
<TableRow>
|
||||||
</TableRow>
|
<StyledTableCell>{getTotalDamage(infLowPiercing)}</StyledTableCell>
|
||||||
</TableHead>
|
<StyledTableCell>{getTotalDamage(infMedPiercing)}</StyledTableCell>
|
||||||
<TableBody>
|
<StyledTableCell>{getTotalDamage(infHighPiercing)}</StyledTableCell>
|
||||||
<TableRow>
|
<StyledTableCell>{getTotalDamage(infHeavyMedPiercing)}</StyledTableCell>
|
||||||
<StyledTableCell>{getTotalDamage(airPiercing)}</StyledTableCell>
|
<StyledTableCell>{getTotalDamage(infHeavyHighPiercing)}</StyledTableCell>
|
||||||
<StyledTableCell>{getTotalDamage(vehLowPiercing)}</StyledTableCell>
|
<StyledTableCell>{getTotalDamage(commanderPiercing)}</StyledTableCell>
|
||||||
<StyledTableCell>{getTotalDamage(vehMedPiercing)}</StyledTableCell>
|
<StyledTableCell>{getTotalDamage(demonPiercing)}</StyledTableCell>
|
||||||
<StyledTableCell>{getTotalDamage(vehHighPiercing)}</StyledTableCell>
|
<StyledTableCell>{getTotalDamage(demonHighPiercing)}</StyledTableCell>
|
||||||
<StyledTableCell>{getTotalDamage(buildingLowPiercing)}</StyledTableCell>
|
<StyledTableCell>{getTotalDamage(airPiercing, true)}</StyledTableCell>
|
||||||
<StyledTableCell>{getTotalDamage(buildingMedPiercing)}</StyledTableCell>
|
<StyledTableCell>{getTotalDamage(vehLowPiercing)}</StyledTableCell>
|
||||||
<StyledTableCell>{getTotalDamage(buildingHighPiercing)}</StyledTableCell>
|
<StyledTableCell>{getTotalDamage(vehMedPiercing)}</StyledTableCell>
|
||||||
<StyledTableCell>{getMoraleDamage()}</StyledTableCell>
|
<StyledTableCell>{getTotalDamage(vehHighPiercing)}</StyledTableCell>
|
||||||
</TableRow>
|
<StyledTableCell>{getTotalDamage(buildingLowPiercing)}</StyledTableCell>
|
||||||
</TableBody>
|
<StyledTableCell>{getTotalDamage(buildingMedPiercing)}</StyledTableCell>
|
||||||
</Table>
|
<StyledTableCell>{getTotalDamage(buildingHighPiercing)}</StyledTableCell>
|
||||||
|
<StyledTableCell>{getMoraleDamage()}</StyledTableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
<i className="rgdFrom">{weapon.filename}</i>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
37
src/classes/WeaponSlot.tsx
Normal file
37
src/classes/WeaponSlot.tsx
Normal 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;
|
||||||
@ -1,5 +1,10 @@
|
|||||||
.unitIcon{
|
.unitIcon{
|
||||||
width: 64px;
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sergeantIcon{
|
||||||
|
padding: 0px;
|
||||||
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.unitHeader{
|
.unitHeader{
|
||||||
@ -16,6 +21,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#filtered-units{
|
.rgdFrom{
|
||||||
margin-top: 10px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
@ -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 React, {useEffect, useRef, useState} from "react";
|
||||||
import {NavLink, useLocation, useParams} from "react-router-dom";
|
import {NavLink, useLocation, useParams} from "react-router-dom";
|
||||||
import {withRouter} from "../core/withrouter";
|
import {withRouter} from "../core/withrouter";
|
||||||
import {IMod} from "../types/Imod";
|
import {IMod} from "../types/Imod";
|
||||||
import {Irace} from "../types/Irace";
|
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 {ArrowBack} from "@mui/icons-material";
|
||||||
|
import {IRaceUnits, IUnitShort} from "../types/IUnitShort";
|
||||||
|
import UnitsTable from "../classes/UnitsTable";
|
||||||
|
|
||||||
|
|
||||||
interface ModPageState {
|
interface ModPageState {
|
||||||
mod: IMod,
|
mod: IMod,
|
||||||
races: Irace[],
|
|
||||||
modId: Number,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ModPage extends React.Component<any, ModPageState> {
|
class ModPage extends React.Component<any, ModPageState> {
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
console.log(this.props.match.params.id);
|
fetch(AvailableMods + "/" + this.props.match.params.modId)
|
||||||
this.setState({
|
.then(res => res.json())
|
||||||
modId : this.props.match.params.id
|
.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() {
|
render() {
|
||||||
if(this.state != null && this.state.races != null ){
|
if(this.state != null && this.state.mod != null ){
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<a href="/"><Button id="back-button" variant="contained"
|
<a href="/"><Button id="back-button" variant="contained"
|
||||||
startIcon={<ArrowBack/>}> Back</Button></a>
|
startIcon={<ArrowBack/>}> Back</Button></a>
|
||||||
<h1>{this.state.mod.name} ({this.state.mod.version})</h1>
|
<h1>{this.state.mod.name} ({this.state.mod.version})</h1>
|
||||||
{this.state.races.map(race => <ul><NavLink state={race.id}
|
<UnitsTable modId={this.state.mod.id}/>
|
||||||
to={"/mod/" + this.state.mod.id + "/race/" + race.id}>{race.name}</NavLink>
|
|
||||||
</ul>)}
|
|
||||||
</div>;
|
</div>;
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
@ -59,4 +47,6 @@ class ModPage extends React.Component<any, ModPageState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default withRouter(ModPage);
|
export default withRouter(ModPage);
|
||||||
@ -24,7 +24,13 @@ function Mods (mods: IMod[]) {
|
|||||||
return(
|
return(
|
||||||
<div>
|
<div>
|
||||||
<h1>{modName}</h1>
|
<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>
|
</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);
|
|
||||||
163
src/pages/RacePageFast.tsx
Normal file
163
src/pages/RacePageFast.tsx
Normal 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> <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
src/pages/UnitPage.tsx
Normal file
263
src/pages/UnitPage.tsx
Normal 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"/> {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);
|
||||||
@ -16,6 +16,10 @@ export interface IUnit {
|
|||||||
description: string
|
description: string
|
||||||
buildCostRequisition: number
|
buildCostRequisition: number
|
||||||
buildCostPower: number
|
buildCostPower: number
|
||||||
|
buildCostPopulation: number
|
||||||
|
buildCostFaith: number
|
||||||
|
buildCostSouls: number
|
||||||
|
buildCostTime: number
|
||||||
capInfantry: number
|
capInfantry: number
|
||||||
capSupport: number
|
capSupport: number
|
||||||
squadStartSize: number
|
squadStartSize: number
|
||||||
@ -34,9 +38,36 @@ export interface IUnit {
|
|||||||
detectRadius: number
|
detectRadius: number
|
||||||
reinforceCostRequisition?: number
|
reinforceCostRequisition?: number
|
||||||
reinforceCostPower?: number
|
reinforceCostPower?: number
|
||||||
|
reinforceCostPopulation ?: number
|
||||||
|
reinforceCostFaith ?: number
|
||||||
reinforceTime?: number
|
reinforceTime?: number
|
||||||
icon: string
|
icon: string
|
||||||
modId: number
|
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[]
|
weapons: WeaponHardpoint[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,11 +75,14 @@ export interface IUnit {
|
|||||||
export interface WeaponHardpoint {
|
export interface WeaponHardpoint {
|
||||||
weapon: IWeapon
|
weapon: IWeapon
|
||||||
hardpoint: number
|
hardpoint: number
|
||||||
|
hardpointOrder: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWeapon {
|
export interface IWeapon {
|
||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
|
filename: string
|
||||||
|
description: string
|
||||||
costRequisition: number
|
costRequisition: number
|
||||||
costPower: number
|
costPower: number
|
||||||
costTimeSeconds: number
|
costTimeSeconds: number
|
||||||
@ -64,11 +98,13 @@ export interface IWeapon {
|
|||||||
isMeleeWeapon: boolean
|
isMeleeWeapon: boolean
|
||||||
canAttackAir: boolean
|
canAttackAir: boolean
|
||||||
canAttackGround: boolean
|
canAttackGround: boolean
|
||||||
|
haveEquipButton: boolean
|
||||||
|
icon: string
|
||||||
modId: number
|
modId: number
|
||||||
weaponPiercings: WeaponPiercing[]
|
weaponPiercings: IWeaponPiercing[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WeaponPiercing {
|
export interface IWeaponPiercing {
|
||||||
id: number
|
id: number
|
||||||
armorType: IArmorType
|
armorType: IArmorType
|
||||||
piercingValue: number
|
piercingValue: number
|
||||||
|
|||||||
16
src/types/IUnitShort.tsx
Normal file
16
src/types/IUnitShort.tsx
Normal 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
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user