Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.tacz.guns.client.resource.GunDisplayInstance;
import com.tacz.guns.client.resource.index.ClientGunIndex;
import com.tacz.guns.client.sound.SoundPlayManager;
import com.tacz.guns.entity.shooter.LivingEntityShoot;
import com.tacz.guns.network.NetworkHandler;
import com.tacz.guns.network.message.ClientMessagePlayerShoot;
import com.tacz.guns.resource.index.CommonGunIndex;
Expand Down Expand Up @@ -281,8 +282,10 @@ private void doShoot(GunDisplayInstance display, IGun iGun, ItemStack mainHandIt
// 记录新的开火时间戳
data.clientLastShootTimestamp = data.clientShootTimestamp;
data.clientShootTimestamp = System.currentTimeMillis();
// 发送开火的数据包,通知服务器
NetworkHandler.CHANNEL.sendToServer(new ClientMessagePlayerShoot(data.clientShootTimestamp - data.clientBaseTimestamp, chargeProgress));
// AUTO 模式由服务端 tick 驱动射击,不发送 per-bullet 包
if (!LivingEntityShoot.isAutoShootMode(fireMode, iGun, mainHandItem)) {
NetworkHandler.CHANNEL.sendToServer(new ClientMessagePlayerShoot(data.clientShootTimestamp - data.clientBaseTimestamp, chargeProgress));
}
}

// todo 需要检查
Expand Down
70 changes: 55 additions & 15 deletions src/main/java/com/tacz/guns/client/input/ShootKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import com.tacz.guns.client.gameplay.LocalPlayerSprint;
import com.tacz.guns.client.sound.SoundPlayManager;
import com.tacz.guns.compat.controllable.ControllableCompat;
import com.tacz.guns.entity.shooter.LivingEntityShoot;
import com.tacz.guns.network.NetworkHandler;
import com.tacz.guns.network.message.ClientMessagePlayerAutoShoot;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
Expand All @@ -35,6 +38,7 @@ public class ShootKey {
"key.category.tacz");
private static boolean lastTimeShootSuccess = false;
private static boolean controllerShootDown = false;
private static boolean autoShootSent = false;

