Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@KingDarBoja
Copy link
Collaborator

@KingDarBoja KingDarBoja commented Dec 8, 2025

I believe this is the correct approach to handle the weapons multipliers per target type.

Summary by CodeRabbit

  • Refactor

    • Improved type safety for unit classifications and target tables.
    • Consolidated and standardized armor lookup logic for consistent unit calculations.
  • Bug Fixes / Improvements

    • More accurate weapon damage and AOE computations using per-target multipliers and bonuses.
    • Added guards to skip empty target data and prevent incorrect multipliers from being applied.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 8, 2025

Walkthrough

Tightens 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 def_ebps, and revises single-target and AOE damage multiplier composition and application.

Changes

Cohort / File(s) Summary
Type Definitions & Schemas
src/unitStats/types.ts
Adds TargetUnitTypeMultipliers, WeaponMultipliers, and TargetTypeTableItemSchema; narrows WeaponBag.target_type_table from any[] to TargetTypeTableItemSchema[].
Unit Type Mapping
src/unitStats/mappingSbps.ts
Narrows SbpsType.unitType to "aircraft" | "emplacements" | "infantry" | "team_weapons" | "vehicles" and assigns via as SbpsType["unitType"].
Weapon Target Mapping
src/unitStats/mappingWeapon.ts
Imports TargetUnitTypeMultipliers; changes TargetType.unit_type to TargetUnitTypeMultipliers["unit_type"]; guards empty target_type_table and updates mapping to access nested multiplier fields.
DPS / Armor Lookup Adjustments
src/unitStats/dpsCommon.ts
Changes CustomizableUnit.unit_type to SbpsType["unitType"]; replaces ebpsUnit armor lookups with def_ebps properties and removes a redundant local def_ebps redeclaration.
Damage Multiplier & AOE Logic
src/unitStats/weaponLib.ts
Reworks target multiplier extraction to produce target_damage_mp, target_accuracy_mp, and target_damage_bonus; applies these in single-target and AOE damage calculations and alters final AOE composition.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Focus review on: src/unitStats/weaponLib.ts (AOE composition and multiplier application).
  • Verify type propagation and casts: SbpsType["unitType"] in mappingSbps.ts and CustomizableUnit.
  • Confirm mappingWeapon.ts accesses match the new TargetTypeTableItemSchema shape and that empty target_type_table is handled correctly.
  • Check armor lookup substitutions in dpsCommon.ts to ensure def_ebps is defined in scope and used consistently.

Suggested reviewers

  • petrvecera

Poem

🐰 I hopped through types and tiny gaps,

Matched multipliers and tightened maps,
Armor checks now find their place,
AOE numbers fall in pace,
Hooray — a tidy rabbit's tap! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'fix: weapon dps for AT guns' focuses narrowly on AT guns, but the changeset implements broader type-safety improvements across multiple files affecting armor calculations and weapon multipliers for all unit types, not just AT guns. Consider a more accurate title like 'refactor: add type-safe weapon multipliers and armor lookups' or 'fix: improve weapon dps calculations with type-safe multipliers' to reflect the comprehensive changes made.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dps-improvements

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@KingDarBoja KingDarBoja added the patch Mark MR with simple patch changes label Dec 8, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 casting parent to SbpsType["unitType"]

unitType: parent as SbpsType["unitType"] bypasses the new type guarantees. Consider normalizing/validating parent (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 missing armorLayout

def_ebps is guarded, but armorLayout on the vehicle override line isn’t:

if (custUnit.unit_type == "vehicles")
  custUnit.armor = def_ebps?.health.armorLayout.frontArmor || 1;

If armorLayout is 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 frontArmor once 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.unitTypes is always defined; if ebps_default was never resolved, this will throw. Guard it (or default to an empty array) before .find(...).
  • target_damage_mp and target_accuracy_mp only feed into aoeDamageCombines; they don’t affect the direct-hit term finalHitChance * 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 into aoePenetrationChance and/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: Tighten target_type_table mapping to avoid invalid unit types and swallowing 0 multipliers

Two things to watch in this mapper:

  1. unit_type fallback breaks the literal union and can misrepresent data
unit_type: targetType.target_unit_type_multipliers?.unit_type || "",

"" is not a valid TargetUnitTypeMultipliers["unit_type"] and will cause TypeScript friction or force you back toward any. It also means you push entries even when target_unit_type_multipliers is missing.

  1. || 1 hides explicit zero multipliers

For 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 into 1, 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

📥 Commits

Reviewing files that changed from the base of the PR and between 07b064e and 4d4b1e0.

📒 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 type SbpsType.unitType

Restricting unitType to known literals improves safety and keeps downstream code (e.g. filters in getSbpsStats) much clearer.

src/unitStats/dpsCommon.ts (1)

108-108: Consistent typing of unit_type with SbpsType["unitType"]

Aligning CustomizableUnit.unit_type with SbpsType["unitType"] removes a whole class of stringly-typed bugs.

src/unitStats/mappingWeapon.ts (1)

147-153: Stronger TargetType.unit_type typing is a nice improvement

Using 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: Typing target_type_table and multiplier structures is consistent with the new mapping/usage

Switching WeaponBag.target_type_table to TargetTypeTableItemSchema[] and introducing TargetUnitTypeMultipliers / WeaponMultipliers provides a clear, typed contract that matches how mappingWeapon.ts and weaponLib.ts now consume these fields. This should make future schema changes much easier to track and refactor.

Also applies to: 118-134

@github-actions
Copy link
Contributor

github-actions bot commented Dec 8, 2025

🌐 Branch deployed as preview to:
https://coh3-stats-f0oyofus4-cohstats.vercel.app

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 guarding ebps_default with optional chaining as well

target_unit?.ebps_default.unitTypes only short‑circuits when target_unit is null/undefined; if a CustomizableUnit instance can exist without ebps_default, this will throw at runtime when trying to read .unitTypes from undefined.

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_default is always present whenever target_unit is, this is purely a safety/clarity win and not strictly required.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d4b1e0 and 3706b85.

📒 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

Comment on lines +228 to +245
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 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 || true

Repository: 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.ts

Repository: 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 -n

Repository: 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 -100

Repository: 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.ts

Repository: 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 -60

Repository: 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 in mappingWeapon.ts before reaching this code.

  • target_damage_bonus is added after all other AoE factors as aoeDamageCombines = target_damage_bonus + aoe_damage_multi. This means:

    • It is not scaled by aoePenetrationChance or memberHit.
    • It contributes to DPS even when aoe_damage_multi is zero (e.g., when aoe_outer_radius is 0).
      If dmg_modifier is intended as "extra damage per effective AoE application", consider folding it into the same chain as aoe_damage rather than adding it unscaled.
  • Currently the target-type multipliers only affect the AoE term (aoeDamageCombines), not the direct-hit term finalHitChance * finalDamage. If target-type multipliers should influence all damage against that target (not just splash), propagate the multipliers into finalDamage / finalAccuracy as 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).

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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_bonus is added to aoeDamageCombines without being scaled by aoePenetrationChance or memberHit, 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 * finalDamage on Line 325). If these multipliers are intended to affect all damage against the target type (not just splash), they should also be applied to finalDamage and finalAccuracy. 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_bonus consistently:

     /** 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3706b85 and 30452f9.

📒 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

Comment on lines +228 to 236
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;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

@petrvecera petrvecera marked this pull request as draft December 12, 2025 16:01
@petrvecera
Copy link
Member

Changing this to Draft - as we are not sure if this is the right approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch Mark MR with simple patch changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants