Units wiki

This commit is contained in:
v.holodov 2025-01-07 15:39:00 +03:00
parent 4a5101fb6a
commit 27308c2c88
54 changed files with 797 additions and 12339 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
REACT_APP_HOST_URL=http://localhost:8082

20
Dockerfile Normal file
View File

@ -0,0 +1,20 @@
# pull official base image
FROM node:13.12.0-alpine
# set working directory
WORKDIR /app
# add `/app/node_modules/.bin` to $PATH
ENV PATH /app/node_modules/.bin:$PATH
# install app dependencies
COPY package.json ./
RUN npm install --silent
EXPOSE 3000
# add app
COPY . ./
# start app
CMD ["npm", "start"]

12234
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,18 @@
{ {
"name": "crosspick-front", "name": "dow-wiki-frontend",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^6.3.1",
"@mui/material": "^6.3.1",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.28.1",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },

BIN
public/images/ARM_Air.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 B

BIN
public/images/ARM_Cmdr.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

View File

@ -28,6 +28,7 @@
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<script type="text/babel" src="./index_bundle.js"></script>
<div id="root"></div> <div id="root"></div>
<!-- <!--
This HTML file is a template. This HTML file is a template.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -1,38 +0,0 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@ -1,25 +0,0 @@
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;

View File

@ -1,8 +0,0 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

69
src/App.tsx Normal file
View File

@ -0,0 +1,69 @@
import './App.css';
import React from "react";
import {
AppBar,
Button,
Container,
createTheme,
Grid2,
IconButton,
ThemeProvider,
Toolbar,
Typography
} from "@mui/material";
import {MyRoutes} from "./Routes";
function App() {
console.log(process.env);
const steamLogin = (event: React.MouseEvent<HTMLButtonElement>) => {
(document.getElementById('submit-steam-login') as HTMLFormElement).click()
};
const theme = createTheme({
cssVariables: true,
});
return (
<div>
<AppBar position="static">
<Toolbar>
<IconButton
size="large"
edge="start"
color="inherit"
aria-label="menu"
sx={{mr: 2}}
>
</IconButton>
<Typography variant="h6" component="div" sx={{flexGrow: 1}}>
Soulstorm wiki
</Typography>
<Button color="inherit" onClick={steamLogin}>
Log in through Steam
</Button>
</Toolbar>
</AppBar>
<Container>
<MyRoutes/>
</Container>
<form action="https://steamcommunity.com/openid/login" method="post">
<input type="hidden" name="openid.identity"
value="http://specs.openid.net/auth/2.0/identifier_select"/>
<input type="hidden" name="openid.claimed_id"
value="http://specs.openid.net/auth/2.0/identifier_select"/>
<input type="hidden" name="openid.ns" value="http://specs.openid.net/auth/2.0"/>
<input type="hidden" name="openid.mode" value="checkid_setup"/>
<input type="hidden" name="openid.realm" value={process.env.REACT_APP_HOST_URL}/>
<input type="hidden" name="openid.return_to"
value={process.env.REACT_APP_HOST_URL + "\\login"}/>
<button id="submit-steam-login" type="submit" hidden={true}></button>
</form>
</div>
);
}
export default App;

17
src/Routes.tsx Normal file
View File

@ -0,0 +1,17 @@
import {Routes, Route, BrowserRouter} from "react-router-dom";
import RacePage from "./pages/RacePage";
import ModsPage from "./pages/ModsPage";
import ModPage from "./pages/ModPage";
export const MyRoutes = () => {
return(
<BrowserRouter>
<Routes>
<Route path="/" element={<ModsPage/>}/>
<Route path="/mod/:id" element={<ModPage/>}/>
<Route path="/mod/:modId/race/:raceId" element={<RacePage/>}/>
</Routes>
</BrowserRouter>
)
}

15
src/classes/ArmorType.tsx Normal file
View File

@ -0,0 +1,15 @@
import React from "react";
interface IArmorType{
value: string
}
class ArmorType extends React.Component<IArmorType, any> {
render() {
return this.props.value;
}
}
export default ArmorType;

16
src/classes/Item.js Normal file
View File

@ -0,0 +1,16 @@
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>
);
}
}

