Skip to content

Commit

Permalink
Merge pull request #632
Browse files Browse the repository at this point in the history
* Climb

* fix scaffolding check

* fix

* 修正部分内容
  • Loading branch information
Azumic authored Jan 3, 2025
1 parent daa45fb commit dc85eef
Show file tree
Hide file tree
Showing 12 changed files with 1,611 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ default boolean hasFishingHook() {
return false;
}

default boolean onClimbable() {
return false;
}

// 下方为 Deprecated 方法,仅用于适配旧版本模型,无需 Override

@Deprecated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.github.tartaricacid.touhoulittlemaid.entity.item.EntitySit;
import com.github.tartaricacid.touhoulittlemaid.geckolib3.core.builder.ILoopType;
import com.github.tartaricacid.touhoulittlemaid.geckolib3.core.event.predicate.AnimationEvent;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.vehicle.Boat;

Expand All @@ -17,6 +18,10 @@ public static void registerAnimationState() {
register("death", ILoopType.EDefaultLoopTypes.PLAY_ONCE, Priority.HIGHEST, (maid, event) -> maid.asEntity().isDeadOrDying());
register("sleep", Priority.HIGHEST, (maid, event) -> maid.asEntity().getPose() == Pose.SLEEPING);

register("ladder_up", Priority.HIGHEST, (maid, event) -> maid.onClimbable() && getVerticalSpeed(maid) > 0);
register("ladder_stillness", Priority.HIGHEST, (maid, event) -> maid.onClimbable() && getVerticalSpeed(maid) == 0);
register("ladder_down", Priority.HIGHEST, (maid, event) -> maid.onClimbable() && getVerticalSpeed(maid) < 0);

register("gomoku", Priority.HIGH, (maid, event) -> sitInJoy(maid, Type.GOMOKU));
register("bookshelf", Priority.HIGH, (maid, event) -> sitInJoy(maid, Type.BOOKSHELF));
register("computer", Priority.HIGH, (maid, event) -> sitInJoy(maid, Type.COMPUTER));
Expand Down Expand Up @@ -49,4 +54,9 @@ private static void register(String animationName, ILoopType loopType, int prior
private static void register(String animationName, int priority, BiPredicate<IMaid, AnimationEvent<?>> predicate) {
register(animationName, ILoopType.EDefaultLoopTypes.LOOP, priority, predicate);
}

private static float getVerticalSpeed(IMaid maid) {
Mob entity = maid.asEntity();
return 20 * (float) (entity.position().y - entity.yo);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,16 @@ protected void initAdditionWidgets() {
button.setValue(Component.translatable("gui.touhou_little_maid.maid_config.value." + this.syncNetwork.openFenceGate()));
}
));
buttonTop += 13;

this.addRenderableWidget(new MaidConfigButton(buttonLeft, buttonTop,
Component.translatable("gui.touhou_little_maid.maid_config.active_climbing"),
Component.translatable("gui.touhou_little_maid.maid_config.value." + this.syncNetwork.activeClimbing()),
button -> {
this.syncNetwork.setActiveClimbing(!this.syncNetwork.activeClimbing());
button.setValue(Component.translatable("gui.touhou_little_maid.maid_config.value." + this.syncNetwork.activeClimbing()));
}
));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ private static void registerSchedule(Brain<EntityMaid> brain, EntityMaid maid) {

private static void registerCoreGoals(Brain<EntityMaid> brain) {
Pair<Integer, BehaviorControl<? super EntityMaid>> swim = Pair.of(0, new Swim(0.8f));
Pair<Integer, BehaviorControl<? super EntityMaid>> climb = Pair.of(0, new MaidClimbTask());
Pair<Integer, BehaviorControl<? super EntityMaid>> look = Pair.of(0, new LookAtTargetSink(45, 90));
Pair<Integer, BehaviorControl<? super EntityMaid>> maidPanic = Pair.of(1, new MaidPanicTask());
Pair<Integer, BehaviorControl<? super EntityMaid>> maidAwait = Pair.of(1, new MaidAwaitTask());
Expand All @@ -95,7 +96,7 @@ private static void registerCoreGoals(Brain<EntityMaid> brain) {
Pair<Integer, BehaviorControl<? super EntityMaid>> pickupItem = Pair.of(10, new MaidPickupEntitiesTask(EntityMaid::isPickup, 0.6f));
Pair<Integer, BehaviorControl<? super EntityMaid>> clearSleep = Pair.of(99, new MaidClearSleepTask());

brain.addActivity(Activity.CORE, ImmutableList.of(swim, look, maidPanic, maidAwait, interactWithDoor, walkToTarget, followOwner, healSelf, pickupItem, clearSleep));
brain.addActivity(Activity.CORE, ImmutableList.of(swim, climb, look, maidPanic, maidAwait, interactWithDoor, walkToTarget, followOwner, healSelf, pickupItem, clearSleep));
}

private static void registerIdleGoals(Brain<EntityMaid> brain) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.github.tartaricacid.touhoulittlemaid.entity.ai.brain.task;

import com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid;
import com.google.common.collect.ImmutableMap;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.behavior.BlockPosTracker;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.pathfinder.Node;
import net.minecraft.world.level.pathfinder.Path;
import net.minecraft.world.phys.Vec3;

public class MaidClimbTask extends Behavior<EntityMaid> {
public MaidClimbTask() {
super(ImmutableMap.of());
}

@Override
protected void start(ServerLevel pLevel, EntityMaid maid, long pGameTime) {
// 初始化女仆动量
// 将女仆定格在楼梯中心,取消掉 x、z 轴的动量,避免爬楼梯过程中摔死
BlockPos currentPosition = maid.blockPosition().mutable();
Vec3 centerPos = Vec3.atCenterOf(currentPosition);
maid.moveTo(centerPos.x, currentPosition.getY(), centerPos.z);
maid.setDeltaMovement(0, maid.getDeltaMovement().y(), 0);
}

@Override
protected boolean checkExtraStartConditions(ServerLevel pLevel, EntityMaid maid) {
// 如果禁用了主动攀爬能力,就直接返回,不执行后续的操作
if (!maid.getConfigManager().isActiveClimbing()) {
return false;
}
return maid.onClimbable();
}

@Override
protected boolean canStillUse(ServerLevel pLevel, EntityMaid pEntity, long pGameTime) {
return this.checkExtraStartConditions(pLevel, pEntity);
}

@Override
protected void tick(ServerLevel level, EntityMaid maid, long pGameTime) {
Path path = maid.getNavigation().getPath();
if (path == null) {
return;
}

// 获取基础信息:下一个要到达的节点、女仆当前所处坐标、方块
int beGoNodeIndex = path.getNextNodeIndex();
Node beGoNode = path.getNode(beGoNodeIndex);
BlockPos maidFeetPos = maid.blockPosition();
BlockState feetBlock = level.getBlockState(maidFeetPos);

// 判断上行还是下行
boolean up = true;
if (beGoNodeIndex > 0) {
Node currentNext = path.getNode(beGoNodeIndex - 1);
Node pointNext = path.getNode(beGoNodeIndex);
if (pointNext.y <= currentNext.y) {
up = false;
}
}

// 如果是下行,添加 shift 行为
maid.setShiftKeyDown(!up);

// 控制上行和下行楼梯的动量
// 将水平方向的动量关掉,与上面的初始化,定格在方块中心,不然会摔死……
// 先给一个大点的 y 轴向量,再拉回一点,这样能连续下去
// 原版的爬楼梯数值为 0.15,有些慢,加快点……
// 而且速度太慢的话,爬楼梯时间过长,路径就被掐断了,
// 就又会重新规划路线……这样控制比较麻烦,而且也还有其他的东西在干扰……
// 最好的是一次路径控制完,这样的效果是最好的
if (maidFeetPos.getY() <= beGoNode.y && up && feetBlock.isLadder(level, maidFeetPos, maid)) {
double yMotion0 = 1;
double yMotion = 0.25;
maid.setDeltaMovement(0, yMotion0, 0);
maid.setDeltaMovement(0, yMotion, 0);
} else {
double yMotion0 = -1;
double yMotion = -0.25;
maid.setDeltaMovement(0, yMotion0, 0);
maid.setDeltaMovement(0, yMotion, 0);
}

// 对下行做出额外处理
// 下行的最近节点索引值很奇怪……
// 女仆都还没到那个下一个节点附近,就启动切换到下一个节点了……
// 上行的就没有这个问题……
if (!up && beGoNode.y != maidFeetPos.getY()) {
int nodeCount = path.getNodeCount();
for (int i1 = 0; i1 < nodeCount; i1++) {
Node node = path.getNode(i1);
Node nextNode = path.getNode(Math.min(i1 + 1, nodeCount - 1));
// 获取正确的节点信息
if (node.y == maidFeetPos.getY() && node.x == maidFeetPos.getX() && node.z == maidFeetPos.getZ() && node.y == nextNode.y) {
beGoNodeIndex = i1;
beGoNode = node;
// 更正最近索引点
path.setNextNodeIndex(i1);
break;
}
}
}
// 控制正常情况下到达该段楼梯节点顶部或者底部向着平台进发
if ((beGoNode.y - maidFeetPos.getY() >= 0 && beGoNode.y - maidFeetPos.getY() <= 1.2) && beGoNodeIndex + 1 < path.getNodeCount()) {
Node currentNext = path.getNode(beGoNodeIndex);
Node pointNext = path.getNode(beGoNodeIndex + 1);

boolean beWalkSurface = pointNext.y == currentNext.y;
if (beWalkSurface || pointNext == path.getEndNode() || maidFeetPos.getY() == currentNext.y) {
// 给予女仆当前坐标与水平节点的x、z方向的差值向量,
// 让其向着那个水平节点进发,脱离楼梯等可爬行物体,不再继续爬楼梯或者停留在上面
int x1 = pointNext.x - currentNext.x;
int z1 = pointNext.z - currentNext.z;
double y = maid.getDeltaMovement().y();
maid.setDeltaMovement(0.2, 1, 0.2);
maid.setDeltaMovement(x1 * 0.3, y + 0.012, z1 * 0.3);
// TODO:将身体转向下一个节点
maid.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, new BlockPosTracker(pointNext.asVec3()));
}
}
}

@Override
protected void stop(ServerLevel pLevel, EntityMaid maid, long pGameTime) {
maid.getNavigation().stop();
maid.setShiftKeyDown(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,63 @@
import com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.DoorBlock;
import net.minecraft.world.level.block.FenceGateBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.pathfinder.BlockPathTypes;
import net.minecraft.world.level.pathfinder.Node;
import net.minecraft.world.level.pathfinder.WalkNodeEvaluator;

/**
* 该方法仅修改了栅栏门的寻路判断
* 该方法仅修改了栅栏门和梯子的寻路判断
*/
public class MaidNodeEvaluator extends WalkNodeEvaluator {
@Override
public BlockPathTypes getBlockPathType(BlockGetter level, int pX, int pY, int pZ) {
return getMaidBlockPathTypeStatic(level, new BlockPos.MutableBlockPos(pX, pY, pZ));
}

@Override
public int getNeighbors(Node[] outputArray, Node node) {
int nodeId = super.getNeighbors(outputArray, node);
return this.createClimbNode(nodeId, outputArray, node);
}

// 将可爬行物加入寻路节点里头
// 一般这些物体都是相连的,所以向上向下搜寻下
protected int createClimbNode(int nodeId, Node[] nodes, Node origin) {
// 只有在开启攀爬能力,才将梯子加入寻路节点里
if (this.mob instanceof EntityMaid maid && maid.getConfigManager().isActiveClimbing()) {
// 向上搜寻
BlockPos.MutableBlockPos upPos = new BlockPos.MutableBlockPos(origin.x, origin.y + 1, origin.z);
if (isMaidCanClimbBlock(upPos, maid)) {
Node node = this.getNode(upPos);
if (!node.closed) {
node.costMalus = 0;
node.type = BlockPathTypes.WALKABLE;
if (nodeId + 1 < nodes.length) {
nodes[nodeId++] = node;
}
}
}
// 向下搜寻
BlockPos.MutableBlockPos downPos = new BlockPos.MutableBlockPos(origin.x, origin.y - 1, origin.z);
if (isMaidCanClimbBlock(downPos, maid)) {
Node node = this.getNode(downPos);
if (!node.closed) {
node.costMalus = 0;
node.type = BlockPathTypes.WALKABLE;
if (nodeId + 1 < nodes.length) {
nodes[nodeId++] = node;
}
}
}
}
return nodeId;
}

private BlockPathTypes getMaidBlockPathTypeStatic(BlockGetter level, BlockPos.MutableBlockPos pos) {
int x = pos.getX();
int y = pos.getY();
Expand Down Expand Up @@ -70,6 +111,9 @@ private BlockPathTypes getMaidBlockPathTypeRaw(BlockGetter level, BlockPos pos)
return BlockPathTypes.OPEN;
} else if (blockState.getBlock() instanceof FenceGateBlock) {
pathType = blockState.getValue(FenceGateBlock.OPEN) ? BlockPathTypes.DOOR_OPEN : BlockPathTypes.DOOR_WOOD_CLOSED;
} else if (this.mob instanceof EntityMaid maid && this.canClimb(blockState, pos, maid)) {
// 将楼梯视为可行走方块,便于后续将楼梯加入路径节点
pathType = BlockPathTypes.WALKABLE;
} else {
pathType = WalkNodeEvaluator.getBlockPathTypeRaw(level, pos);
}
Expand All @@ -88,4 +132,21 @@ private boolean canOpenDoor(Block block, EntityMaid maid) {
}
return true;
}

private boolean canClimb(BlockState blockState, BlockPos blockPos, EntityMaid maid) {
if (isMaidCanClimbBlock(blockState, blockPos, maid)) {
return maid.getConfigManager().isActiveClimbing();
}
return false;
}

public static boolean isMaidCanClimbBlock(BlockPos blockPos, EntityMaid maid) {
Level level = maid.level;
BlockState blockState = level.getBlockState(blockPos);
return isMaidCanClimbBlock(blockState, blockPos, maid);
}

public static boolean isMaidCanClimbBlock(BlockState blockState, BlockPos blockPos, EntityMaid maid) {
return blockState.isLadder(maid.level, blockPos, maid);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.BaseFireBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.LevelEvent;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
Expand Down Expand Up @@ -182,6 +183,7 @@ public class EntityMaid extends TamableAnimal implements CrossbowAttackMob, IMai
static final EntityDataAccessor<Integer> PICKUP_TYPE = SynchedEntityData.defineId(EntityMaid.class, EntityDataSerializers.INT);
static final EntityDataAccessor<Boolean> OPEN_DOOR = SynchedEntityData.defineId(EntityMaid.class, EntityDataSerializers.BOOLEAN);
static final EntityDataAccessor<Boolean> OPEN_FENCE_GATE = SynchedEntityData.defineId(EntityMaid.class, EntityDataSerializers.BOOLEAN);
static final EntityDataAccessor<Boolean> ACTIVE_CLIMBING = SynchedEntityData.defineId(EntityMaid.class, EntityDataSerializers.BOOLEAN);

/**
* 开辟空间给任务存储使用,也便于附属模组存储数据
Expand Down Expand Up @@ -1938,4 +1940,60 @@ public ItemStack[] getHandItemsForAnimation() {
return handItemsForAnimation;
}

@Override
public Vec3 handleOnClimbable(Vec3 deltaMovement) {
Vec3 oriDelta = super.handleOnClimbable(deltaMovement);
// 主动爬行过程中严禁水平方向偏移,防止摔伤,y轴保持原样
if (this.onClimbable()) {
Vec3 vec3 = this.position();
if (vec3.x() % 1 != 0.5D || vec3.z() % 1 != 0.5) {
BlockPos currentPosition = this.blockPosition().mutable();
Vec3 centerPos = Vec3.atBottomCenterOf(currentPosition);
this.moveTo(centerPos.x, vec3.y(), centerPos.z);
}
oriDelta = new Vec3(0, oriDelta.y, 0);
}
return oriDelta;
}

/**
* 爬梯子状态加上路径判断
*/
@Override
public boolean onClimbable() {
boolean result = super.onClimbable();
if (level.isClientSide) {
// 客户端检测不到路径,所以客户端需要额外返回
return result;
}
if (result) {
// 爬梯时,禁止旋转
this.getLastClimbablePos().ifPresent(climbablePos -> {
BlockState blockState = this.level.getBlockState(climbablePos);
blockState.getOptionalValue(HorizontalDirectionalBlock.FACING).ifPresent(direction -> {
int yRot = direction.getOpposite().get2DDataValue() * 90;
this.setYRot(yRot);
this.setYHeadRot(yRot);
});
});
}
return result && !this.getNavigation().isDone();
}

/**
* 略微修改原版的方法,禁用了向上的动力源
*/
@Override
public Vec3 handleRelativeFrictionAndCalculateMovement(Vec3 deltaMovement, float friction) {
this.moveRelative(this.getFrictionInfluencedSpeed(friction), deltaMovement);
this.setDeltaMovement(this.handleOnClimbable(this.getDeltaMovement()));
this.move(MoverType.SELF, this.getDeltaMovement());
Vec3 vec3 = this.getDeltaMovement();
boolean isCollisionOrJump = this.horizontalCollision || this.jumping;
// 如果是爬行,就需要禁用这个由于碰撞箱等的向上的动力源
if (isCollisionOrJump && !this.onClimbable()) {
vec3 = new Vec3(vec3.x, 0.2, vec3.z);
}
return vec3;
}
}
Loading

0 comments on commit dc85eef

Please sign in to comment.