Skip to content

Conversation

@Anan1a
Copy link

@Anan1a Anan1a commented Aug 19, 2025

Add configurable model options

@coderabbitai
Copy link

coderabbitai bot commented Aug 19, 2025

Walkthrough

Adds new configuration fields controlling offsets and render toggles, updates LogicHandler to respect these flags for rendering and position offset behavior, extends the config UI to expose the new options, and adds English/Chinese localization strings for all new settings.

Changes

Cohort / File(s) Summary
Settings model
FPVersionless/src/main/java/dev/tr7zw/firstperson/versionless/config/FirstPersonSettings.java
Adds public fields: swimXOffset, crawlXOffset, swimOrCrawlY, renderSleepingModel, renderSpinAttackModel, renderFlyingModel, renderSwimTransitionModel, renderScopingModel, modifyPlayerModel, keepModelVisible; defaulted with inline comments.
Rendering logic
src/main/java/dev/tr7zw/firstperson/LogicHandler.java
Integrates per-state render toggles gated by keepModelVisible; adds renderScopingModel handling; guards offset updates with modifyPlayerModel; applies swim/crawl X offsets; conditionally applies swim/crawl Y via swimOrCrawlY.
Config UI
src/main/java/dev/tr7zw/firstperson/config/ConfigScreenProvider.java
Adds sliders/toggles for new settings (swimXOffset, crawlXOffset, swimOrCrawlY, render*Model, modifyPlayerModel, keepModelVisible, renderStuckFeatures) and binds them to config.
Localization
src/main/resources/assets/firstperson/lang/en_us.json, src/main/resources/assets/firstperson/lang/zh_cn.json
Adds labels/tooltips for new options and tabs (in zh_cn). No removals; existing keys kept.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Player
  participant LogicHandler
  participant Config
  participant Renderer

  Player->>LogicHandler: onTick()
  LogicHandler->>Config: read modifyPlayerModel
  alt modifyPlayerModel == true
    LogicHandler->>Config: read swimXOffset, crawlXOffset, swimOrCrawlY
    LogicHandler->>LogicHandler: compute offsets (state: swim/crawl)
  else
    LogicHandler-->>LogicHandler: skip position offset update
  end

  Renderer->>LogicHandler: shouldRenderModel(state, scoping?)
  LogicHandler->>Config: read keepModelVisible
  alt keepModelVisible == true
    LogicHandler-->>Renderer: render model
  else
    LogicHandler->>Config: read per-state flags (sleeping, spinAttack, flying, swimTransition, scoping)
    alt state allowed by its flag
      LogicHandler-->>Renderer: render model
    else
      LogicHandler-->>Renderer: skip rendering model
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
B Maintainability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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: 1

🧹 Nitpick comments (6)
src/main/resources/assets/firstperson/lang/en_us.json (1)

32-55: Tighten wording; fix spacing; align tooltip with actual behavior

  • Missing space before the parenthesis in the swimming tooltip.
  • Label can be made clearer: “Swim/Crawl Y Offset” reads better.
  • Tooltip for “Keep Model Visible” mentions dynamic mode, but LogicHandler doesn't tie this flag to dynamicMode; it gates per-state model visibility. The current tooltip is misleading.

Apply this diff to improve clarity and correctness:

-    "text.firstperson.option.firstperson.vanillaHandsSkipSwimming.tooltip": "Disables the Vanilla Hands while swimming(also ignoring the dynamic mode)",
+    "text.firstperson.option.firstperson.vanillaHandsSkipSwimming.tooltip": "Disables the Vanilla Hands while swimming (also ignores Dynamic Mode)",

-    "text.firstperson.option.firstperson.swimOrCrawlY": "Enable Swim Or Crawl Y Offset",
+    "text.firstperson.option.firstperson.swimOrCrawlY": "Enable Swim/Crawl Y Offset",

-    "text.firstperson.option.firstperson.keepModelVisible.tooltip": "Whether to keep the model visible (except in dynamic mode)"
+    "text.firstperson.option.firstperson.keepModelVisible.tooltip": "Whether to keep the model visible regardless of per-state visibility settings"

If “keepModelVisible” is intended to interact with dynamicMode elsewhere, please point me to that code and I’ll adjust the wording accordingly. Otherwise, the above matches LogicHandler’s checks.

src/main/java/dev/tr7zw/firstperson/config/ConfigScreenProvider.java (1)

59-92: Reduce repetition and localize config access; minor UX suggestion

  • Repeated lambda access to FirstPersonModelCore.instance.getConfig() hurts readability and is easy to mistype. Pull a local reference.
  • Optional: Rename the Chinese comment “渲染开关” to English to keep code comments consistent.

Apply this refactor within this block:

-            // NEW
-            options.add(getIntOption("text.firstperson.option.firstperson.swimXOffset", -100, 100,
-                    () -> FirstPersonModelCore.instance.getConfig().swimXOffset,
-                    i -> FirstPersonModelCore.instance.getConfig().swimXOffset = i));
+            // NEW
+            final var cfg = FirstPersonModelCore.instance.getConfig();
+            options.add(getIntOption("text.firstperson.option.firstperson.swimXOffset", -100, 100,
+                    () -> cfg.swimXOffset, i -> cfg.swimXOffset = i));
             options.add(getIntOption("text.firstperson.option.firstperson.crawlXOffset", -100, 100,
-                    () -> FirstPersonModelCore.instance.getConfig().crawlXOffset,
-                    i -> FirstPersonModelCore.instance.getConfig().crawlXOffset = i));
+                    () -> cfg.crawlXOffset, i -> cfg.crawlXOffset = i));
             options.add(getOnOffOption("text.firstperson.option.firstperson.swimOrCrawlY",
-                    () -> FirstPersonModelCore.instance.getConfig().swimOrCrawlY,
-                    b -> FirstPersonModelCore.instance.getConfig().swimOrCrawlY = b));
-            // 渲染开关
+                    () -> cfg.swimOrCrawlY, b -> cfg.swimOrCrawlY = b));
+            // Render toggles
             options.add(getOnOffOption("text.firstperson.option.firstperson.renderSleepingModel",
-                    () -> FirstPersonModelCore.instance.getConfig().renderSleepingModel,
-                    b -> FirstPersonModelCore.instance.getConfig().renderSleepingModel = b));
+                    () -> cfg.renderSleepingModel, b -> cfg.renderSleepingModel = b));
             options.add(getOnOffOption("text.firstperson.option.firstperson.renderSpinAttackModel",
-                    () -> FirstPersonModelCore.instance.getConfig().renderSpinAttackModel,
-                    b -> FirstPersonModelCore.instance.getConfig().renderSpinAttackModel = b));
+                    () -> cfg.renderSpinAttackModel, b -> cfg.renderSpinAttackModel = b));
             options.add(getOnOffOption("text.firstperson.option.firstperson.renderFlyingModel",
-                    () -> FirstPersonModelCore.instance.getConfig().renderFlyingModel,
-                    b -> FirstPersonModelCore.instance.getConfig().renderFlyingModel = b));
+                    () -> cfg.renderFlyingModel, b -> cfg.renderFlyingModel = b));
             options.add(getOnOffOption("text.firstperson.option.firstperson.renderSwimTransitionModel",
-                    () -> FirstPersonModelCore.instance.getConfig().renderSwimTransitionModel,
-                    b -> FirstPersonModelCore.instance.getConfig().renderSwimTransitionModel = b));
+                    () -> cfg.renderSwimTransitionModel, b -> cfg.renderSwimTransitionModel = b));
             options.add(getOnOffOption("text.firstperson.option.firstperson.renderScopingModel",
-                    () -> FirstPersonModelCore.instance.getConfig().renderScopingModel,
-                    b -> FirstPersonModelCore.instance.getConfig().renderScopingModel = b));
+                    () -> cfg.renderScopingModel, b -> cfg.renderScopingModel = b));
             options.add(getOnOffOption("text.firstperson.option.firstperson.modifyPlayerModel",
-                    () -> FirstPersonModelCore.instance.getConfig().modifyPlayerModel,
-                    b -> FirstPersonModelCore.instance.getConfig().modifyPlayerModel = b));
+                    () -> cfg.modifyPlayerModel, b -> cfg.modifyPlayerModel = b));
             options.add(getOnOffOption("text.firstperson.option.firstperson.keepModelVisible",
-                    () -> FirstPersonModelCore.instance.getConfig().keepModelVisible,
-                    b -> FirstPersonModelCore.instance.getConfig().keepModelVisible = b));
+                    () -> cfg.keepModelVisible, b -> cfg.keepModelVisible = b));

Optional UX: Consider disabling the offset controls when “Modify Player Model” is off, to signal they won’t have effect. I can wire that up if the widget API supports dependent enable/disable.

src/main/resources/assets/firstperson/lang/zh_cn.json (1)

31-54: 修正措辞并与实际行为对齐

  • “在游泳时禁用原版手部显示(当然也会忽略动态模式)”用语不太自然;简化为“同时忽略”更好。
  • “保持模型显示(动态模式除外)”与实现不符。LogicHandler 中 keepModelVisible 仅影响各状态的显示开关,不与 dynamicMode(手部逻辑)绑定。

建议如下修改:

-    "text.firstperson.option.firstperson.vanillaHandsSkipSwimming.tooltip": "在游泳时禁用原版手部显示(当然也会忽略动态模式)",
+    "text.firstperson.option.firstperson.vanillaHandsSkipSwimming.tooltip": "在游泳时禁用原版手部显示(同时忽略动态模式)",

-    "text.firstperson.option.firstperson.modifyPlayerModel": "修改玩家模型偏移",
+    "text.firstperson.option.firstperson.modifyPlayerModel": "修改玩家模型",

-    "text.firstperson.option.firstperson.keepModelVisible.tooltip": "保持模型显示(动态模式除外)"
+    "text.firstperson.option.firstperson.keepModelVisible.tooltip": "无视各状态渲染开关保持模型显示"

如 keepModelVisible 确实应与 dynamicMode 交互,请指出对应实现位置,我会调整表述。

src/main/java/dev/tr7zw/firstperson/LogicHandler.java (2)

43-48: Guard against null player before querying state

When no world is loaded, client.player can be null. Add a quick guard to avoid potential NPEs in the activation handler.

         FirstPersonAPI.registerPlayerHandler((ActivationHandler) () -> {
+            if (client.player == null) {
+                return false;
+            }
             if (((client.player.isSleeping() && !fpm.getConfig().renderSleepingModel)
                     || (client.player.isAutoSpinAttack() && !fpm.getConfig().renderSpinAttackModel)
                     || (client.player.isFallFlying() && !fpm.getConfig().renderFlyingModel)
                     || (client.player.getSwimAmount(1f) != 0 && !isCrawlingOrSwimming(client.player) && !fpm.getConfig().renderSwimTransitionModel))
                     && !fpm.getConfig().keepModelVisible) {

If the event cannot fire without a player, feel free to skip this.


141-147: Optional: use current/lerped pitch for smoother Y offset

Using xRotO can cause a 1-tick lag. Consider using the current/lerped X-rot for smoother motion.

Example:

-                if (player.xRotO > 0 && player.isUnderWater()) {
-                    y += 0.6f * Math.sin(Math.toRadians(player.xRotO));
+                float xRot = player.getXRot(); // or Mth.lerp(delta, player.xRotO, player.getXRot())
+                if (xRot > 0 && player.isUnderWater()) {
+                    y += 0.6f * Math.sin(Math.toRadians(xRot));
                 } else {
-                    y += 0.01f * -Math.sin(Math.toRadians(player.xRotO));
+                    y += 0.01f * -Math.sin(Math.toRadians(xRot));
                 }
FPVersionless/src/main/java/dev/tr7zw/firstperson/versionless/config/FirstPersonSettings.java (1)

24-24: Style nit: spacing around assignment

Minor consistency nit.

-    public boolean renderSwimTransitionModel= false;    // 游泳过渡
+    public boolean renderSwimTransitionModel = false;   // 游泳过渡
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 592da9d and 74331ba.

📒 Files selected for processing (5)
  • FPVersionless/src/main/java/dev/tr7zw/firstperson/versionless/config/FirstPersonSettings.java (1 hunks)
  • src/main/java/dev/tr7zw/firstperson/LogicHandler.java (5 hunks)
  • src/main/java/dev/tr7zw/firstperson/config/ConfigScreenProvider.java (1 hunks)
  • src/main/resources/assets/firstperson/lang/en_us.json (1 hunks)
  • src/main/resources/assets/firstperson/lang/zh_cn.json (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/dev/tr7zw/firstperson/config/ConfigScreenProvider.java (1)
src/main/java/dev/tr7zw/firstperson/FirstPersonModelCore.java (1)
  • FirstPersonModelCore (14-100)
🔇 Additional comments (4)
src/main/resources/assets/firstperson/lang/zh_cn.json (1)

6-10: 新增分栏翻译对齐 UI 使用点,LGTM

Keys align with the tabs added in ConfigScreenProvider; wording looks natural.

src/main/java/dev/tr7zw/firstperson/LogicHandler.java (2)

60-62: Scoping behavior toggle is correct

The gating matches the intended “don’t render while scoping unless allowed or forced visible”.


107-108: Early-return check adds the missing guard

Respecting modifyPlayerModel here avoids unnecessary math and side effects. Good addition.

FPVersionless/src/main/java/dev/tr7zw/firstperson/versionless/config/FirstPersonSettings.java (1)

16-28: Default render toggles now hide the model—please confirm backward-compatibility intent

By defaulting all new render* flags to false, existing users will no longer see the first-person model in those states. If you intend to preserve current behavior, consider:

• In FPVersionless/src/main/java/dev/tr7zw/firstperson/versionless/config/FirstPersonSettings.java:

  • Change renderSleepingModel, renderSpinAttackModel, renderFlyingModel, and renderSwimTransitionModel to true.
  • Leave renderScopingModel as false (matches existing scope behavior).
    • Bump configVersion (currently 2) to 3 and implement migration logic so users’ old settings carry over.

Suggested diff:

--- a/FirstPersonSettings.java
+++ b/FirstPersonSettings.java
@@ lines 7,16
-    public int configVersion = 2;
+    public int configVersion = 3;

@@ lines 16-21
-    public boolean renderSleepingModel = false;         // 睡觉
-    public boolean renderSpinAttackModel = false;       // 三叉戟旋风斩
-    public boolean renderFlyingModel = false;           // 是否渲染飞行模型
-    public boolean renderSwimTransitionModel = false;   // 游泳过渡
-    public boolean renderScopingModel = false;          // 望远镜
+    public boolean renderSleepingModel = true;          // 睡觉
+    public boolean renderSpinAttackModel = true;        // 三叉戟旋风斩
+    public boolean renderFlyingModel = true;            // 是否渲染飞行模型
+    public boolean renderSwimTransitionModel = true;    // 游泳过渡
+    public boolean renderScopingModel = false;          // 望远镜 (保持当前隐藏行为)

Comment on lines 116 to 120
if (player.xRotO > 0) {
bodyOffset = Constants.SWIM_UP_BODY_OFFSET;
bodyOffset = Constants.SWIM_UP_BODY_OFFSET + fpm.getConfig().swimXOffset / 100f; // 保留原值并加上配置项
} else {
bodyOffset = Constants.SWIM_DOWN_BODY_OFFSET;
bodyOffset = Constants.SWIM_DOWN_BODY_OFFSET + fpm.getConfig().crawlXOffset / 100f; // 保留原值并加上配置项
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Bug: crawlXOffset applied based on pitch, not state (swim vs crawl)

Within isCrawlingOrSwimming, you’re choosing crawlXOffset when looking down, which will also trigger during underwater swimming. The choice of swimXOffset vs. crawlXOffset should depend on whether the player is underwater, not on pitch.

Proposed fix:

-                if (player.xRotO > 0) {
-                    bodyOffset = Constants.SWIM_UP_BODY_OFFSET + fpm.getConfig().swimXOffset / 100f; // 保留原值并加上配置项
-                } else {
-                    bodyOffset = Constants.SWIM_DOWN_BODY_OFFSET + fpm.getConfig().crawlXOffset / 100f; // 保留原值并加上配置项
-                }
+                final boolean underwater = player.isUnderWater();
+                final float base = (player.xRotO > 0)
+                        ? Constants.SWIM_UP_BODY_OFFSET
+                        : Constants.SWIM_DOWN_BODY_OFFSET;
+                bodyOffset = base + (underwater
+                        ? fpm.getConfig().swimXOffset / 100f
+                        : fpm.getConfig().crawlXOffset / 100f);

If there’s a more precise crawl detector available (e.g., Pose.SWIMMING vs. conditions), we can use that instead of isUnderWater().

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (player.xRotO > 0) {
bodyOffset = Constants.SWIM_UP_BODY_OFFSET;
bodyOffset = Constants.SWIM_UP_BODY_OFFSET + fpm.getConfig().swimXOffset / 100f; // 保留原值并加上配置项
} else {
bodyOffset = Constants.SWIM_DOWN_BODY_OFFSET;
bodyOffset = Constants.SWIM_DOWN_BODY_OFFSET + fpm.getConfig().crawlXOffset / 100f; // 保留原值并加上配置项
}
// Determine whether the player is actually underwater (swimming) or on ground (crawling)
final boolean underwater = player.isUnderWater();
// Choose the base offset by pitch
final float base = (player.xRotO > 0)
? Constants.SWIM_UP_BODY_OFFSET
: Constants.SWIM_DOWN_BODY_OFFSET;
// Add the configured offset for swim vs. crawl
bodyOffset = base + (underwater
? fpm.getConfig().swimXOffset / 100f
: fpm.getConfig().crawlXOffset / 100f);
🤖 Prompt for AI Agents
In src/main/java/dev/tr7zw/firstperson/LogicHandler.java around lines 116 to
120, the code currently selects crawlXOffset when player.xRotO <= 0 (looking
down), which incorrectly applies crawl offsets while underwater; change the
branch to pick swimXOffset when the player is swimming/underwater and
crawlXOffset when the player is actually crawling. Replace the pitch-based
condition with a swim/crawl state check (e.g., use isUnderWater() or
Pose.SWIMMING when available) so that bodyOffset = (swimming ?
Constants.SWIM_*_BODY_OFFSET + fpm.getConfig().swimXOffset/100f :
Constants.SWIM_*_BODY_OFFSET + fpm.getConfig().crawlXOffset/100f) choosing the
appropriate up/down constant per state and keeping the original offsets plus the
configured addition.

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.

1 participant