-
-
Notifications
You must be signed in to change notification settings - Fork 19
fix: weapon dps for AT guns #860
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
WalkthroughTightens typing for unit and target-type fields, introduces structured target multipliers, updates mapping functions to the new shapes, adjusts armor lookups in DPS code to use Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (4)
src/unitStats/mappingSbps.ts (1)
108-108: Avoid blindly castingparenttoSbpsType["unitType"]
unitType: parent as SbpsType["unitType"]bypasses the new type guarantees. Consider normalizing/validatingparent(e.g. via a small mapping or type guard) and either skipping or logging unknown values instead of asserting.src/unitStats/dpsCommon.ts (1)
231-236: Harden armor lookup against missingarmorLayout
def_ebpsis guarded, butarmorLayouton the vehicle override line isn’t:if (custUnit.unit_type == "vehicles") custUnit.armor = def_ebps?.health.armorLayout.frontArmor || 1;If
armorLayoutis ever absent, this will throw. Matching the earlier optional chaining keeps it safe:- if (custUnit.unit_type == "vehicles") - custUnit.armor = def_ebps?.health.armorLayout.frontArmor || 1; + if (custUnit.unit_type == "vehicles") + custUnit.armor = def_ebps?.health.armorLayout?.frontArmor || 1;You could also cache
frontArmoronce to avoid repeating the property chain.src/unitStats/weaponLib.ts (1)
219-255: Target-type multipliers wiring looks directionally right but a few edge-cases need tightening
- You assume
target_unit.ebps_default.unitTypesis always defined; ifebps_defaultwas never resolved, this will throw. Guard it (or default to an empty array) before.find(...).target_damage_mpandtarget_accuracy_mponly feed intoaoeDamageCombines; they don’t affect the direct-hit termfinalHitChance * finalDamage. If engine semantics expect these multipliers to apply to all shots vs that target type (not only AoE), you may still be under/over-estimating DPS, especially for low-AoE weapons like AT guns.weaponTargetType.penetration_multiplier(and suppression-related multipliers) are currently ignored. If those matter for per-target behavior, they likely should factor intoaoePenetrationChanceand/or the single-target penetration path.Given the PR goal is “correct approach to handle the weapons multipliers per target type”, it’s worth double-checking these design points against the game data/schema.
src/unitStats/mappingWeapon.ts (1)
328-343: Tightentarget_type_tablemapping to avoid invalid unit types and swallowing 0 multipliersTwo things to watch in this mapper:
unit_typefallback breaks the literal union and can misrepresent dataunit_type: targetType.target_unit_type_multipliers?.unit_type || "",
""is not a validTargetUnitTypeMultipliers["unit_type"]and will cause TypeScript friction or force you back towardany. It also means you push entries even whentarget_unit_type_multipliersis missing.
|| 1hides explicit zero multipliersFor example:
accuracy_multiplier: targetType.target_unit_type_multipliers?.weapon_multipliers?.accuracy_multiplier || 1,If the JSON contains a real
0(e.g. “never hit infantry”), this code turns it into1, completely changing behavior.A safer pattern:
- if (weapon_bag.target_type_table && weapon_bag.target_type_table.length > 0) { - for (const targetType of weapon_bag.target_type_table) { - weaponData.weapon_bag.target_type_table.push({ - unit_type: targetType.target_unit_type_multipliers?.unit_type || "", - dmg_modifier: targetType.target_unit_type_multipliers?.base_damage_modifier || 0, - accuracy_multiplier: - targetType.target_unit_type_multipliers?.weapon_multipliers?.accuracy_multiplier || 1, - penetration_multiplier: - targetType.target_unit_type_multipliers?.weapon_multipliers?.penetration_multiplier || 1, - damage_multiplier: - targetType.target_unit_type_multipliers?.weapon_multipliers?.damage_multiplier || 1, - }); - } - } + if (weapon_bag.target_type_table && weapon_bag.target_type_table.length > 0) { + for (const { target_unit_type_multipliers } of weapon_bag.target_type_table) { + if (!target_unit_type_multipliers) continue; + const wm = target_unit_type_multipliers.weapon_multipliers ?? {}; + weaponData.weapon_bag.target_type_table.push({ + unit_type: target_unit_type_multipliers.unit_type, + dmg_modifier: target_unit_type_multipliers.base_damage_modifier ?? 0, + accuracy_multiplier: wm.accuracy_multiplier ?? 1, + penetration_multiplier: wm.penetration_multiplier ?? 1, + damage_multiplier: wm.damage_multiplier ?? 1, + }); + } + }This keeps the literal union intact and preserves explicit 0 values while still defaulting missing fields sensibly.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/unitStats/dpsCommon.ts(2 hunks)src/unitStats/mappingSbps.ts(2 hunks)src/unitStats/mappingWeapon.ts(3 hunks)src/unitStats/types.ts(2 hunks)src/unitStats/weaponLib.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/unitStats/mappingWeapon.ts (1)
src/unitStats/types.ts (1)
TargetUnitTypeMultipliers(122-126)
src/unitStats/dpsCommon.ts (1)
src/unitStats/mappingSbps.ts (1)
SbpsType(413-413)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Deploy Web Preview
- GitHub Check: Build and run prettier
🔇 Additional comments (4)
src/unitStats/mappingSbps.ts (1)
20-21: Good move to strongly typeSbpsType.unitTypeRestricting
unitTypeto known literals improves safety and keeps downstream code (e.g. filters ingetSbpsStats) much clearer.src/unitStats/dpsCommon.ts (1)
108-108: Consistent typing ofunit_typewithSbpsType["unitType"]Aligning
CustomizableUnit.unit_typewithSbpsType["unitType"]removes a whole class of stringly-typed bugs.src/unitStats/mappingWeapon.ts (1)
147-153: StrongerTargetType.unit_typetyping is a nice improvementUsing
TargetUnitTypeMultipliers["unit_type"]here keeps target-type strings aligned with the schema and avoids ad‑hoc values creeping in.src/unitStats/types.ts (1)
99-116: Typingtarget_type_tableand multiplier structures is consistent with the new mapping/usageSwitching
WeaponBag.target_type_tabletoTargetTypeTableItemSchema[]and introducingTargetUnitTypeMultipliers/WeaponMultipliersprovides a clear, typed contract that matches howmappingWeapon.tsandweaponLib.tsnow consume these fields. This should make future schema changes much easier to track and refactor.Also applies to: 118-134
|
🌐 Branch deployed as preview to: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/unitStats/weaponLib.ts (1)
224-227: Consider guardingebps_defaultwith optional chaining as well
target_unit?.ebps_default.unitTypesonly short‑circuits whentarget_unitis null/undefined; if aCustomizableUnitinstance can exist withoutebps_default, this will throw at runtime when trying to read.unitTypesfromundefined.To make this more defensive without changing behavior when everything is populated, consider:
const targetUnitTypes = target_unit?.ebps_default?.unitTypes ?? [];If the type system guarantees
ebps_defaultis always present whenevertarget_unitis, this is purely a safety/clarity win and not strictly required.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/unitStats/mappingWeapon.ts(3 hunks)src/unitStats/weaponLib.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/unitStats/mappingWeapon.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Deploy Web Preview
- GitHub Check: Build and run prettier
| for (const weaponTargetType of weapon_bag.target_type_table) { | ||
| const foundTargetType = targetUnitTypes.find((ut) => ut === weaponTargetType.unit_type); | ||
|
|
||
| if (foundTargetType) { | ||
| target_damage_bonus += weaponTargetType.dmg_modifier; | ||
| target_damage_mp *= weaponTargetType.damage_multiplier; | ||
| target_accuracy_mp *= weaponTargetType.accuracy_multiplier; | ||
| } | ||
| } | ||
|
|
||
| // new | ||
| const aoe_damage_multi = aoe_damage * type_damage_mp * aoePenetrationChance * memberHit; | ||
| /** Compute the combined AOE damage multipliers. */ | ||
| const aoe_damage_multi = | ||
| aoe_damage * target_damage_mp * target_accuracy_mp * aoePenetrationChance * memberHit; | ||
|
|
||
| // new | ||
| aoeDamageCombines = aoe_damage_multi; | ||
| /** Compute the final AOE damage combined with the base damage modifier (if | ||
| * applies). */ | ||
| // aoeDamageCombines = aoe_damage_multi; | ||
| aoeDamageCombines = target_damage_bonus + aoe_damage_multi; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for TargetTypeTableItemSchema and target_type_table definitions
echo "=== Searching for TargetTypeTableItemSchema ==="
rg -n "TargetTypeTableItemSchema" --type ts --type js -B5 -A10 || true
echo -e "\n=== Searching for target_type_table ==="
rg -n "target_type_table" --type ts --type js -B3 -A3 || true
echo -e "\n=== Searching for WeaponBag type definition ==="
rg -n "WeaponBag" --type ts --type js -B2 -A10 | head -100 || trueRepository: cohstats/coh3-stats
Length of output: 11304
🏁 Script executed:
#!/bin/bash
# Get the WeaponMultipliers definition and check the broader context in weaponLib.ts
echo "=== WeaponMultipliers type definition ==="
rg -n "type WeaponMultipliers" --type ts -A10 src/unitStats/types.ts
echo -e "\n=== Full context around the problem area in weaponLib.ts ==="
sed -n '200,260p' src/unitStats/weaponLib.ts | cat -n
echo -e "\n=== Check TargetType in mappingWeapon.ts ==="
rg -n "^type TargetType" --type ts -A10 src/unitStats/mappingWeapon.tsRepository: cohstats/coh3-stats
Length of output: 3425
🏁 Script executed:
#!/bin/bash
# Find the function containing the problem code and check its signature
echo "=== Search for function containing lines 228-245 ==="
sed -n '150,260p' src/unitStats/weaponLib.ts | cat -n
echo -e "\n=== Check imports in weaponLib.ts to understand types ==="
head -30 src/unitStats/weaponLib.ts | cat -nRepository: cohstats/coh3-stats
Length of output: 5885
🏁 Script executed:
#!/bin/bash
# Find WeaponStatsType definition
echo "=== WeaponStatsType definition ==="
rg -n "^type WeaponStatsType|^export type WeaponStatsType" --type ts -A50 src/unitStats/mappingWeapon.ts | head -100Repository: cohstats/coh3-stats
Length of output: 1656
🏁 Script executed:
#!/bin/bash
# Get more of the WeaponStatsType definition, particularly the weapon_bag section
echo "=== Full WeaponStatsType definition focusing on weapon_bag ==="
rg -n "weapon_bag:" --type ts -B5 -A20 src/unitStats/mappingWeapon.ts
echo -e "\n=== Lines around target_type_table in WeaponStatsType ==="
rg -n "target_type_table" --type ts -B3 -A3 src/unitStats/mappingWeapon.tsRepository: cohstats/coh3-stats
Length of output: 4539
🏁 Script executed:
#!/bin/bash
# Check the complete context around AOE damage calculations and direct hit damage to verify the second set of concerns
echo "=== Check direct hit damage calculation and how it relates to target-type multipliers ==="
sed -n '100,200p' src/unitStats/weaponLib.ts | cat -n
echo -e "\n=== Check the finalDamage calculation ==="
rg -n "finalDamage|finalHitChance|finalAccuracy" --type ts -B2 -A2 src/unitStats/weaponLib.ts | head -60Repository: cohstats/coh3-stats
Length of output: 5114
Clarify target-type multiplier application and target_damage_bonus scaling
A couple of design clarifications needed:
-
The schema properties (
unit_type,dmg_modifier,damage_multiplier,accuracy_multiplier) are correctly accessed at the top level—they have already been flattened by the mapping function inmappingWeapon.tsbefore reaching this code. -
target_damage_bonusis added after all other AoE factors asaoeDamageCombines = target_damage_bonus + aoe_damage_multi. This means:- It is not scaled by
aoePenetrationChanceormemberHit. - It contributes to DPS even when
aoe_damage_multiis zero (e.g., whenaoe_outer_radiusis 0).
Ifdmg_modifieris intended as "extra damage per effective AoE application", consider folding it into the same chain asaoe_damagerather than adding it unscaled.
- It is not scaled by
-
Currently the target-type multipliers only affect the AoE term (
aoeDamageCombines), not the direct-hit termfinalHitChance * finalDamage. If target-type multipliers should influence all damage against that target (not just splash), propagate the multipliers intofinalDamage/finalAccuracyas well. If AoE-only is deliberate (e.g., to model AT-gun AoE vs infantry), a code comment explaining that choice would help future readers.
🤖 Prompt for AI Agents
In src/unitStats/weaponLib.ts around lines 228-245, the code adds
target_damage_bonus after AoE multipliers which leaves it unscaled by
aoePenetrationChance/memberHit and by the target multipliers; to fix this, fold
the dmg_modifier into the AoE chain by applying the same multipliers (multiply
target_damage_bonus by target_damage_mp * target_accuracy_mp *
aoePenetrationChance * memberHit and add that into aoe_damage_multi instead of
adding it raw), and if target-type multipliers should also affect direct hits,
apply target_damage_mp to finalDamage and target_accuracy_mp to finalAccuracy
(or document intentionally AoE-only behavior if you do not propagate them).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/unitStats/weaponLib.ts (1)
238-245: Target-type multipliers and bonus are applied inconsistently.As noted in the previous review,
target_damage_bonusis added toaoeDamageCombineswithout being scaled byaoePenetrationChanceormemberHit, which means it contributes to DPS even when the AoE doesn't actually hit or penetrate.Additionally, the target-type multipliers (
target_damage_mp,target_accuracy_mp) only affect the AoE damage term (aoeDamageCombines) but not the direct-hit damage (finalHitChance * finalDamageon Line 325). If these multipliers are intended to affect all damage against the target type (not just splash), they should also be applied tofinalDamageandfinalAccuracy. If the AoE-only behavior is intentional for AT guns (as the PR title suggests), please add a comment explaining why direct hits are excluded.Consider scaling
target_damage_bonusconsistently:/** Compute the combined AOE damage multipliers. */ const aoe_damage_multi = aoe_damage * target_damage_mp * target_accuracy_mp * aoePenetrationChance * memberHit; + const scaled_target_damage_bonus = + target_damage_bonus * target_damage_mp * target_accuracy_mp * aoePenetrationChance * memberHit; /** Compute the final AOE damage combined with the base damage modifier (if * applies). */ - aoeDamageCombines = target_damage_bonus + aoe_damage_multi; + aoeDamageCombines = scaled_target_damage_bonus + aoe_damage_multi;Or if direct hits should also be affected by target-type modifiers:
let finalAccuracy = acc_combined; let finalDamage = avgDamage; + + // Apply target-type multipliers to direct hits + finalDamage = finalDamage * target_damage_mp; + finalAccuracy = finalAccuracy * target_accuracy_mp;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/unitStats/weaponLib.ts(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Deploy Web Preview
- GitHub Check: Build and run prettier
| for (const weaponTargetType of weapon_bag.target_type_table) { | ||
| const foundTargetType = targetUnitTypes.find((ut) => ut === weaponTargetType.unit_type); | ||
|
|
||
| if (foundTargetType) { | ||
| target_damage_bonus += weaponTargetType.dmg_modifier; | ||
| target_damage_mp *= weaponTargetType.damage_multiplier; | ||
| target_accuracy_mp *= weaponTargetType.accuracy_multiplier; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add safety check for target_type_table and clarify accumulation logic.
The code accesses weapon_bag.target_type_table without verifying it exists or is iterable, which could cause a runtime error if undefined.
Additionally, the accumulation logic using += for target_damage_bonus and *= for multipliers implies that if multiple weapon target types match multiple unit types on the target, the bonuses and multipliers will stack. Typically, you'd expect only one target type to match. If stacking is intentional (e.g., a unit has both "infantry" and "heavy_armor" types and the weapon has entries for both), please add a comment explaining this behavior. Otherwise, consider using early return or break after the first match.
Apply this diff to add a safety check:
const targetUnitTypes = target_unit?.ebps_default?.unitTypes || [];
- for (const weaponTargetType of weapon_bag.target_type_table) {
+ for (const weaponTargetType of weapon_bag.target_type_table || []) {
const foundTargetType = targetUnitTypes.find((ut) => ut === weaponTargetType.unit_type);
if (foundTargetType) {
target_damage_bonus += weaponTargetType.dmg_modifier;
target_damage_mp *= weaponTargetType.damage_multiplier;
target_accuracy_mp *= weaponTargetType.accuracy_multiplier;
}
}If only one match is intended, consider adding a break:
if (foundTargetType) {
target_damage_bonus += weaponTargetType.dmg_modifier;
target_damage_mp *= weaponTargetType.damage_multiplier;
target_accuracy_mp *= weaponTargetType.accuracy_multiplier;
+ break; // Only apply the first matching target type
}🤖 Prompt for AI Agents
In src/unitStats/weaponLib.ts around lines 228-236, add a safety check that
weapon_bag.target_type_table exists and is iterable before iterating (e.g., skip
the loop if it's falsy or not an array) to avoid runtime errors; also clarify
accumulation intent by either breaking out of the loop after the first matching
target type (if only one match is expected) or, if stacking multiple matches is
intentional, add a concise comment above the loop explaining that dmg_modifier
and multipliers should accumulate across multiple matching target types so the
use of += and *= is deliberate.
|
Changing this to Draft - as we are not sure if this is the right approach. |
I believe this is the correct approach to handle the weapons multipliers per target type.
Summary by CodeRabbit
Refactor
Bug Fixes / Improvements
✏️ Tip: You can customize this high-level summary in your review settings.