@SubscribeEvent
public static void autoShoot(TickEvent.ClientTickEvent event) {
Expand All @@ -54,26 +58,62 @@ public static void autoShoot(TickEvent.ClientTickEvent event) {
boolean isBurstAuto = fireMode == FireMode.BURST && TimelessAPI.getCommonGunIndex(iGun.getGunId(mainHandItem))
.map(index -> index.getGunData().getBurstData().isContinuousShoot())
.orElse(false);
boolean isAutoMode = LivingEntityShoot.isAutoShootMode(fireMode, iGun, mainHandItem);
IClientPlayerGunOperator operator = IClientPlayerGunOperator.fromLocalPlayer(player);
boolean isShootDown = SHOOT_KEY.isDown() || controllerShootDown;
boolean canContinuouslyShoot = fireMode == FireMode.AUTO || isBurstAuto;
boolean shouldCharge = isShootDown && (canContinuouslyShoot || !lastTimeShootSuccess);
if (operator.chargeShoot(shouldCharge)) {
LocalPlayerSprint.stopSprint = true;
if (!canContinuouslyShoot && lastTimeShootSuccess) {
// 非全自动情况,禁止连续开火,也不应在按住上一枪扳机时继续蓄力
return;

if (isAutoMode) {
if (operator.chargeShoot(isShootDown)) {
LocalPlayerSprint.stopSprint = true;
if (operator.shoot() == ShootResult.SUCCESS) {
if (!autoShootSent) {
NetworkHandler.CHANNEL.sendToServer(new ClientMessagePlayerAutoShoot(true));
autoShootSent = true;
}
lastTimeShootSuccess = true;
ControllableCompat.onGunShoot(mainHandItem, fireMode);
}
}
if (operator.shoot() == ShootResult.SUCCESS) {
lastTimeShootSuccess = true;
ControllableCompat.onGunShoot(mainHandItem, fireMode);
if (isShootDown) {
LocalPlayerSprint.stopSprint = true;
} else {
if (autoShootSent) {
NetworkHandler.CHANNEL.sendToServer(new ClientMessagePlayerAutoShoot(false));
autoShootSent = false;
}
lastTimeShootSuccess = false;
SoundPlayManager.resetDryFireSound();
}
}
if (isShootDown) {
LocalPlayerSprint.stopSprint = true;
} else {
lastTimeShootSuccess = false;
SoundPlayManager.resetDryFireSound();
if (autoShootSent) {
NetworkHandler.CHANNEL.sendToServer(new ClientMessagePlayerAutoShoot(false));
autoShootSent = false;
}
// 非全自动:上一发成功后不再主动蓄力,但仍需调用 chargeShoot 驱动蓄力衰减
boolean shouldCharge = isShootDown && !lastTimeShootSuccess;
if (operator.chargeShoot(shouldCharge)) {
LocalPlayerSprint.stopSprint = true;
// HOLD 蓄力武器松开扳机时,若剩余蓄力仍超过阈值,chargeShoot(false) 也会返回 true
// 此处阻止已成功开火后的重复射击
if (lastTimeShootSuccess) {
return;
}
if (operator.shoot() == ShootResult.SUCCESS) {
lastTimeShootSuccess = true;
ControllableCompat.onGunShoot(mainHandItem, fireMode);
}
}
if (isShootDown) {
LocalPlayerSprint.stopSprint = true;
} else {
lastTimeShootSuccess = false;
SoundPlayManager.resetDryFireSound();
}
}
} else {
if (autoShootSent) {
NetworkHandler.CHANNEL.sendToServer(new ClientMessagePlayerAutoShoot(false));
autoShootSent = false;
}
}
}
Expand Down
57 changes: 57 additions & 0 deletions src/main/java/com/tacz/guns/entity/shooter/LivingEntityShoot.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.tacz.guns.network.message.event.ServerMessageGunShoot;
import com.tacz.guns.resource.index.CommonGunIndex;
import com.tacz.guns.resource.pojo.data.gun.Bolt;
import com.tacz.guns.resource.pojo.data.gun.BurstData;
import com.tacz.guns.resource.pojo.data.gun.ChargeData;
import com.tacz.guns.resource.pojo.data.gun.ChargeType;
import net.minecraft.resources.ResourceLocation;
Expand Down Expand Up @@ -280,4 +281,60 @@ public void consumeAmmoFromPlayer(int neededAmount, ItemStack itemStack, boolean
.map(cap -> abstractGunItem.findAndExtractInventoryAmmo(cap, itemStack, neededAmount));
}
}

public void tickAutoShoot(Supplier<Float> pitch, Supplier<Float> yaw) {
if (!data.isAutoShooting) {
return;
}
if (data.currentGunItem == null) {
data.isAutoShooting = false;
return;
}
ItemStack currentGunItem = data.currentGunItem.get();
if (!(currentGunItem.getItem() instanceof IGun iGun)) {
data.isAutoShooting = false;
return;
}
FireMode fireMode = iGun.getFireMode(currentGunItem);
if (!isAutoShootMode(fireMode, iGun, currentGunItem)) {
data.isAutoShooting = false;
return;
}
// 额外 5ms 容差补偿 tick 抖动,避免高射速武器丢失射击
// 此检查仅在 auto-shoot 路径中执行,不影响非 auto 武器
if (getShootCoolDown() > 5) {
return;
}
long timestamp = System.currentTimeMillis() - data.baseTimestamp;
ShootResult result = shoot(pitch, yaw, timestamp);
switch (result) {
case SUCCESS:
case COOL_DOWN:
case IS_SPRINTING:
case IS_DRAWING:
case IS_BOLTING:
case IS_MELEE:
case NETWORK_FAIL:
break;
default:
data.isAutoShooting = false;
break;
}
}

public static boolean isAutoShootMode(FireMode fireMode, IGun iGun, ItemStack gunItem) {
if (fireMode == FireMode.AUTO) {
return true;
}
if (fireMode == FireMode.BURST) {
ResourceLocation gunId = iGun.getGunId(gunItem);
return TimelessAPI.getCommonGunIndex(gunId)
.map(index -> {
BurstData burstData = index.getGunData().getBurstData();
return burstData != null && burstData.isContinuousShoot();
})
.orElse(false);
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ public class ShooterDataHolder {
public LuaValue scriptData = null;

public long heatTimestamp = -1;
/**
* 是否正在自动射击(服务端 tick 驱动)
*/
public volatile boolean isAutoShooting = false;
/**
* 配件修改过的各种属性缓存
*/
Expand All @@ -124,5 +128,6 @@ public void initialData() {
chargeProgress = 0f;
scriptData = null;
heatTimestamp = -1;
isAutoShooting = false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ public void zoom() {
private void onTickServerSide(CallbackInfo ci) {
// 仅在服务端调用
if (!level().isClientSide()) {
// 自动射击 tick
this.tacz$shoot.tickAutoShoot(tacz$shooter::getXRot, tacz$shooter::getYRot);
// 完成各种 tick 任务
ReloadState reloadState = this.tacz$reload.tickReloadState();
this.tacz$aim.tickAimingProgress();
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/tacz/guns/network/NetworkHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ public static void init() {
CHANNEL.registerMessage(ID_COUNT.getAndIncrement(), ClientMessageLaserColor.class, ClientMessageLaserColor::encode, ClientMessageLaserColor::decode, ClientMessageLaserColor::handle,
Optional.of(NetworkDirection.PLAY_TO_SERVER));

CHANNEL.registerMessage(ID_COUNT.getAndIncrement(), ClientMessagePlayerAutoShoot.class, ClientMessagePlayerAutoShoot::encode, ClientMessagePlayerAutoShoot::decode, ClientMessagePlayerAutoShoot::handle,
Optional.of(NetworkDirection.PLAY_TO_SERVER));

registerAcknowledge();
registerHandshakeMessage(ServerMessageSyncedEntityDataMapping.class, null);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.tacz.guns.network.message;

import com.tacz.guns.api.entity.IGunOperator;
import com.tacz.guns.api.item.IGun;
import com.tacz.guns.api.item.gun.FireMode;
import com.tacz.guns.entity.shooter.LivingEntityShoot;
import com.tacz.guns.entity.shooter.ShooterDataHolder;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.network.NetworkEvent;

import java.util.function.Supplier;

public class ClientMessagePlayerAutoShoot {
private final boolean shooting;

public ClientMessagePlayerAutoShoot(boolean shooting) {
this.shooting = shooting;
}

public static void encode(ClientMessagePlayerAutoShoot message, FriendlyByteBuf buf) {
buf.writeBoolean(message.shooting);
}

public static ClientMessagePlayerAutoShoot decode(FriendlyByteBuf buf) {
return new ClientMessagePlayerAutoShoot(buf.readBoolean());
}

public static void handle(ClientMessagePlayerAutoShoot message, Supplier<NetworkEvent.Context> contextSupplier) {
NetworkEvent.Context context = contextSupplier.get();
if (context.getDirection().getReceptionSide().isServer()) {
context.enqueueWork(() -> {
ServerPlayer entity = context.getSender();
if (entity == null) {
return;
}
ShooterDataHolder dataHolder = IGunOperator.fromLivingEntity(entity).getDataHolder();
if (message.shooting) {
ItemStack mainHandItem = entity.getMainHandItem();
if (!(mainHandItem.getItem() instanceof IGun iGun)) {
return;
}
FireMode fireMode = iGun.getFireMode(mainHandItem);
if (!LivingEntityShoot.isAutoShootMode(fireMode, iGun, mainHandItem)) {
return;
}
dataHolder.isAutoShooting = true;
} else {
dataHolder.isAutoShooting = false;
}
});
}
context.setPacketHandled(true);
}
}