Add some params

- start jump charge
- building limit
- movement params

Rework weapon dps one hit and add dps move
This commit is contained in:
anibus
2026-10-04 02:58:50 +03:00
parent fa0baa62c1
commit eebe50a373
15 changed files with 154 additions and 58 deletions
+7 -3
View File
@@ -224,6 +224,12 @@ function Building(building: IBuilding, mod: IMod, theme: Theme, racesUnits: IRac
<TableCell component="th" scope="row" >Repair max</TableCell>
<TableCell>{building.repairMax}</TableCell>
</TableRow>
{building.buildingLimit !== undefined && building.buildingLimit !== null &&
<TableRow sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
<TableCell component="th" scope="row" >Limit</TableCell>
<TableCell>{building.buildingLimit}</TableCell>
</TableRow>
}
</TableBody>
</Table>
</StatsPaper>
@@ -258,9 +264,7 @@ function Building(building: IBuilding, mod: IMod, theme: Theme, racesUnits: IRac
</Grid2>}
{building.deathExplosions.length > 0 &&
<Grid2 size={12}>
{building.deathExplosions.map(da =>
<DeathExplosion deathExplosion={da} mod={mod} />
)}
<DeathExplosion deathExplosions={building.deathExplosions} mod={mod} />
</Grid2>
}
{building.addons.length > 0 && <Grid2 size={12}>
+29 -5
View File
@@ -260,8 +260,33 @@ function Unit(unit: IUnit, mod: IMod, theme: Theme, racesUnits: IRaceUnits[], ra
</TableCell>
</TableRow>
<TableRow>
<TableCell >Move speed</TableCell>
<TableCell>{unit.moveSpeed}</TableCell>
<TableCell >Movement</TableCell>
<TableCell>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
<Tooltip title="Movement speed"><span><img style={{ height: 20, verticalAlign: "top" }} src="/images/Movement_speed.svg" /></span></Tooltip>&nbsp;
{unit.moveSpeed}
</span>
{unit.accelerationTime !== undefined && unit.accelerationTime !== null &&
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
<Tooltip title="Acceleration time"><span><img style={{ height: 20, verticalAlign: "top" }} src="/images/Movement_acceleration.svg" /></span></Tooltip>&nbsp;
{unit.accelerationTime}s
</span>
}
{unit.decelerationTime !== undefined && unit.decelerationTime !== null &&
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
<Tooltip title="Deceleration time"><span><img style={{ height: 20, verticalAlign: "top" }} src="/images/Movement_deceleration.svg" /></span></Tooltip>&nbsp;
{unit.decelerationTime}s
</span>
}
{unit.rotationRate !== undefined && unit.rotationRate !== null &&
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
<Tooltip title="Rotation rate"><span><img style={{ height: 20, verticalAlign: "top" }} src="/images/Movement_rotation.svg" /></span></Tooltip>&nbsp;
{unit.rotationRate}
</span>
}
</span>
</TableCell>
</TableRow>
<TableRow>
<TableCell >Morale</TableCell>
@@ -316,6 +341,7 @@ function Unit(unit: IUnit, mod: IMod, theme: Theme, racesUnits: IRaceUnits[], ra
{unit.sergeants.map(s =>
<SergeantAccordion key={s.id} TransitionProps={{ unmountOnExit: true, timeout: 100 }}>
<AccordionSummary
expandIcon={<ExpandMore sx={{ color: isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)' }} />}
sx={{ color: isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)', '& .MuiAccordionSummary-content': { margin: '12px 0' }, '& .MuiAccordionSummary-content.Mui-expanded': { margin: '12px 0' } }}>
<SergeantShort name={s.name} icon={s.icon} canDetect={s.detectRadius > 0} />
</AccordionSummary>
@@ -356,9 +382,7 @@ function Unit(unit: IUnit, mod: IMod, theme: Theme, racesUnits: IRaceUnits[], ra
</Grid2>
{unit.deathExplosions.length > 0 &&
<Grid2 size={12}>
{unit.deathExplosions.map(da =>
<DeathExplosion deathExplosion={da} mod={mod} />
)}
<DeathExplosion deathExplosions={unit.deathExplosions} mod={mod} />
</Grid2>
}
<Grid2 size={12}>
+1 -1
View File
@@ -9,7 +9,7 @@ import { IUnit } from '@/src/types/IUnit';
import { IBuilding } from '@/src/types/IBuilding';
const API = process.env.HOST_URL || '';
export const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://wiki.dawn-of-war.pro';
export const SITE_URL = 'https://wiki.dawn-of-war.pro';
async function fetchJson<T>(url: string): Promise<T | null> {
try {
+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#64b5f6" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<path d="M7 21 V9"/>
<path d="M2.5 13.5 L7 9 L11.5 13.5"/>
<path d="M17 21 V3"/>
<path d="M12.5 7.5 L17 3 L21.5 7.5"/>
</svg>

After

Width:  |  Height:  |  Size: 313 B

+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#64b5f6" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<path d="M7 3 V15"/>
<path d="M2.5 10.5 L7 15 L11.5 10.5"/>
<path d="M17 3 V21"/>
<path d="M12.5 16.5 L17 21 L21.5 16.5"/>
</svg>

After

Width:  |  Height:  |  Size: 317 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#64b5f6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12 a9 9 0 1 1 -2.64 -6.36"/>
<path d="M21 3 v6 h-6"/>
</svg>

After

Width:  |  Height:  |  Size: 255 B

+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#64b5f6" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 8 H16"/>
<path d="M12 4 L16 8 L12 12"/>
<path d="M3 16 H16"/>
<path d="M12 12 L16 16 L12 20"/>
</svg>

After

Width:  |  Height:  |  Size: 301 B

+1 -3
View File
@@ -134,9 +134,7 @@ function AbilitySpawnedObject(props: AbilityEnvironmentProps) {
</Grid2>}
{abilityEnvironment.deathExplosions.length > 0 &&
<Grid2 size={12}>
{abilityEnvironment.deathExplosions.map(da =>
<DeathExplosion deathExplosion={da} mod={props.mod}/>
)}
<DeathExplosion deathExplosions={abilityEnvironment.deathExplosions} mod={props.mod}/>
</Grid2>
}
</Grid2>
+9 -5
View File
@@ -31,7 +31,7 @@ import {StyledTableCell} from "../commons/StyledTableCell";
interface DeathExpProps {
deathExplosion: IDeathExplosion,
deathExplosions: IDeathExplosion[],
mod: IMod,
}
@@ -40,10 +40,11 @@ function DeathExplosion(props: DeathExpProps) {
const isDark = theme.palette.mode === 'dark';
const textColor = isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)';
const deathExplosion = props.deathExplosion
const chanceTotalNotZero = deathExplosion.globalChance * deathExplosion.chance != 0
const deathExplosions = props.deathExplosions.filter(deathExplosion =>
deathExplosion.globalChance * deathExplosion.chance != 0
)
return chanceTotalNotZero ?
return deathExplosions.length > 0 ?
<div className='addon-research-accordion' id='death-explosion'><StyledAccordion>
<AccordionSummary
sx={{color: textColor, '& .MuiAccordionSummary-content': { margin: '12px 0' }, '& .MuiAccordionSummary-content.Mui-expanded': { margin: '12px 0' }}}
@@ -55,7 +56,9 @@ function DeathExplosion(props: DeathExpProps) {
</span>
</AccordionSummary>
<AccordionDetails sx={{color: textColor}}>
<Grid2 container spacing={2}>
{deathExplosions.map((deathExplosion, index) =>
<Grid2 container spacing={2} key={deathExplosion.id ?? index}
sx={{mt: index > 0 ? 2 : 0}}>
<Grid2 size={{xs: 12, md: 4}}>
<TableContainer component={StyledPaper} elevation={0}>
<Table size="small" aria-label="a dense table">
@@ -87,6 +90,7 @@ function DeathExplosion(props: DeathExpProps) {
moraleDamage={deathExplosion.moraleDamage}/>
</Grid2>
</Grid2>
)}
</AccordionDetails>
</StyledAccordion></div> : <span></span>
+1
View File
@@ -55,6 +55,7 @@ function Jump(props: IJumpProps) {
{ label: "Distance", value: jumps.jumpDistanceMax.toFixed(0) },
{ label: "Setup time", value: <span><img style={{verticalAlign: "top"}} src="/images/Time_icon.webp"/>&nbsp;{jumps.setupTime}s</span> },
{ label: "Breakdown time", value: <span><img style={{verticalAlign: "top"}} src="/images/Time_icon.webp"/>&nbsp;{jumps.breakdownTime}s</span> },
{ label: "Starting charge", value: `${(jumps.chargeStartingFraction * 100).toFixed(0)}%` }
];
if (!isTeleport) {
if (jumps.goUpTime !== undefined && jumps.goUpTime !== null) {
+1 -1
View File
@@ -76,7 +76,7 @@ function Required (props: IRequiredProps) {
return <span>&nbsp; One <img style={{verticalAlign: "top", height: 25}}
src={IconUrl + limitPerBuilding.building.icon.replaceAll('\\', '/')}/>
&nbsp;{" "}<StyledLink href={`/mod/${props.modId}/race/${props.raceId}/building/${limitPerBuilding.building.id}`}>
{limitPerBuilding.building.name} </StyledLink> allow produce max {limitPerBuilding.limit}</span>
{limitPerBuilding.building.name} </StyledLink> allows producing a maximum of {limitPerBuilding.limit}</span>
}
function renderRequirementResearches(requirementResearches: IResearchRequirements[],) {
+1 -3
View File
@@ -187,9 +187,7 @@ const Sergeant = (props: SergeantProps) => {
</Grid2>
{sergeant.deathExplosions.length > 0 &&
<Grid2 size={12}>
{sergeant.deathExplosions.map(da =>
<DeathExplosion deathExplosion={da} mod={props.mod}/>
)}
<DeathExplosion deathExplosions={sergeant.deathExplosions} mod={props.mod}/>
</Grid2>
}
<Grid2 size={12}>
+47 -7
View File
@@ -9,7 +9,7 @@ import {
TableBody,
TableCell,
TableContainer, TableHead,
TableRow, ToggleButton, ToggleButtonGroup
TableRow, ToggleButton, ToggleButtonGroup, Slider
} from "@mui/material";
import ArmorType from "./ArmorType";
import Required from "./Required";
@@ -43,6 +43,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
});
const [enabledResearchIds, setEnabledResearchIds] = useState<Set<number>>(new Set());
const [damageRoll, setDamageRoll] = useState<number | null>(null);
function applyResearchEffects(weapon: IWeapon, enabledIds: Set<number>): IWeapon {
if (enabledIds.size === 0 || !weapon.researches || weapon.researches.length === 0) {
@@ -137,10 +138,18 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
currentTable: selectedDamageTable,
weapon: weaoponFull.weapon,
});
if (selectedDamageTable === "one hit") {
setDamageRoll((displayWeapon.minDamage + displayWeapon.maxDamage) / 2);
}
}
if(displayWeapon.reloadTime < 0.125) displayWeapon.reloadTime = 0.125
const dpsK = (weaoponFull.currentTable == "dps") ? displayWeapon.accuracy * (1 / (displayWeapon.reloadTime - (displayWeapon.reloadTime % 0.125))) : 1
const movingAccuracy = displayWeapon.accuracy - displayWeapon.accuracyReductionMoving
const hasMovingAccuracy = displayWeapon.setupTime === 0 && movingAccuracy > 0
const reloadK = 1 / (displayWeapon.reloadTime - (displayWeapon.reloadTime % 0.125))
const dpsK = (weaoponFull.currentTable == "dps") ? displayWeapon.accuracy * reloadK
: (weaoponFull.currentTable == "dps move") ? movingAccuracy * reloadK
: 1
const infLowPiercing = getPiercingK(ArmorTypeNames.InfantryLow)
const infMedPiercing = getPiercingK(ArmorTypeNames.InfantryMedium)
@@ -165,6 +174,16 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
const livingMetalPiercing = getPiercingK(ArmorTypeNames.LivingMetal)
const titanPiercing = getPiercingK(ArmorTypeNames.Titan)
const damageRollMin = displayWeapon.minDamage
const damageRollMax = displayWeapon.maxDamage
const hasDamageRollRange = damageRollMax > damageRollMin
const currentDamageRoll = damageRoll !== null
? Math.min(Math.max(damageRoll, damageRollMin), damageRollMax)
: (damageRollMin + damageRollMax) / 2
const damageRollFraction = hasDamageRollRange
? (currentDamageRoll - damageRollMin) / (damageRollMax - damageRollMin)
: 0.5
const getTotalDamage = (damagePiercing: number, isAir: boolean = false) => {
if (!isAir && !weapon.canAttackGround && !weapon.isMeleeWeapon) return ""
@@ -180,9 +199,11 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
maxDamage = displayWeapon.minDamageValue
}
const averageDmg = (minDamage + maxDamage) / 2
const hitDamage = weaoponFull.currentTable === "one hit"
? minDamage + (maxDamage - minDamage) * damageRollFraction
: (minDamage + maxDamage) / 2
return (averageDmg * dpsK).toFixed(2)
return (hitDamage * dpsK).toFixed(2)
}
const getMoraleDamage = () => {
@@ -217,7 +238,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
sx={{'&:last-child td, &:last-child th': {border: 0}}}
>
<TableCell component="th" scope="row" >Accuracy moving</TableCell>
<TableCell>{displayWeapon.setupTime === 0 && displayWeapon.accuracy - displayWeapon.accuracyReductionMoving > 0 ? (displayWeapon.accuracy - displayWeapon.accuracyReductionMoving).toFixed(3).replace(/[,.]?0+$/, '') : "-"}</TableCell>
<TableCell>{hasMovingAccuracy ? movingAccuracy.toFixed(3).replace(/[,.]?0+$/, '') : "-"}</TableCell>
</TableRow>
</TableBody>
</Table>
@@ -301,10 +322,28 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
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="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="dps move" disabled={!hasMovingAccuracy} sx={{ color: textColor, '&.Mui-selected': { borderColor: textColor, color: textColor, backgroundColor: isDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.15)' } }}>Dps move</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 damage</ToggleButton>
</ToggleButtonGroup>
{weaoponFull.currentTable === 'one hit' && (
<div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 200, flexGrow: 1, maxWidth: 220 }}>
<span style={{ whiteSpace: 'nowrap', fontSize: 14 }}>{damageRollMin.toFixed(2)}</span>
<Slider
size="small"
min={damageRollMin}
max={damageRollMax}
step={hasDamageRollRange ? (damageRollMax - damageRollMin) / 100 : 1}
value={currentDamageRoll}
onChange={(e, value) => setDamageRoll(value as number)}
disabled={!hasDamageRollRange}
sx={{ color: textColor, flexGrow: 1 }}
/>
<span style={{ whiteSpace: 'nowrap', fontSize: 14 }}>{damageRollMax.toFixed(2)}</span>
<span style={{ whiteSpace: 'nowrap', fontSize: 14, minWidth: 44, textAlign: 'right', fontWeight: 700 }}>{Math.round((1 - damageRollFraction) * 100)}%</span>
</div>
)}
{weapon.researches && weapon.researches.length > 0 && (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, flexBasis: '100%', width: '100%' }}>
{(() => {
// topological order: a research is always rendered to the right of its requirements
const ordered: IWeaponResearch[] = [];
@@ -358,6 +397,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
}
}
setEnabledResearchIds(next);
setDamageRoll(null);
}}
sx={{
color: textColor,
+1
View File
@@ -32,6 +32,7 @@ export interface IBuilding {
sightRadius: number
detectRadius: number
repairMax: number
buildingLimit?: number
icon: string
modId: number
modifiers: IModifier[]
+4
View File
@@ -22,6 +22,7 @@ export interface IJumps {
chargeJumpCostMax: number;
jumpHeight: number;
chargeMax: number;
chargeStartingFraction: number;
setupTime: number;
goDownTime?: number;
isTeleport: boolean;
@@ -58,6 +59,9 @@ export interface IUnit {
mass: number
upTime: number
moveSpeed: number
accelerationTime?: number
decelerationTime?: number
rotationRate?: number
sightRadius: number
detectRadius: number
reinforceCostRequisition?: number