Add abilities support

This commit is contained in:
Anibus 2026-01-11 21:27:49 +03:00
parent 318b5c47e9
commit e0fe94b8ca
23 changed files with 645 additions and 54 deletions

6
.idea/sbt.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ScalaSbtSettings">
<option name="customVMPath" />
</component>
</project>

BIN
public/images/NoImg.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
public/images/psd/NoImg.psd Normal file

Binary file not shown.

Binary file not shown.

42
src/classes/Ability.tsx Normal file
View File

@ -0,0 +1,42 @@
import {Accordion, AccordionDetails, AccordionSummary} from "@mui/material";
import {ExpandMore} from "@mui/icons-material";
import React from "react";
import {IAbilityShort} from "../types/IAbility";
import {getIcon} from "./ModifiersProvideTable";
import {IMod} from "../types/Imod";
import AbilityFull from "./AbilityFull";
import {Irace} from "../types/Irace";
interface IAbilityProps {
ability: IAbilityShort,
race: Irace,
mod: IMod,
}
function Ability(props: IAbilityProps){
const ability = props.ability
return <div className='addon-research-accordion' id={"research-" + ability.id}><Accordion TransitionProps={{ unmountOnExit: true, timeout: 100 }}>
<AccordionSummary
expandIcon={<ExpandMore/>}
aria-controls="panel1-content"
>
<span style={{fontSize: 20}}>
{ability.activationType === "Passive ability" ? <img className="abilityIcon" src="/images/PassiveIcon.gif"/> : <img className="abilityIcon" src={getIcon(ability.icon)}/>}
&nbsp; {ability.name ? ability.name : ability.fileName.charAt(0).toUpperCase() + ability.fileName.slice(1).replaceAll('_', ' ').replace('.rgd', '')}
<i style={{fontSize: 12}}> ({ability.activationType})</i>
</span>
</AccordionSummary>
<AccordionDetails>
<AbilityFull abilityId={ability.id} mod={props.mod} race={props.race} />
</AccordionDetails>
</Accordion></div>
}
export default Ability;

423
src/classes/AbilityFull.tsx Normal file
View File

@ -0,0 +1,423 @@
import React, {useEffect, useState} from "react";
import {AbilityUrl, AvailableBuildings, AvailableUnits, UserUrl, WeaponUrl} from "../core/api";
import {IWeapon} from "../types/IUnit";
import ArmorTypeNames from "../types/ArmorTypeValues";
import {
AccordionDetails,
Grid2,
Paper,
styled,
Table,
TableBody,
TableCell,
tableCellClasses,
TableContainer, TableHead,
TableRow, ToggleButton, ToggleButtonGroup
} from "@mui/material";
import ArmorType from "./ArmorType";
import Required from "./Required";
import {renderAffectedResearches} from "./building/Research";
import {IMod} from "../types/Imod";
import {Irace} from "../types/Irace";
import {IRaceUnits} from "../types/IUnitShort";
import {IRaceBuildings} from "../types/IBuildingShort";
import {ModifiersProvidesTable} from "./ModifiersProvideTable";
import {IAbilityFill, IAbilityShort} from "../types/IAbility";
import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
import ability from "./Ability";
interface IAbilityFullState {
ability?: IAbilityFill;
}
export default function AbilityFull(props: { abilityId?: number, mod: IMod, race: Irace, isChild?: boolean, abilityFull?: IAbilityFill }) {
const [abilityFull, setAbilityFull] = useState<IAbilityFullState>({
ability: undefined,
});
const StyledTableCell = styled(TableCell)(({theme}) => ({
[`&.${tableCellClasses.head}`]: {
backgroundColor: "rgb(244,244,244)",
marginRight: 'auto',
marginLeft: 'auto',
paddingLeft: 10
},
[`&.${tableCellClasses.body}`]: {
fontSize: 12,
textAlign: 'center',
paddingRight: 18,
paddingLeft: 10
},
}));
useEffect(() => {
if(props.abilityFull !== undefined){
setAbilityFull({
ability: props.abilityFull,
})
} else {
fetch(AbilityUrl + "/" + props.mod.id + "/" + props.abilityId)
.then(res => res.json())
.then((ability: IAbilityFill) => {
setAbilityFull({
ability: ability,
})
});
}
}, []);
if(abilityFull.ability !== undefined){
const ability = abilityFull.ability
function getPiercingK(armorType: string): number|null {
if(ability.targetFilter.length > 0 && ability.targetFilter.find(tf => tf.name == armorType) === undefined) return null
const abilityPiercing = ability.piercings.find((p) => p.armorType.name === armorType)
return (typeof abilityPiercing !== "undefined" ? abilityPiercing.piercingValue : 10) / 100
}
const infLowPiercing = getPiercingK(ArmorTypeNames.InfantryLow)
const infMedPiercing = getPiercingK(ArmorTypeNames.InfantryMedium)
const infHighPiercing = getPiercingK(ArmorTypeNames.InfantryHigh)
const infHeavyMedPiercing = getPiercingK(ArmorTypeNames.InfantryHeavyMedium)
const infHeavyHighPiercing = getPiercingK(ArmorTypeNames.InfantryHeavyHigh)
const demonPiercing = getPiercingK(ArmorTypeNames.DemonMedium)
const demonHighPiercing = getPiercingK(ArmorTypeNames.DemonHigh)
const commanderPiercing = getPiercingK(ArmorTypeNames.Commander)
const airPiercing = getPiercingK(ArmorTypeNames.Air)
const vehLowPiercing = getPiercingK(ArmorTypeNames.VehicleLow)
const vehMedPiercing = getPiercingK(ArmorTypeNames.VehicleMedium)
const vehHighPiercing = getPiercingK(ArmorTypeNames.VehicleHigh)
const buildingLowPiercing = getPiercingK(ArmorTypeNames.BuildingLow)
const buildingMedPiercing = getPiercingK(ArmorTypeNames.BuildingMedium)
const buildingHighPiercing = getPiercingK(ArmorTypeNames.BuildingHigh)
// UA mod
const demonLowPiercing = getPiercingK(ArmorTypeNames.DemonLow)
const buildingSuperPiercing = getPiercingK(ArmorTypeNames.BuildingSuper)
const livingMetalPiercing = getPiercingK(ArmorTypeNames.LivingMetal)
const titanPiercing = getPiercingK(ArmorTypeNames.Titan)
const getTotalDamage = (damagePiercing: number|null) => {
if(damagePiercing === null) return ""
if(ability.minDamageValue !== undefined && ability.minDamage !== undefined && ability.maxDamage !== undefined){
var minDamage = damagePiercing * ability.minDamage
var maxDamage = damagePiercing * ability.maxDamage
if(ability.minDamageValue !== undefined){
if (minDamage < ability.minDamageValue) {
minDamage = ability.minDamageValue
}
if (maxDamage < ability.minDamageValue) {
maxDamage = ability.minDamageValue
}
}
const averageDmg = (minDamage + maxDamage) / 2
return averageDmg.toFixed(2)
}
}
const getMoraleDamage = () => {
if(ability.moraleDamage !== undefined) return (ability.moraleDamage / 2).toFixed(2)
}
return (
<div>
<Grid2 container spacing={2}>
<Grid2 size={{xs: 12}}>
{ability.initialDelayTime !== undefined && props.isChild === true &&
<b><br/><center>&nbsp;After {ability.initialDelayTime.toFixed(1)}s&nbsp;
<img style={{verticalAlign: "top"}}
src="/images/Time_icon.webp"/></center></b>}
</Grid2>
<Grid2 size={{xs: 12, md: 4}}>
<TableContainer component={Paper}>
<Table size="small" aria-label="a dense table">
<TableBody id="unit-stats-table">
<TableRow>
<TableCell component="th" scope="row">Affected on</TableCell>
<TableCell component="th" scope="row">{ability.areaFilter}</TableCell>
</TableRow>
{props.isChild !== true && (ability.costPower !== undefined && ability.costPower > 0 ||
ability.costRequisition !== undefined && ability.costRequisition > 0 ||
ability.costFaith !== undefined && ability.costFaith > 0 ||
ability.costSouls !== undefined && ability.costSouls > 0) &&
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">Cost</TableCell>
<TableCell component="th" scope="row">
{ability.costRequisition !== undefined && ability.costRequisition > 0 &&
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_requisition.gif"/>&nbsp;
{ability.costRequisition.toFixed(0)}</span>}
{ability.costPower !== undefined && ability.costPower > 0 && <span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_power.gif"/>&nbsp;
{ability.costPower.toFixed(0)}</span>}
{(ability.costFaith !== undefined && ability.costFaith > 0) &&
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_faith.gif"/>&nbsp;
{ability.costFaith}</span>}
{(ability.costSouls !== undefined && ability.costSouls > 0) &&
<span>&nbsp;<img style={{verticalAlign: "top"}}
src="/images/Resource_souls.gif"/>&nbsp;
{ability.costSouls.toFixed(0)}</span>}
</TableCell>
</TableRow>}
{props.isChild !== true && ability.rechargeTime !== undefined && ability.rechargeTime > 0 && ability.activation !== "On/off ability" &&
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
<TableCell component="th" scope="row">Recharge time</TableCell>
<TableCell component="th" scope="row">
<img style={{verticalAlign: "top"}}
src="/images/Time_icon.webp"/>&nbsp;
{ability.rechargeTime.toFixed(0)}s&nbsp;
{ability.rechargeTimerGlobal && <i style={{fontSize: 10}}>global cooldown</i>}
</TableCell>
</TableRow>
}
{props.isChild !== true && ability.initialDelayTime !== undefined && ability.initialDelayTime > 0 &&
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
<TableCell component="th" scope="row">Delay after cast</TableCell>
<TableCell component="th" scope="row">
<img style={{verticalAlign: "top"}}
src="/images/Time_icon.webp"/>&nbsp;
{ability.initialDelayTime.toFixed(1)}s
</TableCell>
</TableRow>
}
{ability.durationTime !== undefined && ability.durationTime > 1.5 &&
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
<TableCell component="th" scope="row">Duration time</TableCell>
<TableCell component="th" scope="row">
<img style={{verticalAlign: "top"}}
src="/images/Time_icon.webp"/>&nbsp;
{ability.durationTime.toFixed(1)}s
</TableCell>
</TableRow>
}
{ability.areaEffect !== null && ability.radius !== undefined && ability.radius > 0 &&
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
<TableCell component="th" scope="row">Radius</TableCell>
<TableCell component="th" scope="row">
{ability.radius.toFixed(0)}&nbsp;<i style={{fontSize: 10}}>{ability.areaEffect}</i>
</TableCell>
</TableRow>
}
{props.isChild !== true && ability.range !== undefined && ability.areaFilter !== "Own" && ability.range > 0 &&
<TableRow sx={{'&:last-child td, &:last-child th': {border: 0}}}>
<TableCell component="th" scope="row">Range</TableCell>
<TableCell component="th" scope="row">
{ability.range.toFixed(0)}
</TableCell>
</TableRow>
}
{ability.throwForceMin !== undefined && ability.throwForceMax !== undefined && ability.throwForceMax > 0 && ability.throwForceMin > 0 &&
<TableRow
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row">Throw force</TableCell>
<TableCell component="th"
scope="row">{ability.throwForceMin != 0 ? ability.throwForceMin + " - " + ability.throwForceMax : "-"}
</TableCell>
</TableRow>}
<TableRow>
</TableRow>
</TableBody>
</Table>
</TableContainer><br/>
</Grid2>
<Grid2 size={8}>
<div style={{whiteSpace: "pre-wrap"}}>
{props.isChild !== true && ability.description}
</div>
</Grid2>
{(ability.minDamage !== undefined && ability.minDamage > 0 || ability.moraleDamage !== undefined && ability.moraleDamage > 0) &&
<Grid2 size={12}>
{(ability.refreshTime !== undefined && ability.durationTime !== undefined && ability.refreshTime * 2 < ability.durationTime) && <i>Every <b>{ability.refreshTime.toFixed(1)}s</b> deal damege:</i>}
<TableContainer>
{ props.mod !== undefined && props.mod.name.includes("Ultimate Apocalypse") ?
<Table sx={{marginTop: "10px", textAlign: "center"}} size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<StyledTableCell><ArmorType
name={ArmorTypeNames.InfantryLow}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.InfantryMedium}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.InfantryHigh}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.InfantryHeavyMedium}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.InfantryHeavyHigh}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.Commander}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.DemonLow}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.DemonMedium}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.DemonHigh}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.Air}/></StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<StyledTableCell>{getTotalDamage(infLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infHeavyMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infHeavyHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(commanderPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(demonLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(demonPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(demonHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(airPiercing)}</StyledTableCell>
</TableRow>
</TableBody>
<TableHead>
<TableRow>
<StyledTableCell><ArmorType
name={ArmorTypeNames.VehicleLow}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.VehicleMedium}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.VehicleHigh}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.LivingMetal}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.Titan}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.BuildingLow}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.BuildingMedium}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.BuildingHigh}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.BuildingSuper}/></StyledTableCell>
<StyledTableCell><img style={{verticalAlign: "top"}}
src="/images/ARM_Morale.webp"/>
<div style={{width: 20, fontSize: 12, height: 50}}>
<i>Morale</i></div>
</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<StyledTableCell>{getTotalDamage(vehLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(vehMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(vehHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(livingMetalPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(titanPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(buildingLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(buildingMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(buildingHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(buildingSuperPiercing)}</StyledTableCell>
<StyledTableCell>{getMoraleDamage()}</StyledTableCell>
</TableRow>
</TableBody>
</Table> :
<Table sx={{marginTop: "10px"}} size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<StyledTableCell><ArmorType
name={ArmorTypeNames.InfantryLow}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.InfantryMedium}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.InfantryHigh}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.InfantryHeavyMedium}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.InfantryHeavyHigh}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.Commander}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.DemonMedium}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.DemonHigh}/></StyledTableCell>
<StyledTableCell><ArmorType name={ArmorTypeNames.Air}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.VehicleLow}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.VehicleMedium}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.VehicleHigh}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.BuildingLow}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.BuildingMedium}/></StyledTableCell>
<StyledTableCell><ArmorType
name={ArmorTypeNames.BuildingHigh}/></StyledTableCell>
<StyledTableCell><img style={{verticalAlign: "top"}}
src="/images/ARM_Morale.webp"/>
<div style={{width: 20, fontSize: 12, height: 50}}>
<i>Morale</i></div>
</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<StyledTableCell>{getTotalDamage(infLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infHeavyMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(infHeavyHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(commanderPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(demonPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(demonHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(airPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(vehLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(vehMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(vehHighPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(buildingLowPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(buildingMedPiercing)}</StyledTableCell>
<StyledTableCell>{getTotalDamage(buildingHighPiercing)}</StyledTableCell>
<StyledTableCell>{getMoraleDamage()}</StyledTableCell>
</TableRow>
</TableBody>
</Table>
}
</TableContainer>
</Grid2>}
<ModifiersProvidesTable modifiers={ability.modifiers} affectedData={ability.affectedData} modId={props.mod.id} race={props.race}/>
{/*TODO: spawnedEntityName not parsing correctly, fix it*/}
{ability.spawnedEntityName !== undefined && ability.spawnedEntityName !== null && <span>Spawn {ability.spawnedEntityName}</span>}
</Grid2>
{ability.childAbility !== undefined && ability.childAbility !== null && <AbilityFull
abilityFull={ability.childAbility} isChild={true} mod={props.mod} race={props.race}/>}
{props.isChild !== true && ability.requirements !== undefined && ability.requirements !== null &&
<Grid2 size={{xs: 12, md: 12}}>
<Required requirement={ability.requirements} modId={props.mod.id} raceId={props.race.id}/>
</Grid2>}
{ability.activation !== "Passive ability" && props.isChild !== true && <b className="hotkey">Hotkey: {ability.hotkey}</b>}
</div>
)
} else return <div>loading...</div>;
}

View File

@ -13,7 +13,7 @@ interface IModifiersProvidesTable{
interface IModifiersProvidesResearchTable{
modifiers: IModifier[],
affectedData: IAffectedData,
affectedData: IAffectedData | null,
modId: number,
race: Irace,
}
@ -153,44 +153,45 @@ export function ModifiersProvidesTable (props: IModifiersProvidesResearchTable)
let mods = props.modifiers
function getTarget(target: String) {
console.log(props.affectedData.affectedUnits.map(au => au.filename))
var affectedUnit = props.affectedData.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/${props.modId}/race/${props.race.id}/unit/${affectedUnit.id}`}>
if(props.affectedData !== null){
console.log(props.affectedData.affectedUnits.map(au => au.filename))
var affectedUnit = props.affectedData.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/${props.modId}/race/${props.race.id}/unit/${affectedUnit.id}`}>
{affectedUnit.name}
</a></span>
}
var affectedSergeant = props.affectedData.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/${props.modId}/race/${props.race.id}/unit/${affectedSergeant.unit.id}`}>
}
var affectedSergeant = props.affectedData.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/${props.modId}/race/${props.race.id}/unit/${affectedSergeant.unit.id}`}>
{affectedSergeant.unit.name}
</a></span>
}
var affectedBuilding = props.affectedData.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/${props.modId}/race/${props.race.id}/building/${affectedBuilding.id}`}>
}
var affectedBuilding = props.affectedData.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/${props.modId}/race/${props.race.id}/building/${affectedBuilding.id}`}>
{affectedBuilding.name}
</a> </span>
}
}
var affectedWeapon = props.affectedData.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/${props.modId}/race/${props.race.id}/unit/${unit.id}`}>
var affectedWeapon = props.affectedData.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/${props.modId}/race/${props.race.id}/unit/${unit.id}`}>
{unit.name}
</a> </span>
)
)
}
{
affectedWeapon.sergeants.map(affectedSergeant =>
{
affectedWeapon.sergeants.map(affectedSergeant =>
<span><img style={{verticalAlign: "top", height: 25}}
src={getIcon(affectedSergeant.icon)}/>{affectedSergeant.name} at <img
style={{verticalAlign: "top", height: 25}}
@ -198,20 +199,20 @@ export function ModifiersProvidesTable (props: IModifiersProvidesResearchTable)
href={`/mod//${props.modId}/race/${props.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//${props.modId}/race/${props.race.id}/building/${affectedBuilding.id}`}>
)
}
{
affectedWeapon.buildings.map(affectedBuilding =>
<span><img style={{verticalAlign: "top", height: 25}} src={getIcon(affectedBuilding.icon)}/><a
href={`/mod//${props.modId}/race/${props.race.id}/building/${affectedBuilding.id}`}>
{affectedBuilding.name}
</a> </span>
)
}
)
}
</span>
}
}
return <span><i>{target}</i></span>;
}
@ -224,7 +225,9 @@ export function ModifiersProvidesTable (props: IModifiersProvidesResearchTable)
>
<TableCell component="th" scope="row">{getTarget(m.target)}</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>
<TableCell component="th" scope="row">{getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)}
{m.maxLifeTime !== undefined && m.maxLifeTime > 0 && <span><img style={{verticalAlign: "top"}} src='/images/Time_icon.webp'/>{m.maxLifeTime}s
</span> }<i style={{fontSize: 11}}>{m.exclusive ? " does not stuck" : " does stuck"}</i></TableCell>
</TableRow>
)}
</Table>
@ -235,7 +238,7 @@ export function ModifiersProvidesTable (props: IModifiersProvidesResearchTable)
export function getIcon(icon?: string): string{
if(icon !== null && icon !== undefined){
return IconUrl + icon.replaceAll('\\', '/')
} else return '/images/shakal.png'
} else return '/images/NoImg.gif'
}

