Skip to content
Merged
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
6 changes: 6 additions & 0 deletions docs/IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,14 @@ Disk I/O, HTTP requests, expensive computations are done on owned workers with a

`InputBlocker` routes key events before Minecraft handles them. Features register an `InputHandler` on start and remove it on stop. `key(...)` returns true when it consumes an event. `consumesKey(...)` identifies keys whose held states must not be restored after blocking ends.

While input is locked, only lock owners receive `blockedKey(key, modifiers)`. It receives new key presses without repeats. Use `Keybind.matches(binding, key, modifiers)` to check a binding against the captured modifier state. The input device queues these presses and dispatches them at tick start, outside LWJGL polling.

Use `lock(owner)` to block keyboard and mouse input. The platform supplies an `InputDevice` that masks LWJGL events and polled states. Minecraft's key, movement, and smooth camera states are cleared once when blocking starts. Use `unlock(owner, resume)` when the operation finishes or is cancelled. Each operation releases only its own lock. `blockedFor(owner)` reports whether another operation holds a lock. Input resumes only after all locks are released. Buffered events are discarded before restoring the input backend. When `resume` is true, held keys are restored if the game has focus. `held(key)` reads the current LWJGL state, which is false while blocked.

Use `runAction(owner, action)` for an automatic right-click or container click while holding the input lock. The action passes through vanilla hooks without restoring physical input. `blocksActions()` tells interaction hooks whether to block a call. Another owner's lock still blocks the action.

Use `releaseCursor()` while holding a lock to let the cursor leave the game window. `blocksFocus()` keeps menus from grabbing it again until the user clicks inside the window or all input locks are released. Clicking while a menu is open allows focus to return when that menu closes.

## Config

`FeatureConfig` represents the settings of a feature and their interface metadata. Settings are defined in its subclass constructor:
Expand Down
8 changes: 7 additions & 1 deletion docs/zh-CN/IMPLEMENTATION.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- Source: docs/IMPLEMENTATION.md; Based on: 3c4d7c14a06f45298605bab87c2b6adeed866195 -->
<!-- Source: docs/IMPLEMENTATION.md; Based on: 4d64e449a6fbe6c418a8ae05077b7a2c30327a60 -->

# 12pit 实现

Expand Down Expand Up @@ -53,8 +53,14 @@ public void stop() {

`InputBlocker` 在 Minecraft 处理按键事件前分派事件。功能在启动时注册 `InputHandler`,停止时注销。`key(...)` 返回 true 表示已处理该事件。`consumesKey(...)` 指定输入阻止结束后不应恢复按住状态的按键。

输入被锁定时,只有锁的持有者会收到 `blockedKey(key, modifiers)`。使用 `Keybind.matches(binding, key, modifiers)` 按事件记录的修饰键状态匹配绑定。输入设备先将这些事件加入队列,再在 tick 开始时分派。回调在 LWJGL 轮询之外执行。

使用 `lock(owner)` 阻止键盘和鼠标输入。平台提供的 `InputDevice` 屏蔽 LWJGL 事件和轮询状态。开始阻止时,清除一次 Minecraft 的按键、移动和平滑视角状态。操作完成或取消时,调用 `unlock(owner, resume)`。每个操作只释放自己的锁。`blockedFor(owner)` 判断是否有其他操作持有锁。所有锁释放后才恢复输入。恢复输入后端前,丢弃缓冲事件。`resume` 为 true 且游戏有焦点时,恢复按住的按键状态。`held(key)` 读取当前 LWJGL 状态,阻止期间返回 false。

持有输入锁时,使用 `runAction(owner, action)` 执行右键或容器点击。`blocksActions()` 供交互钩子判断是否阻止调用。其他持有者的锁仍会阻止该操作。

持有锁时,使用 `releaseCursor()` 允许光标移出游戏窗口。`blocksFocus()` 防止菜单再次锁定光标,直到用户点击窗口内或所有输入锁释放。在菜单打开时点击,焦点会在菜单关闭后恢复。

## 配置

`FeatureConfig` 表示功能的设置及其界面元数据。在其子类构造器中定义设置:
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/pit12/bootstrap/ClientBootstrap.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
import pit12.feature.banalert.BanAlertFeature;
import pit12.feature.chatmacro.ChatMacroConfig;
import pit12.feature.chatmacro.ChatMacroFeature;
import pit12.feature.contractreroll.ContractRerollConfig;
import pit12.feature.contractreroll.ContractRerollFeature;
import pit12.feature.denick.DenickConfig;
import pit12.feature.denick.DenickFeature;
import pit12.feature.denick.DenickNamesBinding;
Expand Down Expand Up @@ -178,6 +180,8 @@ public ClientBootstrap() {
registerAutoSprint(configs);
registerChatMacro(minecraft, configs, session, sidebar, input, commands,
new File(minecraft.mcDataDir, "12pit/chat-macros.json").toPath());
registerContractReroll(minecraft, configs, session, pitContext, sidebar, input,
(MinecraftActions) minecraft);
registerNoJumpDelay(configs, (NoJumpDelayBinding) minecraft);
// Category: Render
registerEventList(configs, hudRegistry, commands);
Expand Down Expand Up @@ -309,6 +313,15 @@ private void registerChatMacro(Minecraft minecraft, ConfigCatalog configs,
input, commands, language, path));
}

