diff --git a/README.md b/README.md
index 58beeac..0d741f5 100644
--- a/README.md
+++ b/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
-
-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)
+- react (вперемешку классы и функции)
+- typescript
\ No newline at end of file
diff --git a/public/images/MeleeStance_icon_bw.jpg b/public/images/MeleeStance_icon_bw.jpg
new file mode 100644
index 0000000..356a5eb
Binary files /dev/null and b/public/images/MeleeStance_icon_bw.jpg differ
diff --git a/public/images/RangedStance_icon_bw.jpg b/public/images/RangedStance_icon_bw.jpg
new file mode 100644
index 0000000..adf0c56
Binary files /dev/null and b/public/images/RangedStance_icon_bw.jpg differ
diff --git a/public/images/Resource_orksquadcap.gif b/public/images/Resource_orksquadcap.gif
new file mode 100644
index 0000000..5e08dd1
Binary files /dev/null and b/public/images/Resource_orksquadcap.gif differ
diff --git a/src/App.tsx b/src/App.tsx
index 21611af..8dcd121 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -24,8 +24,9 @@ function App() {
cssVariables: true,
});
+
return (
-
+
- Soulstorm wiki
+ Soulstorm autogeneration wiki
Log in through Steam
diff --git a/src/Routes.tsx b/src/Routes.tsx
index a1ca547..a94c425 100644
--- a/src/Routes.tsx
+++ b/src/Routes.tsx
@@ -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 = () => {
}/>
- }/>
- }/>
+ }/>
+ }/>
+ }/>
)
diff --git a/src/classes/ArmorType.tsx b/src/classes/ArmorType.tsx
index 5f6b5b8..d27b539 100644
--- a/src/classes/ArmorType.tsx
+++ b/src/classes/ArmorType.tsx
@@ -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 {
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 {
case 'Demon High':
return '/images/ARM_Dmn_Hi.webp';
default:
- return 'Unknown armor';
+ return armorTypeId;
}
}
render() {
- return ( {this.props.withName && this.props.name} );
+ return ( {this.props.withName && this.props.name} );
}
}
diff --git a/src/classes/Item.js b/src/classes/Item.js
deleted file mode 100644
index 96ee9af..0000000
--- a/src/classes/Item.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from 'react';
-
-export default class Item extends React.Component {
- render() {
- return (
-
-
Shopping List for {this.props.name}
-
- Instagram
- WhatsApp
- Oculus
-
-
- );
- }
-}
\ No newline at end of file
diff --git a/src/classes/Sergeant.tsx b/src/classes/Sergeant.tsx
new file mode 100644
index 0000000..0bd2db9
--- /dev/null
+++ b/src/classes/Sergeant.tsx
@@ -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 ? {sergeant.detectRadius} :
+
+
+ let mapWithUnitWeapons: Map> = 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 (
+
+
+
+
+
+
+
+ cost
+
+ {sergeant.buildCostRequisition > 0 &&
+
+ {sergeant.buildCostRequisition} }
+ {sergeant.buildCostPower > 0 &&
+ {sergeant.buildCostPower} }
+ {(sergeant.buildCostPopulation !== undefined && sergeant.buildCostPopulation > 0) &&
+ {sergeant.buildCostPopulation} }
+
+ {(sergeant.buildCostTime !== undefined && sergeant.buildCostTime > 0) &&
+ {sergeant.buildCostTime}s }
+
+
+
+ armor type
+
+
+
+
+
+ health
+
+
+ {sergeant.health} {sergeant.healthRegeneration > 0 &&
+ +{sergeant.healthRegeneration}/s }
+
+
+
+ morale
+
+ -{sergeant.moraleDeathPenalty}
+
+
+
+ detect
+
+ {detect}
+
+
+
+
+
+
+
+
+ {sergeant.description}
+
+
+
+
+ {[...mapWithUnitWeapons.keys()].sort(function (a, b) {
+ return a - b;
+ }).map(h => )}
+
+
+
{sergeant.filename}
+
)
+};
+
+export default Sergeant;
+
diff --git a/src/classes/UnitsTable.tsx b/src/classes/UnitsTable.tsx
new file mode 100644
index 0000000..0cf87af
--- /dev/null
+++ b/src/classes/UnitsTable.tsx
@@ -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({
+ racesUnits: []
+ });
+
+
+ function getUnitRef(modId: number, raceId: string, unit: IUnitShort) {
+ return
+ {unit.name}
+
+ }
+
+ 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 (
+ {
+
+
+ {racesUnitsPart.map(raceUnits => {raceUnits.race.name}
+ )}
+
+
+
+
+ {racesUnitsPart.map(raceUnits =>
+
+ {getUnitsRef(raceUnits.infantry, raceUnits.race.id)}
+ )
+ }
+
+
+ {racesUnitsPart.map(raceUnits =>
+
+ {getUnitsRef(raceUnits.tech, raceUnits.race.id)}
+ )
+ }
+
+
+ {racesUnitsPart.map(raceUnits =>
+
+ {getUnitsRef(raceUnits.support, raceUnits.race.id)}
+ )
+ }
+
+
+
}
+ )
+ }
+
+ 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 (
+
+
+ {generateRaceUnitTables(unitsTable.racesUnits, 9)}
+
+
+ {unitsTable.racesUnits.map(raceUnits => {raceUnits.race.name}
+ )}
+
+
+ )
+
+}
+
diff --git a/src/classes/Weapon.tsx b/src/classes/Weapon.tsx
index b5e2e39..a9a7a04 100644
--- a/src/classes/Weapon.tsx
+++ b/src/classes/Weapon.tsx
@@ -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 {
+ 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 {
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 {
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 {
[`&.${tableCellClasses.head}`]: {
backgroundColor: "rgb(234, 234, 234)",
color: theme.palette.common.white,
+ paddingLeft: 10
},
[`&.${tableCellClasses.body}`]: {
- fontSize: 14,
+ fontSize: 13,
+ paddingLeft: 10,
},
}));
return (
-
{this.humanReadableName(weapon.name)}
+
{ weapon.name ? weapon.name : this.humanReadableName(weapon.filename)}
-
+
@@ -163,11 +174,37 @@ class Weapon extends React.Component {
scope="row">{weapon.setupTime != 0 ? (weapon.setupTime).toFixed(2) : "-"}
+ {(weapon.costRequisition > 0 || weapon.costPower > 0) &&
+
+ Cost
+
+ {weapon.costRequisition > 0 &&
+ {weapon.costRequisition} }
+ {weapon.costPower > 0 &&
+ {weapon.costPower} }
+
+
+ }
+
+ {!this.props.isDefault && weapon.icon ?
: (
+ weapon.isMeleeWeapon ?
+
:
+
+ ) }
+ {this.props.isDefault ? "Default weapon" : weapon.description}
+
+
+
{
One hit average damage
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {getTotalDamage(infLowPiercing)}
- {getTotalDamage(infMedPiercing)}
- {getTotalDamage(infHighPiercing)}
- {getTotalDamage(infHeavyMedPiercing)}
- {getTotalDamage(infHeavyHighPiercing)}
- {getTotalDamage(demonPiercing)}
- {getTotalDamage(demonHighPiercing)}
- {getTotalDamage(commanderPiercing)}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {getTotalDamage(airPiercing)}
- {getTotalDamage(vehLowPiercing)}
- {getTotalDamage(vehMedPiercing)}
- {getTotalDamage(vehHighPiercing)}
- {getTotalDamage(buildingLowPiercing)}
- {getTotalDamage(buildingMedPiercing)}
- {getTotalDamage(buildingHighPiercing)}
- {getMoraleDamage()}
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {getTotalDamage(infLowPiercing)}
+ {getTotalDamage(infMedPiercing)}
+ {getTotalDamage(infHighPiercing)}
+ {getTotalDamage(infHeavyMedPiercing)}
+ {getTotalDamage(infHeavyHighPiercing)}
+ {getTotalDamage(commanderPiercing)}
+ {getTotalDamage(demonPiercing)}
+ {getTotalDamage(demonHighPiercing)}
+ {getTotalDamage(airPiercing, true)}
+ {getTotalDamage(vehLowPiercing)}
+ {getTotalDamage(vehMedPiercing)}
+ {getTotalDamage(vehHighPiercing)}
+ {getTotalDamage(buildingLowPiercing)}
+ {getTotalDamage(buildingMedPiercing)}
+ {getTotalDamage(buildingHighPiercing)}
+ {getMoraleDamage()}
+
+
+
+ {weapon.filename}
diff --git a/src/classes/WeaponSlot.tsx b/src/classes/WeaponSlot.tsx
new file mode 100644
index 0000000..14a3085
--- /dev/null
+++ b/src/classes/WeaponSlot.tsx
@@ -0,0 +1,37 @@
+import Weapon from "./Weapon";
+import React from "react";
+import {IWeapon} from "../types/IUnit";
+
+export interface WeaponSlotProps {
+ hardpoint: number,
+ unitWeapons: Map | 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 (
)
+
+ return (
+
+
{header}
+ {weaponsSorted.filter(wp => !wp[1].filename.includes("dummy")).map(wp =>
+
+
+ )}
+
+ )
+}
+
+export default WeaponSlot;
\ No newline at end of file
diff --git a/src/css/Unit.css b/src/css/Unit.css
index fb5220d..120c295 100644
--- a/src/css/Unit.css
+++ b/src/css/Unit.css
@@ -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;
}
\ No newline at end of file
diff --git a/src/pages/ModPage.tsx b/src/pages/ModPage.tsx
index 6c78f80..a1d00cb 100644
--- a/src/pages/ModPage.tsx
+++ b/src/pages/ModPage.tsx
@@ -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 {
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
}> Back
{this.state.mod.name} ({this.state.mod.version})
- {this.state.races.map(race =>
)}
+
;
} else {
return "";
@@ -59,4 +47,6 @@ class ModPage extends React.Component {
}
}
+
+
export default withRouter(ModPage);
\ No newline at end of file
diff --git a/src/pages/ModsPage.tsx b/src/pages/ModsPage.tsx
index 2a715c8..5f72d26 100644
--- a/src/pages/ModsPage.tsx
+++ b/src/pages/ModsPage.tsx
@@ -24,7 +24,13 @@ function Mods (mods: IMod[]) {
return(
{modName}
- {sameMods.map(mod =>
)}
+
+ {sameMods.filter((m) => !m.isBeta).map(mod =>
+
)}
+ {sameMods.find((m) => m.isBeta) != null &&
Beta versions:
}
+ {sameMods.find((m) => m.isBeta) != null &&
+ sameMods.filter((m) => m.isBeta).map(mod =>
+
)}
)
}
diff --git a/src/pages/RacePage.tsx b/src/pages/RacePage.tsx
deleted file mode 100644
index dfa9fe7..0000000
--- a/src/pages/RacePage.tsx
+++ /dev/null
@@ -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) ?
- {unit.moraleMax}
- +{unit.moraleRegeneration}/s
- {unit.moraleDeathPenalty > 0 && -{unit.moraleDeathPenalty} }
-
- : "-"
-
- const detect = unit.detectRadius > 0 ? {unit.detectRadius} :
-
-
- let mapWithUnitWeapons: Map = 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(
-
-
{header}
- {weaponThisSlot.map(weapon =>
-
)}
-
- )
- }
-
-
- return (
-
- }
- aria-controls="panel1-content"
- id={unit.filename}
- >
-
- {unit.name}
- {unit.detectRadius > 0 && }
-
-
-
-
-
-
-
-
-
- cost
-
- {unit.buildCostRequisition > 0 &&
- {unit.buildCostRequisition} }
- {unit.buildCostPower > 0 &&
- {unit.buildCostPower} }
- {unit.capInfantry > 0 &&
- {unit.capInfantry} }
- {unit.capSupport > 0 &&
- {unit.capSupport} }
-
-
-
- armor type
-
-
-
-
-
- health
-
-
- {unit.health} {unit.healthRegeneration > 0 &&
- +{unit.healthRegeneration}/s }
-
-
-
- move speed
-
- {unit.moveSpeed}
-
-
-
- morale
-
- {morale}
-
-
-
- detect
-
- {detect}
-
-
-
-
-
-
-
-
- {unit.description}
-
-
-
- {[...mapWithUnitWeapons.keys()].sort(function(a, b) {
- return a - b;
- }).map(h => WeaponSlot(h))}
-
-
- {unit.filename}
-
- )
-}
-
-interface UnitsState {
- selectedUnits: String | null,
-}
-
-class Units extends React.Component {
-
-
- 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 (
-
- Infantry
- Tech
- Support
-
-
- {unitsToRender.map(unit => Unit(unit))}
-
-
-
)
- } else {
- return "";
- }
- }
-}
-
-class RacePage extends React.Component {
-
- 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 }> Back
-
{this.state.mod.name} ({this.state.mod.version}) - {this.state.race.name}
-
- ;
- } else {
- return "loading...";
- }
- }
-}
-
-export default withRouter(RacePage);
\ No newline at end of file
diff --git a/src/pages/RacePageFast.tsx b/src/pages/RacePageFast.tsx
new file mode 100644
index 0000000..64e2352
--- /dev/null
+++ b/src/pages/RacePageFast.tsx
@@ -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 (
+ {unit.icon && }
+ {unit.name}
+ {unit.canDetect && }
+
+ )
+}
+
+interface UnitsProps{
+ raceId: string,
+ modId: number,
+}
+
+interface UnitsState {
+ selectedUnits: String | null,
+ units: IRaceUnits | null,
+}
+
+class Units extends React.Component {
+
+
+ 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 (
+
+
+ {this.state.units != null ?
+
+
+ Infantry
+ {this.state.units.infantry.map(unit => Unit(unit, this.props.modId, this.props.raceId))}
+
+
+ Tech
+
+ {this.state.units.tech.map(unit => Unit(unit, this.props.modId, this.props.raceId))}
+
+
+
+ Support
+
+ {this.state.units.support.map(unit => Unit(unit, this.props.modId, this.props.raceId))}
+
+
+
+ : "Loading"}
+
+
)
+ } else {
+ return "Loading...";
+ }
+ }
+}
+
+class RacePageFast extends React.Component {
+
+ 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 }> Back
+
{this.state.mod.name} ({this.state.mod.version}) - {this.state.race.name}
+
+ ;
+ } else {
+ return "loading...";
+ }
+ }
+}
+
+export default withRouter(RacePageFast);
\ No newline at end of file
diff --git a/src/pages/UnitPage.tsx b/src/pages/UnitPage.tsx
new file mode 100644
index 0000000..a824f65
--- /dev/null
+++ b/src/pages/UnitPage.tsx
@@ -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) ?
+ {unit.moraleMax}
+ +{unit.moraleRegeneration}/s
+ {unit.moraleDeathPenalty > 0 && -{unit.moraleDeathPenalty} }
+
+ : "-"
+
+ const detect = unit.detectRadius > 0 ? {unit.detectRadius} :
+
+
+ let mapWithUnitWeapons: Map> = 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 (
+
+ {props.icon && }
+ {props.name}
+ {props.canDetect && }
+
+ )
+ }
+
+
+
+ console.log(unit);
+
+
+ return (
+
+
{mod.name} ({mod.version})
+
{unit.icon &&
+ } {unit.name}
+
+
+
+
+
+
+
+ cost
+
+ {unit.buildCostRequisition > 0 &&
+
+ {unit.buildCostRequisition} }
+ {unit.buildCostPower > 0 &&
+ {unit.buildCostPower} }
+ {(unit.buildCostPopulation !== undefined && unit.buildCostPopulation > 0) &&
+
+ {unit.buildCostPopulation} }
+
+ {unit.capInfantry > 0 &&
+ {unit.capInfantry} }
+ {unit.capSupport > 0 &&
+ {unit.capSupport} }
+ {(unit.buildCostTime !== undefined && unit.buildCostTime > 0) &&
+
+ {unit.buildCostTime}s }
+
+
+ {(unit?.reinforceTime !== 0 && unit.reinforceTime !== null) &&
+
+ reinforce cost
+
+ {unit.reinforceCostRequisition && unit.reinforceCostRequisition > 0 &&
+
+ {unit.reinforceCostRequisition} }
+ {unit.reinforceCostPower !== undefined && unit.reinforceCostPower > 0 &&
+
+ {unit.reinforceCostPower} }
+ {(unit.reinforceCostPopulation !== undefined && unit.reinforceCostPopulation > 0) &&
+
+ {unit.reinforceCostPopulation} }
+ {(unit.reinforceTime !== undefined && unit.reinforceTime > 0) &&
+
+ {unit.reinforceTime}s }
+
+
+ }
+
+ armor type
+
+
+
+
+
+ health
+
+
+ {unit.health} {unit.healthRegeneration > 0 &&
+ +{unit.healthRegeneration}/s }
+
+
+
+ move speed
+
+ {unit.moveSpeed}
+
+
+
+ morale
+
+ {morale}
+
+
+
+ detect
+
+ {detect}
+
+
+
+
+
+
+
+
+ {unit.description}
+
+
+
+ {unit.sergeants.map(s =>
+
+ }
+ aria-controls="panel1-content"
+ id="panel1-header"
+ >
+ 0}/>
+
+
+
+
+
+
+ )}
+
+
+ {[...mapWithUnitWeapons.keys()].sort(function (a, b) {
+ return a - b;
+ }).map(h => )}
+
+
+
{unit.filename}
+
+
+ )
+}
+
+
+class UnitPage extends React.Component {
+
+
+ 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 }> Back
+ {Unit(this.state.unit, this.state.mod)}
+
;
+ } else {
+ return "loading...";
+ }
+ }
+}
+
+export default withRouter(UnitPage);
\ No newline at end of file
diff --git a/src/types/IUnit.tsx b/src/types/IUnit.tsx
index 45842c9..7d12a0c 100644
--- a/src/types/IUnit.tsx
+++ b/src/types/IUnit.tsx
@@ -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
diff --git a/src/types/IUnitShort.tsx b/src/types/IUnitShort.tsx
new file mode 100644
index 0000000..ab6cc57
--- /dev/null
+++ b/src/types/IUnitShort.tsx
@@ -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
+}