27
src/classes/LobbyList.js Normal file
View File

@ -0,0 +1,27 @@
import React from 'react';
export default class LobbyList extends React.Component {
constructor(props){
super(props);
this.state = { messages : [] }
}
componentDidMount(){
// this is an "echo" websocket service
this.connection = new WebSocket('wss://echo.websocket.org');
// listen to onmessage event
this.connection.onmessage = evt => {
// add the new message to state
this.setState({
messages : this.state.messages.concat([ evt.data ])
})
};
// for testing purposes: sending to the echo service which will send it back back
setInterval( _ => {
this.connection.send( Math.random() )
}, 2000 )
}
render() {
// slice(-5) gives us the five most recent messages
return <ul>{ this.state.messages.slice(-5).map( (msg, idx) => <li key={'msg-' + idx }>{ msg }</li> )}</ul>;
}
};

View File

@ -0,0 +1,54 @@
import React from 'react';
import Item from "./Item";
export class TimerExample extends React.Component {
constructor(props) {
super(props);
// Don't call this.setState() here!
this.state = { elapsed: 10 };
this.tick = this.tick.bind(this);
console.log(this.props);
}
componentDidMount() {
// componentDidMount вызывается react'ом, когда компонент
// был отрисован на странице. Мы можем установить интервал здесь:
console.log(this.props);
this.timer = setInterval(this.tick, 50);
}
componentWillUnmount() {
// Этот метод вызывается сразу после того, как компонент удален
// со страницы и уничтожен. Мы можем удалить интервал здесь:
clearInterval(this.timer);
}
tick() {
// Эта функция вызывается каждые 50мс. Она обновляет
// счетчик затраченного времени. Вызов setState заставляет компонент перерисовываться
console.log(this.state);
this.setState({elapsed: new Date() - this.props.start});
}
render() {
let elapsed = Math.round(this.state.elapsed / 100);
// Это даст нам число с одной цифрой после запятой dot (xx.x):
let seconds = (elapsed / 10).toFixed(1);
// Хоть мы и возвращаем целый <p> элемент, react разумно обновит
// только измененные части, содержащие переменную seconds.
return <p>This example was started <b>{seconds} seconds</b> ago.</p>;
}
};

38
src/classes/User.tsx Normal file
View File

@ -0,0 +1,38 @@
import React, {useState} from "react";
import {UserUrl} from "../core/api";
interface IUser {
name?: string;
avatarUrl?: string;
}
export default function User() {
const [user, setUser] = useState<IUser>({
name: "---",
avatarUrl: ""
});
fetch(UserUrl)
.then(res => res.json())
.then(
(data) => {
console.log(data);
setUser({name: data.name, avatarUrl: data.avatarUrl});
},
(error) => {
console.log("error");
}
);
return (
<span>
Welcome {user.name} -
<span> </span><img width="50" height="50" src={user.avatarUrl}/>
</span>
)
}

6
src/core/api.js Normal file
View File

@ -0,0 +1,6 @@
export const UserUrl = process.env.REACT_APP_HOST_URL + '/api/v1/user';
export const AvailableMods = process.env.REACT_APP_HOST_URL + '/api/v1/mods';
export const AvailableRacesPart = process.env.REACT_APP_HOST_URL + '/api/v1/races';
export const AvailableUnits = process.env.REACT_APP_HOST_URL + '/api/v1/units';
export const IconUrl = process.env.REACT_APP_HOST_URL + '/api/v1/grapics/icon/';

8
src/core/withrouter.js Normal file
View File

@ -0,0 +1,8 @@
import { useParams } from 'react-router-dom';
export function withRouter(Children) {
return (props) => {
const match = { params: useParams() };
return <Children {...props} match={match} />
}
}

21
src/css/Unit.css Normal file
View File

@ -0,0 +1,21 @@
.unitIcon{
width: 64px;
}
.unitHeader{
margin-left: 15px;
}
.horizontalElements{
display: flex;
}
.stats-icon{
width: 16px;
height: 16px;
}
#filtered-units{
margin-top: 10px;
}

View File

@ -1,17 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

