Skip to content

feat: add support for mod sets#161

Merged
SlayerOrnstein merged 11 commits intoWFCD:mainfrom
SlayerOrnstein:feat-headers
Feb 24, 2026
Merged

feat: add support for mod sets#161
SlayerOrnstein merged 11 commits intoWFCD:mainfrom
SlayerOrnstein:feat-headers

Conversation

@SlayerOrnstein
Copy link
Member

@SlayerOrnstein SlayerOrnstein commented Apr 5, 2025

What did you fix?

Add support for creating set mods

image image

Reproduction steps


Evidence/screenshot/link to line

Considerations

  • Does this contain a new dependency? No
  • Does this introduce opinionated data formatting or manual data entry? Yes
  • Does this pr include updated data files in a separate commit that can be reverted for a clean code-only pr? No
  • Have I run the linter? Yes
  • Is is a bug fix, feature request, or enhancement? Feature

Summary by CodeRabbit

  • Documentation

    • Updated the user guide with new images for "Augur Message" and "Primed Flow" to enhance visual content.
  • New Features

    • Enhanced visual presentation of mod details with improved header imagery and clearer display of set bonus information.
    • Streamlined the mod generation process for a more consistent and flexible presentation of mod elements.
    • Introduced new utility functions for improved image handling and color conversion.
  • Bug Fixes

    • Corrected naming inconsistencies in variable definitions.
  • Refactor

    • Improved parameter handling in various functions for better clarity and usability.

@coderabbitai
Copy link

coderabbitai bot commented Apr 5, 2025

Warning

Rate limit exceeded

@SlayerOrnstein has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 42 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 14e02e5 and 466cfc8.

⛔ Files ignored due to path filters (9)
  • assets/readme/Afterburn.png is excluded by !**/*.png
  • assets/readme/Archgun_Riven_Mod.png is excluded by !**/*.png
  • assets/readme/Archon_Intensify.png is excluded by !**/*.png
  • assets/readme/Augur_Message_empty.png is excluded by !**/*.png
  • assets/readme/Augur_Message_filled.png is excluded by !**/*.png
  • assets/readme/Primed_Flow.png is excluded by !**/*.png
  • assets/readme/Steel_Charge.png is excluded by !**/*.png
  • assets/readme/Streamline.png is excluded by !**/*.png
  • assets/readme/Vitality.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • README.md
  • src/generator.ts
  • src/utils.ts
📝 Walkthrough

Walkthrough

The PR introduces a new styling module consolidating font and color constants, adds header rendering for mod sets with image tinting, refactors the generator API to accept a props object, migrates styling exports from utils to the new module, and adds utility functions for header fetching and hex-to-RGB color conversion.

Changes

Cohort / File(s) Summary
Styling Consolidation
src/styling.ts
New module exporting titleFont, descriptionFont, modRarityMap, and tierColor; centralizes styling constants previously scattered across modules.
Core Generation Pipeline
src/generator.ts, src/drawers.ts
Introduces drawHeader function for image tinting; extends BackgroundProps with modSetRank and setBonus; refactors generate signature to accept GenerateModProps object; integrates header rendering when mod.modSet exists; updates layout constants (horizantalPad → horizontalPad) and font usage.
Utility Functions & Exports
src/utils.ts
Moves modRarityMap and tierColor to styling; adds fetchHeader and hexToRGB functions; reorders modDescription parameters with rank defaulting to 0; updates textHeight to accept optional title; adjusts exportCanvas quality handling.
Tests & Configuration
tests/generator.spec.ts, package.json
Updates generator test to use new GenerateModProps API shape; adds new mod set path to test data; reorders format array; reduces mocha timeout from 22000 to 15000ms.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • TobiTenno

Poem

🐰 Header brushstrokes, colors dance so bright,
Mod sets now gleaming with prismatic light,
Styling consolidated, fonts aligned just so,
APIs reshapen, cleaner flow we know!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add support for mod sets' accurately summarizes the main objective of the PR, which adds support for creating and rendering mod set cards.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

@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: 2

🧹 Nitpick comments (3)
README.md (1)

31-31: Updated function call structure

Updating the function call to use an object parameter structure improves code clarity by making parameter names explicit at the call site.

However, there's a typo in the comment: "defualt" should be "default".

-const image = generateBasicMod({mod, rank: 3}); // You can set rank to whatever rank you want by defualt it's 0
+const image = generateBasicMod({mod, rank: 3}); // You can set rank to whatever rank you want by default it's 0
src/utils.ts (2)

111-115: Consider adding JSDoc to document parameter reordering

The modDescription function parameters have been reordered and defaults added, which is a breaking change to the function signature.

Consider adding JSDoc to clearly document the new parameter order:

