Add on/off weapon research dps and params tables feature
This commit is contained in:
parent
23bfad8956
commit
fa0baa62c1
4
.env
4
.env
@ -1,5 +1,5 @@
|
|||||||
NEXT_PUBLIC_HOST_URL=https://wiki-backend.dawn-of-war.pro
|
|
||||||
#NEXT_PUBLIC_HOST_URL=http://localhost:8082
|
#NEXT_PUBLIC_HOST_URL=http://localhost:8082
|
||||||
|
NEXT_PUBLIC_HOST_URL=https://wiki-backend.dawn-of-war.pro
|
||||||
|
HOST_URL=http://localhost:8082
|
||||||
|
|
||||||
# Домен сайта для sitemap/robots/canonical (замените на реальный)
|
|
||||||
NEXT_PUBLIC_SITE_URL=https://wiki.dawn-of-war.pro
|
NEXT_PUBLIC_SITE_URL=https://wiki.dawn-of-war.pro
|
||||||
67
build.ps1
Normal file
67
build.ps1
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# Скрипт сборки дистрибутива dow-wiki-frontend
|
||||||
|
# Запуск: .\build.ps1
|
||||||
|
# С шагами: .\build.ps1 -VerboseOutput
|
||||||
|
|
||||||
|
param(
|
||||||
|
[switch]$VerboseOutput
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$root = $PSScriptRoot
|
||||||
|
$deployDir = Join-Path $root "deploy"
|
||||||
|
$archive = Join-Path $root "dow-wiki-deploy.tar.gz"
|
||||||
|
|
||||||
|
# Файлы и папки, попадающие в дистрибутив
|
||||||
|
$artifacts = @(
|
||||||
|
".next",
|
||||||
|
"public",
|
||||||
|
"node_modules",
|
||||||
|
".env",
|
||||||
|
"next.config.mjs",
|
||||||
|
"package.json",
|
||||||
|
"package-lock.json",
|
||||||
|
"tsconfig.json",
|
||||||
|
"next-env.d.ts"
|
||||||
|
)
|
||||||
|
|
||||||
|
function Step($message) {
|
||||||
|
Write-Host "==> $message" -ForegroundColor Cyan
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Location $root
|
||||||
|
|
||||||
|
try {
|
||||||
|
# 1. Сборка Next.js
|
||||||
|
Step "Сборка Next.js (npm run build)"
|
||||||
|
npm run build
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Сборка Next.js завершилась с ошибкой" }
|
||||||
|
|
||||||
|
# 2. Очистка папки deploy
|
||||||
|
Step "Очистка папки deploy"
|
||||||
|
if (Test-Path $deployDir) { Remove-Item $deployDir -Recurse -Force }
|
||||||
|
New-Item -ItemType Directory -Path $deployDir | Out-Null
|
||||||
|
|
||||||
|
# 3. Копирование артефактов
|
||||||
|
Step "Копирование артефактов в deploy"
|
||||||
|
Copy-Item -Path $artifacts -Destination $deployDir -Recurse -Force
|
||||||
|
|
||||||
|
# 4. Удаление старого архива
|
||||||
|
Step "Удаление старого архива"
|
||||||
|
if (Test-Path $archive) { Remove-Item $archive -Force }
|
||||||
|
|
||||||
|
# 5. Создание архива
|
||||||
|
Step "Создание архива dow-wiki-deploy.tar.gz"
|
||||||
|
tar -czf $archive -C $deployDir .
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Ошибка при создании архива" }
|
||||||
|
|
||||||
|
# Итог
|
||||||
|
$size = "{0:N1} МБ" -f ((Get-Item $archive).Length / 1MB)
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Сборка завершена успешно." -ForegroundColor Green
|
||||||
|
Write-Host "Дистрибутив: $archive ($size)"
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "ОШИБКА: $_" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
// Server-side API helpers for metadata generation.
|
// Server-side API helpers for metadata generation.
|
||||||
// Runs only on the server (Node.js) — uses NEXT_PUBLIC_HOST_URL which is inlined at build time.
|
// Runs only on the server (Node.js) — uses HOST_URL (not exposed to the browser).
|
||||||
|
|
||||||
import { IMod } from '@/src/types/Imod';
|
import { IMod } from '@/src/types/Imod';
|
||||||
import { Irace } from '@/src/types/Irace';
|
import { Irace } from '@/src/types/Irace';
|
||||||
@ -8,8 +8,8 @@ import { IRaceBuildings } from '@/src/types/IBuildingShort';
|
|||||||
import { IUnit } from '@/src/types/IUnit';
|
import { IUnit } from '@/src/types/IUnit';
|
||||||
import { IBuilding } from '@/src/types/IBuilding';
|
import { IBuilding } from '@/src/types/IBuilding';
|
||||||
|
|
||||||
const API = process.env.NEXT_PUBLIC_HOST_URL || '';
|
const API = process.env.HOST_URL || '';
|
||||||
export const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://dow-wiki.example.com';
|
export const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://wiki.dawn-of-war.pro';
|
||||||
|
|
||||||
async function fetchJson<T>(url: string): Promise<T | null> {
|
async function fetchJson<T>(url: string): Promise<T | null> {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import React, {useEffect, useState} from "react";
|
|||||||
import {useTheme} from "@mui/material/styles";
|
import {useTheme} from "@mui/material/styles";
|
||||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||||
import {WeaponUrl} from "../core/api";
|
import {WeaponUrl} from "../core/api";
|
||||||
import {IWeapon} from "../types/IUnit";
|
|
||||||
import ArmorTypeNames from "../types/ArmorTypeValues";
|
import ArmorTypeNames from "../types/ArmorTypeValues";
|
||||||
import {
|
import {
|
||||||
Grid2,
|
Grid2,
|
||||||
@ -17,10 +16,12 @@ import Required from "./Required";
|
|||||||
import {AffectedResearches} from "./building/Research";
|
import {AffectedResearches} from "./building/Research";
|
||||||
import {IMod} from "../types/Imod";
|
import {IMod} from "../types/Imod";
|
||||||
import {Irace} from "../types/Irace";
|
import {Irace} from "../types/Irace";
|
||||||
import {ModifiersProvidesTable} from "./ModifiersProvideTable";
|
import {ModifiersProvidesTable, getIcon} from "./ModifiersProvideTable";
|
||||||
import {StyledPaper} from "../commons/StyledPaper";
|
import {StyledPaper} from "../commons/StyledPaper";
|
||||||
import {StyledTableCell} from "../commons/StyledTableCell";
|
import {StyledTableCell} from "../commons/StyledTableCell";
|
||||||
import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
|
import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer";
|
||||||
|
import {IWeapon, IWeaponResearch} from "../types/IUnit";
|
||||||
|
|
||||||
|
|
||||||
interface IWeaponFull {
|
interface IWeaponFull {
|
||||||
currentTable: string;
|
currentTable: string;
|
||||||
@ -41,6 +42,71 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
|||||||
weapon: undefined,
|
weapon: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [enabledResearchIds, setEnabledResearchIds] = useState<Set<number>>(new Set());
|
||||||
|
|
||||||
|
function applyResearchEffects(weapon: IWeapon, enabledIds: Set<number>): IWeapon {
|
||||||
|
if (enabledIds.size === 0 || !weapon.researches || weapon.researches.length === 0) {
|
||||||
|
return weapon;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modified: any = { ...weapon };
|
||||||
|
const enabledResearches = weapon.researches.filter((r: IWeaponResearch) => enabledIds.has(r.research.id));
|
||||||
|
|
||||||
|
for (const research of enabledResearches) {
|
||||||
|
if (!research.effects || research.effects.length === 0) continue;
|
||||||
|
|
||||||
|
for (const effect of research.effects) {
|
||||||
|
const usageType: string = effect.usageType;
|
||||||
|
|
||||||
|
const apply = (value: number): number => {
|
||||||
|
if (usageType.includes('multiplication')) {
|
||||||
|
return value * effect.modifierValue;
|
||||||
|
} else if (usageType.includes('addition')) {
|
||||||
|
return value + effect.modifierValue;
|
||||||
|
} else if (usageType.includes('replace')) {
|
||||||
|
return effect.modifierValue;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const field = effect.field;
|
||||||
|
|
||||||
|
switch (field) {
|
||||||
|
case 'armourPiercing':
|
||||||
|
if (!modified.weaponArmorPiercing || modified.weaponArmorPiercing.length === 0) continue;
|
||||||
|
modified.weaponArmorPiercing = modified.weaponArmorPiercing.map((p: any) => ({
|
||||||
|
...p,
|
||||||
|
piercingValue: apply(p.piercingValue),
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
case 'accuracyReductionMoving':
|
||||||
|
modified.accuracyReductionMoving = apply(modified.accuracyReductionMoving);
|
||||||
|
break;
|
||||||
|
case 'accuracy':
|
||||||
|
modified.accuracy = apply(modified.accuracy);
|
||||||
|
break;
|
||||||
|
case 'maxDamage':
|
||||||
|
modified.maxDamage = apply(modified.maxDamage);
|
||||||
|
break;
|
||||||
|
case 'maxRange':
|
||||||
|
modified.maxRange = apply(modified.maxRange);
|
||||||
|
break;
|
||||||
|
case 'minDamage':
|
||||||
|
modified.minDamage = apply(modified.minDamage);
|
||||||
|
break;
|
||||||
|
case 'reloadTime':
|
||||||
|
modified.reloadTime = apply(modified.reloadTime);
|
||||||
|
break;
|
||||||
|
case 'setupTime':
|
||||||
|
modified.setupTime = apply(modified.setupTime);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modified as IWeapon;
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
fetch(WeaponUrl + "/" + props.mod.id + "/" + props.weaponId)
|
fetch(WeaponUrl + "/" + props.mod.id + "/" + props.weaponId)
|
||||||
@ -59,8 +125,10 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
|||||||
|
|
||||||
const weapon = weaoponFull.weapon
|
const weapon = weaoponFull.weapon
|
||||||
|
|
||||||
|
const displayWeapon = applyResearchEffects(weapon, enabledResearchIds);
|
||||||
|
|
||||||
function getPiercingK(armorType: string): number {
|
function getPiercingK(armorType: string): number {
|
||||||
const weaponPiercing = weapon.weaponArmorPiercing.find((p) => p.armorType.name === armorType)
|
const weaponPiercing = displayWeapon.weaponArmorPiercing.find((p) => p.armorType.name === armorType)
|
||||||
return (typeof weaponPiercing !== "undefined" ? weaponPiercing.piercingValue : 10) / 100
|
return (typeof weaponPiercing !== "undefined" ? weaponPiercing.piercingValue : 10) / 100
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,8 +139,8 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(weapon.reloadTime < 0.125) weapon.reloadTime = 0.125
|
if(displayWeapon.reloadTime < 0.125) displayWeapon.reloadTime = 0.125
|
||||||
const dpsK = (weaoponFull.currentTable == "dps") ? weapon.accuracy * (1 / (weapon.reloadTime - (weapon.reloadTime % 0.125))) : 1
|
const dpsK = (weaoponFull.currentTable == "dps") ? displayWeapon.accuracy * (1 / (displayWeapon.reloadTime - (displayWeapon.reloadTime % 0.125))) : 1
|
||||||
|
|
||||||
const infLowPiercing = getPiercingK(ArmorTypeNames.InfantryLow)
|
const infLowPiercing = getPiercingK(ArmorTypeNames.InfantryLow)
|
||||||
const infMedPiercing = getPiercingK(ArmorTypeNames.InfantryMedium)
|
const infMedPiercing = getPiercingK(ArmorTypeNames.InfantryMedium)
|
||||||
@ -102,14 +170,14 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
|||||||
if (!isAir && !weapon.canAttackGround && !weapon.isMeleeWeapon) return ""
|
if (!isAir && !weapon.canAttackGround && !weapon.isMeleeWeapon) return ""
|
||||||
if (isAir && !weapon.canAttackAir) return ""
|
if (isAir && !weapon.canAttackAir) return ""
|
||||||
|
|
||||||
var minDamage = damagePiercing * weapon.minDamage
|
var minDamage = damagePiercing * displayWeapon.minDamage
|
||||||
var maxDamage = damagePiercing * weapon.maxDamage
|
var maxDamage = damagePiercing * displayWeapon.maxDamage
|
||||||
|
|
||||||
if (minDamage < weapon.minDamageValue) {
|
if (minDamage < displayWeapon.minDamageValue) {
|
||||||
minDamage = weapon.minDamageValue
|
minDamage = displayWeapon.minDamageValue
|
||||||
}
|
}
|
||||||
if (maxDamage < weapon.minDamageValue) {
|
if (maxDamage < displayWeapon.minDamageValue) {
|
||||||
maxDamage = weapon.minDamageValue
|
maxDamage = displayWeapon.minDamageValue
|
||||||
}
|
}
|
||||||
|
|
||||||
const averageDmg = (minDamage + maxDamage) / 2
|
const averageDmg = (minDamage + maxDamage) / 2
|
||||||
@ -118,7 +186,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getMoraleDamage = () => {
|
const getMoraleDamage = () => {
|
||||||
return (weapon.moraleDamage * dpsK / 2).toFixed(2)
|
return (displayWeapon.moraleDamage * dpsK / 2).toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -137,19 +205,19 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
|||||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||||
>
|
>
|
||||||
<TableCell component="th" scope="row" >Base damage</TableCell>
|
<TableCell component="th" scope="row" >Base damage</TableCell>
|
||||||
<TableCell>{weapon.minDamage} {weapon.maxDamage !== weapon.minDamage && "- " + weapon.maxDamage}</TableCell>
|
<TableCell>{displayWeapon.minDamage.toFixed(2)} {displayWeapon.maxDamage !== displayWeapon.minDamage && "- " + displayWeapon.maxDamage.toFixed(2)}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow
|
<TableRow
|
||||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||||
>
|
>
|
||||||
<TableCell component="th" scope="row" >Accuracy</TableCell>
|
<TableCell component="th" scope="row" >Accuracy</TableCell>
|
||||||
<TableCell>{weapon.accuracy.toFixed(3).replace(/[,.]?0+$/, '')}</TableCell>
|
<TableCell>{displayWeapon.accuracy.toFixed(3).replace(/[,.]?0+$/, '')}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow
|
<TableRow
|
||||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||||
>
|
>
|
||||||
<TableCell component="th" scope="row" >Accuracy moving</TableCell>
|
<TableCell component="th" scope="row" >Accuracy moving</TableCell>
|
||||||
<TableCell>{weapon.setupTime === 0 && weapon.accuracy - weapon.accuracyReductionMoving > 0 ? (weapon.accuracy - weapon.accuracyReductionMoving).toFixed(3).replace(/[,.]?0+$/, '') : "-"}</TableCell>
|
<TableCell>{displayWeapon.setupTime === 0 && displayWeapon.accuracy - displayWeapon.accuracyReductionMoving > 0 ? (displayWeapon.accuracy - displayWeapon.accuracyReductionMoving).toFixed(3).replace(/[,.]?0+$/, '') : "-"}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
@ -163,13 +231,13 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
|||||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||||
>
|
>
|
||||||
<TableCell component="th" scope="row" >Reload time</TableCell>
|
<TableCell component="th" scope="row" >Reload time</TableCell>
|
||||||
<TableCell>{weapon.reloadTime}</TableCell>
|
<TableCell>{displayWeapon.reloadTime}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow
|
<TableRow
|
||||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||||
>
|
>
|
||||||
<TableCell component="th" scope="row" >Range</TableCell>
|
<TableCell component="th" scope="row" >Range</TableCell>
|
||||||
<TableCell>{weapon.maxRange ? (weapon.maxRange) : "-"} {weapon.minRange ? "(min " + (weapon.minRange) + ")" : ""}</TableCell>
|
<TableCell>{displayWeapon.maxRange ? (displayWeapon.maxRange) : "-"} {weapon.minRange ? "(min " + (weapon.minRange) + ")" : ""}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow
|
<TableRow
|
||||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||||
@ -210,7 +278,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
|||||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||||
>
|
>
|
||||||
<TableCell component="th" scope="row" >Setup time</TableCell>
|
<TableCell component="th" scope="row" >Setup time</TableCell>
|
||||||
<TableCell>{weapon.setupTime != 0 ? (weapon.setupTime).toFixed(3).replace(/[,.]?0+$/, '') : "-"}</TableCell>
|
<TableCell>{displayWeapon.setupTime != 0 ? (displayWeapon.setupTime).toFixed(3).replace(/[,.]?0+$/, '') : "-"}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow
|
<TableRow
|
||||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||||
@ -224,17 +292,98 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
|||||||
</Grid2>
|
</Grid2>
|
||||||
|
|
||||||
<Grid2 size={12}>
|
<Grid2 size={12}>
|
||||||
<ToggleButtonGroup
|
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 12, marginBottom: 8 }}>
|
||||||
color="primary"
|
<ToggleButtonGroup
|
||||||
exclusive
|
color="primary"
|
||||||
aria-label="Platform"
|
exclusive
|
||||||
value={weaoponFull.currentTable}
|
aria-label="Platform"
|
||||||
onChange={handleChange}
|
value={weaoponFull.currentTable}
|
||||||
sx={{ mb: 2 }}
|
onChange={handleChange}
|
||||||
>
|
>
|
||||||
<ToggleButton size="small" value="dps" sx={{ color: textColor, '&.Mui-selected': { borderColor: textColor, color: textColor, backgroundColor: isDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.15)' } }}>Dps</ToggleButton>
|
<ToggleButton size="small" value="dps" sx={{ color: textColor, '&.Mui-selected': { borderColor: textColor, color: textColor, backgroundColor: isDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.15)' } }}>Dps</ToggleButton>
|
||||||
<ToggleButton size="small" value="one hit" sx={{ color: textColor, '&.Mui-selected': {borderColor: textColor, color: textColor, backgroundColor: isDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.15)' } }}>One hit average damage</ToggleButton>
|
<ToggleButton size="small" value="one hit" sx={{ color: textColor, '&.Mui-selected': {borderColor: textColor, color: textColor, backgroundColor: isDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.15)' } }}>One hit average damage</ToggleButton>
|
||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
|
{weapon.researches && weapon.researches.length > 0 && (
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||||
|
{(() => {
|
||||||
|
// topological order: a research is always rendered to the right of its requirements
|
||||||
|
const ordered: IWeaponResearch[] = [];
|
||||||
|
const visited = new Set<number>();
|
||||||
|
const visit = (wr: IWeaponResearch) => {
|
||||||
|
if (visited.has(wr.research.id)) return;
|
||||||
|
visited.add(wr.research.id);
|
||||||
|
wr.researchRequired?.forEach(req => {
|
||||||
|
const dep = weapon.researches.find((w: IWeaponResearch) => w.research.id === req.id);
|
||||||
|
if (dep) visit(dep);
|
||||||
|
});
|
||||||
|
ordered.push(wr);
|
||||||
|
};
|
||||||
|
weapon.researches.forEach(visit);
|
||||||
|
return ordered.map((r: IWeaponResearch) => {
|
||||||
|
const rs = r.research;
|
||||||
|
const selected = enabledResearchIds.has(rs.id);
|
||||||
|
return (
|
||||||
|
<ToggleButton
|
||||||
|
key={rs.id}
|
||||||
|
value={rs.id}
|
||||||
|
size="small"
|
||||||
|
selected={selected}
|
||||||
|
onChange={() => {
|
||||||
|
const next = new Set(enabledResearchIds);
|
||||||
|
if (next.has(rs.id)) {
|
||||||
|
next.delete(rs.id);
|
||||||
|
// researches that require this one (directly or transitively) can no longer be applied
|
||||||
|
let changed = true;
|
||||||
|
while (changed) {
|
||||||
|
changed = false;
|
||||||
|
weapon.researches.forEach((wr: IWeaponResearch) => {
|
||||||
|
if (next.has(wr.research.id) && wr.researchRequired?.some(req => !next.has(req.id))) {
|
||||||
|
next.delete(wr.research.id);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next.add(rs.id);
|
||||||
|
// research can only be applied if its required researches are done
|
||||||
|
// (requirements can have their own requirements, so enable transitively)
|
||||||
|
const queue = [...(r.researchRequired ?? [])];
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const req = queue.pop()!;
|
||||||
|
if (!next.has(req.id)) {
|
||||||
|
next.add(req.id);
|
||||||
|
const wr = weapon.researches.find((w: IWeaponResearch) => w.research.id === req.id);
|
||||||
|
wr?.researchRequired?.forEach(rr => queue.push(rr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setEnabledResearchIds(next);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
color: textColor,
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: isDark ? 'rgba(255,255,255,0.23)' : 'rgba(0,0,0,0.23)',
|
||||||
|
textTransform: 'none',
|
||||||
|
'&.Mui-selected': {
|
||||||
|
borderColor: isDark ? 'rgba(25, 118, 210, 0.7)' : 'rgba(25, 118, 210, 0.5)',
|
||||||
|
color: textColor,
|
||||||
|
backgroundColor: isDark ? 'rgba(25, 118, 210, 0.3)' : 'rgba(25, 118, 210, 0.15)',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: isDark ? 'rgba(25, 118, 210, 0.4)' : 'rgba(25, 118, 210, 0.22)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{rs.icon && <img style={{height: 20, marginRight: 6, verticalAlign: 'middle'}} src={getIcon(rs.icon)} alt={rs.name}/>}
|
||||||
|
{rs.name}
|
||||||
|
</ToggleButton>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
{isMobile ? (() => {
|
{isMobile ? (() => {
|
||||||
const items = props.mod !== undefined && props.mod.technicalName === 'UltimateApocalypse' ? [
|
const items = props.mod !== undefined && props.mod.technicalName === 'UltimateApocalypse' ? [
|
||||||
|
|||||||
@ -2,5 +2,6 @@ export interface IResearchShort {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
|
uiIndexHint: number;
|
||||||
buildingId: number;
|
buildingId: number;
|
||||||
}
|
}
|
||||||
@ -141,6 +141,18 @@ export interface IShortWeapon {
|
|||||||
isMeleeWeapon: boolean
|
isMeleeWeapon: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IResearchEffect {
|
||||||
|
field: string;
|
||||||
|
usageType: string;
|
||||||
|
modifierValue: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWeaponResearch {
|
||||||
|
research: IResearchShort;
|
||||||
|
researchRequired: IResearchShort[];
|
||||||
|
effects: IResearchEffect[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface IWeapon {
|
export interface IWeapon {
|
||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
@ -172,6 +184,7 @@ export interface IWeapon {
|
|||||||
modifiers: IModifier[]
|
modifiers: IModifier[]
|
||||||
weaponArmorPiercing: IPiercing[]
|
weaponArmorPiercing: IPiercing[]
|
||||||
affectedResearches: IResearchShort[],
|
affectedResearches: IResearchShort[],
|
||||||
|
researches: IWeaponResearch[],
|
||||||
hotkey?: string,
|
hotkey?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user