13
src/index.tsx Normal file
View File

@ -0,0 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

54
src/pages/ModPage.tsx Normal file
View File

@ -0,0 +1,54 @@
import {AvailableMods, AvailableRacesPart} 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";
interface ModPageState {
mod: IMod,
races: Irace[],
modId: Number,
}
class ModPage extends React.Component<any, ModPageState> {
constructor(props: any) {
super(props);
console.log(this.props.match.params.id);
this.setState({
modId : this.props.match.params.id
});
}
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 ){
return <div><h1>{this.state.mod.name} ({this.state.mod.version})</h1>
{this.state.races.map(race => <ul><NavLink state={race.id} to= {"/mod/" + this.state.mod.id + "/race/" + race.id} >{race.name}</NavLink></ul>)}
</div>;
} else {
return "";
}
}
}
export default withRouter(ModPage);

67
src/pages/ModsPage.tsx Normal file
View File

@ -0,0 +1,67 @@
import {AvailableMods} from "../core/api";
import React, {useEffect, useState} from "react";
import {NavLink} from "react-router-dom";
import {IMod} from "../types/Imod";
function Mods (mods: IMod[]) {
let mapWithModVersions: Map<String, IMod[]> = new Map();
mods.forEach( mod => {
const versionList = mapWithModVersions.get(mod.name)
if(versionList == null){
mapWithModVersions.set(mod.name, new Array(mod))
} else {
versionList.push(mod)
}
})
function Mod (modName: String) {
let sameMods = mapWithModVersions.get(modName) ?? []
return(
<div>
<h1>{modName}</h1>
{sameMods.map(mod => <ul><NavLink state={mod.id} to= {"/mod/" + mod.id} >{mod.version}</NavLink></ul>)}
</div>
)
}
mapWithModVersions.keys()
return(
[...mapWithModVersions.keys()].map(m => Mod(m))
)
}
interface ModsPageState {
mods: IMod[]
}
class ModsPage extends React.Component<any, ModsPageState> {
constructor({props}: { props: any }) {
super(props);
this.state = {
mods: []
}
}
async componentDidMount() {
const response = await fetch(AvailableMods);
const data: IMod[] = await response.json();
console.log(data);
this.setState({
mods : data
});
}
render() {
return <div>{Mods(this.state.mods)}</div>;
}
}
export default ModsPage

252
src/pages/RacePage.tsx Normal file
View File

