Add research support

This commit is contained in:
Anibus 2025-08-18 13:02:10 +03:00
parent f94553bb9a
commit 851bb71601
25 changed files with 714 additions and 311 deletions

View File

@ -2,5 +2,6 @@
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UsePropertyAccessSyntax" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@ -1,15 +1,16 @@
import React from "react";
import {Tooltip} from "@mui/material";
import ArmorTypeValues from "../types/ArmorTypeValues";
interface IArmorType{
name: string,
withName: boolean
compact: boolean
}
class ArmorType extends React.Component<IArmorType, any> {
public static defaultProps = {
withName: false
compact: true
};
renderArmorImage(armorTypeId: string): string {
@ -20,11 +21,11 @@ class ArmorType extends React.Component<IArmorType, any> {
return '/images/ARM_Inf_Mid.webp';
case 'Infantry High':
return '/images/ARM_Inf_Hi.webp';
case 'Infantry Heavy Medium':
case ArmorTypeValues.InfantryHeavyMedium:
return '/images/ARM_Hvy_Inf_Mid.webp';
case 'Infantry Heavy High':
case ArmorTypeValues.InfantryHeavyHigh:
return '/images/ARM_Hvy_Inf_Hi.webp';
case 'Commander':
case ArmorTypeValues.Commander:
return '/images/ARM_Cmdr.webp';
case 'Living Metal':
return '/images/ARM_living_metal.webp';
@ -34,7 +35,7 @@ class ArmorType extends React.Component<IArmorType, any> {
return '/images/ARM_Veh_Mid.webp';
case 'Vehicle High':
return '/images/ARM_Veh_Hi.webp';
case 'Air':
case 'Vehicle Air':
return '/images/ARM_Air.webp';
case 'Building Low':
return '/images/ARM_Bld_Lo.webp';
@ -58,9 +59,14 @@ class ArmorType extends React.Component<IArmorType, any> {
}
render() {
if(this.props.compact){
return (<span> {this.props.compact ?
<div style={{ fontSize: 12, width: 40, textAlign: "center"}}><img src={this.renderArmorImage(this.props.name)}/><i>{this.props.name}</i>
</div> :
<span><img src={this.renderArmorImage(this.props.name)}/> {this.props.name}</span>} </span>);
} else
return <span ><img style={{verticalAlign: "top"}} src={this.renderArmorImage(this.props.name)}/> {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>);
}
}

View File

@ -1,27 +0,0 @@
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,223 @@
import {Paper, Table, TableCell, TableContainer, TableRow} from "@mui/material";
import React from "react";
import {IModifier} from "../types/IModifier";
import {IconUrl} from "../core/api";
import {IResearch} from "../types/IResearch";
import {Irace} from "../types/Irace";
interface IModifiersProvidesTable{
modifiers: IModifier[],
}
interface IModifiersProvidesResearchTable{
modifiers: IModifier[],
research: IResearch,
race: Irace,
}
function getModName(mt: String){
switch(mt) {
case 'modifiers\\armour_modifier.lua':
return 'Armor';
case 'modifiers\\health_maximum_modifier.lua':
return 'Health';
case 'modifiers\\keen_sight_radius_modifier.lua':
return 'Detect';
case 'modifiers\\sight_radius_modifier.lua':
return 'Sight';
case 'modifiers\\garrison_requisition_modifier.lua':
return 'Requisition';
case 'modifiers\\faith_max_modifier.lua':
return 'Faith max';
case 'modifiers\\holy_icon_cost_power.lua':
return <span>Next icon cost <img style={{verticalAlign: "top"}} src='/images/Resource_power.gif'/></span>;
case 'modifiers\\holy_icon_cost_requisition.lua':
return <span>Next icon cost <img style={{verticalAlign: "top"}} src='/images/Resource_requisition.gif'/></span>;
case 'modifiers\\enable_hardpoint_02.lua':
return 'Weapon slot 2';
case 'modifiers\\default_weapon_modifier_hardpoint1.lua':
return 'Weapon slot 1 change weapon';
case 'modifiers\\default_weapon_modifier_hardpoint2.lua':
return 'Weapon slot 2 change weapon';
case 'modifiers\\default_weapon_modifier_hardpoint3.lua':
return 'Weapon slot 3 change weapon';
case 'modifiers\\default_weapon_modifier_hardpoint4.lua':
return 'Weapon slot 4 change weapon';
case 'modifiers\\default_weapon_modifier_hardpoint5.lua':
return 'Weapon slot 5 change weapon';
case 'modifiers\\default_weapon_modifier_hardpoint6.lua':
return 'Weapon slot 6 change weapon';
case 'modifiers\\morale_maximum_squad_modifier.lua':
return 'Morale';
case 'modifiers\\max_upgrades_squad_modifier.lua':
return 'Max equip weapons';
case 'modifiers\\speed_maximum_modifier.lua':
return 'Max speed';
case 'modifiers\\min_damage_weapon_modifier.lua':
return 'Min damage';
case 'modifiers\\max_damage_weapon_modifier.lua':
return 'Max damage';
case 'modifiers\\enable_armour_2.lua':
return 'Change armor type';
case 'modifiers\\cost_requisition_modifier.lua':
return 'Requisition cost';
default:
return mt.replace("modifiers\\", "").replace("modifier.lua", "").replace(".lua", "").replaceAll("_", " ");
}
}
function getModIcon(mt: String){
switch(mt) {
case 'modifiers\\armour_modifier.lua':
return <img style={{height: 20, verticalAlign: "top"}} src='/images/defence.png'/>;
case 'modifiers\\health_maximum_modifier.lua':
return <img style={{verticalAlign: "top"}} src='/images/Health_icon.webp'/>;
case 'modifiers\\keen_sight_radius_modifier.lua':
return <img style={{verticalAlign: "top"}} src='/images/DETECT_YES.webp'/>;
case 'modifiers\\sight_radius_modifier.lua':
return <img style={{verticalAlign: "top"}} src='/images/DETECT_NO.webp'/>;
case 'modifiers\\faith_max_modifier.lua':
return <img style={{verticalAlign: "top"}} src='/images/Resource_faith.gif'/>;
case 'modifiers\\garrison_requisition_modifier.lua':
return <img style={{verticalAlign: "top"}} src='/images/Resource_requisition.gif'/>;
case 'modifiers\\cost_requisition_modifier.lua':
return <img style={{verticalAlign: "top"}} src='/images/Resource_requisition.gif'/>;
case 'modifiers\\morale_maximum_squad_modifier.lua':
return <img style={{verticalAlign: "top"}} src='/images/ARM_Morale.webp'/>;
default:
return "";
}
}
function getChangeDescription(ref: String, v: number) {
switch(ref) {
case 'type_modifierusagetype\\tp_mod_usage_addition.lua':
if(v < 0){
return v
}else return '+' + v;
case 'type_modifierusagetype\\tp_mod_usage_multiplication.lua':
return 'x' + v;
case 'type_modifierusagetype\\tp_mod_usage_percentage.lua':
return '%' + v;
case 'type_modifierusagetype\\tp_mod_usage_enable.lua':
if(v === 1){
return ' enable ';
} else {
return ' disable ';
}
default:
return ref;
}
}
export function ModifiersProvidesAddonTable (props: IModifiersProvidesTable) {
let noWeaponMods = props.modifiers.filter(m => !m.reference.includes("default_weapon_modifier_hardpoint"))
return (
<TableContainer component={Paper}>
<Table size="small" aria-label="a dense table">
{noWeaponMods.map(m =>
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">{getModName(m.reference)}</TableCell>
<TableCell component="th" scope="row">{getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)}</TableCell>
</TableRow>
)}
</Table>
</TableContainer>
)
}
export function ModifiersProvidesResearchTable (props: IModifiersProvidesResearchTable) {
let mods = props.modifiers
function getTarget(target: String, research: IResearch, race: Irace) {
console.log(research.affectedUnits.map(au => au.filename))
var affectedUnit = research.affectedUnits.find(au => au.filename.replaceAll('.rgd', '').split(";")[0] === target || au.filename.replaceAll('.rgd', '').split(";")[1] === target);
if(affectedUnit != null) {
return <span><img style={{verticalAlign: "top", height: 25}}
src={getIcon(affectedUnit.icon)}/><a
href={`/mod/${research.modId}/race/${race.id}/unit/${affectedUnit.id}`}>
{affectedUnit.name}
</a></span>
}
var affectedSergeant = research.affectedSergeants.find(as => as.filename.replaceAll('.rgd', '') === target);
if(affectedSergeant != null) {
return <span><img style={{verticalAlign: "top", height: 25}}
src={getIcon(affectedSergeant.icon)}/>{affectedSergeant.name} at <img style={{verticalAlign: "top", height: 25}}
src={getIcon(affectedSergeant.unit.icon)}/><a
href={`/mod/${research.modId}/race/${race.id}/unit/${affectedSergeant.unit.id}`}>
{affectedSergeant.unit.name}
</a></span>
}
var affectedBuilding = research.affectedBuildings.find(ab => ab.filename.replaceAll('.rgd', '') === target);
if(affectedBuilding != null) {
return <span><img style={{verticalAlign: "top", height: 25}} src={getIcon(affectedBuilding.icon)}/><a
href={`/mod/${research.modId}/race/${race.id}/building/${affectedBuilding.id}`}>
{affectedBuilding.name}
</a> </span>
}
var affectedWeapon = research.affectedWeapons.find(aw => aw.filename.replaceAll('.rgd', '') === target);
if (affectedWeapon != null) {
return <span>{affectedWeapon.name ? affectedWeapon.name : affectedWeapon.filename.replaceAll('_', ' ').replace('.rgd', '')} at {
affectedWeapon.units.map(unit =>
<span><img style={{verticalAlign: "top", height: 25}} src={getIcon(unit.icon)}/><a
href={`/mod/${research.modId}/race/${race.id}/unit/${unit.id}`}>
{unit.name}
</a> </span>
)
}
{
affectedWeapon.sergeants.map(affectedSergeant =>
<span><img style={{verticalAlign: "top", height: 25}}
src={getIcon(affectedSergeant.icon)}/>{affectedSergeant.name} at <img
style={{verticalAlign: "top", height: 25}}
src={getIcon(affectedSergeant.unit.icon)}/><a
href={`/mod/${research.modId}/race/${race.id}/unit/${affectedSergeant.unit.id}`}>
{affectedSergeant.unit.name}
</a> </span>
)
}
{
affectedWeapon.buildings.map(affectedBuilding =>
<span><img style={{verticalAlign: "top", height: 25}} src={getIcon(affectedBuilding.icon)}/><a
href={`/mod/${research.modId}/race/${race.id}/building/${affectedBuilding.id}`}>
{affectedBuilding.name}
</a> </span>
)
}
</span>
}
return <span>Unknown target <i>{target}</i></span>;
}
return (
<TableContainer component={Paper}>
<Table size="small" aria-label="a dense table">
{mods.map(m =>
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">{getTarget(m.target, props.research, props.race)}</TableCell>
<TableCell style={{minWidth: 120}} component="th" scope="row">{getModName(m.reference)}</TableCell>
<TableCell component="th" scope="row">{getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)}</TableCell>
</TableRow>
)}
</Table>
</TableContainer>
)
}
export function getIcon(icon?: string): string{
if(icon !== null && icon !== undefined){
return IconUrl + icon.replaceAll('\\', '/')
} else return '/images/shakal.png'
}

97
src/classes/Required.tsx Normal file
View File

@ -0,0 +1,97 @@
import {Paper, Table, TableCell, TableContainer, TableRow} from "@mui/material";
import React from "react";
import {IModifier} from "../types/IModifier";
import {IBuildingShort} from "../types/IBuildingShort";
import {IBuilding, IBuildingAddonShort} from "../types/IBuilding";
import {IconUrl} from "../core/api";
import {IResearchRequirements} from "../types/IResearchRequirements";
import {goToAddon} from "../pages/BuildingPage";
import {IRequirement} from "../types/IRequirement";
interface IRequiredProps{
requirement: IRequirement,
building: IBuilding,
}
function Required (props: IRequiredProps) {
function renderRequirementGlobalAddons(rgas: IBuildingAddonShort[], building: IBuilding, modId: number) {
return <div> {rgas.map(rga =>
<span>&nbsp; Global addon: <img style={{verticalAlign: "top", height: 25}}
src={getIcon(rga.icon)}/>
&nbsp;<a href={`/mod/${modId}/race/${building.race.id}/building/${rga.buildingId}#addon-${rga.id}`}>
{rga.name}
</a></span>
)}</div>
}
function renderRequirementBuilding(requirementBuildings: IBuildingShort[], building: IBuilding, modId: number) {
return requirementBuildings.length > 0 && (
<div>&nbsp; Buildings: <img style={{verticalAlign: "top", height: 25}}
src={IconUrl + requirementBuildings[0].icon.replaceAll('\\', '/')}/>
&nbsp;{" "}<a href={`/mod/${modId}/race/${building.race.id}/building/${requirementBuildings[0].id}`}>
{requirementBuildings[0].name}
</a> {requirementBuildings[1] !== undefined && <span>or &nbsp;<img style={{verticalAlign: "top", height: 25}}
src={IconUrl + requirementBuildings[1].icon.replaceAll('\\', '/')}/>
&nbsp;{" "}<a href={`/mod/${modId}/race/${building.race.id}/building/${requirementBuildings[1].id}`}>
{requirementBuildings[1].name}</a></span> } </div>
);
}
function renderRequirementResearches(requirementResearches: IResearchRequirements[], modId: number, raceId: string) {
return <div> {requirementResearches.map(rs =>
<span>&nbsp; Research {rs.researchMustNotBeComplete ? "not" : ""}: <img style={{verticalAlign: "top", height: 25}}
src={getIcon(rs.researchShortDto.icon)}/>
&nbsp;<a href={`/mod/${modId}/race/${raceId}/building/${rs.researchShortDto.buildingId}#research-${rs.researchShortDto.id}`}>
{rs.researchShortDto.name}
</a></span>
)}</div>
}
const requirement = props.requirement
const building = props.building
return (
<div>
<h4>Required: </h4>
{requirement.requiredTotalPop !== undefined && requirement.requiredTotalPop !== null &&
<div>&nbsp; Population: <img style={{verticalAlign: "top", height: 25}}
src="/images/Resource_orksquadcap.gif"/>&nbsp;
{requirement.requiredTotalPop}</div>
}
{requirement.requireAddon !== null &&
<div>&nbsp; Addon: <img style={{verticalAlign: "top", height: 25}}
src={IconUrl + requirement.requireAddon.icon.replaceAll('\\', '/')}/>&nbsp;
<a href={"#addon-" + requirement.requireAddon.id} onClick={() => goToAddon(`#addon-` + requirement.requireAddon.id)}>{requirement.requireAddon.name}</a>
{requirement.replaceWhenDone && <i> (old provides replace)</i>}
</div>
}
{requirement.requirementBuildings.map(b =>
<div>&nbsp; Building: <img style={{verticalAlign: "top", height: 25}}
src={IconUrl + b.icon.replaceAll('\\', '/')}/>&nbsp;
<a href= {'/mod/'+ building.modId +'/race/'+ building.race.id +'/building/' + b.id + "/"}>{b.name}</a></div>)
}
{requirement.requirementResearches.length !== 0 &&
renderRequirementResearches(requirement.requirementResearches, building.modId, building.race.id)}
{requirement.requirementBuildingsEither.length !== 0 &&
renderRequirementBuilding(requirement.requirementBuildingsEither, building, building.modId)}
{requirement.requirementsGlobalAddons.length !== 0 &&
renderRequirementGlobalAddons(requirement.requirementsGlobalAddons, building, building.modId)}
</div>
)
}
export function getIcon(icon?: string): string{
if(icon !== null && icon !== undefined){
return IconUrl + icon.replaceAll('\\', '/')
} else return '/images/shakal.png'
}
export default Required;

View File

@ -18,12 +18,15 @@ import {ExpandMore} from "@mui/icons-material";
import WeaponSlot from "./WeaponSlot";
import Vision from "./Vision";
import {IMod} from "../types/Imod";
import {renderAffectedResearches} from "./building/Research";
import {Irace} from "../types/Irace";
export interface SergeantProps {
sergeant: ISergeant,
mod: IMod
mod: IMod,
race: Irace,
}
const Sergeant = (props: SergeantProps) => {
@ -100,7 +103,9 @@ const Sergeant = (props: SergeantProps) => {
>
<TableCell component="th" scope="row">Armor type</TableCell>
<TableCell component="th" scope="row">
<ArmorType name={sergeant.armorType.name}/>
<ArmorType name={sergeant.armorType.name} compact={false}/>
{sergeant.armorType2 && <span style={{fontSize: 12}} ><br/><i>after upgrade can become:<br/><ArmorType
name={sergeant.armorType2.name} compact={false}/></i></span> }
</TableCell>
</TableRow>
<TableRow
@ -126,7 +131,7 @@ const Sergeant = (props: SergeantProps) => {
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">Detect</TableCell>
<TableCell component="th" scope="row">Vision</TableCell>
<TableCell component="th" scope="row">
<Vision sight={sergeant.sightRadius} detect={sergeant.detectRadius}/>
</TableCell>
@ -144,7 +149,10 @@ const Sergeant = (props: SergeantProps) => {
<Grid2 size={12}>
{[...mapWithUnitWeapons.keys()].sort(function (a, b) {
return a - b;
}).map(h => <WeaponSlot mod={props.mod} unitWeapons={mapWithUnitWeapons.get(h)} hardpoint={h}/>)}
}).map(h => <WeaponSlot race={props.race} mod={props.mod} unitWeapons={mapWithUnitWeapons.get(h)} hardpoint={h}/>)}
</Grid2>
<Grid2 size={12}>
{renderAffectedResearches(sergeant.affectedResearches, props.mod.id, props.race.id)}
</Grid2>
</Grid2>
<i className="rgdFrom">{sergeant.filename}</i>

View File

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

View File

@ -21,11 +21,14 @@ import {IconUrl} from "../core/api";
import {ExpandMore} from "@mui/icons-material";
import Sergeant from "./Sergeant";
import {IMod} from "../types/Imod";
import {Irace} from "../types/Irace";
import {renderAffectedResearches} from "./building/Research";
interface IWeaponProps {
weapon: IWeapon,
isDefault: Boolean,
mod: IMod,
race: Irace,
}
interface IWeaponState {
@ -74,7 +77,7 @@ class Weapon extends React.Component<IWeaponProps, any> {
const infMedPiercing = this.getPiercingK(ArmorTypeNames.InfantryMedium)
const infHighPiercing = this.getPiercingK(ArmorTypeNames.InfantryHigh)
const infHeavyMedPiercing = this.getPiercingK(ArmorTypeNames.InfantryHeavyMedium)
const infHeavyHighPiercing = this.getPiercingK(ArmorTypeNames.InfantryHeavyMedium)
const infHeavyHighPiercing = this.getPiercingK(ArmorTypeNames.InfantryHeavyHigh)
const demonPiercing = this.getPiercingK(ArmorTypeNames.DemonMedium)
const demonHighPiercing = this.getPiercingK(ArmorTypeNames.DemonHigh)
const commanderPiercing = this.getPiercingK(ArmorTypeNames.Commander)
@ -120,13 +123,16 @@ class Weapon extends React.Component<IWeaponProps, any> {
const StyledTableCell = styled(TableCell)(({theme}) => ({
[`&.${tableCellClasses.head}`]: {
backgroundColor: "rgb(234, 234, 234)",
color: theme.palette.common.white,
backgroundColor: "rgb(244,244,244)",
marginRight: 'auto',
marginLeft: 'auto',
paddingLeft: 10
},
[`&.${tableCellClasses.body}`]: {
fontSize: 13,
paddingLeft: 10,
fontSize: 12,
textAlign: 'center',
paddingRight: 18,
paddingLeft: 10
},
}));
@ -276,7 +282,7 @@ class Weapon extends React.Component<IWeaponProps, any> {
</ToggleButtonGroup>
<TableContainer>
{ this.props.mod !== undefined && this.props.mod.name.includes("Ultimate Apocalypse") ?
<Table sx={{marginTop: "10px"}} size="small" aria-label="a dense table">
<Table sx={{marginTop: "10px", textAlign: "center"}} size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<StyledTableCell><ArmorType
@ -335,7 +341,10 @@ class Weapon extends React.Component<IWeaponProps, any> {
<StyledTableCell><ArmorType
name={ArmorTypeNames.BuildingSuper}/></StyledTableCell>
<StyledTableCell><img style={{verticalAlign: "top"}}
src="/images/ARM_Morale.webp"/></StyledTableCell>
src="/images/ARM_Morale.webp"/>
<div style={{width: 20, fontSize: 12, height: 50}}>
<i>Morale</i></div>
</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
@ -386,7 +395,10 @@ class Weapon extends React.Component<IWeaponProps, any> {
<StyledTableCell><ArmorType
name={ArmorTypeNames.BuildingHigh}/></StyledTableCell>
<StyledTableCell><img style={{verticalAlign: "top"}}
src="/images/ARM_Morale.webp"/></StyledTableCell>
src="/images/ARM_Morale.webp"/>
<div style={{width: 20, fontSize: 12, height: 50}}>
<i>Morale</i></div>
</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
@ -415,6 +427,9 @@ class Weapon extends React.Component<IWeaponProps, any> {
</TableContainer>
<i className="rgdFrom">{weapon.filename}</i>
</Grid2>
<Grid2 size={12}>
{renderAffectedResearches(weapon.affectedResearches, this.props.mod.id, this.props.race.id)}
</Grid2>
</Grid2>
</AccordionDetails>
</Accordion></div>

View File

@ -2,11 +2,13 @@ import Weapon from "./Weapon";
import React from "react";
import {IWeapon} from "../types/IUnit";
import {IMod} from "../types/Imod";
import {Irace} from "../types/Irace";
export interface WeaponSlotProps {
hardpoint: number,
unitWeapons: Map<number, IWeapon> | undefined,
mod: IMod,
race: Irace,
showOnlyDefault?: boolean ,
}
@ -33,7 +35,7 @@ const WeaponSlot= (props: WeaponSlotProps) => {
return (
<div>
<h3>{header}</h3>
<div style={{marginLeft: 20}}><Weapon mod={props.mod} isDefault={true} weapon={firstWeapon}/></div>
<div style={{marginLeft: 20}}><Weapon race={props.race} mod={props.mod} isDefault={true} weapon={firstWeapon}/></div>
</div>
)
}
@ -43,7 +45,7 @@ const WeaponSlot= (props: WeaponSlotProps) => {
<h3>{header}</h3>
{weaponsSorted.filter(wp => !wp[1].filename.includes("dummy")).map(wp =>
<div style={{marginLeft: 20}}><Weapon mod={props.mod} isDefault={!haveDummy || wp[1].filename === firstWeapon.filename} weapon={wp[1]}/></div>
<div style={{marginLeft: 20}}><Weapon race={props.race} mod={props.mod} isDefault={!haveDummy || wp[1].filename === firstWeapon.filename} weapon={wp[1]}/></div>
)}
</div>
)

View File

@ -11,120 +11,30 @@ import {
TableContainer,
TableRow
} from "@mui/material";
import {IAddonModifier, IBuilding, IBuildingAddon, IBuildingAddonShort} from "../../types/IBuilding";
import {IBuilding, IBuildingAddon} from "../../types/IBuilding";
import {WeaponHardpoint} from "../../types/IUnit";
import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
import WeaponSlot from "../WeaponSlot";
import {ExpandMore} from "@mui/icons-material";
import {IconUrl} from "../../core/api";
import {IBuildingShort} from "../../types/IBuildingShort";
import {goToAddon} from "../../pages/BuildingPage";
import {IMod} from "../../types/Imod";
import {IModifier} from "../../types/IModifier";
import {getIcon, ModifiersProvidesAddonTable,} from "../ModifiersProvideTable";
import Required from "../Required";
import {Irace} from "../../types/Irace";
interface IAddonModifiersProvidesTable{
modifiers: IAddonModifier[],
}
function AddonModifiersProvidesTable (props: IAddonModifiersProvidesTable) {
function getModName(mt: String){
switch(mt) {
case 'modifiers\\armour_modifier.lua':
return 'Armor';
case 'modifiers\\health_maximum_modifier.lua':
return 'Health';
case 'modifiers\\keen_sight_radius_modifier.lua':
return 'Detect';
case 'modifiers\\sight_radius_modifier.lua':
return 'Sight';
case 'modifiers\\garrison_requisition_modifier.lua':
return 'Requisition';
case 'modifiers\\faith_max_modifier.lua':
return 'Faith max';
case 'modifiers\\holy_icon_cost_power.lua':
return <span>Next icon cost <img style={{verticalAlign: "top"}} src='/images/Resource_power.gif'/></span>;
case 'modifiers\\holy_icon_cost_requisition.lua':
return <span>Next icon cost <img style={{verticalAlign: "top"}} src='/images/Resource_requisition.gif'/></span>;
case 'modifiers\\enable_hardpoint_02.lua':
return 'Weapon slot 2';
case 'modifiers\\cost_requisition_modifier.lua':
return 'Requisition cost';
default:
return mt.replace("modifiers\\", "").replace("modifier.lua", "").replace(".lua", "").replaceAll("_", " ");
}
}
function getModIcon(mt: String){
switch(mt) {
case 'modifiers\\armour_modifier.lua':
return <img style={{height: 20, verticalAlign: "top"}} src='/images/defence.png'/>;
case 'modifiers\\health_maximum_modifier.lua':
return <img style={{verticalAlign: "top"}} src='/images/Health_icon.webp'/>;
case 'modifiers\\keen_sight_radius_modifier.lua':
return <img style={{verticalAlign: "top"}} src='/images/DETECT_YES.webp'/>;
case 'modifiers\\sight_radius_modifier.lua':
return <img style={{verticalAlign: "top"}} src='/images/DETECT_NO.webp'/>;
case 'modifiers\\faith_max_modifier.lua':
return <img style={{verticalAlign: "top"}} src='/images/Resource_faith.gif'/>;
case 'modifiers\\garrison_requisition_modifier.lua':
return <img style={{verticalAlign: "top"}} src='/images/Resource_requisition.gif'/>;
case 'modifiers\\cost_requisition_modifier.lua':
return <img style={{verticalAlign: "top"}} src='/images/Resource_requisition.gif'/>;
default:
return "";
}
}
function getChangeDescription(ref: String, v: number) {
switch(ref) {
case 'type_modifierusagetype\\tp_mod_usage_addition.lua':
if(v < 0){
return v
}else return '+' + v;
case 'type_modifierusagetype\\tp_mod_usage_multiplication.lua':
return 'x' + v;
case 'type_modifierusagetype\\tp_mod_usage_percentage.lua':
return '%' + v;
case 'type_modifierusagetype\\tp_mod_usage_enable.lua':
if(v === 1){
return ' enable ';
} else {
return ' disable ';
}
default:
return ref;
}
}
let noWeaponMods = props.modifiers.filter(m => !m.reference.includes("default_weapon_modifier_hardpoint"))
return (
<TableContainer component={Paper}>
<Table size="small" aria-label="a dense table">
{noWeaponMods.map(m =>
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">{getModName(m.reference)}</TableCell>
<TableCell component="th" scope="row">{getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)}</TableCell>
</TableRow>
)}
</Table>
</TableContainer>
)
}
interface IAddonModifiersProvidesWeapons{
modifiers: IAddonModifier[],
replacedAddonModifiers: IAddonModifier[],
modifiers: IModifier[],
replacedAddonModifiers: IModifier[],
weapons: WeaponHardpoint[],
mod: IMod,
race: Irace
}
function AddonModifiersProvidesWeapons (props: IAddonModifiersProvidesWeapons) {
function getWeaponByAddonMod(ar: IAddonModifier){
function getWeaponByAddonMod(ar: IModifier){
let hardpoint = Number(ar.reference.replace("modifiers\\default_weapon_modifier_hardpoint", "").replace(".lua", ""));
let value = ar.value;
@ -133,7 +43,7 @@ function AddonModifiersProvidesWeapons (props: IAddonModifiersProvidesWeapons) {
let weapon = props.weapons.find(w => w.hardpoint === hardpoint && w.hardpointOrder === 1 + value )?.weapon
if (weapon != null){
return <WeaponSlot mod={props.mod} unitWeapons={new Map().set(hardpoint, weapon)} hardpoint={hardpoint}/>;
return <WeaponSlot race={props.race} mod={props.mod} unitWeapons={new Map().set(hardpoint, weapon)} hardpoint={hardpoint}/>;
}else{
return "Can't find weapon, replaced by addon:" + value + " - "
}
@ -162,43 +72,20 @@ function BuildingAddon(props: IBuildingAddonProps){
const building = props.building
const prevAddonModifiers: IAddonModifier[] = (function() {
const prevAddonModifiers: IModifier[] = (function() {
if (addon.addonRequirement && addon.addonRequirement.requireAddon !== null && !addon.addonRequirement.replaceWhenDone){
return building.addons.find(a => a.id === addon.addonRequirement.requireAddon.id)?.addonModifiers ?? [];
} else return [];
})()
function renderRequirementBuilding(requirementBuildings: IBuildingShort[], building: IBuilding) {
return requirementBuildings.length > 0 && (
<div>&nbsp; Buildings: <img style={{verticalAlign: "top", height: 25}}
src={IconUrl + requirementBuildings[0].icon.replaceAll('\\', '/')}/>
&nbsp;{" "}<a href={`/mod/${props.mod.id}/race/${building.race.id}/building/${requirementBuildings[0].id}`}>
{requirementBuildings[0].name}
</a> {requirementBuildings[1] !== undefined && <span>or &nbsp;<img style={{verticalAlign: "top", height: 25}}
src={IconUrl + requirementBuildings[1].icon.replaceAll('\\', '/')}/>
&nbsp;{" "}<a href={`/mod/${props.mod.id}/race/${building.race.id}/building/${requirementBuildings[1].id}`}>
{requirementBuildings[1].name}</a></span> } </div>
);
}
function renderRequirementGlobalAddons(rgas: IBuildingAddonShort[], building: IBuilding) {
return <div> {rgas.map(rga =>
<span>&nbsp; Global addon: <img style={{verticalAlign: "top", height: 25}}
src={IconUrl + rga.icon.replaceAll('\\', '/')}/>
&nbsp;<a href={`/mod/${props.mod.id}/race/${building.race.id}/building/${rga.buildingId}#addon-${rga.id}`}>
{rga.name}
</a></span>
)}</div>
}
return <div className='addon-accordion' id={"addon-" + addon.id} ><Accordion>
return <div className='addon-research-accordion' id={"addon-" + addon.id} ><Accordion>
<AccordionSummary
expandIcon={<ExpandMore/>}
aria-controls="panel1-content"
>
<span style={{fontSize: 20}}>
{addon.icon && <img className="sergeantIcon" src={IconUrl + addon.icon.replaceAll('\\', '/')}/>}
<img className="sergeantIcon" src={getIcon(addon.icon)}/>
&nbsp; {addon.name}
</span>
</AccordionSummary>
@ -242,7 +129,7 @@ function BuildingAddon(props: IBuildingAddonProps){
</TableBody>
</Table>
</TableContainer><br/>
<AddonModifiersProvidesTable modifiers={addon.addonModifiers}/>
<ModifiersProvidesAddonTable modifiers={addon.addonModifiers}/>
</Grid2>
<Grid2 size={{xs: 12, md: 8}}>
<div style={{whiteSpace: "pre-wrap"}}>
@ -251,32 +138,11 @@ function BuildingAddon(props: IBuildingAddonProps){
</Grid2>
{addon.addonModifiers !== null &&
<Grid2 size={{xs: 12, md: 12}}>
<AddonModifiersProvidesWeapons mod={props.mod} modifiers={addon.addonModifiers} replacedAddonModifiers={prevAddonModifiers} weapons={building.weapons}/>
<AddonModifiersProvidesWeapons race={building.race} mod={props.mod} modifiers={addon.addonModifiers} replacedAddonModifiers={prevAddonModifiers} weapons={building.weapons}/>
</Grid2>}
{addon.addonRequirement !== null &&
<Grid2 size={{xs: 12, md: 12}}>
<h4>Required: </h4>
{addon.addonRequirement.requiredTotalPop !== undefined && addon.addonRequirement.requiredTotalPop !== null &&
<div>&nbsp; Population: <img style={{verticalAlign: "top", height: 25}}
src="/images/Resource_orksquadcap.gif"/>&nbsp;
{addon.addonRequirement.requiredTotalPop}</div>
}
{addon.addonRequirement.requireAddon !== null &&
<div>&nbsp; Addon: <img style={{verticalAlign: "top", height: 25}}
src={IconUrl + addon.addonRequirement.requireAddon.icon.replaceAll('\\', '/')}/>&nbsp;
<a href={"#addon-" + addon.addonRequirement.requireAddon.id} onClick={() => goToAddon(`#addon-` + addon.addonRequirement.requireAddon.id)}>{addon.addonRequirement.requireAddon.name}</a>
{addon.addonRequirement.replaceWhenDone && <i> (old provides replace)</i>}
</div>
}
{addon.addonRequirement.requirementBuildings.map(b =>
<div>&nbsp; Building: <img style={{verticalAlign: "top", height: 25}}
src={IconUrl + b.icon.replaceAll('\\', '/')}/>&nbsp;
<a href= {'/mod/'+ props.mod.id +'/race/'+ building.race.id +'/building/' + b.id + "/"}>{b.name}</a></div>)
}
{addon.addonRequirement.requirementBuildingsEither.length !== 0 &&
renderRequirementBuilding(addon.addonRequirement.requirementBuildingsEither, building)}
{addon.addonRequirement.requirementsGlobalAddons.length !== 0 &&
renderRequirementGlobalAddons(addon.addonRequirement.requirementsGlobalAddons, building)}
<Required requirement={addon.addonRequirement} building={building}/>
</Grid2>}
</Grid2>
<i className="rgdFrom">{addon.filename}</i>

View File

@ -0,0 +1,124 @@
import {IMod} from "../../types/Imod";
import {IResearch} from "../../types/IResearch";
import {
Accordion,
AccordionDetails,
AccordionSummary,
Grid2,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableRow
} from "@mui/material";
import {ExpandMore} from "@mui/icons-material";
import {getIcon, ModifiersProvidesResearchTable} from "../ModifiersProvideTable";
import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
import Required from "../Required";
import React from "react";
import {IBuilding} from "../../types/IBuilding";
import {IResearchShort} from "../../types/IResearchShort";
interface IResearchProps {
research: IResearch,
building: IBuilding,
mod: IMod,
}
function ResearchFull(props: IResearchProps){
const research = props.research
const building = props.building
return <div className='addon-research-accordion' id={"research-" + research.id}><Accordion>
<AccordionSummary
expandIcon={<ExpandMore/>}
aria-controls="panel1-content"
>
<span style={{fontSize: 20}}>
<img className="sergeantIcon" src={getIcon(research.icon)}/>
&nbsp; {research.name}
</span>
</AccordionSummary>
<AccordionDetails>
<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">
{research.costRequisition > 0 &&
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_requisition.gif"/>&nbsp;
{research.costRequisition.toFixed(0)}</span>}
{research.costPower > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_power.gif"/>&nbsp;
{research.costPower.toFixed(0)}</span>}
{(research.costPopulation !== undefined && research.costPopulation > 0) &&
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_orksquadcap.gif"/>&nbsp;
{research.costPopulation.toFixed(0)}</span>}
{(research.costFaith !== undefined && research.costFaith > 0) &&
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_faith.gif"/>&nbsp;
{research.costFaith}</span>}
{(research.costSouls !== undefined && research.costSouls > 0) &&
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_souls.gif"/>&nbsp;
{research.costSouls.toFixed(0)}</span>}
{(research.costTime !== undefined && research.costTime > 0) &&
<span>&nbsp;<AvTimerOutlinedIcon
style={{verticalAlign: "top", fontSize: "18px"}}/>&nbsp;
{research.costTime}s</span>}
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer><br/>
</Grid2>
<Grid2 size={{xs: 12, md: 8}}>
<div style={{whiteSpace: "pre-wrap"}}>
{research.description}
</div>
</Grid2>
<Grid2 size={{xs: 12, md: 12}}>
<ModifiersProvidesResearchTable modifiers={research.modifiers} research={research} race={building.race}/>
</Grid2>
{research.requirements !== null &&
<Grid2 size={{xs: 12, md: 12}}>
<Required requirement={research.requirements} building={building}/>
</Grid2>}
</Grid2>
<i className="rgdFrom">{research.filename}</i>
</AccordionDetails>
</Accordion></div>
}
export function renderAffectedResearches(researches: IResearchShort[], modId: number, raceId: string) {
function researchLink(rs: IResearchShort) {
return <span><img style={{verticalAlign: "top", height: 25}}
src={getIcon(rs.icon)}/>
&nbsp;<a href={`/mod/${modId}/race/${raceId}/building/${rs.buildingId}#research-${rs.id}`}>
{rs.name}
</a></span>
}
return <div> {researches.map(rs =>
rs.buildingId == null ? <span></span> :
<span>Research affect: {researchLink(rs)}<br/></span>
)}</div>
}
export default ResearchFull;

View File

@ -1,4 +1,4 @@
.addon-accordion{
.addon-research-accordion{
margin-top: 10px;
scroll-margin-top: 15px;
}
@ -6,5 +6,5 @@
.selected-addon div {
background-color: antiquewhite;
background-color: #fafaf2;
}

View File

@ -3,7 +3,18 @@ import React from "react";
import {withRouter} from "../core/withrouter";
import {IWeapon} from "../types/IUnit";
import '../css/Building.css'
import {Button, Grid2, Paper, Table, TableBody, TableCell, TableContainer, TableRow} from "@mui/material";
import {
Button,
Grid2,
Link,
ListItem,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableRow
} from "@mui/material";
import {ArrowBack} from "@mui/icons-material";
import ArmorType from "../classes/ArmorType";
import AvTimerOutlinedIcon from '@mui/icons-material/AvTimer';
@ -13,12 +24,25 @@ import {IMod} from "../types/Imod";
import {IBuilding} from "../types/IBuilding";
import Vision from "../classes/Vision";
import BuildingAddon from "../classes/building/BuildingAddon";
import {IUnitShort} from "../types/IUnitShort";
import Research, {renderAffectedResearches} from "../classes/building/Research";
interface UintPageState {
building: IBuilding,
mod: IMod,
}
function Unit (unit: IUnitShort, modId: number, raceId: String) {
return (<Link href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id}><ListItem>
{unit.icon && <img className="unitIcon" src={IconUrl + unit.icon.replaceAll('\\', '/')}/> }
{unit.name}
{unit.canDetect && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/DETECT_YES.webp"/></span>}
</ListItem></Link>)
}
function Building(building: IBuilding, mod: IMod) {
@ -36,13 +60,16 @@ function Building(building: IBuilding, mod: IMod) {
}
})
var buildingName = building.name;
if(building.name == null){
buildingName = building.filename.replaceAll('_', ' ').replace('.rgd', '');
}
return (
<div>
<h1>{mod.name} ({mod.version})</h1>
<h2>{building.icon &&
<img className="unitIcon" src={IconUrl + building.icon.replaceAll('\\', '/')}/>} {building.name} </h2>
<img className="unitIcon" src={IconUrl + building.icon.replaceAll('\\', '/')}/>} {buildingName} </h2>
<Grid2 container spacing={2}>
<Grid2 size={{xs: 12, md: 4}}>
@ -103,7 +130,8 @@ function Building(building: IBuilding, mod: IMod) {
>
<TableCell component="th" scope="row">Armor type</TableCell>
<TableCell component="th" scope="row">
<ArmorType name={building.armorType.name}/>
<ArmorType name={building.armorType.name} compact={false}/>
{building.armorType2 && <span><br/><ArmorType name={building.armorType2.name} compact={false}/></span> }
</TableCell>
</TableRow>
<TableRow
@ -142,16 +170,31 @@ function Building(building: IBuilding, mod: IMod) {
{building.description}
</div>
</Grid2>
{building.units.length > 0 && <Grid2 size={12}>
<h3>Unit production</h3>
{building.units.map(unit =>
Unit(unit, mod.id, building.race.id)
)}
</Grid2>}
{building.addons.length > 0 && <Grid2 size={12}>
<h3>Addons</h3>
{building.addons.map(b =>
<BuildingAddon mod={mod} addon={b} building={building}/>
)}
</Grid2> }
{building.researches.length > 0 && <Grid2 size={12}>
<h3>Researches</h3>
{building.researches.map(r =>
<Research mod={mod} research={r} building={building}/>
)}
</Grid2> }
<Grid2 size={12}>
{[...mapBuildingWeapons.keys()].sort(function (a, b) {
return a - b;
}).map(h => <WeaponSlot mod={mod} showOnlyDefault={true} unitWeapons={mapBuildingWeapons.get(h)} hardpoint={h}/>)}
}).map(h => <WeaponSlot race={building.race} mod={mod} showOnlyDefault={true} unitWeapons={mapBuildingWeapons.get(h)} hardpoint={h}/>)}
</Grid2>
<Grid2 size={12}>
{renderAffectedResearches(building.affectedResearches, mod.id, building.race.id)}
</Grid2>
</Grid2>
<i className="rgdFrom">{building.filename}</i>
@ -162,7 +205,6 @@ function Building(building: IBuilding, mod: IMod) {
function goToAddon(addonHash: string) {
let addonId = addonHash.replace("#", "");
console.log(addonId);
[].forEach.call(document.querySelectorAll('div'), function (el: HTMLElement) {
el?.classList?.remove('selected-addon');
});
@ -170,6 +212,15 @@ function goToAddon(addonHash: string) {
document?.getElementById(addonId)?.scrollIntoView()
}
function goToResearch(researchHash: string) {
let researchId = researchHash.replace("#", "");
[].forEach.call(document.querySelectorAll('div'), function (el: HTMLElement) {
el?.classList?.remove('selected-research');
});
document?.getElementById(researchId)?.classList?.add("selected-research");
document?.getElementById(researchId)?.scrollIntoView()
}
class BuildingPage extends React.Component<any, UintPageState> {
@ -188,6 +239,7 @@ class BuildingPage extends React.Component<any, UintPageState> {
mod: modData
});
goToAddon(window.location.hash);
goToResearch(window.location.hash);
}
render() {
@ -207,4 +259,4 @@ class BuildingPage extends React.Component<any, UintPageState> {
export default withRouter(BuildingPage);
export { goToAddon };
export { goToAddon, goToResearch };

View File

@ -21,6 +21,8 @@ function Mods (mods: IMod[]) {
function Mod (modName: String) {
let sameMods = mapWithModVersions.get(modName) ?? []
let lastBeta = sameMods.filter((m) => m.isBeta).reverse()[0]
return(
<div>
<h1>{modName}</h1>
@ -28,9 +30,9 @@ function Mods (mods: IMod[]) {
{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>)}
{lastBeta != null &&
<ul><NavLink state={lastBeta.id} to={"/mod/" + lastBeta.id}>{lastBeta.version}</NavLink></ul>
}
</div>
)
}

View File

@ -62,7 +62,7 @@ function UnitSmall (unit: IUnitShort, modId: number, raceId: String) {
return (<Link href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id}>
{unit.icon && <img className="unitIconSmall" src={IconUrl + unit.icon.replaceAll('\\', '/')}/>}
<span style={{fontSize: 12}}>{unitName}</span>
{unit.canDetect && <span>&nbsp;<img style={{verticalAlign: "top"}}
{unit.canDetect && <span>&nbsp;<img
src="/images/DETECT_YES.webp"/></span>}<br/></Link>)
}
@ -140,6 +140,7 @@ class Units extends React.Component<UnitsProps, UnitsState> {
<h3>All units</h3>
</AccordionSummary>
<AccordionDetails>
<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))}
@ -156,6 +157,7 @@ class Units extends React.Component<UnitsProps, UnitsState> {
{this.state.units.support.map(unit => Unit(unit, this.props.modId, this.props.raceId))}
</List>
</Grid2>
</Grid2>
</AccordionDetails>
</Accordion>
</Grid2><br/>

View File

@ -24,6 +24,10 @@ import WeaponSlot from "../classes/WeaponSlot";
import UnitsTable from "../classes/UnitsTable";
import {IMod} from "../types/Imod";
import Vision from "../classes/Vision";
import {IResearchRequirements} from "../types/IResearchRequirements";
import {getIcon} from "../classes/Required";
import {IResearchShort} from "../types/IResearchShort";
import {renderAffectedResearches} from "../classes/building/Research";
interface UintPageState {
unit: IUnit,
@ -188,7 +192,9 @@ function Unit(unit: IUnit, mod: IMod) {
>
<TableCell component="th" scope="row">Armor type</TableCell>
<TableCell component="th" scope="row">
<ArmorType name={unit.armorType.name}/>
<ArmorType name={unit.armorType.name} compact={false}/>
{unit.armorType2 && <span style={{fontSize: 12}} ><br/><i>after upgrade can become:<br/><ArmorType
name={unit.armorType2.name} compact={false}/></i></span> }
</TableCell>
</TableRow>
<TableRow
@ -200,7 +206,7 @@ function Unit(unit: IUnit, mod: IMod) {
src="/images/Health_icon.webp"/>&nbsp;
{unit.health} {unit.healthRegeneration > 0 &&
<span>+{unit.healthRegeneration}/s</span>}
{unit.armour !== undefined && unit.armour > 0 && <span><img style={{height: 20, verticalAlign: "top"}} src="/images/defence.png"/>{unit.armour} </span> }
{unit.armour !== undefined && unit.armour !== 0 && <span><img style={{height: 20, verticalAlign: "top"}} src="/images/defence.png"/>{unit.armour} </span> }
</TableCell>
</TableRow>
<TableRow
@ -243,11 +249,11 @@ function Unit(unit: IUnit, mod: IMod) {
</TableCell>
</TableRow>
}
{unit.repairSpeed !== undefined && unit.repairSpeed !== null &&
{unit.repairSpeed !== undefined && unit.repairSpeed !== null && unit.repairCostPercent !== null &&
<TableRow>
<TableCell>Repair speed</TableCell>
<TableCell>Repair</TableCell>
<TableCell>
{unit.repairSpeed}
{unit.repairSpeed} hp/s; {unit.repairCostPercent}% cost
</TableCell>
</TableRow>
}
@ -271,7 +277,7 @@ function Unit(unit: IUnit, mod: IMod) {
<SergeantShort name={s.name} icon={s.icon} canDetect={s.detectRadius > 0}/>
</AccordionSummary>
<AccordionDetails>
<Sergeant mod={mod} sergeant={s}/>
<Sergeant mod={mod} sergeant={s} race={unit.race}/>
<hr/>
</AccordionDetails>
@ -280,7 +286,10 @@ function Unit(unit: IUnit, mod: IMod) {
<Grid2 size={12}>
{[...mapWithUnitWeapons.keys()].sort(function (a, b) {
return a - b;
}).map(h => <WeaponSlot mod={mod} unitWeapons={mapWithUnitWeapons.get(h)} hardpoint={h}/>)}
}).map(h => <WeaponSlot race={unit.race} mod={mod} unitWeapons={mapWithUnitWeapons.get(h)} hardpoint={h}/>)}
</Grid2>
<Grid2 size={12}>
{renderAffectedResearches(unit.affectedResearches, mod.id, unit.race.id)}
</Grid2>
</Grid2>
<i className="rgdFrom">{unit.filename}</i>
@ -290,6 +299,8 @@ function Unit(unit: IUnit, mod: IMod) {
}
class UnitPage extends React.Component<any, UintPageState> {

View File

@ -2,15 +2,15 @@ enum ArmorTypeValues {
InfantryLow = 'Infantry Low',
InfantryMedium = 'Infantry Medium',
InfantryHigh = 'Infantry High',
InfantryHeavyMedium = 'Infantry Heavy Medium',
InfantryHeavyHigh = 'Infantry Heavy High',
Commander = 'Commander',
InfantryHeavyMedium = 'Infantry H.Med.',
InfantryHeavyHigh = 'Infantry H.High',
Commander = 'Infantry Com.',
VehicleLow = 'Vehicle Low',
VehicleMedium = 'Vehicle Medium',
VehicleHigh = 'Vehicle High',
LivingMetal = 'Living Metal',
Titan = 'Titan',
Air = 'Air',
Air = 'Vehicle Air',
BuildingLow = 'Building Low',
BuildingMedium = 'Building Medium',
BuildingHigh = 'Building High',

View File

@ -0,0 +1,23 @@
export interface IAffectedEntity {
id: number
name: string
icon: string
filename: string
}
export interface ISergeantUnitShortDto {
id: number
name: string
icon: string
filename: string
unit: IAffectedEntity
}
export interface IWeaponUnitShortDto {
id: number
name: string
filename: string
units: IAffectedEntity[]
sergeants: ISergeantUnitShortDto[]
buildings: IAffectedEntity[]
}

View File

@ -1,7 +1,11 @@
import {Irace} from "./Irace";
import {IArmorType} from "./IArmorType";
import {WeaponHardpoint} from "./IUnit";
import {IBuildingShort} from "./IBuildingShort";
import {IUnitShort} from "./IUnitShort";
import {IModifier} from "./IModifier";
import {IRequirement} from "./IRequirement";
import {IResearch} from "./IResearch";
import {IResearchShort} from "./IResearchShort";
export interface IBuilding {
id: number
@ -30,6 +34,9 @@ export interface IBuilding {
modId: number
weapons: WeaponHardpoint[]
addons: IBuildingAddon[]
researches: IResearch[]
units: IUnitShort[]
affectedResearches: IResearchShort[],
}
export interface IBuildingAddon {
@ -43,8 +50,8 @@ export interface IBuildingAddon {
addonCostFaith: number;
addonCostSouls: number;
addonCostTime: number;
addonModifiers: IAddonModifier[];
addonRequirement: IAddonRequirement;
addonModifiers: IModifier[];
addonRequirement: IRequirement;
icon?: string;
}
@ -54,19 +61,3 @@ export interface IBuildingAddonShort {
name: string;
icon: string;
}
export interface IAddonRequirement {
requirementBuildings: IBuildingShort[];
requirementBuildingsEither: IBuildingShort[];
requirementsGlobalAddons: IBuildingAddonShort[];
replaceWhenDone: Boolean;
requireAddon: IBuildingAddonShort;
requiredTotalPop?: number;
}
export interface IAddonModifier {
id: number;
reference: string;
usageType: string;
value: number;
}

8
src/types/IModifier.tsx Normal file
View File

@ -0,0 +1,8 @@
export interface IModifier {
id: number;
reference: string;
usageType: string;
target: string;
value: number;
}

View File

@ -0,0 +1,13 @@
import {IBuildingShort} from "./IBuildingShort";
import {IBuildingAddonShort} from "./IBuilding";
import {IResearchRequirements} from "./IResearchRequirements";
export interface IRequirement {
requirementBuildings: IBuildingShort[];
requirementBuildingsEither: IBuildingShort[];
requirementsGlobalAddons: IBuildingAddonShort[];
requirementResearches: IResearchRequirements[];
replaceWhenDone: Boolean;
requireAddon: IBuildingAddonShort;
requiredTotalPop?: number;
}

24
src/types/IResearch.tsx Normal file
View File

@ -0,0 +1,24 @@
import {IModifier} from "./IModifier";
import {IRequirement} from "./IRequirement";
import {IAffectedEntity, ISergeantUnitShortDto, IWeaponUnitShortDto} from "./IAffectedEntity";
export interface IResearch {
id: number
name: string
filename: string
description: string
costRequisition: number
costPower: number
costPopulation: number
costFaith: number
costSouls: number
costTime: number
icon: string
modId: number
affectedUnits: IAffectedEntity[]
affectedSergeants: ISergeantUnitShortDto[]
affectedBuildings: IAffectedEntity[]
affectedWeapons: IWeaponUnitShortDto[]
requirements: IRequirement
modifiers: IModifier[]
}

View File

@ -0,0 +1,6 @@
import {IResearchShort} from "./IResearchShort";
export interface IResearchRequirements {
researchShortDto: IResearchShort,
researchMustNotBeComplete: Boolean,
}

View File

@ -0,0 +1,6 @@
export interface IResearchShort {
id: string;
name: string;
icon: string;
buildingId: number;
}

View File

@ -1,5 +1,6 @@
import {Irace} from "./Irace";
import {IArmorType} from "./IArmorType";
import {IResearchShort} from "./IResearchShort";
export interface IUnitResponse {
race: string
@ -53,6 +54,7 @@ export interface IUnit {
modId: number
sergeants: ISergeant[]
weapons: WeaponHardpoint[]
affectedResearches: IResearchShort[],
}
export interface ISergeant {
@ -81,6 +83,7 @@ export interface ISergeant {
detectRadius: number
icon: string
weapons: WeaponHardpoint[]
affectedResearches: IResearchShort[],
}
@ -118,6 +121,7 @@ export interface IWeapon {
icon: string
modId: number
weaponArmorPiercing: IWeaponPiercing[]
affectedResearches: IResearchShort[],
}
export interface IWeaponPiercing {