View File

@ -9,6 +9,8 @@ import {IMod} from "../types/Imod";
import {renderAffectedResearches} from "./building/Research";
import {Irace} from "../types/Irace";
import Required from "./Required";
import {ModifiersProvidesTable} from "./ModifiersProvideTable";
import Ability from "./Ability";
export interface SergeantProps {
@ -111,6 +113,9 @@ const Sergeant = (props: SergeantProps) => {
>
<TableCell component="th" scope="row">Morale</TableCell>
<TableCell component="th" scope="row">
{sergeant.moraleIncomeMax && <span><img style={{verticalAlign: "top"}}
src="/images/ARM_Morale.webp"/> +{sergeant.moraleIncomeMax}&nbsp;</span>}
{sergeant.moraleRate && <span>+ {sergeant.moraleRate}/s</span> }
<img style={{verticalAlign: "top"}}
src="/images/Kills_icon.webp"/> -{sergeant.moraleDeathPenalty}
</TableCell>
@ -136,6 +141,16 @@ const Sergeant = (props: SergeantProps) => {
<Grid2 size={{xs: 12, md: 12}}>
<Required requirement={sergeant.requirements} modId={props.mod.id} raceId={props.race.id}/>
</Grid2>}
{sergeant.modifiers.length > 0 && <Grid2 size={12}>
<h3>Affected on</h3>
<ModifiersProvidesTable modifiers={sergeant.modifiers} modId={props.mod.id} race={props.race} affectedData={null}/>
</Grid2>}
{sergeant.abilities.length > 0 && <Grid2 size={12}>
<h3>Abilities</h3>
{sergeant.abilities.map(a =>
<Ability mod={props.mod} ability={a} race={props.race}/>
)}
</Grid2>}
<Grid2 size={12}>
{[...mapWithUnitWeapons.keys()].sort(function (a, b) {
@ -146,7 +161,7 @@ const Sergeant = (props: SergeantProps) => {
{renderAffectedResearches(sergeant.affectedResearches, props.mod.id, props.race.id)}
</Grid2>
</Grid2>
<i className="rgdFrom">{sergeant.filename}</i>
<b className="hotkey">Hotkey: {sergeant.hotkey}</b>
</div>)
};

View File

@ -21,6 +21,7 @@ import {IMod} from "../types/Imod";
import {Irace} from "../types/Irace";
import {IRaceUnits} from "../types/IUnitShort";
import {IRaceBuildings} from "../types/IBuildingShort";
import {ModifiersProvidesTable} from "./ModifiersProvideTable";
interface IWeaponFull {
currentTable: string;
@ -407,8 +408,12 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
}
</TableContainer>
<i className="rgdFrom">{weapon.filename}</i>
</Grid2>
{weapon.modifiers.length > 0 && <Grid2 size={12}>
<h3>Modifiers</h3>
<ModifiersProvidesTable modifiers={weapon.modifiers} modId={props.mod.id} race={props.race} affectedData={null}/>
</Grid2>}
{weapon.requirements !== null && !props.isDefault && props.haveReinforceMenu &&
<Grid2 size={{xs: 12, md: 12}}>
<Required requirement={weapon.requirements} modId={props.mod.id} raceId={props.race.id}/>
@ -417,6 +422,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
{renderAffectedResearches(weapon.affectedResearches, props.mod.id, props.race.id)}
</Grid2>
</Grid2>
{!props.isDefault && props.haveReinforceMenu && <b className="hotkey">Hotkey: {weapon.hotkey}</b>}
</div>
)
} else return <div>loading...</div>;

View File

@ -93,7 +93,7 @@ function BuildingAddon(props: IBuildingAddonProps){
<Required requirement={addon.addonRequirement} modId={building.modId} raceId={building.race.id}/>
</Grid2>}
</Grid2>
<i className="rgdFrom">{addon.filename}</i>
<b className="hotkey">Hotkey: {addon.hotkey}</b>
</AccordionDetails>
</Accordion></div>
}

View File

@ -107,6 +107,7 @@ function ResearchFull(props: { id: number, building: IBuilding }){
<Required requirement={research.requirements} modId={building.modId} raceId={building.race.id}/>
</Grid2>}
</Grid2>
<b className="hotkey">Hotkey: {research.hotkey}</b>
</div>
} else return <div>loading...</div>
}