@ -0,0 +1,252 @@
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} from "../types/IUnit";
import '../css/Unit.css'
import {
Accordion, AccordionDetails,
AccordionSummary,
Button,
ButtonGroup, Grid2, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow,
ToggleButton,
ToggleButtonClassKey,
ToggleButtonGroup, Typography
} from "@mui/material";
import {ArrowDropDown} from "@mui/icons-material";
import ArmorType from "../classes/ArmorType";
import Item from "../classes/Item";
interface RacePageState {
mod: IMod,
modId: Number,
race: Irace,
units: IUnitResponse,
}
function humanReadableUnitName(unit: string) {
const firstUpper = String(unit).charAt(0).toUpperCase() + String(unit).slice(1);
return firstUpper.replaceAll('_', ' ').replace('.rgd', '');
}
function Unit (unit: IUnit) {
const morale = (unit.moraleMax !== null) ? <span>
<img style={{verticalAlign: "top"}} src="/images/ARM_Morale.webp"/>&nbsp;{unit.moraleMax}
+{unit.moraleRegeneration}/s
{unit.moraleDeathPenalty > 0 && <span> <img style={{verticalAlign: "top"}} src="/images/Kills_icon.webp"/> -{unit.moraleDeathPenalty}</span>}
</span> : "-"
const detect = unit.detectRadius > 0 ? <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/DETECT_YES.webp"/> {unit.detectRadius}</span> :
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/DETECT_NO.webp"/></span>
return (
<Accordion>
<AccordionSummary
expandIcon={<ArrowDropDown/>}
aria-controls="panel1-content"
id={unit.filename}
>
<img className="unitIcon" src={IconUrl + unit.icon.replaceAll('\\', '/')}/>
<h2> {unit.name}</h2>
{unit.detectRadius > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/DETECT_YES.webp"/></span>}
</AccordionSummary>
<AccordionDetails>
<Grid2 container spacing={2}>
<Grid2 size={4}>
<TableContainer component={Paper}>
<Table size="small" aria-label="a dense table">
<TableBody id="unit-stats-table">
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">cost</TableCell>
<TableCell component="th" scope="row">
{unit.buildCostRequisition > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_requisition.gif"/>&nbsp;
{unit.buildCostRequisition}</span>}
{unit.buildCostPower > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_power.gif"/>&nbsp;
{unit.buildCostPower}</span>}
{unit.capInfantry > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_cap_infantry.gif"/>&nbsp;
{unit.capInfantry}</span>}
{unit.capSupport > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_cap_vehicle.gif"/>&nbsp;
{unit.capSupport}</span>}
</TableCell>
</TableRow>
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">armor type</TableCell>
<TableCell component="th" scope="row">
<ArmorType value={unit.armorType.name}/>
</TableCell>
</TableRow>
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">health</TableCell>
<TableCell component="th" scope="row">
<span><img style={{verticalAlign: "top"}}
src="/images/Health_icon.webp"/>&nbsp;
{unit.health} {unit.healthRegeneration > 0 &&
<span>+{unit.healthRegeneration}/s</span>} </span>
</TableCell>
</TableRow>
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">move speed</TableCell>
<TableCell component="th" scope="row">
{unit.moveSpeed}
</TableCell>
</TableRow>
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">morale</TableCell>
<TableCell component="th" scope="row">
{morale}
</TableCell>
</TableRow>
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">detect</TableCell>
<TableCell component="th" scope="row">
{detect}
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid2>
<Grid2 size={8}>
<div style={{whiteSpace: "pre-wrap"}} >
{unit.description}
</div>
</Grid2>
</Grid2>
<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
});
{this.props.units.map(unit => Unit(unit))}
}
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 {
unitsToRender = this.props.units.filter(unit => unit.capSupport == 0 && unit.capInfantry == 0)
}
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 ){
return <div><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 "";
}
}
}
export default withRouter(RacePage);

View File

@ -1,13 +0,0 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

4
src/types/IArmorType.ts Normal file
View File

@ -0,0 +1,4 @@
export interface IArmorType {
id: string
name: string
}

71
src/types/IUnit.tsx Normal file
View File

@ -0,0 +1,71 @@
import {Irace} from "./Irace";
import {IArmorType} from "./IArmorType";
export interface IUnitResponse {
race: string
units: IUnit[]
}
export interface IUnit {
id: number
race: Irace
armorType: IArmorType
armorType2?: IArmorType
name: string
filename: string
description: string
buildCostRequisition: number
buildCostPower: number
capInfantry: number
capSupport: number
squadStartSize: number
squadMaxSize: number
squadLimit?: number
health: number
healthRegeneration: number
moraleDeathPenalty: number
moraleMax?: number
moraleBroken?: number
moraleRegeneration?: number
mass: number
upTime: number
moveSpeed: number
sightRadius: number
detectRadius: number
reinforceCostRequisition?: number
reinforceCostPower?: number
reinforceTime?: number
icon: string
modId: number
weapons: WeaponHardpoint[]
}
export interface WeaponHardpoint {
weapon: Weapon
hardpoint: number
}
export interface Weapon {
id: number
name: string
costRequisition: number
costPower: number
costTimeSeconds: number
accuracy: number
reloadTime: number
setupTime: number
accuracyReductionMoving: number
minDamage: number
maxDamage: number
moraleDamage: number
modId: number
weaponPiercings: WeaponPiercing[]
}
export interface WeaponPiercing {
id: number
armorType: IArmorType
piercingValue: number
}

7
src/types/Imod.tsx Normal file
View File

@ -0,0 +1,7 @@
export interface IMod {
id: number;
name: string;
version: string;
technicalName: string;
isBeta: boolean;
}

4
src/types/Irace.tsx Normal file
View File

@ -0,0 +1,4 @@
export interface Irace {
id: string;
name: string;
}

26
tsconfig.json Normal file
View File

@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "ES6",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}