优化了伐木任务的异常占用

为伐木任务的各个行为添加了额外的配置选项
This commit is contained in:
xypp
2025-07-28 02:04:51 +08:00
parent 2b6f231efc
commit ee3cb071d7
13 changed files with 152 additions and 55 deletions

View File

@@ -5,8 +5,6 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.config.ModConfigEvent;
import java.util.List;
// An example config class. This is not required, but it's a good idea to have one to keep your config organized.
// Demonstrates how to use Forge's config APIs
@Mod.EventBusSubscriber(modid = MaidUsefulTask.MODID, bus = Mod.EventBusSubscriber.Bus.MOD)
@@ -25,6 +23,9 @@ public class Config {
private static final ForgeConfigSpec.BooleanValue ENABLE_REVIVE_TOTEM = BUILDER
.define("revive.totem", true);
private static final ForgeConfigSpec.BooleanValue LOGGING_DISABLE_BLOCKUP = BUILDER
.define("logging.disable_blockup", false);
private static final ForgeConfigSpec.BooleanValue ENABLE_VEHICLE_CONTROL_FULL = BUILDER
.define("vehicle_control.full", true);
private static final ForgeConfigSpec.BooleanValue ENABLE_VEHICLE_CONTROL_ROTATE = BUILDER
@@ -41,6 +42,9 @@ public class Config {
public static boolean enableVehicleControlFull = false;
public static boolean enableVehicleControlRotate = false;
public static boolean disableLoggingBlockUp = false;
@SubscribeEvent
static void onLoad(final ModConfigEvent event) {
enableLoggingTask = ENABLE_LOGGING.get();
@@ -50,5 +54,6 @@ public class Config {
enableReviveTotem = ENABLE_REVIVE_TOTEM.get();
enableVehicleControlFull = ENABLE_VEHICLE_CONTROL_FULL.get();
enableVehicleControlRotate = ENABLE_VEHICLE_CONTROL_ROTATE.get();
disableLoggingBlockUp = LOGGING_DISABLE_BLOCKUP.get();
}
}

View File

@@ -7,7 +7,6 @@ import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import net.minecraft.world.level.block.state.BlockState;
import studio.fantasyit.maid_useful_task.memory.BlockUpContext;
import studio.fantasyit.maid_useful_task.memory.CurrentWork;
import studio.fantasyit.maid_useful_task.task.IMaidBlockUpTask;
@@ -29,15 +28,15 @@ public class BlockUpDestroyBehavior extends Behavior<EntityMaid> {
}
public BlockUpDestroyBehavior() {
super(Map.of(),500);
super(Map.of(), 500);
}
@Override
protected boolean checkExtraStartConditions(ServerLevel p_22538_, EntityMaid p_22539_) {
if(!Conditions.isCurrent(p_22539_, CurrentWork.BLOCKUP_DOWN)) return false;
if (!Conditions.isCurrent(p_22539_, CurrentWork.BLOCKUP_DOWN)) return false;
if (!MemoryUtil.getBlockUpContext(p_22539_).hasTarget()) return false;
if (MemoryUtil.getBlockUpContext(p_22539_).getStatus() != BlockUpContext.STATUS.DOWN) return false;
return Conditions.hasReachedValidTargetOrReset(p_22539_, 0.8f);
return true;
}
@Override
@@ -73,6 +72,7 @@ public class BlockUpDestroyBehavior extends Behavior<EntityMaid> {
}
}
}
@Override
protected void stop(ServerLevel p_22548_, EntityMaid maid, long p_22550_) {
super.stop(p_22548_, maid, p_22550_);

View File

@@ -7,7 +7,6 @@ import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import studio.fantasyit.maid_useful_task.memory.BlockUpContext;
@@ -45,6 +44,7 @@ public class BlockUpPlaceBehavior extends Behavior<EntityMaid> {
protected boolean canStillUse(ServerLevel p_22545_, EntityMaid maid, long p_22547_) {
if (MemoryUtil.getBlockUpContext(maid).getStatus() != BlockUpContext.STATUS.UP) return false;
if (maid.onGround() && !p_22545_.getBlockState(maid.blockPosition().above().above()).isAir()) return false;
if (maid.getY() > context.getTargetPos().getY() + 2) return false;
return !(maid.blockPosition().equals(context.getTargetPos()) && maid.onGround());
}
@@ -60,9 +60,7 @@ public class BlockUpPlaceBehavior extends Behavior<EntityMaid> {
AABB boundingBox = maid.getBoundingBox();
BlockPos startPos = context.getStartPos();
Vec3 move = maid.getDeltaMovement();
if (boundingBox.maxX <= startPos.getX() + 1 && boundingBox.maxZ <= startPos.getZ() + 1
&& boundingBox.minX >= startPos.getX() && boundingBox.minZ >= startPos.getZ()
) {
if (boundingBox.maxX <= startPos.getX() + 1 && boundingBox.maxZ <= startPos.getZ() + 1 && boundingBox.minX >= startPos.getX() && boundingBox.minZ >= startPos.getZ()) {
maid.setDeltaMovement(0, move.y, 0);
return true;
}
@@ -80,9 +78,7 @@ public class BlockUpPlaceBehavior extends Behavior<EntityMaid> {
task.swapValidItemToHand(maid);
BlockPos pos = maid.blockPosition();
BlockPos below = pos.below();
if (context.isOnLine(pos))
if (below.equals(context.getTargetPos()))
return;
if (context.isOnLine(pos)) if (below.equals(context.getTargetPos())) return;
if (level.getBlockState(below).canBeReplaced() && level.getBlockState(pos).canBeReplaced()) {
maid.swing(InteractionHand.MAIN_HAND);
MaidUtils.placeBlock(maid, below);

View File

@@ -61,7 +61,7 @@ public class DestoryBlockMoveBehavior extends MaidCenterMoveToBlockTask {
if (!PosUtils.isSafePos(serverLevel, pos)) continue;
if (!Conditions.isGlobalValidTarget(entityMaid, pos, targetPos)) continue;
if (pos.distSqr(targetPos) > task.reachDistance() * task.reachDistance()) continue;
if (entityMaid.isWithinRestriction(pos) && pathfindingBFS.canPathReach(pos)) {
if (pos.equals(entityMaid.blockPosition()) || (entityMaid.isWithinRestriction(pos) && pathfindingBFS.canPathReach(pos))) {
blockPosSet = task.toDestroyFromStanding(entityMaid, targetPos, pos);
if (blockPosSet != null) {
mb.set(pos);

View File

@@ -1,6 +1,7 @@
package studio.fantasyit.maid_useful_task.data;
import com.github.tartaricacid.touhoulittlemaid.api.entity.data.TaskDataKey;
import com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import studio.fantasyit.maid_useful_task.MaidUsefulTask;
@@ -8,15 +9,23 @@ import studio.fantasyit.maid_useful_task.MaidUsefulTask;
public class MaidLoggingConfig implements TaskDataKey<MaidLoggingConfig.Data> {
public static Data get(EntityMaid maid) {
return maid.getOrCreateData(KEY, Data.getDefault());
}
public static final class Data implements IConfigSetter {
private boolean plant;
private boolean blockUp;
private boolean skipNonNature;
public Data(boolean plant) {
public Data(boolean plant, boolean blockUp, boolean skipNonNature) {
this.plant = plant;
this.blockUp = blockUp;
this.skipNonNature = skipNonNature;
}
public static Data getDefault() {
return new Data(true);
return new Data(true, true, true);
}
public boolean plant() {
@@ -27,12 +36,34 @@ public class MaidLoggingConfig implements TaskDataKey<MaidLoggingConfig.Data> {
this.plant = plant;
}
public boolean blockUp() {
return blockUp;
}
public void blockUp(boolean blockUp) {
this.blockUp = blockUp;
}
public boolean skipNonNature() {
return skipNonNature;
}
public void skipNonNature(boolean skipNonNature) {
this.skipNonNature = skipNonNature;
}
@Override
public void setConfigValue(String name, String value) {
switch (name) {
case "plant":
plant = Boolean.parseBoolean(value);
break;
case "blockUp":
blockUp = Boolean.parseBoolean(value);
break;
case "skipNonNature":
skipNonNature = Boolean.parseBoolean(value);
break;
}
}
}
@@ -49,12 +80,16 @@ public class MaidLoggingConfig implements TaskDataKey<MaidLoggingConfig.Data> {
public CompoundTag writeSaveData(Data data) {
CompoundTag tag = new CompoundTag();
tag.putBoolean("plant", data.plant);
tag.putBoolean("blockUp", data.blockUp);
tag.putBoolean("skipNonNature", data.skipNonNature);
return tag;
}
@Override
public Data readSaveData(CompoundTag compound) {
boolean plant = compound.getBoolean("plant");
return new Data(plant);
boolean blockUp = compound.getBoolean("blockUp");
boolean skipNonNature = compound.getBoolean("skipNonNature");
return new Data(plant, blockUp, skipNonNature);
}
}

View File

@@ -5,9 +5,9 @@ import com.github.tartaricacid.touhoulittlemaid.client.gui.widget.button.MaidCon
import com.github.tartaricacid.touhoulittlemaid.inventory.container.task.TaskConfigContainer;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;
import studio.fantasyit.maid_useful_task.Config;
import studio.fantasyit.maid_useful_task.data.MaidLoggingConfig;
import studio.fantasyit.maid_useful_task.network.MaidConfigurePacket;
import studio.fantasyit.maid_useful_task.network.Network;
import studio.fantasyit.maid_useful_task.registry.GuiRegistry;
import studio.fantasyit.maid_useful_task.util.TranslateUtil;
@@ -35,7 +35,7 @@ public class MaidLoggingConfigGui extends MaidTaskConfigGui<MaidLoggingConfigGui
int startLeft = leftPos + 87;
int startTop = topPos + 36;
this.addRenderableWidget(new MaidConfigButton(startLeft, startTop + 0,
this.addRenderableWidget(new MaidConfigButton(startLeft, startTop,
Component.translatable("gui.maid_useful_task.logging.plant"),
TranslateUtil.getBooleanTranslate(this.currentData.plant()),
button -> {
@@ -49,5 +49,37 @@ public class MaidLoggingConfigGui extends MaidTaskConfigGui<MaidLoggingConfigGui
MaidConfigurePacket.send(this.maid, MaidLoggingConfig.LOCATION, "plant", "true");
}
));
if (!Config.disableLoggingBlockUp) {
startTop += 13;
this.addRenderableWidget(new MaidConfigButton(startLeft, startTop,
Component.translatable("gui.maid_useful_task.logging.block_up"),
TranslateUtil.getBooleanTranslate(this.currentData.blockUp()),
button -> {
this.currentData.blockUp(false);
button.setValue(TranslateUtil.getBooleanTranslate(false));
MaidConfigurePacket.send(this.maid, MaidLoggingConfig.LOCATION, "blockUp", "false");
},
button -> {
this.currentData.blockUp(true);
button.setValue(TranslateUtil.getBooleanTranslate(true));
MaidConfigurePacket.send(this.maid, MaidLoggingConfig.LOCATION, "blockUp", "true");
}
));
}
startTop += 13;
this.addRenderableWidget(new MaidConfigButton(startLeft, startTop,
Component.translatable("gui.maid_useful_task.logging.skip_non_nature"),
TranslateUtil.getBooleanTranslate(this.currentData.skipNonNature()),
button -> {
this.currentData.skipNonNature(false);
button.setValue(TranslateUtil.getBooleanTranslate(false));
MaidConfigurePacket.send(this.maid, MaidLoggingConfig.LOCATION, "skipNonNature", "false");
},
button -> {
this.currentData.skipNonNature(true);
button.setValue(TranslateUtil.getBooleanTranslate(true));
MaidConfigurePacket.send(this.maid, MaidLoggingConfig.LOCATION, "skipNonNature", "true");
}
));
}
}

View File

@@ -8,6 +8,8 @@ import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import studio.fantasyit.maid_useful_task.task.MaidLocateTask;
import studio.fantasyit.maid_useful_task.task.MaidTreeTask;
import studio.fantasyit.maid_useful_task.util.MemoryUtil;
@Mixin(MaidMoveControl.class)
@@ -18,11 +20,12 @@ abstract public class MaidMoveControlMixin {
@Inject(method = "tick", at = @At("HEAD"), cancellable = true)
public void tick(CallbackInfo ci) {
if (switch (MemoryUtil.getCurrent(maid)) {
case BLOCKUP_DESTROY, BLOCKUP_DOWN -> true;
default -> false;
}) {
ci.cancel();
}
if (maid.getTask().getUid().equals(MaidTreeTask.UID))
if (switch (MemoryUtil.getCurrent(maid)) {
case BLOCKUP_DESTROY, BLOCKUP_DOWN -> true;
default -> false;
}) {
ci.cancel();
}
}
}

View File

@@ -8,6 +8,7 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import studio.fantasyit.maid_useful_task.memory.CurrentWork;
import studio.fantasyit.maid_useful_task.task.MaidTreeTask;
import studio.fantasyit.maid_useful_task.util.Conditions;
import studio.fantasyit.maid_useful_task.util.MemoryUtil;
@@ -15,8 +16,9 @@ import studio.fantasyit.maid_useful_task.util.MemoryUtil;
abstract public class MaidRunOneMixin {
@Inject(method = "tryStart(Lnet/minecraft/server/level/ServerLevel;Lcom/github/tartaricacid/touhoulittlemaid/entity/passive/EntityMaid;J)Z", at = @At("HEAD"), cancellable = true, remap = false)
public void runOne(ServerLevel pLevel, EntityMaid maid, long pGameTime, CallbackInfoReturnable<Boolean> cir) {
if (!Conditions.isCurrent(maid, CurrentWork.IDLE)) {
cir.setReturnValue(false);
}
if (maid.getTask().getUid().equals(MaidTreeTask.UID))
if (!Conditions.isCurrent(maid, CurrentWork.IDLE)) {
cir.setReturnValue(false);
}
}
}

View File

@@ -13,6 +13,8 @@ import oshi.util.tuples.Pair;
import studio.fantasyit.maid_useful_task.util.MaidUtils;
import studio.fantasyit.maid_useful_task.util.PosUtils;
import java.util.function.Function;
public interface IMaidBlockUpTask {
default boolean isFindingBlock(EntityMaid maid, BlockPos target, BlockPos standPos) {
if (target.distSqr(standPos) > touchLimit() * touchLimit())
@@ -41,10 +43,21 @@ public interface IMaidBlockUpTask {
default Pair<BlockPos, BlockPos> findTargetPosBlockUp(EntityMaid maid, BlockPos center, int maxUp) {
ServerLevel level = (ServerLevel) maid.level();
int maxHeight = verticalOffset() + verticalDistance();
CenterOffsetBlockPosSet notAvailable = new CenterOffsetBlockPosSet(scanRange(maid), scanRange(maid) + maxHeight / 2 + 1, scanRange(maid), center.getX(), center.getY() + maxHeight / 2, center.getZ());
MaidPathFindingBFS pathFindingBFS = new MaidPathFindingBFS(maid.getNavigation().getNodeEvaluator(), level, maid, 7, scanRange(maid));
for (int dx = 0; dx < scanRange(maid); dx = dx <= 0 ? 1 - dx : -dx) {
for (int dz = 0; dz < scanRange(maid); dz = dz <= 0 ? 1 - dz : -dz) {
boolean hasRestriction = maid.hasRestriction();
BlockPos restrictCenter = maid.getRestrictCenter();
float restrictRadius = maid.getRestrictRadius();
int scanRange = scanRange(maid);
Function<BlockPos, Boolean> withinRestriction = (BlockPos pos) -> {
if (hasRestriction) {
return restrictCenter.distSqr(pos) < (double) (restrictRadius * restrictRadius);
} else {
return true;
}
};
CenterOffsetBlockPosSet notAvailable = new CenterOffsetBlockPosSet(scanRange, scanRange + maxHeight / 2 + 1, scanRange, center.getX(), center.getY() + maxHeight / 2, center.getZ());
MaidPathFindingBFS pathFindingBFS = new MaidPathFindingBFS(maid.getNavigation().getNodeEvaluator(), level, maid, 7, scanRange);
for (int dx = 0; dx < scanRange; dx = dx <= 0 ? 1 - dx : -dx) {
for (int dz = 0; dz < scanRange; dz = dz <= 0 ? 1 - dz : -dz) {
//计算地面的位置
BlockPos.MutableBlockPos ground = center.offset(dx, 0, dz).mutable();
while (level.getBlockState(ground).canBeReplaced()) ground.move(0, -1, 0);
@@ -84,7 +97,7 @@ public interface IMaidBlockUpTask {
for (int dy = verticalOffset(); dy < verticalDistance() + verticalOffset(); dy++) {
BlockPos targetPos = ground.offset(sdx, dy, sdz);
if (targetPos.distSqr(standPos) > touchLimit * touchLimit) break;
if (maid.hasRestriction() && !maid.isWithinRestriction(standPos)) break;
if (hasRestriction && !withinRestriction.apply(standPos)) break;
if (isFindingBlock(maid, targetPos, standPos)) {
return new Pair<>(ground.immutable(), standPos);
}
@@ -92,7 +105,7 @@ public interface IMaidBlockUpTask {
if (continuous) {
if (!level.getBlockState(standPos.above().above()).isAir()) {
continuous = false;
} else if (maid.hasRestriction() && !maid.isWithinRestriction(standPos.above())) {
} else if (hasRestriction && !withinRestriction.apply((standPos.above().above()))) {
continuous = false;
}
}

View File

@@ -29,7 +29,6 @@ import studio.fantasyit.maid_useful_task.behavior.common.*;
import studio.fantasyit.maid_useful_task.data.MaidLoggingConfig;
import studio.fantasyit.maid_useful_task.memory.BlockValidationMemory;
import studio.fantasyit.maid_useful_task.menu.MaidLoggingConfigGui;
import studio.fantasyit.maid_useful_task.registry.GuiRegistry;
import studio.fantasyit.maid_useful_task.util.MaidUtils;
import studio.fantasyit.maid_useful_task.util.MemoryUtil;
import studio.fantasyit.maid_useful_task.util.WrappedMaidFakePlayer;
@@ -40,9 +39,11 @@ import java.util.List;
import java.util.Set;
public class MaidTreeTask implements IMaidTask, IMaidBlockPlaceTask, IMaidBlockDestroyTask, IMaidBlockUpTask {
public static final ResourceLocation UID = new ResourceLocation(MaidUsefulTask.MODID, "maid_tree");
@Override
public ResourceLocation getUid() {
return new ResourceLocation(MaidUsefulTask.MODID, "maid_tree");
return UID;
}
@Override
@@ -89,7 +90,10 @@ public class MaidTreeTask implements IMaidTask, IMaidBlockPlaceTask, IMaidBlockD
}
}
BlockState blockState = maid.level().getBlockState(pos);
return blockState.is(BlockTags.LOGS) && isValidNatureTree(maid, pos);
if (blockState.is(BlockTags.LOGS)) {
return !MaidLoggingConfig.get(maid).skipNonNature() || isValidNatureTree(maid, pos);
}
return false;
}
@Override
@@ -247,7 +251,10 @@ public class MaidTreeTask implements IMaidTask, IMaidBlockPlaceTask, IMaidBlockD
public boolean isFindingBlock(EntityMaid maid, BlockPos target, BlockPos standPos) {
if (target.distSqr(standPos) > touchLimit() * touchLimit())
return false;
return maid.level().getBlockState(target).is(BlockTags.LOGS) && isValidNatureTree(maid, target);
if (maid.level().getBlockState(target).is(BlockTags.LOGS)) {
return !MaidLoggingConfig.get(maid).skipNonNature() || isValidNatureTree(maid, target);
}
return false;
}
@Override
@@ -275,6 +282,7 @@ public class MaidTreeTask implements IMaidTask, IMaidBlockPlaceTask, IMaidBlockD
/**
* 此处判断当home模式未开启时不允许上搭。
*
* @param maid
* @param center
* @param maxUp
@@ -282,8 +290,8 @@ public class MaidTreeTask implements IMaidTask, IMaidBlockPlaceTask, IMaidBlockD
*/
@Override
public oshi.util.tuples.Pair<BlockPos, BlockPos> findTargetPosBlockUp(EntityMaid maid, BlockPos center, int maxUp) {
if (maid.isHomeModeEnable())
if (maid.isHomeModeEnable() && MaidLoggingConfig.get(maid).blockUp() && !Config.disableLoggingBlockUp)
return IMaidBlockUpTask.super.findTargetPosBlockUp(maid, center, maxUp);
return null;
}
}
}

View File

@@ -8,7 +8,6 @@ import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.WalkTarget;
import net.minecraft.world.phys.Vec3;
import studio.fantasyit.maid_useful_task.memory.CurrentWork;
import studio.fantasyit.maid_useful_task.registry.MemoryModuleRegistry;
import java.util.Optional;
@@ -43,7 +42,7 @@ public class Conditions {
public static boolean isGlobalValidTarget(EntityMaid maid, BlockPos pos, BlockPos targetPos) {
if (MemoryUtil.getBlockUpContext(maid).hasTarget()) {
return MemoryUtil.getBlockUpContext(maid).isTarget(pos);
return pos.equals(maid.blockPosition());
}
return true;
}

View File

@@ -13,5 +13,7 @@
"gui.maid_useful_task.logging.plant": "Plant Saplings",
"gui.maid_useful_task.no": "No",
"gui.maid_useful_task.yes": "Yes",
"gui.maid_useful_task.revive.ownerOnly": "Only revive owner"
"gui.maid_useful_task.revive.ownerOnly": "Only revive owner",
"gui.maid_useful_task.logging.block_up": "Block Up",
"gui.maid_useful_task.logging.skip_non_nature": "Skip Non-nature Logs"
}

View File

@@ -1,17 +1,19 @@
{
"gui.maid_useful_task.logging.plant": "种植树苗",
"gui.maid_useful_task.no": "否",
"gui.maid_useful_task.revive.ownerOnly": "只救援主人",
"gui.maid_useful_task.yes": "是",
"key.maid_useful_tasks.categories.main": "女仆实用任务",
"key.maid_useful_tasks.switch_vehicle_control": "切换载具控制模式",
"maid_useful_task.allow_handle_vehicle.full": "允许女仆控制载具",
"maid_useful_task.allow_handle_vehicle.none": "不允许女仆控制载具",
"maid_useful_task.allow_handle_vehicle.rot_only": "只允许女仆控制旋转",
"task.maid_useful_task.locate": "定位",
"task.maid_useful_task.locate.desc": "女仆会根据手持物品定位指定的结构或你的出生点",
"task.maid_useful_task.maid_tree": "伐木",
"task.maid_useful_task.maid_tree.desc": "女仆会砍伐身边的原木并种下树苗",
"task.maid_useful_task.locate": "定位",
"task.maid_useful_task.locate.desc": "女仆会根据手持物品定位指定的结构或你的出生点",
"task.maid_useful_task.revive_player": "救援玩家",
"task.maid_useful_task.revive_player.desc": "女仆会救援附近倒地的玩家"
"task.maid_useful_task.revive_player.desc": "女仆会救援附近倒地的玩家",
"key.maid_useful_tasks.switch_vehicle_control": "切换载具控制模式",
"maid_useful_task.allow_handle_vehicle.none": "不允许女仆控制载具",
"maid_useful_task.allow_handle_vehicle.rot_only": "只允许女仆控制旋转",
"maid_useful_task.allow_handle_vehicle.full": "允许女仆控制载具",
"key.maid_useful_tasks.categories.main": "女仆实用任务",
"gui.maid_useful_task.logging.plant": "种植树苗",
"gui.maid_useful_task.no": "否",
"gui.maid_useful_task.yes": "是",
"gui.maid_useful_task.revive.ownerOnly": "只救援主人",
"gui.maid_useful_task.logging.block_up": "上搭方块",
"gui.maid_useful_task.logging.skip_non_nature": "跳过非自然原木"
}