v1.2.6: 自动攻击敌对生物(豁免铁傀儡/僵尸猪灵等中立生物) + 低耐久自动/u return回城保护
这个提交包含在:
@@ -14,6 +14,8 @@
|
|||||||
- 目标有效性校验:目标若已被其他邦国占领则立即跳过
|
- 目标有效性校验:目标若已被其他邦国占领则立即跳过
|
||||||
- 现实时间限制:仅 11:00-22:00(本地现实时间)允许开拓,其余时段启动会被拒绝、运行中到点自动停止
|
- 现实时间限制:仅 11:00-22:00(本地现实时间)允许开拓,其余时段启动会被拒绝、运行中到点自动停止
|
||||||
- 自动进食(饥饿时自动吃背包食物)
|
- 自动进食(饥饿时自动吃背包食物)
|
||||||
|
- 自动攻击敌对生物:扩地途中自动面向并攻击索敌范围内的敌对生物(满蓄力出手、不追击、不打断寻路);严格豁免不会主动攻击玩家的中立生物——铁傀儡、僵尸猪灵永不攻击,末影人(仅被激怒)、蜘蛛(仅黑暗中)、猪灵(仅玩家未穿金甲)按原版敌对条件反击,狼、蜜蜂、羊驼等其它中立生物一律不打
|
||||||
|
- 耐久保护回城:每 2 秒检查主手/副手/四件盔甲耐久,任意一件剩余耐久低于阈值(默认 15%,可在 GUI 调整)时自动发送 `/u return` 回城并停止扩地,防止装备被怪物磨没
|
||||||
- 实时地图 GUI(通过 Ctrl+R 打开)
|
- 实时地图 GUI(通过 Ctrl+R 打开)
|
||||||
- 监听聊天/字幕消息判断开垦结果
|
- 监听聊天/字幕消息判断开垦结果
|
||||||
- 刷新地图时与最新 `markers.json` 核对已占领区块,丢弃地图未确认的超期假占领,避免高地扩地残留空洞
|
- 刷新地图时与最新 `markers.json` 核对已占领区块,丢弃地图未确认的超期假占领,避免高地扩地残留空洞
|
||||||
@@ -26,7 +28,7 @@
|
|||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
1. 安装 Fabric Loader 0.16.14+ 和 Fabric API
|
1. 安装 Fabric Loader 0.16.14+ 和 Fabric API
|
||||||
2. 将 `autostore-1.2.5.jar` 放入 `.minecraft/mods/` 目录
|
2. 将 `autostore-1.2.6.jar` 放入 `.minecraft/mods/` 目录
|
||||||
3. 启动游戏,按 `Ctrl+R` 打开 GUI
|
3. 启动游戏,按 `Ctrl+R` 打开 GUI
|
||||||
|
|
||||||
## 构建
|
## 构建
|
||||||
@@ -36,7 +38,7 @@
|
|||||||
./build_mod.sh
|
./build_mod.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
输出文件:`autostore-1.2.5.jar`
|
输出文件:`autostore-1.2.6.jar`
|
||||||
|
|
||||||
## 赞助支持
|
## 赞助支持
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ yarn_mappings=1.21.4+build.8
|
|||||||
loader_version=0.19.3
|
loader_version=0.19.3
|
||||||
|
|
||||||
# Mod Properties
|
# Mod Properties
|
||||||
mod_version=1.2.5
|
mod_version=1.2.6
|
||||||
maven_group=autostore
|
maven_group=autostore
|
||||||
archives_base_name=autostore
|
archives_base_name=autostore
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,238 @@
|
|||||||
|
/*
|
||||||
|
* AutoStore 模组战斗辅助:自动攻击敌对生物与装备耐久保护。
|
||||||
|
*/
|
||||||
|
package autostore.client.combat;
|
||||||
|
|
||||||
|
import net.fabricmc.api.EnvType;
|
||||||
|
import net.fabricmc.api.Environment;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import net.minecraft.entity.EquipmentSlot;
|
||||||
|
import net.minecraft.entity.LivingEntity;
|
||||||
|
import net.minecraft.entity.mob.EndermanEntity;
|
||||||
|
import net.minecraft.entity.mob.Monster;
|
||||||
|
import net.minecraft.entity.mob.PiglinEntity;
|
||||||
|
import net.minecraft.entity.mob.SpiderEntity;
|
||||||
|
import net.minecraft.entity.mob.ZombifiedPiglinEntity;
|
||||||
|
import net.minecraft.entity.passive.IronGolemEntity;
|
||||||
|
import net.minecraft.entity.passive.TameableEntity;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.registry.Registries;
|
||||||
|
import net.minecraft.util.Hand;
|
||||||
|
import net.minecraft.util.math.Box;
|
||||||
|
import net.minecraft.util.math.MathHelper;
|
||||||
|
import net.minecraft.util.math.Vec3d;
|
||||||
|
|
||||||
|
@Environment(EnvType.CLIENT)
|
||||||
|
/**
|
||||||
|
* 战斗辅助工具类:自动攻击与耐久检查的纯函数集合。
|
||||||
|
*
|
||||||
|
* <p>目标筛选遵循一条核心原则:只攻击<b>会主动攻击玩家</b>的生物。
|
||||||
|
* 铁傀儡、僵尸猪灵等中立生物永远不在目标内;末影人/蜘蛛/猪灵等
|
||||||
|
* "条件敌对"生物只在确实处于主动敌对状态时才被攻击,判定依据
|
||||||
|
* 与原版 AI 一致且均可客户端验证(激怒标志/眼睛亮度/金盔甲)。</p>
|
||||||
|
*/
|
||||||
|
public final class AutoCombat {
|
||||||
|
|
||||||
|
/** 原版实体交互距离(格):视线到目标碰撞箱最近点超过该距离无法命中 */
|
||||||
|
private static final double ATTACK_RANGE_SQUARE = 3.0 * 3.0;
|
||||||
|
|
||||||
|
/** 耐久保护检查的装备槽位:主手、副手与四件盔甲 */
|
||||||
|
private static final EquipmentSlot[] GUARD_SLOTS = {
|
||||||
|
EquipmentSlot.MAINHAND, EquipmentSlot.OFFHAND,
|
||||||
|
EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET
|
||||||
|
};
|
||||||
|
|
||||||
|
private AutoCombat() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断实体是否为合法攻击目标。
|
||||||
|
*
|
||||||
|
* <p>判定规则(按优先级):
|
||||||
|
* <ul>
|
||||||
|
* <li>玩家、已驯服宠物:绝不攻击</li>
|
||||||
|
* <li>铁傀儡:不会主动攻击玩家的中立生物,绝不攻击</li>
|
||||||
|
* <li>僵尸猪灵:群体报复机制,绝不攻击</li>
|
||||||
|
* <li>末影人:仅被激怒(瞪视)时主动攻击,此时才反击</li>
|
||||||
|
* <li>蜘蛛:仅黑暗中(眼睛亮度 < 0.5,与原版索敌条件一致)主动攻击</li>
|
||||||
|
* <li>猪灵:玩家穿戴金盔甲时保持中立,未穿戴时才会攻击玩家</li>
|
||||||
|
* <li>其余敌对生物(僵尸/骷髅/苦力怕/卫道士/疣猪兽等):主动攻击</li>
|
||||||
|
* </ul></p>
|
||||||
|
*
|
||||||
|
* @param entity 待判定实体
|
||||||
|
* @param player 玩家自身
|
||||||
|
* @return 是否可作为攻击目标
|
||||||
|
*/
|
||||||
|
public static boolean isAttackTarget(Entity entity, PlayerEntity player) {
|
||||||
|
if (!(entity instanceof LivingEntity living)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (living == player || !living.isAlive() || living.isSpectator()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 绝不攻击玩家(含其他真实玩家)
|
||||||
|
if (living instanceof PlayerEntity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 已驯服的宠物不攻击
|
||||||
|
if (living instanceof TameableEntity tameable && tameable.isTamed()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 铁傀儡:不会主动攻击玩家的中立生物,绝不攻击
|
||||||
|
if (living instanceof IronGolemEntity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 僵尸猪灵:激怒一只引来一群,绝不攻击
|
||||||
|
if (living instanceof ZombifiedPiglinEntity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 末影人:仅被激怒(瞪视/先手攻击)时才主动敌对
|
||||||
|
if (living instanceof EndermanEntity enderman) {
|
||||||
|
return enderman.isAngry();
|
||||||
|
}
|
||||||
|
// 蜘蛛:与原版一致,仅黑暗中(眼睛亮度 < 0.5)才主动索敌
|
||||||
|
if (living instanceof SpiderEntity) {
|
||||||
|
return living.getBrightnessAtEyes() < 0.5f;
|
||||||
|
}
|
||||||
|
// 猪灵:玩家穿戴金盔甲时保持中立
|
||||||
|
if (living instanceof PiglinEntity) {
|
||||||
|
return !wearsGoldArmor(player);
|
||||||
|
}
|
||||||
|
// 其余敌对生物:直接攻击
|
||||||
|
return living instanceof Monster;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在索敌范围内查找最近的合法攻击目标(不追击,仅就地战斗)。
|
||||||
|
*
|
||||||
|
* @param client 客户端实例
|
||||||
|
* @param player 玩家
|
||||||
|
* @param scanRange 索敌半径(格)
|
||||||
|
* @return 最近的攻击目标,范围内没有时返回 null
|
||||||
|
*/
|
||||||
|
public static LivingEntity findTarget(MinecraftClient client, PlayerEntity player, double scanRange) {
|
||||||
|
if (client == null || client.world == null || player == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Box box = player.getBoundingBox().expand(scanRange);
|
||||||
|
LivingEntity best = null;
|
||||||
|
double bestDistance = Double.MAX_VALUE;
|
||||||
|
for (LivingEntity entity : client.world.getEntitiesByClass(LivingEntity.class, box,
|
||||||
|
candidate -> isAttackTarget(candidate, player))) {
|
||||||
|
double distance = eyeToBoxSquared(player.getEyePos(), entity.getBoundingBox());
|
||||||
|
if (distance < bestDistance) {
|
||||||
|
bestDistance = distance;
|
||||||
|
best = entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行一帧攻击:面向目标,在攻击距离内且攻击冷却充满时出手。
|
||||||
|
* 距离不够只面向不挥击(等 Baritone 寻路或目标靠近),不空挥不追击。
|
||||||
|
*
|
||||||
|
* @param client 客户端实例
|
||||||
|
* @param target 攻击目标
|
||||||
|
* @return 本帧是否实际发起了攻击
|
||||||
|
*/
|
||||||
|
public static boolean tickAttack(MinecraftClient client, LivingEntity target) {
|
||||||
|
PlayerEntity player = client == null ? null : client.player;
|
||||||
|
if (player == null || client.interactionManager == null || target == null || !target.isAlive()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 使用物品(如进食/举盾)时不攻击,避免打断
|
||||||
|
if (player.isSpectator() || player.isUsingItem()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 面向目标:原版服务端不校验朝向,但部分服务器反作弊会校验
|
||||||
|
faceEntity(player, target);
|
||||||
|
// 攻击距离校验:视线到目标碰撞箱最近点 ≤ 3 格(原版实体交互距离),够不到不空挥
|
||||||
|
if (eyeToBoxSquared(player.getEyePos(), target.getBoundingBox()) > ATTACK_RANGE_SQUARE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 攻击冷却充满才出手,保证每次都满蓄力
|
||||||
|
if (player.getAttackCooldownProgress(0.0f) < 1.0f) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
client.interactionManager.attackEntity(player, target);
|
||||||
|
player.swingHand(Hand.MAIN_HAND);
|
||||||
|
player.resetLastAttackedTicks();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查已装备物品(主手/副手/四件盔甲)中是否有耐久低于阈值的。
|
||||||
|
*
|
||||||
|
* @param player 玩家
|
||||||
|
* @param thresholdPercent 耐久阈值(剩余耐久百分比 ≤ 该值视为过低)
|
||||||
|
* @return 第一个耐久过低的装备槽位,全部正常时返回 null
|
||||||
|
*/
|
||||||
|
public static EquipmentSlot findLowDurabilitySlot(PlayerEntity player, int thresholdPercent) {
|
||||||
|
if (player == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int threshold = Math.max(1, thresholdPercent);
|
||||||
|
for (EquipmentSlot slot : GUARD_SLOTS) {
|
||||||
|
ItemStack stack = player.getEquippedStack(slot);
|
||||||
|
if (stack.isEmpty() || !stack.isDamageable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int maxDamage = stack.getMaxDamage();
|
||||||
|
int remaining = maxDamage - stack.getDamage();
|
||||||
|
// 用整数乘法避免浮点误差:剩余百分比 ≤ 阈值
|
||||||
|
if (remaining * 100 <= threshold * maxDamage) {
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 返回装备槽位的中文名称(用于日志与聊天提示) */
|
||||||
|
public static String slotName(EquipmentSlot slot) {
|
||||||
|
return switch (slot) {
|
||||||
|
case MAINHAND -> "主手";
|
||||||
|
case OFFHAND -> "副手";
|
||||||
|
case HEAD -> "头盔";
|
||||||
|
case CHEST -> "胸甲";
|
||||||
|
case LEGS -> "护腿";
|
||||||
|
case FEET -> "靴子";
|
||||||
|
case BODY -> "动物盔甲";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 让玩家面向目标(转向目标中躯位置) */
|
||||||
|
private static void faceEntity(PlayerEntity player, LivingEntity target) {
|
||||||
|
Vec3d eye = player.getEyePos();
|
||||||
|
Vec3d aim = target.getPos().add(0.0, target.getHeight() * 0.5, 0.0);
|
||||||
|
double dx = aim.x - eye.x;
|
||||||
|
double dy = aim.y - eye.y;
|
||||||
|
double dz = aim.z - eye.z;
|
||||||
|
double horizontal = Math.sqrt(dx * dx + dz * dz);
|
||||||
|
float yaw = (float) (Math.toDegrees(Math.atan2(dz, dx)) - 90.0);
|
||||||
|
float pitch = (float) (-Math.toDegrees(Math.atan2(dy, horizontal)));
|
||||||
|
player.setYaw(yaw);
|
||||||
|
player.setPitch(MathHelper.clamp(pitch, -90.0f, 90.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 计算视线点到包围盒最近点的距离平方(用于原版攻击距离判定) */
|
||||||
|
private static double eyeToBoxSquared(Vec3d eye, Box box) {
|
||||||
|
double dx = Math.max(Math.max(box.minX - eye.x, eye.x - box.maxX), 0.0);
|
||||||
|
double dy = Math.max(Math.max(box.minY - eye.y, eye.y - box.maxY), 0.0);
|
||||||
|
double dz = Math.max(Math.max(box.minZ - eye.z, eye.z - box.maxZ), 0.0);
|
||||||
|
return dx * dx + dy * dy + dz * dz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 判断玩家四件盔甲中是否至少一件为金质(金盔甲可使猪灵保持中立) */
|
||||||
|
private static boolean wearsGoldArmor(PlayerEntity player) {
|
||||||
|
for (EquipmentSlot slot : new EquipmentSlot[]{EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET}) {
|
||||||
|
ItemStack stack = player.getEquippedStack(slot);
|
||||||
|
if (!stack.isEmpty() && Registries.ITEM.getId(stack.getItem()).getPath().contains("golden")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,10 @@ public final class ConfigStore {
|
|||||||
// 注意:maxRings 由 /autostore rings 命令持久化,不得在此覆盖,否则命令设置会在重启后失效
|
// 注意:maxRings 由 /autostore rings 命令持久化,不得在此覆盖,否则命令设置会在重启后失效
|
||||||
loaded.autoEat = true;
|
loaded.autoEat = true;
|
||||||
loaded.eatThreshold = 14;
|
loaded.eatThreshold = 14;
|
||||||
|
// 耐久保护阈值钳制到合理区间,防止异常值导致永不回城或刚出门就回城
|
||||||
|
if (loaded.durabilityThreshold < 1 || loaded.durabilityThreshold > 99) {
|
||||||
|
loaded.durabilityThreshold = 15;
|
||||||
|
}
|
||||||
return loaded;
|
return loaded;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 解析失败时回退到默认配置
|
// 解析失败时回退到默认配置
|
||||||
|
|||||||
@@ -91,6 +91,15 @@ public class ExpansionConfig {
|
|||||||
/** 触发自动进食的饥饿值阈值(0-20) */
|
/** 触发自动进食的饥饿值阈值(0-20) */
|
||||||
public int eatThreshold = 14;
|
public int eatThreshold = 14;
|
||||||
|
|
||||||
|
/** 是否启用自动攻击:扩地途中自动面向并攻击范围内的敌对生物(铁傀儡/僵尸猪灵等中立生物永不攻击) */
|
||||||
|
public boolean autoAttack = true;
|
||||||
|
|
||||||
|
/** 是否启用耐久保护:装备耐久过低时发送 /u return 回城并停止扩地,防止装备被怪物磨没 */
|
||||||
|
public boolean durabilityGuard = true;
|
||||||
|
|
||||||
|
/** 耐久保护阈值:主手/副手/盔甲任意一件剩余耐久百分比 ≤ 该值时触发回城(1-99,默认 15) */
|
||||||
|
public int durabilityThreshold = 15;
|
||||||
|
|
||||||
/** 扩圈上限(0 表示不限制) */
|
/** 扩圈上限(0 表示不限制) */
|
||||||
public int maxRings;
|
public int maxRings;
|
||||||
|
|
||||||
@@ -129,6 +138,9 @@ public class ExpansionConfig {
|
|||||||
c.avoidMobs = this.avoidMobs;
|
c.avoidMobs = this.avoidMobs;
|
||||||
c.autoEat = this.autoEat;
|
c.autoEat = this.autoEat;
|
||||||
c.eatThreshold = this.eatThreshold;
|
c.eatThreshold = this.eatThreshold;
|
||||||
|
c.autoAttack = this.autoAttack;
|
||||||
|
c.durabilityGuard = this.durabilityGuard;
|
||||||
|
c.durabilityThreshold = this.durabilityThreshold;
|
||||||
c.maxRings = this.maxRings;
|
c.maxRings = this.maxRings;
|
||||||
c.refreshIntervalSeconds = this.refreshIntervalSeconds;
|
c.refreshIntervalSeconds = this.refreshIntervalSeconds;
|
||||||
return c;
|
return c;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
package autostore.client.controller;
|
package autostore.client.controller;
|
||||||
|
|
||||||
import autostore.client.bridge.BaritoneBridge;
|
import autostore.client.bridge.BaritoneBridge;
|
||||||
|
import autostore.client.combat.AutoCombat;
|
||||||
import autostore.client.config.ExpansionConfig;
|
import autostore.client.config.ExpansionConfig;
|
||||||
import autostore.client.monitor.SubtitleChatMonitor;
|
import autostore.client.monitor.SubtitleChatMonitor;
|
||||||
import autostore.client.planner.ExpansionPlanner;
|
import autostore.client.planner.ExpansionPlanner;
|
||||||
@@ -29,6 +30,8 @@ import net.fabricmc.api.EnvType;
|
|||||||
import net.fabricmc.api.Environment;
|
import net.fabricmc.api.Environment;
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
import net.minecraft.util.Hand;
|
import net.minecraft.util.Hand;
|
||||||
|
import net.minecraft.entity.EquipmentSlot;
|
||||||
|
import net.minecraft.entity.LivingEntity;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
import net.minecraft.entity.player.PlayerInventory;
|
import net.minecraft.entity.player.PlayerInventory;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
@@ -44,7 +47,8 @@ import net.minecraft.text.Text;
|
|||||||
*
|
*
|
||||||
* <p>工作流程:规划(PLANNING)→ 寻路(PATHING)→ 等待开垦(WAITING)→ 成功/失败(SUCCESS/FAIL)→ 下一个目标(NEXT)→ 完成(COMPLETE)。
|
* <p>工作流程:规划(PLANNING)→ 寻路(PATHING)→ 等待开垦(WAITING)→ 成功/失败(SUCCESS/FAIL)→ 下一个目标(NEXT)→ 完成(COMPLETE)。
|
||||||
* 每帧由 {@link #tick()} 驱动,通过 Baritone 自动寻路到目标区块,
|
* 每帧由 {@link #tick()} 驱动,通过 Baritone 自动寻路到目标区块,
|
||||||
* 并监听聊天/字幕消息判断开垦是否成功。支持自动进食、超时重试、手动停止等功能。
|
* 并监听聊天/字幕消息判断开垦是否成功。支持自动进食、自动攻击敌对生物、
|
||||||
|
* 低耐久自动回城、超时重试、手动停止等功能。
|
||||||
*/
|
*/
|
||||||
public final class ExpansionController
|
public final class ExpansionController
|
||||||
implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
||||||
@@ -56,6 +60,10 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
private static final int CLAIM_START_HOUR = 11;
|
private static final int CLAIM_START_HOUR = 11;
|
||||||
/** 允许开拓的现实时间结束小时(不含,本地时区),即 22:00 起停止开拓 */
|
/** 允许开拓的现实时间结束小时(不含,本地时区),即 22:00 起停止开拓 */
|
||||||
private static final int CLAIM_END_HOUR = 22;
|
private static final int CLAIM_END_HOUR = 22;
|
||||||
|
/** 自动攻击索敌半径(格):只攻击进入该范围的敌对生物,不追击,避免打断扩地寻路 */
|
||||||
|
private static final double AUTO_ATTACK_SCAN_RANGE = 4.5;
|
||||||
|
/** 耐久保护检查间隔(tick),40 tick ≈ 2 秒 */
|
||||||
|
private static final int DURABILITY_CHECK_INTERVAL_TICKS = 40;
|
||||||
/** Baritone 寻路桥接(负责自动移动) */
|
/** Baritone 寻路桥接(负责自动移动) */
|
||||||
private final BaritoneBridge baritoneBridge = new BaritoneBridge();
|
private final BaritoneBridge baritoneBridge = new BaritoneBridge();
|
||||||
/** 扩地规划器(决定下一个目标区块) */
|
/** 扩地规划器(决定下一个目标区块) */
|
||||||
@@ -112,6 +120,12 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
private long sideStepSinceMillis;
|
private long sideStepSinceMillis;
|
||||||
/** 边走两步开始时的玩家方块坐标(用于判断是否已走开约 2.5 格) */
|
/** 边走两步开始时的玩家方块坐标(用于判断是否已走开约 2.5 格) */
|
||||||
private BlockPos sideStepStartPos;
|
private BlockPos sideStepStartPos;
|
||||||
|
/** 耐久保护检查计数器:每 {@link #DURABILITY_CHECK_INTERVAL_TICKS} 帧检查一次装备耐久 */
|
||||||
|
private int durabilityCheckCounter;
|
||||||
|
/** 本次回城是否由耐久保护触发:RETURNING 等待结束后停止任务(等待玩家修复装备),而非重新规划 */
|
||||||
|
private boolean returnThenStop;
|
||||||
|
/** 自动攻击当前目标的实体 ID(-1 为无目标),仅用于避免重复记录战斗日志 */
|
||||||
|
private int combatTargetEntityId = -1;
|
||||||
|
|
||||||
public ExpansionController(TerritoryDataService dataService) {
|
public ExpansionController(TerritoryDataService dataService) {
|
||||||
this.dataService = dataService;
|
this.dataService = dataService;
|
||||||
@@ -231,6 +245,9 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
this.consecutiveTimeoutMillis = 0L;
|
this.consecutiveTimeoutMillis = 0L;
|
||||||
this.sideStepCount = 0;
|
this.sideStepCount = 0;
|
||||||
this.sideStepping = false;
|
this.sideStepping = false;
|
||||||
|
this.returnThenStop = false;
|
||||||
|
this.durabilityCheckCounter = 0;
|
||||||
|
this.combatTargetEntityId = -1;
|
||||||
this.queue.clear();
|
this.queue.clear();
|
||||||
this.claimedSet.clear();
|
this.claimedSet.clear();
|
||||||
this.target = null;
|
this.target = null;
|
||||||
@@ -265,8 +282,8 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 每帧驱动逻辑(由客户端 tick 事件调用):
|
* 每帧驱动逻辑(由客户端 tick 事件调用):
|
||||||
* 更新玩家区块,优先处理自动进食,再按当前状态分发到对应的 tick 方法。
|
* 更新玩家区块,依次处理自动进食、耐久保护、自动游泳、自动攻击,
|
||||||
* 若已退出世界则自动停止任务。
|
* 再按当前状态分发到对应的 tick 方法。若已退出世界则自动停止任务。
|
||||||
*/
|
*/
|
||||||
public void tick() {
|
public void tick() {
|
||||||
MinecraftClient client = MinecraftClient.getInstance();
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
@@ -296,12 +313,18 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
if (this.tickAutoFood(client)) {
|
if (this.tickAutoFood(client)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 耐久保护优先于一切寻路/战斗:装备耐久过低时先回城,防止装备被怪物磨没
|
||||||
|
if (this.tickDurabilityGuard(client)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.tickAutoSwim(client);
|
this.tickAutoSwim(client);
|
||||||
// 处于"往边上走两步"阶段:每帧驱动手动横向行走,走开后自动重试原目标
|
// 处于"往边上走两步"阶段:每帧驱动手动横向行走,走开后自动重试原目标(该阶段不自动攻击,避免抢方向键)
|
||||||
if (this.sideStepping) {
|
if (this.sideStepping) {
|
||||||
this.tickSideStep();
|
this.tickSideStep();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 自动攻击:面向并攻击范围内的敌对生物(不追击,不打断寻路状态机)
|
||||||
|
this.tickAutoAttack(client);
|
||||||
switch (this.state) {
|
switch (this.state) {
|
||||||
case PLANNING: {
|
case PLANNING: {
|
||||||
this.plan();
|
this.plan();
|
||||||
@@ -452,36 +475,48 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 连续寻路超时达到阈值时触发回城:
|
* 发送 /u return 回城:取消当前寻路,向服务器发送回城指令(约 10 秒后自动传送回安全位置),
|
||||||
* 取消当前寻路,向服务器发送 /u return 指令(约 10 秒后自动传送回可寻路的安全位置),
|
* 随后进入 RETURNING 状态等待传送完成。
|
||||||
* 随后进入 RETURNING 状态等待传送完成,传送后由 {@link #tickReturning()} 重新规划并从头开始。
|
*
|
||||||
|
* <p>传送完成后的走向由 {@link #returnThenStop} 决定:
|
||||||
|
* 寻路超时回城会重新规划继续扩地;耐久保护回城则停止任务,等待玩家修复装备后手动重新开始。</p>
|
||||||
|
*
|
||||||
|
* @param reason 触发回城的原因(写入日志与聊天提示)
|
||||||
*/
|
*/
|
||||||
private void sendReturnCommand() {
|
private void sendReturnCommand(String reason) {
|
||||||
this.baritoneBridge.cancel();
|
this.baritoneBridge.cancel();
|
||||||
MinecraftClient client = MinecraftClient.getInstance();
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
if (client != null && client.player != null) {
|
if (client != null && client.player != null) {
|
||||||
this.log("\u8fde\u7eed\u5bfb\u8def\u8d85\u65f6\u8d85\u8fc7 " + this.config.returnThresholdSeconds + " \u79d2\uff0c\u53d1\u9001 /u return \u56de\u57ce\uff0c10 \u79d2\u540e\u81ea\u52a8\u4f20\u9001");
|
this.log(reason + ",发送 /u return 回城,10 秒后自动传送");
|
||||||
// 通过玩家网络处理器发送聊天指令 /u return(Yarn 映射:sendChatCommand 自动带上前缀斜杠)
|
// 通过玩家网络处理器发送聊天指令 /u return(Yarn 映射:sendChatCommand 自动带上前缀斜杠)
|
||||||
client.player.networkHandler.sendChatCommand("u return");
|
client.player.networkHandler.sendChatCommand("u return");
|
||||||
client.player.sendMessage(Text.literal("[AutoStore] \u8fde\u7eed\u5bfb\u8def\u8d85\u65f6\uff0c\u5df2\u53d1\u9001 /u return\uff0c\u7b49\u5f85\u4f20\u9001\u540e\u91cd\u65b0\u6269\u5730"), false);
|
client.player.sendMessage(Text.literal("[AutoStore] " + reason + ",已发送 /u return,传送后"
|
||||||
|
+ (this.returnThenStop ? "停止扩地" : "重新扩地")), false);
|
||||||
} else {
|
} else {
|
||||||
this.log("\u8fde\u7eed\u5bfb\u8def\u8d85\u65f6\uff0c\u4f46\u73a9\u5bb6\u4e0d\u5728\u4e16\u754c\uff0c\u8df3\u8fc7");
|
this.log(reason + ",但玩家不在世界,跳过");
|
||||||
}
|
}
|
||||||
this.returnStartMillis = System.currentTimeMillis();
|
this.returnStartMillis = System.currentTimeMillis();
|
||||||
this.setState(State.RETURNING, "\u7b49\u5f85\u56de\u57ce\u4f20\u9001\uff0810 \u79d2\u540e\u81ea\u52a8\u4f20\u9001\uff09");
|
this.setState(State.RETURNING, "等待回城传送(10 秒后自动传送)");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 回城传送等待阶段每帧逻辑:
|
* 回城传送等待阶段每帧逻辑:
|
||||||
* 发送 /u return 后服务器约 10 秒把玩家传送回安全位置。等待 {@code returnWaitSeconds}
|
* 发送 /u return 后服务器约 10 秒把玩家传送回安全位置。
|
||||||
* 秒(覆盖传送延迟并留余量)后,清零连续超时计数并重新规划扩地计划,
|
* 等待 {@code returnWaitSeconds} 秒(覆盖传送延迟并留余量)后:
|
||||||
* 从最高优先级区块(队首)重新开始寻路。
|
* 耐久保护触发的回城 → 停止任务并提示修复装备;
|
||||||
|
* 寻路超时触发的回城 → 清零连续超时计数并重新规划扩地计划,从最高优先级区块重新开始。
|
||||||
*/
|
*/
|
||||||
private void tickReturning() {
|
private void tickReturning() {
|
||||||
long elapsedMillis = System.currentTimeMillis() - this.returnStartMillis;
|
long elapsedMillis = System.currentTimeMillis() - this.returnStartMillis;
|
||||||
if (elapsedMillis < (long)this.config.returnWaitSeconds * 1000L) {
|
if (elapsedMillis < (long)this.config.returnWaitSeconds * 1000L) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this.returnThenStop) {
|
||||||
|
this.returnThenStop = false;
|
||||||
|
this.log("耐久保护回城完成,任务已停止;请修复/更换装备后重新开始扩地");
|
||||||
|
this.setState(State.STOPPED, "装备耐久过低已回城,请修复装备后重新开始");
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.consecutiveTimeoutMillis = 0L;
|
this.consecutiveTimeoutMillis = 0L;
|
||||||
this.log("\u56de\u57ce\u4f20\u9001\u5b8c\u6210\uff0c\u91cd\u65b0\u89c4\u5212\u5e76\u4ece\u6700\u9ad8\u4f18\u5148\u7ea7\u533a\u5757\u5f00\u59cb");
|
this.log("\u56de\u57ce\u4f20\u9001\u5b8c\u6210\uff0c\u91cd\u65b0\u89c4\u5212\u5e76\u4ece\u6700\u9ad8\u4f18\u5148\u7ea7\u533a\u5757\u5f00\u59cb");
|
||||||
// 重新规划:清空队列重新生成计划,从队首(最高优先级)区块开始寻路
|
// 重新规划:清空队列重新生成计划,从队首(最高优先级)区块开始寻路
|
||||||
@@ -564,7 +599,7 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
*/
|
*/
|
||||||
private void giveUpTarget(String reason) {
|
private void giveUpTarget(String reason) {
|
||||||
if (this.consecutiveTimeoutMillis >= (long)this.config.returnThresholdSeconds * 1000L) {
|
if (this.consecutiveTimeoutMillis >= (long)this.config.returnThresholdSeconds * 1000L) {
|
||||||
this.sendReturnCommand();
|
this.sendReturnCommand("连续寻路超时超过 " + this.config.returnThresholdSeconds + " 秒");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.target != null) {
|
if (this.target != null) {
|
||||||
@@ -868,6 +903,57 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动攻击逻辑(每帧调用):
|
||||||
|
* 扩地途中索敌半径内出现敌对生物时,面向该生物并在攻击距离内自动出手(满蓄力),
|
||||||
|
* 不追击、不打断 Baritone 寻路;目标筛选由 {@link AutoCombat#isAttackTarget} 保证
|
||||||
|
* 绝不攻击铁傀儡、僵尸猪灵等不会主动攻击玩家的中立生物。
|
||||||
|
*/
|
||||||
|
private void tickAutoAttack(MinecraftClient client) {
|
||||||
|
if (!this.config.autoAttack || !this.isRunning()) {
|
||||||
|
this.combatTargetEntityId = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LivingEntity target = AutoCombat.findTarget(client, client.player, AUTO_ATTACK_SCAN_RANGE);
|
||||||
|
if (target == null) {
|
||||||
|
this.combatTargetEntityId = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 仅在新目标出现时记录一次日志,避免每帧刷屏
|
||||||
|
if (target.getId() != this.combatTargetEntityId) {
|
||||||
|
this.combatTargetEntityId = target.getId();
|
||||||
|
this.log("发现敌对生物 " + target.getDisplayName().getString() + ",自动攻击中");
|
||||||
|
}
|
||||||
|
AutoCombat.tickAttack(client, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 耐久保护逻辑(每 {@link #DURABILITY_CHECK_INTERVAL_TICKS} 帧调用):
|
||||||
|
* 主手/副手/四件盔甲任意一件剩余耐久低于阈值时,发送 /u return 回城并停止扩地,
|
||||||
|
* 防止装备在继续战斗中被怪物磨没。回城传送完成后由 {@link #tickReturning()} 停止任务。
|
||||||
|
*
|
||||||
|
* @return true 表示本帧已触发回城(进入 RETURNING 状态)
|
||||||
|
*/
|
||||||
|
private boolean tickDurabilityGuard(MinecraftClient client) {
|
||||||
|
if (!this.config.durabilityGuard || !this.isRunning() || this.state == State.RETURNING) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (++this.durabilityCheckCounter < DURABILITY_CHECK_INTERVAL_TICKS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.durabilityCheckCounter = 0;
|
||||||
|
EquipmentSlot low = AutoCombat.findLowDurabilitySlot(client.player, this.config.durabilityThreshold);
|
||||||
|
if (low == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String reason = "装备耐久过低(" + AutoCombat.slotName(low)
|
||||||
|
+ " 剩余耐久≤" + this.config.durabilityThreshold + "%)";
|
||||||
|
this.returnThenStop = true;
|
||||||
|
this.log(reason + ",发送 /u return 回城并停止扩地,防止装备被怪物磨没");
|
||||||
|
this.sendReturnCommand(reason);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动进食逻辑(每帧调用):
|
* 自动进食逻辑(每帧调用):
|
||||||
* 开启 autoEat 且饥饿值低于阈值时暂停寻路并自动吃背包里的食物,
|
* 开启 autoEat 且饥饿值低于阈值时暂停寻路并自动吃背包里的食物,
|
||||||
|
|||||||
@@ -50,9 +50,12 @@ extends Screen {
|
|||||||
// ---- 输入控件 ----
|
// ---- 输入控件 ----
|
||||||
private TextFieldWidget stateField;
|
private TextFieldWidget stateField;
|
||||||
private TextFieldWidget targetCountField;
|
private TextFieldWidget targetCountField;
|
||||||
|
private TextFieldWidget durabilityField;
|
||||||
private CheckboxWidget sprintBox;
|
private CheckboxWidget sprintBox;
|
||||||
private CheckboxWidget jumpBox;
|
private CheckboxWidget jumpBox;
|
||||||
private CheckboxWidget avoidBox;
|
private CheckboxWidget avoidBox;
|
||||||
|
private CheckboxWidget attackBox;
|
||||||
|
private CheckboxWidget durabilityGuardBox;
|
||||||
private CheckboxWidget breakBox;
|
private CheckboxWidget breakBox;
|
||||||
private CheckboxWidget placeBox;
|
private CheckboxWidget placeBox;
|
||||||
private ButtonWidget refreshButton;
|
private ButtonWidget refreshButton;
|
||||||
@@ -142,9 +145,10 @@ extends Screen {
|
|||||||
this.addDrawableChild(this.strategyButton("\u7ed5\u56fd\u4e00\u5708", 4, left + 96, strategyY + 24, 90));
|
this.addDrawableChild(this.strategyButton("\u7ed5\u56fd\u4e00\u5708", 4, left + 96, strategyY + 24, 90));
|
||||||
|
|
||||||
this.advancedY = strategyY + 24 + 20 + 8;
|
this.advancedY = strategyY + 24 + 20 + 8;
|
||||||
// ===== 只保留五个权限选择框:疾跑、跳跃、避怪、破坏、放置 =====
|
// ===== 权限/辅助选择框:疾跑、跳跃、避怪、攻击、破坏、放置、耐久回城 =====
|
||||||
int checkY1 = this.advancedY + 14;
|
int checkY1 = this.advancedY + 14;
|
||||||
int checkY2 = checkY1 + advCheckGap;
|
int checkY2 = checkY1 + advCheckGap;
|
||||||
|
int checkY3 = checkY2 + advCheckGap;
|
||||||
int checkX = left;
|
int checkX = left;
|
||||||
this.sprintBox = this.checkbox("\u75be\u8dd1", checkX, checkY1, this.config.allowSprint, value -> {
|
this.sprintBox = this.checkbox("\u75be\u8dd1", checkX, checkY1, this.config.allowSprint, value -> {
|
||||||
this.config.allowSprint = value;
|
this.config.allowSprint = value;
|
||||||
@@ -155,17 +159,29 @@ extends Screen {
|
|||||||
this.avoidBox = this.checkbox("\u907f\u602a", checkX + 160, checkY1, this.config.avoidMobs, value -> {
|
this.avoidBox = this.checkbox("\u907f\u602a", checkX + 160, checkY1, this.config.avoidMobs, value -> {
|
||||||
this.config.avoidMobs = value;
|
this.config.avoidMobs = value;
|
||||||
});
|
});
|
||||||
this.breakBox = this.checkbox("\u7834\u574f", checkX, checkY2, this.config.allowBreak, value -> {
|
this.attackBox = this.checkbox("\u653b\u51fb", checkX, checkY2, this.config.autoAttack, value -> {
|
||||||
|
this.config.autoAttack = value;
|
||||||
|
});
|
||||||
|
this.breakBox = this.checkbox("\u7834\u574f", checkX + 88, checkY2, this.config.allowBreak, value -> {
|
||||||
this.config.allowBreak = value;
|
this.config.allowBreak = value;
|
||||||
});
|
});
|
||||||
this.placeBox = this.checkbox("\u653e\u7f6e", checkX + 88, checkY2, this.config.allowPlace, value -> {
|
this.placeBox = this.checkbox("\u653e\u7f6e", checkX + 160, checkY2, this.config.allowPlace, value -> {
|
||||||
this.config.allowPlace = value;
|
this.config.allowPlace = value;
|
||||||
});
|
});
|
||||||
|
// 第三行:耐久保护开关 + 回城阈值输入框(剩余耐久百分比,范围 1-99)
|
||||||
|
this.durabilityGuardBox = this.checkbox("\u8010\u4e45\u56de\u57ce", checkX, checkY3, this.config.durabilityGuard, value -> {
|
||||||
|
this.config.durabilityGuard = value;
|
||||||
|
});
|
||||||
|
this.durabilityField = this.numberField(this.config.durabilityThreshold, checkX + 96, checkY3, 34);
|
||||||
|
this.durabilityField.setMaxLength(2);
|
||||||
this.addDrawableChild(this.sprintBox);
|
this.addDrawableChild(this.sprintBox);
|
||||||
this.addDrawableChild(this.jumpBox);
|
this.addDrawableChild(this.jumpBox);
|
||||||
this.addDrawableChild(this.avoidBox);
|
this.addDrawableChild(this.avoidBox);
|
||||||
|
this.addDrawableChild(this.attackBox);
|
||||||
this.addDrawableChild(this.breakBox);
|
this.addDrawableChild(this.breakBox);
|
||||||
this.addDrawableChild(this.placeBox);
|
this.addDrawableChild(this.placeBox);
|
||||||
|
this.addDrawableChild(this.durabilityGuardBox);
|
||||||
|
this.addDrawableChild(this.durabilityField);
|
||||||
this.initializeMapView();
|
this.initializeMapView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,6 +492,12 @@ extends Screen {
|
|||||||
context.drawTextWithShadow(this.textRenderer, "\u90a6\u56fd", this.panelX + 4, this.stateFieldY + 4, -5592406);
|
context.drawTextWithShadow(this.textRenderer, "\u90a6\u56fd", this.panelX + 4, this.stateFieldY + 4, -5592406);
|
||||||
context.drawTextWithShadow(this.textRenderer, "\u76ee\u6807\u6570\u91cf", this.panelX + 4, this.targetFieldY + 4, -5592406);
|
context.drawTextWithShadow(this.textRenderer, "\u76ee\u6807\u6570\u91cf", this.panelX + 4, this.targetFieldY + 4, -5592406);
|
||||||
context.drawTextWithShadow(this.textRenderer, "\u6743\u9650\u8bbe\u7f6e", this.panelX + 4, this.advancedY, -5592406);
|
context.drawTextWithShadow(this.textRenderer, "\u6743\u9650\u8bbe\u7f6e", this.panelX + 4, this.advancedY, -5592406);
|
||||||
|
// 耐久回城阈值输入框的单位标签(位于输入框右侧)
|
||||||
|
if (this.durabilityField != null) {
|
||||||
|
context.drawTextWithShadow(this.textRenderer, "\u8010\u4e45%",
|
||||||
|
this.durabilityField.getX() + this.durabilityField.getWidth() + 4,
|
||||||
|
this.durabilityField.getY() + 6, -5592406);
|
||||||
|
}
|
||||||
|
|
||||||
// ===== 底部信息面板:日志(左)+ 状态(右),均单行截断,避免换行导致的重叠 =====
|
// ===== 底部信息面板:日志(左)+ 状态(右),均单行截断,避免换行导致的重叠 =====
|
||||||
int infoTop = this.mapY + this.mapH + 10;
|
int infoTop = this.mapY + this.mapH + 10;
|
||||||
@@ -572,8 +594,13 @@ extends Screen {
|
|||||||
this.config.allowSprint = this.sprintBox.isChecked();
|
this.config.allowSprint = this.sprintBox.isChecked();
|
||||||
this.config.allowJump = this.jumpBox.isChecked();
|
this.config.allowJump = this.jumpBox.isChecked();
|
||||||
this.config.avoidMobs = this.avoidBox.isChecked();
|
this.config.avoidMobs = this.avoidBox.isChecked();
|
||||||
|
this.config.autoAttack = this.attackBox.isChecked();
|
||||||
this.config.allowBreak = this.breakBox.isChecked();
|
this.config.allowBreak = this.breakBox.isChecked();
|
||||||
this.config.allowPlace = this.placeBox.isChecked();
|
this.config.allowPlace = this.placeBox.isChecked();
|
||||||
|
this.config.durabilityGuard = this.durabilityGuardBox.isChecked();
|
||||||
|
int threshold = this.parseInt(this.durabilityField.getText(), 15);
|
||||||
|
// 钳制阈值到 1-99,避免 0(永不触发)或 100(刚装备就回城)
|
||||||
|
this.config.durabilityThreshold = Math.max(1, Math.min(99, threshold));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 将本地配置副本同步到 mod 全局配置并保存到磁盘 */
|
/** 将本地配置副本同步到 mod 全局配置并保存到磁盘 */
|
||||||
@@ -585,8 +612,11 @@ extends Screen {
|
|||||||
target.allowSprint = this.config.allowSprint;
|
target.allowSprint = this.config.allowSprint;
|
||||||
target.allowJump = this.config.allowJump;
|
target.allowJump = this.config.allowJump;
|
||||||
target.avoidMobs = this.config.avoidMobs;
|
target.avoidMobs = this.config.avoidMobs;
|
||||||
|
target.autoAttack = this.config.autoAttack;
|
||||||
target.allowBreak = this.config.allowBreak;
|
target.allowBreak = this.config.allowBreak;
|
||||||
target.allowPlace = this.config.allowPlace;
|
target.allowPlace = this.config.allowPlace;
|
||||||
|
target.durabilityGuard = this.config.durabilityGuard;
|
||||||
|
target.durabilityThreshold = this.config.durabilityThreshold;
|
||||||
// 同步矩形圈选坐标到全局配置(修复:之前忘记同步,导致区域扩地总是为空)
|
// 同步矩形圈选坐标到全局配置(修复:之前忘记同步,导致区域扩地总是为空)
|
||||||
target.regionMinX = this.config.regionMinX;
|
target.regionMinX = this.config.regionMinX;
|
||||||
target.regionMaxX = this.config.regionMaxX;
|
target.regionMaxX = this.config.regionMaxX;
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户