更新版本为1.0.7

1. 修复 #6 的问题:
客户端玩家速度与服务器玩家不一致
通过引入发包来解决
This commit is contained in:
叁玖领域 2025-11-30 13:05:38 +08:00
parent 9511f0074c
commit e3ba40f8ff
7 changed files with 165 additions and 13 deletions

View File

@ -147,6 +147,10 @@ repositories {
includeGroup("maven.modrinth")
}
}
maven {
name = "KosmX"
url = "https://maven.kosmx.dev/"
}
maven {
name = "Fuzs Mod Resources"
url = "https://raw.githubusercontent.com/Fuzss/modresources/main/maven/"
@ -176,6 +180,7 @@ dependencies {
implementation jarJar(fg.deobf("com.leisuretimedock:jsonem-forge-${minecraft_version}:${jsonem_version}")) {
jarJar.ranged(it, "[${jsonem_version},)")
}
implementation fg.deobf ("dev.kosmx.player-anim:player-animation-lib-forge:${player_anim_version}")
// Example mod dependency with JEI - using fg.deobf() ensures the dependency is remapped to your development mappings

View File

@ -41,6 +41,7 @@ mod_license=MIT
mod_version=1.0.7
jsonem_version=0.2.4
lib39_version=1.20.1-0.0.17
player_anim_version=1.0.2-rc1+1.20
mod_source=https://github.com/LeisureTimeDock/BlastTravel_Neo_Forge
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
# This should match the base package used for the mod sources.

View File

@ -39,6 +39,7 @@ import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.fml.javafmlmod.FMLModContainer;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.network.NetworkDirection;
import org.jetbrains.annotations.NotNull;
@ -207,9 +208,12 @@ public class CannonEntity extends Entity {
} else if (stack.is(Items.CHAIN)) {
handlerChain(player, stack);
return true;
} else if (CannonBehavior.isValidBehaviorStack(stack)) {
} else if (CannonBehavior.isValidBehaviorStack(stack) && this.getPassengers().isEmpty()) {
handlerBehaviorItem(player, stack);
return true;
} else if (stack.is(Items.FLINT_AND_STEEL)) {
fireServer();
return true;
}
return false;
}
@ -217,7 +221,6 @@ public class CannonEntity extends Entity {
private void handlerGunPower(Player player, ItemStack stack) {
if (!level().isClientSide()) {
ItemStack currentGunpowder = inventory.getItem(0);
// 如果炮内已有火药先还给玩家
if (currentGunpowder.getCount() >= currentGunpowder.getMaxStackSize()) {
if (!player.isCreative() && !player.getInventory().add(currentGunpowder)) {
@ -225,11 +228,10 @@ public class CannonEntity extends Entity {
player.drop(currentGunpowder, false);
}
}
// 设置新的火药到炮中
ItemStack newGunpowder = stack.copy();
newGunpowder.setCount(currentGunpowder.getCount() + 1); // 只放一个火药
((ServerPlayer) player).sendSystemMessage(Component.literal(String.format("%d / %d", currentGunpowder.getCount(), currentGunpowder.getMaxStackSize())), true);
((ServerPlayer) player).sendSystemMessage(Component.literal(String.format("%d / %d", currentGunpowder.getCount() + 1, currentGunpowder.getMaxStackSize())), true);
inventory.setItem(0, newGunpowder);
if (!player.isCreative()) {
@ -242,17 +244,17 @@ public class CannonEntity extends Entity {
private void handlerChain(Player player, ItemStack stack) {
if (!level().isClientSide()) {
ItemStack currentChain = inventory.getItem(2);
if (currentChain.isEmpty()) { // 添加锁链
ItemStack currentChain = inventory.getItem(1);
if (currentChain.isEmpty()) { // 添加锁链Pl
ItemStack item = stack.copy();
item.setCount(1);
inventory.setItem(2, item);
inventory.setItem(1, item);
if (!player.isCreative()) {
stack.shrink(1);
}
level().playSound(null, this.blockPosition(), SoundEvents.CHAIN_PLACE, SoundSource.BLOCKS, 1.0f, 1.0f);
} else {
inventory.setItem(2, ItemStack.EMPTY);
inventory.setItem(1, ItemStack.EMPTY);
if (!player.isCreative()) { // 移除锁链
if (!player.getInventory().add(currentChain)) {
player.drop(currentChain, false);
@ -267,18 +269,41 @@ public class CannonEntity extends Entity {
if (!level().isClientSide()) {
ItemStack currentBehaviorItem = inventory.getItem(2);
if (currentBehaviorItem.isEmpty()) { // 添加物品
ItemStack item = stack.copy();
item.setCount(1);
inventory.setItem(2, item);
ItemStack copied = stack.copy();
copied.setCount(1);
inventory.setItem(2, copied);
if (!player.isCreative()) {
stack.shrink(1);
}
level().playSound(null, this.blockPosition(), getBehavior().getPlaceInSound(), SoundSource.BLOCKS, 1.0f, 1.0f);
} else {
if (stack.is(currentBehaviorItem.getItem())) { //同类物品 +1
if (stack.getCount() >= currentBehaviorItem.getMaxStackSize()) {
if (!player.isCreative() && !player.getInventory().add(currentBehaviorItem)) {
player.drop(currentBehaviorItem, false);
}
}
ItemStack stack1 = stack.copy();
stack1.setCount(currentBehaviorItem.getCount() + 1);
((ServerPlayer) player).sendSystemMessage(Component.literal(String.format("%d / %d", currentBehaviorItem.getCount() + 1, currentBehaviorItem.getMaxStackSize())), true);
inventory.setItem(2, stack1);
if (!player.isCreative()) {
stack.shrink(1);
}
level().playSound(null, this.blockPosition(), getBehavior().getPlaceInSound(), SoundSource.BLOCKS, 1.0f, 1.0f);
} else { //不同类替换
if (!player.isCreative() && !player.getInventory().add(currentBehaviorItem)) {
player.drop(currentBehaviorItem, false);
}
ItemStack copied = stack.copy();
copied.setCount(1);
inventory.setItem(2, copied);
if (!player.isCreative()) {
stack.shrink(1);
}
level().playSound(null, this.blockPosition(), getBehavior().getPlaceInSound(), SoundSource.BLOCKS, 1.0f, 1.0f);
}
}
}
@ -545,6 +570,7 @@ public class CannonEntity extends Entity {
return super.getPassengersRidingOffset();
}
@Override
protected void defineSynchedData() {
this.entityData.define(BEHAVIOR, 0);

View File

@ -2,10 +2,12 @@ package com.leisuretimedock.blasttravelreborn.mixin;
import com.leisuretimedock.blasttravelreborn.BlastTravelReborn;
import com.leisuretimedock.blasttravelreborn.network.BTRNetwork;
import com.leisuretimedock.blasttravelreborn.network.toClient.SyncFlightStatePayload;
import com.leisuretimedock.blasttravelreborn.network.toServer.StopCannonFlightServerPayload;
import com.leisuretimedock.blasttravelreborn.util.PlayerEntityDuck;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.EntityDimensions;
@ -17,6 +19,8 @@ import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.network.NetworkDirection;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@ -59,6 +63,30 @@ public abstract class PlayerEntityMixin extends LivingEntity implements PlayerEn
@Inject(method = "tick", at = @At("HEAD"))
private void blasttravel$beginTick(CallbackInfo ci) {
this.blasttravel$prevVel = this.isLocalPlayer() ? blasttravel$vel : blasttravel$trackingVel;
if(!FMLEnvironment.production) {
if (blasttravel$inCannonFlight) {
// 获取当前速度
Vec3 currentVelocity = this.getDeltaMovement();
double speed = currentVelocity.length();
// 记录到对应的速度列表
if (this.isLocalPlayer()) {
// 客户端本地玩家
blasttravel$vel = currentVelocity;
System.out.println("[Client-Local] Tick: " + this.level().getGameTime() +
", Velocity: " + currentVelocity +
", Speed: " + String.format("%.3f", speed) +
", Position: " + this.position());
} else {
System.out.println("[Server] Tick: " + this.level().getGameTime() +
", Velocity: " + currentVelocity +
", Speed: " + String.format("%.3f", speed) +
", Position: " + String.format("(%.1f, %.1f, %.1f)", this.getX(), this.getY(), this.getZ()) +
", OnGround: " + this.onGround() +
", noPhysics: " + this.noPhysics);
}
}
}
}
@Inject(method = "tick", at = @At("TAIL"))
@ -79,6 +107,10 @@ public abstract class PlayerEntityMixin extends LivingEntity implements PlayerEn
entity.hurt(source, (float)(vel.length() * 4));
}
}
try {
ServerPlayer cast = ServerPlayer.class.cast(self);
BTRNetwork.CHANNEL.sendTo(new SyncFlightStatePayload(self.getId(), vel, self.position(), true, noPhysics), cast.connection.connection, NetworkDirection.PLAY_TO_CLIENT);
} catch(Exception ignored){}
}
if (self.isLocalPlayer() &&

View File

@ -3,6 +3,7 @@ package com.leisuretimedock.blasttravelreborn.network;
import com.leisuretimedock.blasttravelreborn.BlastTravelReborn;
import com.leisuretimedock.blasttravelreborn.network.toClient.FireCannonPayload;
import com.leisuretimedock.blasttravelreborn.network.toClient.StopCannonFlightClientPayload;
import com.leisuretimedock.blasttravelreborn.network.toClient.SyncFlightStatePayload;
import com.leisuretimedock.blasttravelreborn.network.toServer.RequestFirePayload;
import com.leisuretimedock.blasttravelreborn.network.toServer.StopCannonFlightServerPayload;
import net.minecraftforge.network.NetworkDirection;
@ -43,6 +44,13 @@ public class BTRNetwork {
.decoder(RequestFirePayload::read)
.consumerNetworkThread(RequestFirePayload::handle)
.add();
CHANNEL
.messageBuilder(SyncFlightStatePayload.class, getIDAndIncrease(), NetworkDirection.PLAY_TO_CLIENT)
.encoder(SyncFlightStatePayload::write)
.decoder(SyncFlightStatePayload::read)
.consumerNetworkThread(SyncFlightStatePayload::handle)
.add();
}
private static int getIDAndIncrease() {
return ++ID;

View File

@ -42,6 +42,7 @@ public record FireCannonPayload(int cannonId, Optional<Integer> launchedId, doub
if (context.getNetworkManager().getPacketListener() instanceof ClientPacketListener clientPacketListener) {
context.enqueueWork(() -> {
if (hasPlayer()) {
//noinspection OptionalGetWithoutIsPresent
if (clientPacketListener.getLevel().getEntity(launchedId.get()) instanceof Player launchedPlayer) {
// 记录发射前状态
if (!FMLEnvironment.production) {
@ -56,7 +57,6 @@ public record FireCannonPayload(int cannonId, Optional<Integer> launchedId, doub
// 直接设置速度不要延迟
launchedPlayer.setDeltaMovement(velocityX, velocityY, velocityZ);
launchedPlayer.hurtMarked = true; // 强制同步运动
((PlayerEntityDuck) launchedPlayer).blasttravel$setCannonFlight(true);
// 记录发射后状态

View File

@ -0,0 +1,80 @@
package com.leisuretimedock.blasttravelreborn.network.toClient;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.network.NetworkEvent;
import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier;
public record SyncFlightStatePayload(
int playerId,
Vec3 velocity,
Vec3 position,
boolean inCannonFlight,
boolean noPhysics
) {
public SyncFlightStatePayload(@NotNull FriendlyByteBuf buf) {
this(
buf.readInt(),
new Vec3(buf.readDouble(), buf.readDouble(), buf.readDouble()),
new Vec3(buf.readDouble(), buf.readDouble(), buf.readDouble()),
buf.readBoolean(),
buf.readBoolean()
);
}
public void write(@NotNull FriendlyByteBuf buf) {
buf.writeInt(playerId);
buf.writeDouble(velocity.x);
buf.writeDouble(velocity.y);
buf.writeDouble(velocity.z);
buf.writeDouble(position.x);
buf.writeDouble(position.y);
buf.writeDouble(position.z);
buf.writeBoolean(inCannonFlight);
buf.writeBoolean(noPhysics);
}
public static SyncFlightStatePayload read(FriendlyByteBuf buf) {
return new SyncFlightStatePayload(buf);
}
public void handle(Supplier<NetworkEvent.Context> context) {
context.get().enqueueWork(() -> {
// 客户端处理逻辑
net.minecraft.client.Minecraft client = net.minecraft.client.Minecraft.getInstance();
if (client.level != null) {
net.minecraft.world.entity.Entity entity = client.level.getEntity(this.playerId);
if (entity instanceof net.minecraft.world.entity.player.Player player) {
// 同步速度
player.setDeltaMovement(this.velocity);
// 同步位置如果差异较大
if (player.position().distanceTo(this.position) > 0.1) {
player.setPos(this.position);
}
// 同步飞行状态
if (player instanceof com.leisuretimedock.blasttravelreborn.util.PlayerEntityDuck duck) {
duck.blasttravel$setCannonFlight(this.inCannonFlight);
}
player.noPhysics = this.noPhysics;
player.hurtMarked = true;
// 调试输出
if (!FMLEnvironment.production) {
System.out.println("[Client] Received flight sync: " +
"Vel=" + this.velocity + ", " +
"Pos=" + this.position + ", " +
"Flight=" + this.inCannonFlight + ", " +
"NoPhysics=" + this.noPhysics);
}
}
}
});
context.get().setPacketHandled(true);
}
}