View File

@ -1,6 +1,7 @@
export const UserUrl = process.env.REACT_APP_HOST_URL + '/api/v1/user';
export const WeaponUrl = process.env.REACT_APP_HOST_URL + '/api/v1/weapon';
export const AbilityUrl = process.env.REACT_APP_HOST_URL + '/api/v1/ability';
export const ResearchUrl = process.env.REACT_APP_HOST_URL + '/api/v1/research';
export const AvailableMods = process.env.REACT_APP_HOST_URL + '/api/v1/mods';
export const AvailableRacesPart = process.env.REACT_APP_HOST_URL + '/api/v1/races';

View File

@ -16,6 +16,11 @@
width: 40px;
}
.abilityIcon{
padding: 0px;
width: 40px;
}
.unitHeader{
margin-left: 15px;
}
@ -30,6 +35,6 @@
}
.rgdFrom{
font-size: 11px;
.hotkey{
font-size: 12px;
}

View File

@ -29,6 +29,7 @@ import {IUnitShort} from "../types/IUnitShort";
import Research, {renderAffectedResearches} from "../classes/building/Research";
import Required from "../classes/Required";
import {ModifiersProvidesTable} from "../classes/ModifiersProvideTable";
import Ability from "../classes/Ability";
interface UintPageState {
building: IBuilding,
@ -191,6 +192,12 @@ function Building(building: IBuilding, mod: IMod) {
)}
</Grid2>
</Grid2>}
{building.abilities.length > 0 && <Grid2 size={12}>
<h3>Abilities</h3>
{building.abilities.map(a =>
<Ability mod={mod} ability={a} race={building.race}/>
)}
</Grid2>}
{building.addons.length > 0 && <Grid2 size={12}>
<h3>Addons</h3>
{building.addons.map(b =>
@ -212,7 +219,7 @@ function Building(building: IBuilding, mod: IMod) {
{renderAffectedResearches(building.affectedResearches, mod.id, building.race.id)}
</Grid2>
</Grid2>
<i className="rgdFrom">{building.filename}</i>
<b className="hotkey">Hotkey: {building.hotkey}</b>
<hr/>
<UnitsTable modId={mod.id}/>
</div>)

View File

@ -27,6 +27,7 @@ function Mods (mods: IMod[]) {
<div>
<h1>{modName}</h1>
{modName == "Unification new races" && <ul><a href="https://dawn-of-war-unification-mod.fandom.com/"><img src="/images/mods/Uni_Lionhead.webp" width='50'/>Official unification wiki<img src="/images/mods/Uni_Lionhead.webp" width='50'/></a></ul>}
{sameMods.filter((m) => !m.isBeta).map(mod =>
<ul><NavLink state={mod.id} to= {"/mod/" + mod.id} >{mod.version}</NavLink></ul>)}
{sameMods.find((m) => m.isBeta) != null && <div><i>Beta versions:</i></div>}

View File

@ -27,6 +27,8 @@ import {IMod} from "../types/Imod";
import Vision from "../classes/Vision";
import Required from "../classes/Required";
import {renderAffectedResearches} from "../classes/building/Research";
import {ModifiersProvidesTable} from "../classes/ModifiersProvideTable";
import Ability from "../classes/Ability";
interface UintPageState {
unit: IUnit,
@ -290,6 +292,18 @@ function Unit(unit: IUnit, mod: IMod) {
</Accordion>)}
</Grid2>
{unit.modifiers.length > 0 && <Grid2 size={12}>
<h3>Affected on</h3>
<ModifiersProvidesTable modifiers={unit.modifiers} modId={unit.modId} race={unit.race} affectedData={unit.affectedData}/>
</Grid2>}
{unit.abilities.length > 0 && <Grid2 size={12}>
<h3>Abilities</h3>
{unit.abilities.map(a =>
<Ability mod={mod} ability={a} race={unit.race}/>
)}
</Grid2>}
<Grid2 size={12}>
{[...mapWithUnitWeapons.keys()].sort(function (a, b) {
return a - b;
@ -299,7 +313,7 @@ function Unit(unit: IUnit, mod: IMod) {
{renderAffectedResearches(unit.affectedResearches, mod.id, unit.race.id)}
</Grid2>
</Grid2>
<i className="rgdFrom">{unit.filename}</i>
<b className="hotkey">Hotkey: {unit.hotkey}</b>
<hr/>
<UnitsTable modId={mod.id}/>
</div>)

56
src/types/IAbility.tsx Normal file
View File

@ -0,0 +1,56 @@
import {IModifier} from "./IModifier";
import {IRequirement} from "./IRequirement";
import {IArmorType} from "./IArmorType";
import {IAffectedData} from "./IAffectedData";
export interface IAbilityShort {
id: number;
name: string;
fileName: string;
icon: string;
activationType: string;
}
export interface IAbilityFill {
id?: number;
activationType: string;
modId?: number;
filename?: string;
name?: string;
description?: string;
costRequisition?: number;
costPower?: number;
costFaith?: number;
costSouls?: number;
icon?: string;
uiIndexHint: number;
hotkey?: string;
childAbility?: IAbilityFill;
initialDelayTime?: number;
range?: number;
rechargeTime?: number;
rechargeTimerGlobal?: boolean;
refreshTime?: number;
durationTime?: number;
spawnedEntityName?: string;
radius?: number;
minDamage?: number;
minDamageValue?: number;
maxDamage?: number;
moraleDamage?: number;
throwForceMin?: number;
throwForceMax?: number;
activation?: string;
areaEffect?: string;
areaFilter?: string;
modifiers: IModifier[];
requirements?: IRequirement;
affectedData: IAffectedData,
targetFilter: IArmorType[];
piercings: IAbilityPiercing[];
}
export interface IAbilityPiercing {
armorType: IArmorType
piercingValue: number
}

View File

@ -4,9 +4,9 @@ import {WeaponHardpoint} from "./IUnit";
import {IUnitShort} from "./IUnitShort";
import {IModifier} from "./IModifier";
import {IRequirement} from "./IRequirement";
import {IResearch} from "./IResearch";
import {IResearchShort} from "./IResearchShort";
import {IAffectedData} from "./IAffectedData";
import {IAbilityShort} from "./IAbility";
export interface IBuilding {
id: number
@ -36,11 +36,13 @@ export interface IBuilding {
modifiers: IModifier[]
affectedData: IAffectedData,
weapons: WeaponHardpoint[]
abilities: IAbilityShort[]
addons: IBuildingAddon[]
researches: IResearchShort[]
units: IUnitShort[]
affectedResearches: IResearchShort[],
requirements: IRequirement,
hotkey: string
}
export interface IBuildingAddon {
@ -58,6 +60,7 @@ export interface IBuildingAddon {
addonModifiers: IModifier[];
addonRequirement: IRequirement;
icon?: string;
hotkey?: string;
}
export interface IBuildingAddonShort {

View File

@ -5,4 +5,8 @@ export interface IModifier {
usageType: string;
target: string;
value: number;
maxLifeTime: number;
applicationType: string;
probabilityOfApplying: number;
exclusive: boolean;
}

View File

@ -18,4 +18,5 @@ export interface IResearch {
affectedData: IAffectedData
requirements: IRequirement
modifiers: IModifier[]
hotkey?: string
}

View File

@ -2,6 +2,9 @@ import {Irace} from "./Irace";
import {IArmorType} from "./IArmorType";
import {IResearchShort} from "./IResearchShort";
import {IRequirement} from "./IRequirement";
import {IModifier} from "./IModifier";
import {IAffectedData} from "./IAffectedData";
import {IAbilityShort} from "./IAbility";
export interface IUnitResponse {
race: string
@ -57,7 +60,11 @@ export interface IUnit {
haveReinforceMenu: boolean,
sergeants: ISergeant[]
weapons: WeaponHardpoint[]
modifiers: IModifier[]
abilities: IAbilityShort[]
affectedData: IAffectedData,
affectedResearches: IResearchShort[],
hotkey?: string,
}
export interface ISergeant {
@ -79,15 +86,21 @@ export interface ISergeant {
health: number
armour?: number
healthRegeneration: number
morale: number
moraleDeathPenalty: number
moraleIncomeMax: number
moraleRate: number
mass: number
upTime: number
sightRadius: number
detectRadius: number
icon: string
modifiers: IModifier[]
requirements: IRequirement,
weapons: WeaponHardpoint[]
abilities: IAbilityShort[]
affectedResearches: IResearchShort[],
hotkey?: string,
}
@ -133,8 +146,10 @@ export interface IWeapon {
icon: string
modId: number
requirements: IRequirement
modifiers: IModifier[]
weaponArmorPiercing: IWeaponPiercing[]
affectedResearches: IResearchShort[],
hotkey?: string,
}
export interface IWeaponPiercing {