+/**
+ * Generates a formatted description for a mod based on rank, description, and level stats
+ * @param rank The rank of the mod (defaults to 0)
+ * @param description Optional description text
+ * @param levelStats Optional level statistics
+ * @returns Formatted description string or undefined
+ */
export const modDescription = (
  rank: number = 0,
  description?: string | undefined,
  levelStats?: LevelStat[] | undefined
): string | undefined => {

194-198: Add error handling for malformed uniqueNames

The setHeader function assumes a specific format for uniqueNames, but doesn't handle potential errors.

Consider adding error handling for uniqueNames that don't follow the expected format:

export const setHeader = async (uniqueName: string) => {
  const parts = uniqueName.split('/');
+  if (parts.length < 2) {
+    throw new Error(`Invalid uniqueName format: ${uniqueName}`);
+  }
  const name = parts.reverse()[1];

  return fetchModPiece(`${name}Header.png`);
};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e660e3 and a100e07.

⛔ Files ignored due to path filters (1)
  • assets/readme/Augur Message.png is excluded by !**/*.png
📒 Files selected for processing (6)
  • README.md (2 hunks)
  • src/data.ts (1 hunks)
  • src/drawers.ts (6 hunks)
  • src/generator.ts (4 hunks)
  • src/utils.ts (3 hunks)
  • tests/generator.spec.ts (1 hunks)
🧰 Additional context used
🧬 Code Definitions (2)
src/generator.ts (2)
src/utils.ts (3)
  • CanvasOutput (166-170)
  • setHeader (194-198)
  • exportCanvas (172-192)
src/drawers.ts (2)
  • horizontalPad (8-8)
  • drawHeader (27-54)
tests/generator.spec.ts (1)
src/utils.ts (1)
  • Format (164-164)
🔇 Additional comments (21)
src/data.ts (2)

1-3: Type definition looks good

The UniquenameType type definition provides a clear structure for the mapping objects that follow. Using a custom type enhances code readability and maintainability.


5-11: Properly structured mapping for mod rarities

Good organization of mod rarity mapping with clear key-value pairs. This modular approach of moving constants to a dedicated data file improves code organization.

README.md (1)

7-7: Good example addition

Adding the Augur Message example image enriches the documentation by showing a mod set example, which aligns with the new feature being implemented.

tests/generator.spec.ts (2)

24-24: Good test coverage for mod sets

Adding a mod set example (WarframeAugurMessageMod) to the test suite ensures the new feature is properly tested.


39-39: Updated function call aligns with new interface

The function call has been properly updated to use the new object parameter structure, which aligns with the changes in the generator interface.

src/generator.ts (6)

4-6: Improved imports organization

The imports have been reorganized to better group related functionality. Good practice to include horizontalPad from 'drawers.js' and add the import for modRarityMap from the new data file.


8-14: Well-structured interface for props

The new GenerateModProps interface provides a clear structure for the parameters, making the function more maintainable and the API more consistent. The optional parameters are appropriately marked.


24-24: Documentation and function signature updated

The function documentation and signature have been updated to reflect the new interface. The destructuring of props makes the code cleaner.

Also applies to: 30-30, 33-33, 37-38


65-72: Improved code organization with variable extraction

Extracting topFrameHeight into a variable improves readability. The consistent use of horizontalPad also fixes a naming inconsistency.


74-84: New support for mod sets

The implementation for mod sets looks good. It checks if mod.modSet exists and renders the appropriate header with the correct tint based on the tier.


107-107: Improved default handling for output parameter

Using the nullish coalescing operator ?? to provide a default value for the output parameter is a good practice. This ensures a consistent default without overriding explicit undefined values.

src/drawers.ts (7)

8-8: Fixed variable name typo

The variable name has been correctly changed from "horizantalPad" to "horizontalPad" for consistency and accuracy.


27-54: Good implementation of the new drawHeader function

The new drawHeader function properly implements image tinting based on tier color. The brightness calculation and weighting approach provides a good balance between the original image and the tint color.


155-155: Interface update supports mod sets

The addition of the optional setBonus property to the BackgroundProps interface properly supports the new mod set functionality.


201-201: Added strokeStyle for visual consistency

Setting the stroke style to match the text color ensures visual consistency when drawing set bonus rectangles.


210-210: Updated modDescription function call

The function call has been updated to match the new parameter order in utils.ts.


227-257: Good implementation of mod set visualization

The code properly implements the visualization of mod sets with:

  1. Rectangle indicators for each set piece
  2. Filled rectangles based on the setBonus value
  3. Display of the active set bonus stat

This provides clear visual feedback about set completion status and benefits.


174-178: Fixed thumbnail positioning with new variable name

The thumbnail positioning and dimensions have been updated to use the corrected horizontalPad variable, maintaining consistent padding.

src/utils.ts (3)

8-8: Good refactoring of constants

Moving modRarityMap and tierColor to a dedicated data file improves code organization by centralizing game-specific data.


200-205: Good implementation of hexToRgb utility

The hexToRgb function properly handles hex color conversion with a fallback for invalid formats.


183-187: Improved default handling in exportCanvas

The changes ensure that default values are used when quality or config parameters are not provided, preventing potential errors.

@SlayerOrnstein SlayerOrnstein marked this pull request as draft April 6, 2025 23:47
@SlayerOrnstein SlayerOrnstein deleted the feat-headers branch February 23, 2026 21:48
@SlayerOrnstein SlayerOrnstein restored the feat-headers branch February 23, 2026 21:49
@SlayerOrnstein SlayerOrnstein marked this pull request as ready for review February 24, 2026 18:14
Copy link

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/drawers.ts (1)

193-258: ⚠️ Potential issue | 🟠 Major

modTextHeight is calculated from stats[0] but a different stat is rendered when setBonus > 0

modTextHeight drives the thumbnail's height budget (thumbHeight = thumb.height - modTextHeight). It is sized for the line count of stats[0], but the drawn text comes from modSet.stats[Math.min(setBonus ?? 0, stats.length - 1)] (line 235/255). When setBonus > 0 and the active stat has more wrapped lines than stats[0], the description overflows the image area; when it has fewer, there is unnecessary blank space.

Fix: compute the active stat index before calling textHeight, then use the same index for both the height measurement and the rendering:

🐛 Proposed fix
  if (mod.modSet) {
    modSet = find.findItem(mod.modSet) as ModSet;
+   const setStatsIndex = Math.min(setBonus ?? 0, (modSet?.stats.length ?? 1) - 1);
    modTextHeight =
-     modTextHeight + textHeight(context, maxWidth, undefined, modSet?.stats[0].split('\n')) + modSetProgressHeight;
+     modTextHeight + textHeight(context, maxWidth, undefined, modSet?.stats[setStatsIndex]?.split('\n')) + modSetProgressHeight;
  }

And below, when rendering, replace the duplicated Math.min derivation:

- const stats = Math.min(setBonus ?? 0, modSet.stats.length - 1);
+ // setStatsIndex already matches the height budget
  ...
- wrapText(context, modSet.stats[stats], maxWidth).forEach(...)
+ wrapText(context, modSet.stats[setStatsIndex], maxWidth).forEach(...)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/drawers.ts` around lines 193 - 258, The mod thumbnail height is computed
using textHeight for modSet.stats[0] but rendering uses Math.min(setBonus ?? 0,
modSet.stats.length - 1), causing mismatched heights; compute the active stat
index once (e.g., const activeStat = Math.min(setBonus ?? 0, modSet.stats.length
- 1)) immediately after resolving modSet and use that activeStat when calling
textHeight (replace the current stats[0] usage) and again when rendering the mod
set text (replace the duplicated Math.min expression), ensuring you null-check
modSet before accessing modSet.stats.
♻️ Duplicate comments (1)
tests/generator.spec.ts (1)

30-38: i <= mods.length intentionally over-iterates — prefer < mods.length

The loop runs one extra iteration (mods[mods.length]undefined) relying on the if (!mod) return guard to no-op. The intent is cleaner as a standard < bound.

-    for (let i = 0; i <= mods.length; i += 1) {
+    for (let i = 0; i < mods.length; i += 1) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/generator.spec.ts` around lines 30 - 38, The for-loop uses "for (let i
= 0; i <= mods.length; i += 1)" which iterates one past the end and relies on
the "if (!mod) return" guard; change the loop to stop at the correct bound ("i <
mods.length") to avoid an extra iteration and remove reliance on the guard.
Update the loop controlling expression where "i" is declared; keep the body
using "const mod = find.findItem(mods[i]) as Mod" and the existing early return
logic unchanged.
🧹 Nitpick comments (2)
src/drawers.ts (2)

241-244: Hoist loop-invariant bonus out of the loop

(setBonus ?? 0) - 1 is constant across all iterations of the for loop.

♻️ Proposed fix
+ const bonus = (setBonus ?? 0) - 1;
  for (let i = 0; i < modSet.stats.length; i++) {
-   // default to negative to imply disabled
-   const bonus = (setBonus ?? 0) - 1;
    if (bonus < i) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/drawers.ts` around lines 241 - 244, Compute the loop-invariant bonus once
before iterating instead of inside the for loop: move the expression (setBonus
?? 0) - 1 into a const named bonus defined just before the for loop that
iterates over modSet.stats, then use that bonus variable inside the loop
(replace the in-loop declaration and comparison if (bonus < i) accordingly).

159-170: modSetRank in BackgroundProps is never used

The field is declared in the interface but is neither destructured in backgroundImage nor accessed elsewhere in the function.

♻️ Proposed fix
-  modSetRank?: number;
   setBonus?: number;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/drawers.ts` around lines 159 - 170, BackgroundProps declares a modSetRank
field that is never used; remove modSetRank from the BackgroundProps interface
to eliminate dead API surface (or if it was intended to affect rendering, add it
to the destructured parameters of backgroundImage and apply it where
rank/mod/set bonus are used). Locate the BackgroundProps interface and the
backgroundImage function to either delete the modSetRank property from the type
or add it to the backgroundImage parameter list and incorporate it into the
existing logic that uses rank/mod/modSet/setBonus so callers and tests remain
consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/drawers.ts`:
- Around line 193-197: The code calls modSet.stats[0].split('\n') without
guarding against an empty stats array which throws if stats[0] is undefined;
update the mod.modSet handling so after retrieving modSet (via find.findItem)
you check that modSet.stats exists and has at least one entry and only then
split the first stat, otherwise pass undefined or an empty array into
textHeight; modify the modTextHeight computation (using symbols mod.modSet,
modSet, modSet.stats, textHeight, modSetProgressHeight, modTextHeight) to use
this guarded value.

In `@src/generator.ts`:
- Around line 8-14: The interface GenerateModProps is currently module-private
but the public entrypoint generate expects that shape; export GenerateModProps
so consumers can import and type their props. Modify the declaration of
GenerateModProps to be exported (export interface GenerateModProps) and ensure
any dependent types or re-exports used by generate (e.g., Mod, CanvasOutput if
referenced) are also exported or imported where needed so external callers can
construct typed props matching generate(modProps).

In `@src/utils.ts`:
- Around line 168-170: The code currently uses the falsy-coalescing pattern
quality || 100 when calling canvas.encode (in the 'webp' and 'jpeg' branches),
which treats an explicit quality of 0 as absent; change those calls to use a
nullish-coalescing or explicit undefined check (e.g., quality ?? 100 or quality
=== undefined ? 100 : quality) so that a caller passing quality: 0 is preserved
and only undefined/null falls back to 100.

---

Outside diff comments:
In `@src/drawers.ts`:
- Around line 193-258: The mod thumbnail height is computed using textHeight for
modSet.stats[0] but rendering uses Math.min(setBonus ?? 0, modSet.stats.length -
1), causing mismatched heights; compute the active stat index once (e.g., const
activeStat = Math.min(setBonus ?? 0, modSet.stats.length - 1)) immediately after
resolving modSet and use that activeStat when calling textHeight (replace the
current stats[0] usage) and again when rendering the mod set text (replace the
duplicated Math.min expression), ensuring you null-check modSet before accessing
modSet.stats.

---

Duplicate comments:
In `@tests/generator.spec.ts`:
- Around line 30-38: The for-loop uses "for (let i = 0; i <= mods.length; i +=
1)" which iterates one past the end and relies on the "if (!mod) return" guard;
change the loop to stop at the correct bound ("i < mods.length") to avoid an
extra iteration and remove reliance on the guard. Update the loop controlling
expression where "i" is declared; keep the body using "const mod =
find.findItem(mods[i]) as Mod" and the existing early return logic unchanged.

---

Nitpick comments:
In `@src/drawers.ts`:
- Around line 241-244: Compute the loop-invariant bonus once before iterating
instead of inside the for loop: move the expression (setBonus ?? 0) - 1 into a
const named bonus defined just before the for loop that iterates over
modSet.stats, then use that bonus variable inside the loop (replace the in-loop
declaration and comparison if (bonus < i) accordingly).
- Around line 159-170: BackgroundProps declares a modSetRank field that is never
used; remove modSetRank from the BackgroundProps interface to eliminate dead API
surface (or if it was intended to affect rendering, add it to the destructured
parameters of backgroundImage and apply it where rank/mod/set bonus are used).
Locate the BackgroundProps interface and the backgroundImage function to either
delete the modSetRank property from the type or add it to the backgroundImage
parameter list and incorporate it into the existing logic that uses
rank/mod/modSet/setBonus so callers and tests remain consistent.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 068d5c6 and 14e02e5.

⛔ Files ignored due to path filters (3)
  • assets/readme/Augur Message.png is excluded by !**/*.png
  • assets/readme/Primed Flow.png is excluded by !**/*.png
  • assets/readme/Steel Charge.png is excluded by !**/*.png
📒 Files selected for processing (6)
  • package.json
  • src/drawers.ts
  • src/generator.ts
  • src/styling.ts
  • src/utils.ts
  • tests/generator.spec.ts

@SlayerOrnstein SlayerOrnstein enabled auto-merge (squash) February 24, 2026 18:42
@SlayerOrnstein SlayerOrnstein merged commit 2a2a6c9 into WFCD:main Feb 24, 2026
2 checks passed
@SlayerOrnstein SlayerOrnstein deleted the feat-headers branch February 24, 2026 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants