Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ public class FirstPersonSettings {

public int sitXOffset = 0;

// NEW
public int swimXOffset = 0; // 游泳时 X 轴偏移
public int crawlXOffset = 0; // 爬行时 X 轴偏移
public boolean swimOrCrawlY = true; // 是否启用游泳或爬行时 Y 轴偏移
// 渲染开关
public boolean renderSleepingModel = false; // 睡觉
public boolean renderSpinAttackModel = false; // 三叉戟旋风斩
public boolean renderFlyingModel = false; // 是否渲染飞行模型
public boolean renderSwimTransitionModel= false; // 游泳过渡
public boolean renderScopingModel = false; // 望远镜
public boolean modifyPlayerModel = true; // 是否修改玩家模型
public boolean keepModelVisible = false; // 保持模型显示(除动态模式)

public boolean renderStuckFeatures = true;
public VanillaHands vanillaHandsMode = VanillaHands.OFF;
public boolean dynamicMode = true;
Expand Down
18 changes: 11 additions & 7 deletions src/main/java/dev/tr7zw/firstperson/LogicHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,11 @@ public class LogicHandler {

void registerDefaultHandlers() {
FirstPersonAPI.registerPlayerHandler((ActivationHandler) () -> {
if (client.player.isSleeping() || client.player.isAutoSpinAttack() || client.player.isFallFlying()
|| (client.player.getSwimAmount(1f) != 0 && !isCrawlingOrSwimming(client.player))) {
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) {
timeout = System.currentTimeMillis() + 100;
return true;
}
Expand All @@ -54,7 +57,8 @@ void registerDefaultHandlers() {
return true;
}
//#if MC >= 11700
if (client.player.isScoping()) {
if (client.player.isScoping() && !fpm.getConfig().renderScopingModel
&& !fpm.getConfig().keepModelVisible) {
return true;
}
//#endif
Expand Down Expand Up @@ -100,7 +104,7 @@ public void updatePositionOffset(Entity entity, float delta) {
AbstractClientPlayer player;
double realYaw;
if ((entity != client.player) || (client.options.getCameraType() != CameraType.FIRST_PERSON)
|| !fpm.isRenderingPlayer()) {
|| !fpm.isRenderingPlayer() || !fpm.getConfig().modifyPlayerModel) { // 添加了对 modifyPlayerModel 的检查
return;
}
player = (AbstractClientPlayer) entity;
Expand All @@ -110,9 +114,9 @@ public void updatePositionOffset(Entity entity, float delta) {
if (isCrawlingOrSwimming(client.player)) {
player.yBodyRot = player.yHeadRot;
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; // 保留原值并加上配置项
}
Comment on lines 116 to 120
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.

// some mods seem to break the isCrouching method
} else if (player.isCrouching() || player.getPose() == Pose.CROUCHING) {
Expand All @@ -134,7 +138,7 @@ public void updatePositionOffset(Entity entity, float delta) {
}
x += bodyOffset * Math.sin(Math.toRadians(realYaw));
z -= bodyOffset * Math.cos(Math.toRadians(realYaw));
if (isCrawlingOrSwimming(client.player)) {
if (isCrawlingOrSwimming(client.player) && fpm.getConfig().swimOrCrawlY) {
if (player.xRotO > 0 && player.isUnderWater()) {
y += 0.6f * Math.sin(Math.toRadians(player.xRotO));
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,40 @@ public CustomConfigScreen(Screen previous) {
options.add(getIntOption("text.firstperson.option.firstperson.sitXOffset", -40, 40,
() -> FirstPersonModelCore.instance.getConfig().sitXOffset,
i -> FirstPersonModelCore.instance.getConfig().sitXOffset = i));

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

options.add(getOnOffOption("text.firstperson.option.firstperson.renderStuckFeatures",
() -> FirstPersonModelCore.instance.getConfig().renderStuckFeatures,
b -> FirstPersonModelCore.instance.getConfig().renderStuckFeatures = b));
Expand Down
26 changes: 25 additions & 1 deletion src/main/resources/assets/firstperson/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,29 @@
"modmenu.summaryTranslation.firstperson": "Enables the third-person Model in first-person",
"modmenu.descriptionTranslation.firstperson": "Enables the third-person Model in first-person",
"text.firstperson.option.firstperson.vanillaHandsSkipSwimming": "Disable Vanilla Hands while Swimming",
"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 ignoring the dynamic mode)",

"text.firstperson.option.firstperson.swimXOffset": "Swim X Offset",
"text.firstperson.option.firstperson.swimXOffset.tooltip": "Moves the body relative to the camera back and forth while swimming",
"text.firstperson.option.firstperson.crawlXOffset": "Crawl X Offset",
"text.firstperson.option.firstperson.crawlXOffset.tooltip": "Moves the body relative to the camera back and forth while crawling",
"text.firstperson.option.firstperson.swimOrCrawlY": "Enable Swim Or Crawl Y Offset",
"text.firstperson.option.firstperson.swimOrCrawlY.tooltip": "Whether to enable Y-axis compensation while swimming or crawling",

"text.firstperson.option.firstperson.renderSleepingModel": "Render Sleeping Model",
"text.firstperson.option.firstperson.renderSleepingModel.tooltip": "Whether to render the sleeping model",
"text.firstperson.option.firstperson.renderSpinAttackModel": "Render Spin Attack Model",
"text.firstperson.option.firstperson.renderSpinAttackModel.tooltip": "Whether to render the spin attack model",
"text.firstperson.option.firstperson.renderFlyingModel": "Render Flying Model",
"text.firstperson.option.firstperson.renderFlyingModel.tooltip": "Whether to render the first-person model while flying",
"text.firstperson.option.firstperson.renderSwimTransitionModel": "Render Swim Transition Model",
"text.firstperson.option.firstperson.renderSwimTransitionModel.tooltip": "Whether to render the swim transition model",
"text.firstperson.option.firstperson.renderScopingModel": "Render Scoping Model",
"text.firstperson.option.firstperson.renderScopingModel.tooltip": "Whether to render the scoping model",
"text.firstperson.option.firstperson.modifyPlayerModel": "Modify Player Model",
"text.firstperson.option.firstperson.modifyPlayerModel.tooltip": "Whether to modify the player model",
"text.firstperson.option.firstperson.keepModelVisible": "Keep Model Visible",
"text.firstperson.option.firstperson.keepModelVisible.tooltip": "Whether to keep the model visible (except in dynamic mode)"


}
29 changes: 28 additions & 1 deletion src/main/resources/assets/firstperson/lang/zh_cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"firstperson.keybind": "第一人称模型",
"key.firstperson.toggle": "切换第一人称",
"category.firstperson.firstperson": "第一人称",
"text.firstperson.tab.settings": "设置",
"text.firstperson.tab.autovanillahands": "自动原版手臂",
"text.firstperson.tab.autovanillahands.tooltip": "手持物品时自动切换至原版手臂",
"text.firstperson.tab.disableitems": "禁用物品",
"text.firstperson.tab.disableitems.tooltip": "手持指定物品时禁用第一人称模组",
"text.firstperson.option.firstperson.xOffset": "站立时的 X 轴补偿",
"text.firstperson.option.firstperson.xOffset.tooltip": "前后调节站立时身体相对于镜头的位置",
"text.firstperson.option.firstperson.sneakXOffset": "潜行时的 X 轴补偿",
Expand All @@ -24,5 +29,27 @@
"modmenu.summaryTranslation.firstperson": "在第一人称的视角启用第三人称的模型",
"modmenu.descriptionTranslation.firstperson": "在第一人称的视角启用第三人称的模型",
"text.firstperson.option.firstperson.vanillaHandsSkipSwimming": "在游泳时禁用原版手部显示",
"text.firstperson.option.firstperson.vanillaHandsSkipSwimming.tooltip": "在游泳时禁用原版手部显示(当然也会忽略动态模式)"
"text.firstperson.option.firstperson.vanillaHandsSkipSwimming.tooltip": "在游泳时禁用原版手部显示(当然也会忽略动态模式)",

"text.firstperson.option.firstperson.swimXOffset": "游泳时的 X 轴补偿",
"text.firstperson.option.firstperson.swimXOffset.tooltip": "前后调节游泳时身体相对于镜头的位置",
"text.firstperson.option.firstperson.crawlXOffset": "爬行时的 X 轴补偿",
"text.firstperson.option.firstperson.crawlXOffset.tooltip": "前后调节爬行时身体相对于镜头的位置",
"text.firstperson.option.firstperson.swimOrCrawlY": "启用游泳或爬行时的 Y 轴补偿",
"text.firstperson.option.firstperson.swimOrCrawlY.tooltip": "是否启用游泳或爬行时的 Y 轴补偿",

"text.firstperson.option.firstperson.renderSleepingModel": "渲染睡觉模型",
"text.firstperson.option.firstperson.renderSleepingModel.tooltip": "是否渲染睡觉模型",
"text.firstperson.option.firstperson.renderSpinAttackModel": "渲染旋转攻击模型",
"text.firstperson.option.firstperson.renderSpinAttackModel.tooltip": "是否渲染旋转攻击模型",
"text.firstperson.option.firstperson.renderFlyingModel": "渲染飞行模型",
"text.firstperson.option.firstperson.renderFlyingModel.tooltip": "在飞行时是否渲染第一人称模型",
"text.firstperson.option.firstperson.renderSwimTransitionModel": "渲染游泳过渡模型",
"text.firstperson.option.firstperson.renderSwimTransitionModel.tooltip": "是否渲染游泳过渡模型",
"text.firstperson.option.firstperson.renderScopingModel": "渲染望远镜模型",
"text.firstperson.option.firstperson.renderScopingModel.tooltip": "是否渲染望远镜模型",
"text.firstperson.option.firstperson.modifyPlayerModel": "修改玩家模型偏移",
"text.firstperson.option.firstperson.modifyPlayerModel.tooltip": "是否修改玩家模型",
"text.firstperson.option.firstperson.keepModelVisible": "保持模型显示",
"text.firstperson.option.firstperson.keepModelVisible.tooltip": "保持模型显示(动态模式除外)"
}