private void registerContractReroll(Minecraft minecraft, ConfigCatalog configs,
ClientSession session, PitContext pitContext, SidebarCache sidebar, InputBlocker input,
MinecraftActions actions) {
ContractRerollConfig config = new ContractRerollConfig();
configs.register(config);
components.add(new ContractRerollFeature(minecraft, configs, config, session, pitContext,
sidebar, input, actions, language));
}

private void registerNoJumpDelay(ConfigCatalog configs, NoJumpDelayBinding binding) {
NoJumpDelayConfig config = new NoJumpDelayConfig();
configs.register(config);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* This file is part of 12pit.
*
* Copyright (C) 2026 The 12pit Authors and contributors <https://github.com/12src/12pit>
*
* 12pit is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 12pit is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 12pit. If not, see <https://www.gnu.org/licenses/>.
*/
package pit12.feature.contractreroll;

import static pit12.runtime.languages.Languages.source;

import org.lwjgl.input.Keyboard;
import pit12.runtime.config.BooleanSetting;
import pit12.runtime.config.ChoiceSetting;
import pit12.runtime.config.ChoiceSetting.Choice;
import pit12.runtime.config.ConfigCategory;
import pit12.runtime.config.FeatureConfig;
import pit12.runtime.config.KeybindSetting;

public final class ContractRerollConfig extends FeatureConfig {
final KeybindSetting key;
final BooleanSetting requireViles;
final ChoiceSetting kills;
final ChoiceSetting noArmor;
final ChoiceSetting fist;
final ChoiceSetting chain;
final ChoiceSetting gold;
final ChoiceSetting bow;
final ChoiceSetting noPerks;
final ChoiceSetting apples;

public ContractRerollConfig() {
super("contractreroll", source("Contract reroll"),
new ConfigCategory("player", source("Player")),
source("Rerolls contracts until one matches your filters."), false);
key = keybindSetting("key", source("Contract reroll key"),
source("Automatically rerolls contracts until one matches your filters."),
Keyboard.KEY_NONE);
requireViles = booleanSetting("require_viles", source("Require bonus viles"),
source("Only accepts matching contracts with a Chunk of Vile bonus."), false);
subsubcategory("tasks", source("Contract filters"));
kills = choiceSetting("kills", source("Kills"), "", 0, new Choice(0, source("Off")),
new Choice(15, source("At most 15")), new Choice(20, source("At most 20")));
noArmor = choiceSetting("no_armor", source("Kills without armor"), "", 0,
new Choice(0, source("Off")), new Choice(3, source("At most 3")),
new Choice(4, source("At most 4")));
fist = choiceSetting("fist", source("Fist kills"), "", 0, new Choice(0, source("Off")),
new Choice(1, source("At most 1")), new Choice(2, source("At most 2")));
chain = choiceSetting("chain", source("Multikills"), "", 0, new Choice(0, source("Off")),
new Choice(3, source("Up to Triple")), new Choice(4, source("Up to Quadra")));
gold = choiceSetting("gold", source("Ingots"), "", 0, new Choice(0, source("Off")),
new Choice(20, source("At most 20")), new Choice(25, source("At most 25")),
new Choice(30, source("At most 30")));
bow = choiceSetting("bow", source("Arrow hits"), "", 0, new Choice(0, source("Off")),
new Choice(100, source("At most 100")), new Choice(125, source("At most 125")),
new Choice(150, source("At most 150")));
noPerks = choiceSetting("no_perks", source("Kills without perks"), "", 0,
new Choice(0, source("Off")), new Choice(8, source("At most 8")),
new Choice(10, source("At most 10")));
apples = choiceSetting("apples", source("Golden apples"), "", 0,
new Choice(0, source("Off")), new Choice(12, source("At most 12")),
new Choice(16, source("At most 16")), new Choice(18, source("At most 18")));
}
}
Loading
Loading