From 4994e27a140eb11993ed5a1324a3b859e58d945f Mon Sep 17 00:00:00 2001 From: minecralogy <2661757878@qq.com> Date: Thu, 12 Feb 2026 06:32:14 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E5=AE=8C=E6=88=90=E9=99=A4=E4=BC=A4?= =?UTF-8?q?=E5=AE=B3=E5=A4=96=E5=AF=B9=E6=9B=B4=E9=AB=98=E5=B0=84=E9=80=9F?= =?UTF-8?q?=E7=9A=84=E9=80=82=E9=85=8D=EF=BC=88=E5=A6=823400rpm=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 + src/main/java/com/tacz/guns/GunMod.java | 2 + .../gameplay/IClientPlayerGunOperator.java | 2 + .../tacz/guns/api/entity/IGunOperator.java | 6 ++ .../guns/api/item/gun/AbstractGunItem.java | 2 +- .../client/gameplay/LocalPlayerShoot.java | 56 ++++++++++- .../com/tacz/guns/client/input/ShootKey.java | 13 ++- .../tacz/guns/entity/EntityKineticBullet.java | 92 +++++++++++++------ .../entity/shooter/LivingEntityShoot.java | 40 +++++++- .../tacz/guns/item/ModernKineticGunItem.java | 4 +- .../guns/item/ModernKineticGunScriptAPI.java | 24 ++++- .../guns/mixin/client/LocalPlayerMixin.java | 6 ++ .../guns/mixin/common/LivingEntityMixin.java | 19 +++- .../com/tacz/guns/network/NetworkHandler.java | 4 + .../ClientMessagePlayerShootBegin.java | 44 +++++++++ .../message/ClientMessagePlayerShootEnd.java | 44 +++++++++ .../java/com/tacz/guns/util/ShootBus.java | 63 +++++++++++++ 17 files changed, 381 insertions(+), 43 deletions(-) create mode 100644 src/main/java/com/tacz/guns/network/message/ClientMessagePlayerShootBegin.java create mode 100644 src/main/java/com/tacz/guns/network/message/ClientMessagePlayerShootEnd.java create mode 100644 src/main/java/com/tacz/guns/util/ShootBus.java diff --git a/build.gradle b/build.gradle index c1d0496b3..966f4f6d3 100644 --- a/build.gradle +++ b/build.gradle @@ -241,6 +241,9 @@ dependencies { jarJar.ranged(it, "[0.3.6,)") } annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' + jarJar('it.unimi.dsi:fastutil:8.5.18') { + jarJar.ranged(it, '[8.5,9.0)') + } } jar { diff --git a/src/main/java/com/tacz/guns/GunMod.java b/src/main/java/com/tacz/guns/GunMod.java index 2bb890c84..f1378b321 100644 --- a/src/main/java/com/tacz/guns/GunMod.java +++ b/src/main/java/com/tacz/guns/GunMod.java @@ -8,6 +8,7 @@ import com.tacz.guns.init.*; import com.tacz.guns.resource.GunPackLoader; import com.tacz.guns.resource.modifier.AttachmentPropertyManager; +import com.tacz.guns.util.ShootBus; import net.minecraft.server.packs.PackType; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.eventbus.api.IEventBus; @@ -56,6 +57,7 @@ public GunMod() { registerDefaultExtraGunPack(); AttachmentPropertyManager.registerModifier(); + ShootBus shootBus = new ShootBus(); } private static void registerDefaultExtraGunPack() { diff --git a/src/main/java/com/tacz/guns/api/client/gameplay/IClientPlayerGunOperator.java b/src/main/java/com/tacz/guns/api/client/gameplay/IClientPlayerGunOperator.java index 201deea05..6d73d8c38 100644 --- a/src/main/java/com/tacz/guns/api/client/gameplay/IClientPlayerGunOperator.java +++ b/src/main/java/com/tacz/guns/api/client/gameplay/IClientPlayerGunOperator.java @@ -27,6 +27,8 @@ static IClientPlayerGunOperator fromLocalPlayer(LocalPlayer player) { */ ShootResult shoot(); + boolean stopFullAuto(); + /** * 执行客户端切枪逻辑。 */ diff --git a/src/main/java/com/tacz/guns/api/entity/IGunOperator.java b/src/main/java/com/tacz/guns/api/entity/IGunOperator.java index 4ce730410..22dad563d 100644 --- a/src/main/java/com/tacz/guns/api/entity/IGunOperator.java +++ b/src/main/java/com/tacz/guns/api/entity/IGunOperator.java @@ -118,6 +118,12 @@ static IGunOperator fromLivingEntity(LivingEntity entity) { */ ShootResult shoot(Supplier pitch, Supplier yaw, long timestamp); + ShootResult shoot(Supplier pitch, Supplier yaw, long timestamp, int count, boolean source); + + boolean startFullAuto(long timestamp); + + boolean stopFullAuto(long timestamp); + /** * 服务端,该操作者是否受弹药数影响 * diff --git a/src/main/java/com/tacz/guns/api/item/gun/AbstractGunItem.java b/src/main/java/com/tacz/guns/api/item/gun/AbstractGunItem.java index 9a99c0f3e..35d0db28a 100644 --- a/src/main/java/com/tacz/guns/api/item/gun/AbstractGunItem.java +++ b/src/main/java/com/tacz/guns/api/item/gun/AbstractGunItem.java @@ -62,7 +62,7 @@ private static Comparator> idNameSor /** * 射击时触发 */ - public abstract void shoot(ShooterDataHolder dataHolder, ItemStack gunItem, Supplier pitch, Supplier yaw, LivingEntity shooter); + public abstract void shoot(ShooterDataHolder dataHolder, ItemStack gunItem, Supplier pitch, Supplier yaw, LivingEntity shooter, int count); /** * 开始换弹时调用 diff --git a/src/main/java/com/tacz/guns/client/gameplay/LocalPlayerShoot.java b/src/main/java/com/tacz/guns/client/gameplay/LocalPlayerShoot.java index 302a3c262..5939d94c8 100644 --- a/src/main/java/com/tacz/guns/client/gameplay/LocalPlayerShoot.java +++ b/src/main/java/com/tacz/guns/client/gameplay/LocalPlayerShoot.java @@ -1,5 +1,6 @@ package com.tacz.guns.client.gameplay; +import com.tacz.guns.GunMod; import com.tacz.guns.api.TimelessAPI; import com.tacz.guns.api.client.animation.statemachine.AnimationStateMachine; import com.tacz.guns.api.client.gameplay.IClientPlayerGunOperator; @@ -15,12 +16,15 @@ import com.tacz.guns.client.sound.SoundPlayManager; import com.tacz.guns.network.NetworkHandler; import com.tacz.guns.network.message.ClientMessagePlayerShoot; +import com.tacz.guns.network.message.ClientMessagePlayerShootBegin; +import com.tacz.guns.network.message.ClientMessagePlayerShootEnd; import com.tacz.guns.resource.index.CommonGunIndex; import com.tacz.guns.resource.modifier.AttachmentCacheProperty; import com.tacz.guns.resource.modifier.custom.SilenceModifier; import com.tacz.guns.resource.pojo.data.gun.Bolt; import com.tacz.guns.resource.pojo.data.gun.GunData; import com.tacz.guns.sound.SoundManager; +import com.tacz.guns.util.ShootBus; import it.unimi.dsi.fastutil.Pair; import net.minecraft.client.Minecraft; import net.minecraft.client.player.LocalPlayer; @@ -30,6 +34,8 @@ import net.minecraftforge.fml.LogicalSide; import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -39,6 +45,13 @@ public class LocalPlayerShoot { private static final Predicate SHOOT_LOCKED_CONDITION = operator -> operator.getSynShootCoolDown() > 0; private final LocalPlayerDataHolder data; private final LocalPlayer player; + private static final ScheduledExecutorService SHOOT_SCHEDULER = + Executors.newSingleThreadScheduledExecutor(r -> { + Thread t = new Thread(r, "Gun-AutoShoot-Scheduler"); + t.setDaemon(true); + return t; + }); + private ScheduledFuture shootTask; public LocalPlayerShoot(LocalPlayerDataHolder data, LocalPlayer player) { this.data = data; @@ -144,6 +157,40 @@ public ShootResult shoot() { private void doShoot(GunDisplayInstance display, IGun iGun, ItemStack mainHandItem, GunData gunData, long delay) { FireMode fireMode = iGun.getFireMode(mainHandItem); + if(fireMode == FireMode.AUTO) { + data.isShootRecorded = true; + NetworkHandler.CHANNEL.sendToServer(new ClientMessagePlayerShootBegin(data.clientShootTimestamp - data.clientBaseTimestamp)); + int rpm = iGun.getRPM(this.player.getMainHandItem()); + double roundsPerSecond = rpm / 60.0; + long intervalNanos = (long) (1_000_000_000.0 / roundsPerSecond); + ScheduledFuture task = SHOOT_SCHEDULER.scheduleAtFixedRate( + () -> { + Minecraft.getInstance().submitAsync(() -> { + // 触发击发事件 + boolean fire = !MinecraftForge.EVENT_BUS.post(new GunFireEvent(player, mainHandItem, LogicalSide.CLIENT)); + if (fire) { + // 动画和声音循环播放 + AnimationStateMachine animationStateMachine = display.getAnimationStateMachine(); + if (animationStateMachine != null) { + animationStateMachine.trigger(GunAnimationConstant.INPUT_SHOOT); + } + // 获取消音 + final boolean useSilenceSound = this.useSilenceSound(); + // 开火需要打断检视 + SoundPlayManager.stopPlayGunSound(display, SoundManager.INSPECT_SOUND); + if (useSilenceSound) { + SoundPlayManager.playSilenceSound(player, display, gunData); + } else { + SoundPlayManager.playShootSound(player, display, gunData); + } + } + }); + }, + 0, intervalNanos, TimeUnit.NANOSECONDS + ); + shootTask = task; + return; + } Bolt boltType = gunData.getBolt(); // 获取余弹数 boolean consumeAmmo = IGunOperator.fromLivingEntity(player).consumesAmmoOrNot(); @@ -216,7 +263,14 @@ private void doShoot(GunDisplayInstance display, IGun iGun, ItemStack mainHandIt count.getAndIncrement(); }, delay, period, TimeUnit.MILLISECONDS); } - + public boolean stopFullAuto() { + data.isShootRecorded = true; + if(shootTask != null) { + shootTask.cancel(true); + } + NetworkHandler.CHANNEL.sendToServer(new ClientMessagePlayerShootEnd(data.clientShootTimestamp - data.clientBaseTimestamp)); + return true; + } private boolean useSilenceSound() { AttachmentCacheProperty cacheProperty = IGunOperator.fromLivingEntity(player).getCacheProperty(); if (cacheProperty != null) { diff --git a/src/main/java/com/tacz/guns/client/input/ShootKey.java b/src/main/java/com/tacz/guns/client/input/ShootKey.java index c1c1f2b30..442608034 100644 --- a/src/main/java/com/tacz/guns/client/input/ShootKey.java +++ b/src/main/java/com/tacz/guns/client/input/ShootKey.java @@ -28,6 +28,7 @@ @OnlyIn(Dist.CLIENT) @Mod.EventBusSubscriber(value = Dist.CLIENT) public class ShootKey { + public static boolean pushDown = false; public static final KeyMapping SHOOT_KEY = new KeyMapping("key.tacz.shoot.desc", KeyConflictContext.IN_GAME, KeyModifier.NONE, @@ -63,10 +64,16 @@ public static void autoShoot(TickEvent.ClientTickEvent event) { // 非全自动情况,禁止连续开火 return; } - if (operator.shoot() == ShootResult.SUCCESS) { - lastTimeShootSuccess = true; + //开始全自动射击 + if(fireMode == FireMode.AUTO && !pushDown) { + if (operator.shoot() == ShootResult.SUCCESS) { + lastTimeShootSuccess = true; + } + pushDown = true; } } else { + operator.stopFullAuto(); + pushDown = false; lastTimeShootSuccess = false; } } @@ -154,4 +161,4 @@ public static boolean semiShootController(boolean isPress) { } return false; } -} +} \ No newline at end of file diff --git a/src/main/java/com/tacz/guns/entity/EntityKineticBullet.java b/src/main/java/com/tacz/guns/entity/EntityKineticBullet.java index b1f03ae3c..3ebd55a2d 100644 --- a/src/main/java/com/tacz/guns/entity/EntityKineticBullet.java +++ b/src/main/java/com/tacz/guns/entity/EntityKineticBullet.java @@ -128,6 +128,8 @@ public class EntityKineticBullet extends Projectile implements IEntityAdditional private boolean explosionKnockback = false; private boolean explosionDestroyBlock = false; private float damageModifier = 1; + //子弹数量(将多发子弹压入同一个实体内处理,实现超过1200的射速) + private int bulletCount = 1; // 穿透数 private int pierce = 1; // 初始位置 @@ -155,17 +157,21 @@ public EntityKineticBullet(EntityType type, double x, doub } public EntityKineticBullet(Level worldIn, LivingEntity throwerIn, ItemStack gunItem, ResourceLocation ammoId, ResourceLocation gunId, - ResourceLocation gunDisplayId, boolean isTracerAmmo, GunData gunData, BulletData bulletData) { - this(TYPE, worldIn, throwerIn, gunItem, ammoId, gunId, gunDisplayId, isTracerAmmo, gunData, bulletData); + ResourceLocation gunDisplayId, boolean isTracerAmmo, GunData gunData, BulletData bulletData) { + this(TYPE, worldIn, throwerIn, gunItem, ammoId, gunId, gunDisplayId, isTracerAmmo, gunData, bulletData, 1); + } + public EntityKineticBullet(Level worldIn, LivingEntity throwerIn, ItemStack gunItem, ResourceLocation ammoId, ResourceLocation gunId, + ResourceLocation gunDisplayId, boolean isTracerAmmo, GunData gunData, BulletData bulletData, int bulletCount) { + this(TYPE, worldIn, throwerIn, gunItem, ammoId, gunId, gunDisplayId, isTracerAmmo, gunData, bulletData, bulletCount); } public EntityKineticBullet(Level worldIn, LivingEntity throwerIn, ItemStack gunItem, ResourceLocation ammoId, ResourceLocation gunId, boolean isTracerAmmo, GunData gunData, BulletData bulletData) { - this(TYPE, worldIn, throwerIn, gunItem, ammoId, gunId, DefaultAssets.DEFAULT_GUN_DISPLAY_ID, isTracerAmmo, gunData, bulletData); + this(TYPE, worldIn, throwerIn, gunItem, ammoId, gunId, DefaultAssets.DEFAULT_GUN_DISPLAY_ID, isTracerAmmo, gunData, bulletData, 1); } protected EntityKineticBullet(EntityType type, Level worldIn, LivingEntity throwerIn, ItemStack gunItem, ResourceLocation ammoId, ResourceLocation gunId, ResourceLocation gunDisplayId, - boolean isTracerAmmo, GunData gunData, BulletData bulletData) { + boolean isTracerAmmo, GunData gunData, BulletData bulletData, int bulletCount) { this(type, throwerIn.getX(), throwerIn.getEyeY() - (double) 0.1F, throwerIn.getZ(), worldIn); this.setOwner(throwerIn); // gunId 提前赋值,以让 modifyProperty 可以在构造函数中运行 @@ -190,6 +196,7 @@ protected EntityKineticBullet(EntityType type, Level world this.igniteBlock = modifyProperty(IGNITE_BLOCK, Boolean.class, bulletData.getIgnite().isIgniteBlock() || ignite.isIgniteBlock()); this.damageAmount = cacheProperty.getCache(DamageModifier.ID); this.distanceAmount = modifyProperty(GunProperties.EFFECTIVE_RANGE, Float.class, cacheProperty.getCache(GunProperties.EFFECTIVE_RANGE)); + this.bulletCount = bulletCount; int pierce = modifyProperty(GunProperties.PIERCE, Integer.class, cacheProperty.getCache(GunProperties.PIERCE)); this.pierce = Mth.clamp(pierce, 1, Integer.MAX_VALUE); ExplosionData explosionData = Objects.requireNonNullElse(cacheProperty.getCache(ExplosionModifier.ID), DEFAULT_EXPLOSION_DATA); @@ -321,27 +328,54 @@ protected void onBulletTick() { } // 当子弹击中实体时,进行被命中的实体读取 if (hitEntities != null && !hitEntities.isEmpty()) { - EntityResult[] hitEntityResult = hitEntities.toArray(new EntityResult[0]); - // 对被命中的实体进行排序,按照距离子弹发射位置的距离进行升序排序 - for (int i = 0; (i < this.pierce || i < 1) && i < (hitEntityResult.length - 1); i++) { - int k = i; - for (int j = i + 1; j < hitEntityResult.length; j++) { - if (hitEntityResult[j].hitVec.distanceTo(startVec) < hitEntityResult[k].hitVec.distanceTo(startVec)) { - k = j; + //判断是否需要更新实体列表 + boolean entityDestroyed = false; + //对于本实体包含的每一发子弹进行一次判定 + for(int i = 0; i < bulletCount; i++) { + if(entityDestroyed) { + // 子弹的击中检测,穿透为 1 或者爆炸类弹药限制为一个实体穿透判定 + if (this.pierce <= 1 || this.explosion) { + EntityResult entityResult = EntityUtil.findEntityOnPath(this, startVec, endVec); + // 将单个命中是实体创建为单个内容的 list + if (entityResult != null) { + hitEntities = Collections.singletonList(entityResult); + } + } else { + hitEntities = EntityUtil.findEntitiesOnPath(this, startVec, endVec); + } + //判断是否还有实体 + if (hitEntities.isEmpty()) { + break; } } - EntityResult t = hitEntityResult[i]; - hitEntityResult[i] = hitEntityResult[k]; - hitEntityResult[k] = t; - } - for (EntityResult entityResult : hitEntityResult) { - result = new TacHitResult(entityResult); - this.onHitEntity((TacHitResult) result, startVec, endVec); - this.pierce--; - if (this.pierce < 1 || this.explosion) { - // 子弹已经穿透所有实体,结束子弹的飞行 - this.discard(); - return; + EntityResult[] hitEntityResult = hitEntities.toArray(new EntityResult[0]); + // 对被命中的实体进行排序,按照距离子弹发射位置的距离进行升序排序 + for (int j = 0; (i < this.pierce || j < 1) && j < (hitEntityResult.length - 1); j++) { + int l = j; + for (int k = j + 1; k < hitEntityResult.length; k++) { + if (hitEntityResult[k].hitVec.distanceTo(startVec) < hitEntityResult[l].hitVec.distanceTo(startVec)) { + l = k; + } + } + EntityResult t = hitEntityResult[j]; + hitEntityResult[j] = hitEntityResult[l]; + hitEntityResult[l] = t; + } + for (EntityResult entityResult : hitEntityResult) { + result = new TacHitResult(entityResult); + String hitResult = this.onHitEntity((TacHitResult) result, startVec, endVec); + if(Objects.equals(hitResult, "DEAD")) { + entityDestroyed = true; + } + + this.pierce--; + if (this.pierce < 1 || this.explosion) { + // 子弹已经穿透所有实体,结束子弹的飞行 + this.bulletCount--; + if(this.bulletCount == 0) + this.discard(); + return; + } } } } @@ -383,12 +417,12 @@ public static MaybeMultipartEntity of(Entity hitPart) { } } - protected void onHitEntity(TacHitResult result, Vec3 startVec, Vec3 endVec) { + protected String onHitEntity(TacHitResult result, Vec3 startVec, Vec3 endVec) { if (result.getEntity() instanceof ITargetEntity targetEntity) { DamageSource source = this.damageSources().thrown(this, this.getOwner()); targetEntity.onProjectileHit(this, result, source, this.getDamage(result.getLocation())); // 打靶直接返回 - return; + return "SHOT_TARGET"; } // 获取Pre事件必要的信息 Entity entity = result.getEntity(); @@ -403,7 +437,7 @@ protected void onHitEntity(TacHitResult result, Vec3 startVec, Vec3 endVec) { var preEvent = new EntityHurtByGunEvent.Pre(this, entity, attacker, this.gunId, this.gunDisplayId, damage, sources, headshot, headShotMultiplier, LogicalSide.SERVER); var cancelled = MinecraftForge.EVENT_BUS.post(preEvent); if (cancelled) { - return; + return "CANCELLED"; } // 刷新由Pre事件修改后的参数 entity = preEvent.getHurtEntity(); @@ -416,7 +450,7 @@ protected void onHitEntity(TacHitResult result, Vec3 startVec, Vec3 endVec) { headshot = preEvent.isHeadShot(); headShotMultiplier = preEvent.getHeadshotMultiplier(); if (entity == null) { - return; + return "NOT_HIT"; } // 点燃 if (this.igniteEntity && AmmoConfig.IGNITE_ENTITY.get()) { @@ -459,12 +493,15 @@ protected void onHitEntity(TacHitResult result, Vec3 startVec, Vec3 endVec) { if (livingCore.isDeadOrDying()) { MinecraftForge.EVENT_BUS.post(new EntityKillByGunEvent(this, livingCore, attacker, newGunId, gunDisplayId, damage, sources, headshot, headShotMultiplier, LogicalSide.SERVER)); NetworkHandler.sendToDimension(new ServerMessageGunKill(getId(), livingCore.getId(), attackerId, newGunId, gunDisplayId, damage, headshot, headShotMultiplier), livingCore); + return "DEAD"; } else { MinecraftForge.EVENT_BUS.post(new EntityHurtByGunEvent.Post(this, livingCore, attacker, newGunId, gunDisplayId, damage, sources, headshot, headShotMultiplier, LogicalSide.SERVER)); NetworkHandler.sendToDimension(new ServerMessageGunHurt(getId(), livingCore.getId(), attackerId, newGunId, gunDisplayId, damage, headshot, headShotMultiplier), livingCore); + return "ALIVE"; } } } + return "UNKNOWN"; } protected void onHitBlock(BlockHitResult result, Vec3 startVec, Vec3 endVec) { @@ -578,6 +615,7 @@ private void tacAttackEntity(MaybeMultipartEntity parts, float damage, Pair { + Thread t = new Thread(r, "Gun-AutoShoot-Scheduler"); + t.setDaemon(true); + return t; + }); + private ScheduledFuture shootTask; public LivingEntityShoot(LivingEntity shooter, ShooterDataHolder data, LivingEntityDrawGun draw) { this.shooter = shooter; this.data = data; this.draw = draw; } - public ShootResult shoot(Supplier pitch, Supplier yaw, long timestamp) { + public ShootResult shoot(Supplier pitch, Supplier yaw, long timestamp, int count, boolean fromServer) { if (data.currentGunItem == null) { return ShootResult.NOT_DRAW; } @@ -63,7 +76,7 @@ public ShootResult shoot(Supplier pitch, Supplier yaw, long timest return ShootResult.COOL_DOWN; } } - if (SyncConfig.SERVER_SHOOT_NETWORK_V.get()) { + if (SyncConfig.SERVER_SHOOT_NETWORK_V.get() && !fromServer) { // 根据 tick time 和 允许的网络延迟波动 计算 时间戳的接受窗口 MinecraftServer server = Objects.requireNonNull(shooter.getServer()); double tickTime = Math.max(server.tickTimes[server.getTickCount() % 100] * 1.0E-6D, 50); @@ -138,11 +151,28 @@ public ShootResult shoot(Supplier pitch, Supplier yaw, long timest data.shootTimestamp = timestamp; // 执行枪械射击逻辑 if (iGun instanceof AbstractGunItem logicGun) { - logicGun.shoot(data, currentGunItem, pitch, yaw, shooter); + logicGun.shoot(data, currentGunItem, pitch, yaw, shooter, count); } return ShootResult.SUCCESS; } - + public boolean startFullAuto(long timestamp) { + UUID playerUuid = this.shooter.getUUID(); + if(this.shooter.getMainHandItem().getItem() instanceof IGun iGun) { + int rpm = iGun.getRPM(this.shooter.getMainHandItem()); + double roundsPerSecond = rpm / 60.0; + long intervalNanos = (long) (1_000_000_000.0 / roundsPerSecond); + ScheduledFuture task = SHOOT_SCHEDULER.scheduleAtFixedRate( + () -> ShootBus.addShot(playerUuid), + 0, intervalNanos, TimeUnit.NANOSECONDS + ); + shootTask = task; + return true; + } + return false; + } + public boolean stopFullAuto(long timestamp) { + return shootTask.cancel(true); + } /** * 以当前时间戳查询射击冷却。返回值一般不会超过枪械的射击间隔 * @return 射击冷却 diff --git a/src/main/java/com/tacz/guns/item/ModernKineticGunItem.java b/src/main/java/com/tacz/guns/item/ModernKineticGunItem.java index 87a859851..96172994e 100644 --- a/src/main/java/com/tacz/guns/item/ModernKineticGunItem.java +++ b/src/main/java/com/tacz/guns/item/ModernKineticGunItem.java @@ -97,7 +97,7 @@ public boolean tickBolt(ShooterDataHolder dataHolder, ItemStack gunItem, LivingE } @Override - public void shoot(ShooterDataHolder dataHolder, ItemStack gunItem, Supplier pitch, Supplier yaw, LivingEntity shooter) { + public void shoot(ShooterDataHolder dataHolder, ItemStack gunItem, Supplier pitch, Supplier yaw, LivingEntity shooter, int count) { ModernKineticGunScriptAPI api = new ModernKineticGunScriptAPI(); api.setItemStack(gunItem); api.setShooter(shooter); @@ -114,7 +114,7 @@ public void shoot(ShooterDataHolder dataHolder, ItemStack gunItem, Supplier checkFunction(script.get("shoot"))) .ifPresentOrElse( func -> func.call(CoerceJavaToLua.coerce(api)), - () -> api.shootOnce(api.isShootingNeedConsumeAmmo())); + () -> api.shootOnce(api.isShootingNeedConsumeAmmo(), count)); } @Override diff --git a/src/main/java/com/tacz/guns/item/ModernKineticGunScriptAPI.java b/src/main/java/com/tacz/guns/item/ModernKineticGunScriptAPI.java index 6718147c2..4270e66a2 100644 --- a/src/main/java/com/tacz/guns/item/ModernKineticGunScriptAPI.java +++ b/src/main/java/com/tacz/guns/item/ModernKineticGunScriptAPI.java @@ -1,5 +1,6 @@ package com.tacz.guns.item; +import com.tacz.guns.GunMod; import com.tacz.guns.api.DefaultAssets; import com.tacz.guns.api.GunProperties; import com.tacz.guns.api.GunProperty; @@ -8,6 +9,7 @@ import com.tacz.guns.api.event.common.GunFireEvent; import com.tacz.guns.api.item.IAmmo; import com.tacz.guns.api.item.IAmmoBox; +import com.tacz.guns.api.item.IGun; import com.tacz.guns.api.item.attachment.AttachmentType; import com.tacz.guns.api.item.gun.AbstractGunItem; import com.tacz.guns.api.item.gun.FireMode; @@ -98,7 +100,7 @@ public LuaValue getCachedProperty(String id) { * 执行一次完整的射击逻辑,会考虑玩家的状态(是否在瞄准、是否在移动、是否在匍匐等)、配件数值影响、多弹丸散射、连发,播放开火音效、 * @param consumeAmmo 本次射击是否消耗弹药 */ - public void shootOnce(boolean consumeAmmo){ + public void shootOnce(boolean consumeAmmo, int count) { GunData gunData = gunIndex.getGunData(); BulletData bulletData = gunIndex.getBulletData(); IGunOperator gunOperator = IGunOperator.fromLivingEntity(shooter); @@ -143,6 +145,7 @@ public void shootOnce(boolean consumeAmmo){ long period = modifyProperty(GunProperties.RuntimeOnly.BURST_SHOOT_INTERVAL, Long.class, fireMode == FireMode.BURST ? gunData.getBurstShootInterval() : 1); CycleTaskHelper.addCycleTask(() -> { + int bulletCount = count; // 如果射击者死亡,取消射击 if (shooter.isDeadOrDying()) { return false; @@ -155,9 +158,24 @@ public void shootOnce(boolean consumeAmmo){ boolean fire = !MinecraftForge.EVENT_BUS.post(new GunFireEvent(shooter, itemStack, LogicalSide.SERVER)); if (fire) { NetworkHandler.sendToTrackingEntity(new ServerMessageGunFire(shooter.getId(), itemStack), shooter); + { + IGun gun = IGun.getIGunOrNull(shooter.getMainHandItem()); + GunMod.LOGGER.info("{} {}", gun.getCurrentAmmoCount(shooter.getMainHandItem()), bulletCount); + if (gun.getCurrentAmmoCount(shooter.getMainHandItem()) < bulletCount) { + GunMod.LOGGER.warn("FUCK!!!!"); + } + } // 削减弹药 if (consumeAmmo) { - if (!this.reduceAmmoOnce()) { + int i = bulletCount; + while (i > 0) { + if (!this.reduceAmmoOnce()) { + bulletCount -= i; + break; + } + i-=1; + } + if(bulletCount <= 0) { return false; } } @@ -179,7 +197,7 @@ public void shootOnce(boolean consumeAmmo){ for (int i = 0; i < bulletAmount; i++) { boolean isTracer = bulletData.hasTracerAmmo() && gunOperator.nextBulletIsTracer(bulletData.getTracerCountInterval()); EntityKineticBullet bullet = new EntityKineticBullet(world, shooter, itemStack, ammoId, gunId, - gunDisplayId, isTracer, gunData, bulletData); + gunDisplayId, isTracer, gunData, bulletData, bulletCount); bullet.applyShotgunDamageSpread(bulletAmount); abstractGunItem.doBulletSpread(dataHolder, itemStack, shooter, bullet, i, processedSpeed, inaccuracy, pitch, yaw); diff --git a/src/main/java/com/tacz/guns/mixin/client/LocalPlayerMixin.java b/src/main/java/com/tacz/guns/mixin/client/LocalPlayerMixin.java index 6326be452..5f9f52f71 100644 --- a/src/main/java/com/tacz/guns/mixin/client/LocalPlayerMixin.java +++ b/src/main/java/com/tacz/guns/mixin/client/LocalPlayerMixin.java @@ -36,6 +36,12 @@ public ShootResult shoot() { return tac$shoot.shoot(); } + @Unique + @Override + public boolean stopFullAuto() { + return tac$shoot.stopFullAuto(); + } + @Unique @Override public void draw(ItemStack lastItem) { diff --git a/src/main/java/com/tacz/guns/mixin/common/LivingEntityMixin.java b/src/main/java/com/tacz/guns/mixin/common/LivingEntityMixin.java index 5e2f18769..caa250e84 100644 --- a/src/main/java/com/tacz/guns/mixin/common/LivingEntityMixin.java +++ b/src/main/java/com/tacz/guns/mixin/common/LivingEntityMixin.java @@ -140,7 +140,24 @@ public ShootResult shoot(Supplier pitch, Supplier yaw) { @Unique @Override public ShootResult shoot(Supplier pitch, Supplier yaw, long timestamp) { - return tacz$shoot.shoot(pitch, yaw, timestamp); + return this.shoot(pitch, yaw, timestamp, 1, false); + } + @Unique + @Override + public ShootResult shoot(Supplier pitch, Supplier yaw, long timestamp, int count, boolean source) { + return tacz$shoot.shoot(pitch, yaw, timestamp, count, source); + } + + @Unique + @Override + public boolean startFullAuto(long timestamp) { + return tacz$shoot.startFullAuto(timestamp); + } + + @Unique + @Override + public boolean stopFullAuto(long timestamp) { + return tacz$shoot.stopFullAuto(timestamp); } @Unique diff --git a/src/main/java/com/tacz/guns/network/NetworkHandler.java b/src/main/java/com/tacz/guns/network/NetworkHandler.java index 1f0ce3308..409b011f2 100644 --- a/src/main/java/com/tacz/guns/network/NetworkHandler.java +++ b/src/main/java/com/tacz/guns/network/NetworkHandler.java @@ -39,6 +39,10 @@ public class NetworkHandler { public static void init() { CHANNEL.registerMessage(ID_COUNT.getAndIncrement(), ClientMessagePlayerShoot.class, ClientMessagePlayerShoot::encode, ClientMessagePlayerShoot::decode, ClientMessagePlayerShoot::handle, Optional.of(NetworkDirection.PLAY_TO_SERVER)); + CHANNEL.registerMessage(ID_COUNT.getAndIncrement(), ClientMessagePlayerShootBegin.class, ClientMessagePlayerShootBegin::encode, ClientMessagePlayerShootBegin::decode, ClientMessagePlayerShootBegin::handle, + Optional.of(NetworkDirection.PLAY_TO_SERVER)); + CHANNEL.registerMessage(ID_COUNT.getAndIncrement(), ClientMessagePlayerShootEnd.class, ClientMessagePlayerShootEnd::encode, ClientMessagePlayerShootEnd::decode, ClientMessagePlayerShootEnd::handle, + Optional.of(NetworkDirection.PLAY_TO_SERVER)); CHANNEL.registerMessage(ID_COUNT.getAndIncrement(), ClientMessagePlayerReloadGun.class, ClientMessagePlayerReloadGun::encode, ClientMessagePlayerReloadGun::decode, ClientMessagePlayerReloadGun::handle, Optional.of(NetworkDirection.PLAY_TO_SERVER)); CHANNEL.registerMessage(ID_COUNT.getAndIncrement(), ClientMessagePlayerCancelReload.class, ClientMessagePlayerCancelReload::encode, ClientMessagePlayerCancelReload::decode, ClientMessagePlayerCancelReload::handle, diff --git a/src/main/java/com/tacz/guns/network/message/ClientMessagePlayerShootBegin.java b/src/main/java/com/tacz/guns/network/message/ClientMessagePlayerShootBegin.java new file mode 100644 index 000000000..eff2c195d --- /dev/null +++ b/src/main/java/com/tacz/guns/network/message/ClientMessagePlayerShootBegin.java @@ -0,0 +1,44 @@ +package com.tacz.guns.network.message; + +import com.tacz.guns.api.entity.IGunOperator; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public class ClientMessagePlayerShootBegin { + /** + * 这里的 timestamp 应该是基于 base timestamp 的相对值 + */ + private long timestamp; + + public ClientMessagePlayerShootBegin() { + } + + public ClientMessagePlayerShootBegin(long timestamp) { + this.timestamp = timestamp; + } + + public static void encode(ClientMessagePlayerShootBegin message, FriendlyByteBuf buf) { + buf.writeLong(message.timestamp); + } + + public static ClientMessagePlayerShootBegin decode(FriendlyByteBuf buf) { + return new ClientMessagePlayerShootBegin(buf.readLong()); + } + + public static void handle(ClientMessagePlayerShootBegin message, Supplier contextSupplier) { + NetworkEvent.Context context = contextSupplier.get(); + if (context.getDirection().getReceptionSide().isServer()) { + context.enqueueWork(() -> { + ServerPlayer entity = context.getSender(); + if (entity == null) { + return; + } + IGunOperator.fromLivingEntity(entity).startFullAuto(message.timestamp); + }); + } + context.setPacketHandled(true); + } +} diff --git a/src/main/java/com/tacz/guns/network/message/ClientMessagePlayerShootEnd.java b/src/main/java/com/tacz/guns/network/message/ClientMessagePlayerShootEnd.java new file mode 100644 index 000000000..2117ffaaf --- /dev/null +++ b/src/main/java/com/tacz/guns/network/message/ClientMessagePlayerShootEnd.java @@ -0,0 +1,44 @@ +package com.tacz.guns.network.message; + +import com.tacz.guns.api.entity.IGunOperator; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public class ClientMessagePlayerShootEnd { + /** + * 这里的 timestamp 应该是基于 base timestamp 的相对值 + */ + private long timestamp; + + public ClientMessagePlayerShootEnd() { + } + + public ClientMessagePlayerShootEnd(long timestamp) { + this.timestamp = timestamp; + } + + public static void encode(ClientMessagePlayerShootEnd message, FriendlyByteBuf buf) { + buf.writeLong(message.timestamp); + } + + public static ClientMessagePlayerShootEnd decode(FriendlyByteBuf buf) { + return new ClientMessagePlayerShootEnd(buf.readLong()); + } + + public static void handle(ClientMessagePlayerShootEnd message, Supplier contextSupplier) { + NetworkEvent.Context context = contextSupplier.get(); + if (context.getDirection().getReceptionSide().isServer()) { + context.enqueueWork(() -> { + ServerPlayer entity = context.getSender(); + if (entity == null) { + return; + } + IGunOperator.fromLivingEntity(entity).stopFullAuto(message.timestamp); + }); + } + context.setPacketHandled(true); + } +} diff --git a/src/main/java/com/tacz/guns/util/ShootBus.java b/src/main/java/com/tacz/guns/util/ShootBus.java new file mode 100644 index 000000000..27832f712 --- /dev/null +++ b/src/main/java/com/tacz/guns/util/ShootBus.java @@ -0,0 +1,63 @@ +package com.tacz.guns.util; + +import com.tacz.guns.GunMod; +import com.tacz.guns.api.entity.IGunOperator; +import com.tacz.guns.client.gameplay.LocalPlayerDataHolder; +import com.tacz.guns.entity.shooter.ShooterDataHolder; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import net.minecraft.client.Minecraft; +import net.minecraft.server.level.ServerEntity; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.LivingEntity; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +import java.util.Iterator; +import java.util.UUID; + +import static com.tacz.guns.util.InputExtraCheck.isInGame; +@Mod.EventBusSubscriber(modid = GunMod.MOD_ID) +public class ShootBus { + private static ShootBus instance; + private final Object2IntOpenHashMap counter = new Object2IntOpenHashMap<>(); + + public ShootBus() { + if (instance == null) { + instance = this; + } + counter.defaultReturnValue(0); // 未找到时返回 0,而非 null + } + public static ShootBus getInstance() { + return instance; + } + // 射出一发子弹 → 原子化 +1 + public static void addShot(UUID playerUUID) { + ShootBus.getInstance().counter.addTo(playerUUID, 1); + } + @SubscribeEvent + // 每刻调用:处理所有累积子弹,然后清空 + public static void processAndClear(TickEvent.ServerTickEvent event) { + if (event.phase != TickEvent.Phase.END && !isInGame()) { + return; + } + Iterator> it = ShootBus.getInstance().counter.object2IntEntrySet().iterator(); + while (it.hasNext()) { + Object2IntMap.Entry entry = it.next(); + UUID playerUUID = entry.getKey(); + int bulletCount = entry.getIntValue(); + ServerPlayer player = event.getServer().getPlayerList().getPlayer(playerUUID); + if (player == null) continue; + IGunOperator shooter = IGunOperator.fromLivingEntity(player); + GunMod.LOGGER.info("ShootBus:56 {} {}", playerUUID.toString(), bulletCount); + ShooterDataHolder data = shooter.getDataHolder(); + // 射击 + shooter.shoot(player::getXRot, player::getYRot, System.currentTimeMillis() - data.baseTimestamp, bulletCount, true); + // 立即从映射中删除,保证下一轮遍历只有真正开火的枪 + it.remove(); + } + } + +} From 16c0b536f536d970a78c2717fc2adf30e9ce3618 Mon Sep 17 00:00:00 2001 From: minecralogy <2661757878@qq.com> Date: Thu, 12 Feb 2026 08:49:43 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=AF=B9=E9=AB=98?= =?UTF-8?q?=E5=B0=84=E9=80=9F=E7=9A=84=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/tacz/guns/GunMod.java | 1 - .../gameplay/IClientPlayerGunOperator.java | 5 +- .../tacz/guns/api/entity/IGunOperator.java | 21 +++++++- .../client/gameplay/LocalPlayerShoot.java | 1 + .../tacz/guns/entity/EntityKineticBullet.java | 6 +-- .../entity/shooter/LivingEntityShoot.java | 11 ++++- .../guns/item/ModernKineticGunScriptAPI.java | 10 ++-- .../guns/mixin/client/LocalPlayerMixin.java | 4 +- .../guns/mixin/common/LivingEntityMixin.java | 8 +-- .../com/tacz/guns/network/NetworkHandler.java | 2 + .../ClientMessagePlayerShootBegin.java | 4 +- .../message/ClientMessagePlayerShootEnd.java | 6 +-- .../network/message/ServerMessageGunStop.java | 49 +++++++++++++++++++ .../java/com/tacz/guns/util/ShootBus.java | 22 +++++---- 14 files changed, 112 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/tacz/guns/network/message/ServerMessageGunStop.java diff --git a/src/main/java/com/tacz/guns/GunMod.java b/src/main/java/com/tacz/guns/GunMod.java index f1378b321..8dbe82695 100644 --- a/src/main/java/com/tacz/guns/GunMod.java +++ b/src/main/java/com/tacz/guns/GunMod.java @@ -29,7 +29,6 @@ public class GunMod { * 默认模型包文件夹 */ public static final String DEFAULT_GUN_PACK_NAME = "tacz_default_gun"; - public GunMod() { ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, CommonConfig.init()); ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, ServerConfig.init()); diff --git a/src/main/java/com/tacz/guns/api/client/gameplay/IClientPlayerGunOperator.java b/src/main/java/com/tacz/guns/api/client/gameplay/IClientPlayerGunOperator.java index 6d73d8c38..191e57ea9 100644 --- a/src/main/java/com/tacz/guns/api/client/gameplay/IClientPlayerGunOperator.java +++ b/src/main/java/com/tacz/guns/api/client/gameplay/IClientPlayerGunOperator.java @@ -27,7 +27,10 @@ static IClientPlayerGunOperator fromLocalPlayer(LocalPlayer player) { */ ShootResult shoot(); - boolean stopFullAuto(); + /** + * 停止全自动射击 + */ + void stopFullAuto(); /** * 执行客户端切枪逻辑。 diff --git a/src/main/java/com/tacz/guns/api/entity/IGunOperator.java b/src/main/java/com/tacz/guns/api/entity/IGunOperator.java index 22dad563d..0cbb260ae 100644 --- a/src/main/java/com/tacz/guns/api/entity/IGunOperator.java +++ b/src/main/java/com/tacz/guns/api/entity/IGunOperator.java @@ -118,11 +118,28 @@ static IGunOperator fromLivingEntity(LivingEntity entity) { */ ShootResult shoot(Supplier pitch, Supplier yaw, long timestamp); + /** + * 从实体的位置,向指定的方向开枪。计算冷却的时候使用指定的 timestamp。指定包含的弹药数和射击发起的来源 + * + * @param pitch 开火方向的俯仰角(即 xRot ) + * @param yaw 开火方向的偏航角(即 yRot ) + * @param timestamp 指定的时间戳,为偏移时间戳(相对于 base timestamp 的时间戳) + * @param count 包含的弹药数 + * @param source true为来自服务器,false为来自客户端 + * @return 本次射击的结果 + */ ShootResult shoot(Supplier pitch, Supplier yaw, long timestamp, int count, boolean source); - boolean startFullAuto(long timestamp); + /** + * 开始全自动射击 + * @param timestamp 开始的时间戳 + */ + void startFullAuto(long timestamp); - boolean stopFullAuto(long timestamp); + /** + * 停止全自动射击 + */ + void stopFullAuto(); /** * 服务端,该操作者是否受弹药数影响 diff --git a/src/main/java/com/tacz/guns/client/gameplay/LocalPlayerShoot.java b/src/main/java/com/tacz/guns/client/gameplay/LocalPlayerShoot.java index 5939d94c8..019b692bc 100644 --- a/src/main/java/com/tacz/guns/client/gameplay/LocalPlayerShoot.java +++ b/src/main/java/com/tacz/guns/client/gameplay/LocalPlayerShoot.java @@ -157,6 +157,7 @@ public ShootResult shoot() { private void doShoot(GunDisplayInstance display, IGun iGun, ItemStack mainHandItem, GunData gunData, long delay) { FireMode fireMode = iGun.getFireMode(mainHandItem); + //如果是全自动则按照射速应用后坐力,知道松开射击键或弹药耗尽由服务器调用stopFullAuto() if(fireMode == FireMode.AUTO) { data.isShootRecorded = true; NetworkHandler.CHANNEL.sendToServer(new ClientMessagePlayerShootBegin(data.clientShootTimestamp - data.clientBaseTimestamp)); diff --git a/src/main/java/com/tacz/guns/entity/EntityKineticBullet.java b/src/main/java/com/tacz/guns/entity/EntityKineticBullet.java index 3ebd55a2d..634ef9b7a 100644 --- a/src/main/java/com/tacz/guns/entity/EntityKineticBullet.java +++ b/src/main/java/com/tacz/guns/entity/EntityKineticBullet.java @@ -331,7 +331,8 @@ protected void onBulletTick() { //判断是否需要更新实体列表 boolean entityDestroyed = false; //对于本实体包含的每一发子弹进行一次判定 - for(int i = 0; i < bulletCount; i++) { + int count = bulletCount; + for(int i = 0; i < count; i++) { if(entityDestroyed) { // 子弹的击中检测,穿透为 1 或者爆炸类弹药限制为一个实体穿透判定 if (this.pierce <= 1 || this.explosion) { @@ -372,9 +373,9 @@ protected void onBulletTick() { if (this.pierce < 1 || this.explosion) { // 子弹已经穿透所有实体,结束子弹的飞行 this.bulletCount--; + //减少子弹数量,如果已全部消耗则清除自身 if(this.bulletCount == 0) this.discard(); - return; } } } @@ -615,7 +616,6 @@ private void tacAttackEntity(MaybeMultipartEntity parts, float damage, Pair pitch, Supplier yaw, long timest } } if (SyncConfig.SERVER_SHOOT_NETWORK_V.get() && !fromServer) { - // 根据 tick time 和 允许的网络延迟波动 计算 时间戳的接受窗口 + // 根据 tick time 和 允许的网络延迟波动 计算 时间戳的接受窗口 如果来自服务器则无需计算窗口可直接使用 MinecraftServer server = Objects.requireNonNull(shooter.getServer()); double tickTime = Math.max(server.tickTimes[server.getTickCount() % 100] * 1.0E-6D, 50); long alpha = System.currentTimeMillis() - data.baseTimestamp - timestamp; @@ -153,6 +155,11 @@ public ShootResult shoot(Supplier pitch, Supplier yaw, long timest if (iGun instanceof AbstractGunItem logicGun) { logicGun.shoot(data, currentGunItem, pitch, yaw, shooter, count); } + if(((IGun)shooter.getMainHandItem().getItem()).getFireMode(shooter.getMainHandItem()) == FireMode.AUTO && + ((IGun)shooter.getMainHandItem().getItem()).getCurrentAmmoCount(shooter.getMainHandItem()) <= 0 && + !((IGun)shooter.getMainHandItem().getItem()).hasBulletInBarrel(shooter.getMainHandItem())) { + NetworkHandler.sendToClientPlayer(new ServerMessageGunStop(shooter.getId()), (Player) shooter); + } return ShootResult.SUCCESS; } public boolean startFullAuto(long timestamp) { @@ -170,7 +177,7 @@ public boolean startFullAuto(long timestamp) { } return false; } - public boolean stopFullAuto(long timestamp) { + public boolean stopFullAuto() { return shootTask.cancel(true); } /** diff --git a/src/main/java/com/tacz/guns/item/ModernKineticGunScriptAPI.java b/src/main/java/com/tacz/guns/item/ModernKineticGunScriptAPI.java index 4270e66a2..22aebd8cc 100644 --- a/src/main/java/com/tacz/guns/item/ModernKineticGunScriptAPI.java +++ b/src/main/java/com/tacz/guns/item/ModernKineticGunScriptAPI.java @@ -21,6 +21,7 @@ import com.tacz.guns.entity.shooter.ShooterDataHolder; import com.tacz.guns.network.NetworkHandler; import com.tacz.guns.network.message.event.ServerMessageGunFire; +import com.tacz.guns.network.message.ServerMessageGunStop; import com.tacz.guns.resource.index.CommonGunIndex; import com.tacz.guns.resource.modifier.AttachmentCacheProperty; import com.tacz.guns.resource.modifier.custom.SilenceModifier; @@ -32,11 +33,13 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.capabilities.ForgeCapabilities; import net.minecraftforge.fml.LogicalSide; +import org.antlr.v4.parse.ANTLRParser; import org.luaj.vm2.LuaError; import org.luaj.vm2.LuaFunction; import org.luaj.vm2.LuaTable; @@ -158,13 +161,6 @@ public void shootOnce(boolean consumeAmmo, int count) { boolean fire = !MinecraftForge.EVENT_BUS.post(new GunFireEvent(shooter, itemStack, LogicalSide.SERVER)); if (fire) { NetworkHandler.sendToTrackingEntity(new ServerMessageGunFire(shooter.getId(), itemStack), shooter); - { - IGun gun = IGun.getIGunOrNull(shooter.getMainHandItem()); - GunMod.LOGGER.info("{} {}", gun.getCurrentAmmoCount(shooter.getMainHandItem()), bulletCount); - if (gun.getCurrentAmmoCount(shooter.getMainHandItem()) < bulletCount) { - GunMod.LOGGER.warn("FUCK!!!!"); - } - } // 削减弹药 if (consumeAmmo) { int i = bulletCount; diff --git a/src/main/java/com/tacz/guns/mixin/client/LocalPlayerMixin.java b/src/main/java/com/tacz/guns/mixin/client/LocalPlayerMixin.java index 5f9f52f71..e5b90e5df 100644 --- a/src/main/java/com/tacz/guns/mixin/client/LocalPlayerMixin.java +++ b/src/main/java/com/tacz/guns/mixin/client/LocalPlayerMixin.java @@ -38,8 +38,8 @@ public ShootResult shoot() { @Unique @Override - public boolean stopFullAuto() { - return tac$shoot.stopFullAuto(); + public void stopFullAuto() { + tac$shoot.stopFullAuto(); } @Unique diff --git a/src/main/java/com/tacz/guns/mixin/common/LivingEntityMixin.java b/src/main/java/com/tacz/guns/mixin/common/LivingEntityMixin.java index caa250e84..427106f8b 100644 --- a/src/main/java/com/tacz/guns/mixin/common/LivingEntityMixin.java +++ b/src/main/java/com/tacz/guns/mixin/common/LivingEntityMixin.java @@ -150,14 +150,14 @@ public ShootResult shoot(Supplier pitch, Supplier yaw, long timest @Unique @Override - public boolean startFullAuto(long timestamp) { - return tacz$shoot.startFullAuto(timestamp); + public void startFullAuto(long timestamp) { + tacz$shoot.startFullAuto(timestamp); } @Unique @Override - public boolean stopFullAuto(long timestamp) { - return tacz$shoot.stopFullAuto(timestamp); + public void stopFullAuto() { + tacz$shoot.stopFullAuto(); } @Unique diff --git a/src/main/java/com/tacz/guns/network/NetworkHandler.java b/src/main/java/com/tacz/guns/network/NetworkHandler.java index 409b011f2..33e211d42 100644 --- a/src/main/java/com/tacz/guns/network/NetworkHandler.java +++ b/src/main/java/com/tacz/guns/network/NetworkHandler.java @@ -43,6 +43,8 @@ public static void init() { Optional.of(NetworkDirection.PLAY_TO_SERVER)); CHANNEL.registerMessage(ID_COUNT.getAndIncrement(), ClientMessagePlayerShootEnd.class, ClientMessagePlayerShootEnd::encode, ClientMessagePlayerShootEnd::decode, ClientMessagePlayerShootEnd::handle, Optional.of(NetworkDirection.PLAY_TO_SERVER)); + CHANNEL.registerMessage(ID_COUNT.getAndIncrement(), ServerMessageGunStop.class, ServerMessageGunStop::encode, ServerMessageGunStop::decode, ServerMessageGunStop::handle, + Optional.of(NetworkDirection.PLAY_TO_CLIENT)); CHANNEL.registerMessage(ID_COUNT.getAndIncrement(), ClientMessagePlayerReloadGun.class, ClientMessagePlayerReloadGun::encode, ClientMessagePlayerReloadGun::decode, ClientMessagePlayerReloadGun::handle, Optional.of(NetworkDirection.PLAY_TO_SERVER)); CHANNEL.registerMessage(ID_COUNT.getAndIncrement(), ClientMessagePlayerCancelReload.class, ClientMessagePlayerCancelReload::encode, ClientMessagePlayerCancelReload::decode, ClientMessagePlayerCancelReload::handle, diff --git a/src/main/java/com/tacz/guns/network/message/ClientMessagePlayerShootBegin.java b/src/main/java/com/tacz/guns/network/message/ClientMessagePlayerShootBegin.java index eff2c195d..a129c971a 100644 --- a/src/main/java/com/tacz/guns/network/message/ClientMessagePlayerShootBegin.java +++ b/src/main/java/com/tacz/guns/network/message/ClientMessagePlayerShootBegin.java @@ -8,9 +8,7 @@ import java.util.function.Supplier; public class ClientMessagePlayerShootBegin { - /** - * 这里的 timestamp 应该是基于 base timestamp 的相对值 - */ + private long timestamp; public ClientMessagePlayerShootBegin() { diff --git a/src/main/java/com/tacz/guns/network/message/ClientMessagePlayerShootEnd.java b/src/main/java/com/tacz/guns/network/message/ClientMessagePlayerShootEnd.java index 2117ffaaf..c275a6d1d 100644 --- a/src/main/java/com/tacz/guns/network/message/ClientMessagePlayerShootEnd.java +++ b/src/main/java/com/tacz/guns/network/message/ClientMessagePlayerShootEnd.java @@ -8,9 +8,7 @@ import java.util.function.Supplier; public class ClientMessagePlayerShootEnd { - /** - * 这里的 timestamp 应该是基于 base timestamp 的相对值 - */ + private long timestamp; public ClientMessagePlayerShootEnd() { @@ -36,7 +34,7 @@ public static void handle(ClientMessagePlayerShootEnd message, Supplier contextSupplier) { + NetworkEvent.Context context = contextSupplier.get(); + if (context.getDirection().getReceptionSide().isClient()) { + context.enqueueWork(() -> doClientEvent(message, context)); + } + context.setPacketHandled(true); + } + + @OnlyIn(Dist.CLIENT) + private static void doClientEvent(ServerMessageGunStop message, NetworkEvent.Context context) { + ClientLevel level = Minecraft.getInstance().level; + if (level == null) { + return; + } + if (level.getEntity(message.shooterId) instanceof LivingEntity shooter) { + IGunOperator.fromLivingEntity(shooter).stopFullAuto(); + } + } +} diff --git a/src/main/java/com/tacz/guns/util/ShootBus.java b/src/main/java/com/tacz/guns/util/ShootBus.java index 27832f712..94de4a185 100644 --- a/src/main/java/com/tacz/guns/util/ShootBus.java +++ b/src/main/java/com/tacz/guns/util/ShootBus.java @@ -2,15 +2,10 @@ import com.tacz.guns.GunMod; import com.tacz.guns.api.entity.IGunOperator; -import com.tacz.guns.client.gameplay.LocalPlayerDataHolder; import com.tacz.guns.entity.shooter.ShooterDataHolder; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import net.minecraft.client.Minecraft; -import net.minecraft.server.level.ServerEntity; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.LivingEntity; -import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; @@ -19,6 +14,10 @@ import java.util.UUID; import static com.tacz.guns.util.InputExtraCheck.isInGame; + +/** + * 增加了一条专门用于处理高频射击的线 + */ @Mod.EventBusSubscriber(modid = GunMod.MOD_ID) public class ShootBus { private static ShootBus instance; @@ -33,12 +32,18 @@ public ShootBus() { public static ShootBus getInstance() { return instance; } - // 射出一发子弹 → 原子化 +1 + + /** + * 射击一发子弹(只记录) + * @param playerUUID 设计者的UUID + */ public static void addShot(UUID playerUUID) { ShootBus.getInstance().counter.addTo(playerUUID, 1); } @SubscribeEvent - // 每刻调用:处理所有累积子弹,然后清空 + /** + * 每刻统一处理一次,把积攒的所有子弹作为一个弹射物发射出去 + */ public static void processAndClear(TickEvent.ServerTickEvent event) { if (event.phase != TickEvent.Phase.END && !isInGame()) { return; @@ -51,11 +56,10 @@ public static void processAndClear(TickEvent.ServerTickEvent event) { ServerPlayer player = event.getServer().getPlayerList().getPlayer(playerUUID); if (player == null) continue; IGunOperator shooter = IGunOperator.fromLivingEntity(player); - GunMod.LOGGER.info("ShootBus:56 {} {}", playerUUID.toString(), bulletCount); ShooterDataHolder data = shooter.getDataHolder(); // 射击 shooter.shoot(player::getXRot, player::getYRot, System.currentTimeMillis() - data.baseTimestamp, bulletCount, true); - // 立即从映射中删除,保证下一轮遍历只有真正开火的枪 + //从中清除 it.remove(); } } From 154005188675168566839da12610bcfeffe6bd63 Mon Sep 17 00:00:00 2001 From: minecralogy <2661757878@qq.com> Date: Thu, 12 Feb 2026 09:18:55 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=AF=B9=E9=AB=98?= =?UTF-8?q?=E5=B0=84=E9=80=9F=E7=9A=84=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 966f4f6d3..56cb6f73e 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ apply plugin: 'eclipse' apply plugin: 'org.spongepowered.mixin' apply plugin: 'java' -version = "1.1.7-hotfix" +version = "1.1.8-hotfix" // 版本号,正式发布需要修改这一行 //version = FORMAT.format(new Date()) group = "com.tacz" From 0edf66c59ee44275a59002c0a4e278854a7e1413 Mon Sep 17 00:00:00 2001 From: minecralogy <2661757878@qq.com> Date: Thu, 12 Feb 2026 10:03:27 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=AF=B9=E9=AB=98?= =?UTF-8?q?=E5=B0=84=E9=80=9F=E7=9A=84=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 56cb6f73e..35bbec3cc 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ apply plugin: 'eclipse' apply plugin: 'org.spongepowered.mixin' apply plugin: 'java' -version = "1.1.8-hotfix" +version = "1.1.7-hotfix" // 版本号,正式发布需要修改这一行 //version = FORMAT.format(new Date()) group = "com.tacz" @@ -241,9 +241,12 @@ dependencies { jarJar.ranged(it, "[0.3.6,)") } annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' - jarJar('it.unimi.dsi:fastutil:8.5.18') { + implementation 'it.unimi.dsi:fastutil:8.5.18' + /* + implementation(jarJar('it.unimi.dsi:fastutil:8.5.18')) { jarJar.ranged(it, '[8.5,9.0)') - } + }*/ + annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' } jar { @@ -265,4 +268,4 @@ java { tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' -} +} \ No newline at end of file From bf1e5aa76f74a5fd24304126055cf73142cb164b Mon Sep 17 00:00:00 2001 From: lljshh <72928567+lljshh@users.noreply.github.com> Date: Sun, 15 Feb 2026 12:25:31 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20ShootBus.java?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/tacz/guns/util/ShootBus.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/tacz/guns/util/ShootBus.java b/src/main/java/com/tacz/guns/util/ShootBus.java index 94de4a185..288182d89 100644 --- a/src/main/java/com/tacz/guns/util/ShootBus.java +++ b/src/main/java/com/tacz/guns/util/ShootBus.java @@ -16,7 +16,7 @@ import static com.tacz.guns.util.InputExtraCheck.isInGame; /** - * 增加了一条专门用于处理高频射击的线 + * 增加了一条专门用于处理全自动射击的线 */ @Mod.EventBusSubscriber(modid = GunMod.MOD_ID) public class ShootBus { @@ -35,7 +35,7 @@ public static ShootBus getInstance() { /** * 射击一发子弹(只记录) - * @param playerUUID 设计者的UUID + * @param playerUUID 射击者的UUID */ public static void addShot(UUID playerUUID) { ShootBus.getInstance().counter.addTo(playerUUID, 1); From 2a34dbc6e8040c4910f5217142e7ea9973253616 Mon Sep 17 00:00:00 2001 From: minecralogy <2661757878@qq.com> Date: Mon, 16 Feb 2026 20:57:05 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E5=BE=AE=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/tacz/guns/GunMod.java | 1 - .../tacz/guns/api/entity/IGunOperator.java | 4 +-- .../client/gameplay/LocalPlayerShoot.java | 2 +- .../java/com/tacz/guns/util/ShootBus.java | 34 ++++++++++--------- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/tacz/guns/GunMod.java b/src/main/java/com/tacz/guns/GunMod.java index 8dbe82695..5c9b1353f 100644 --- a/src/main/java/com/tacz/guns/GunMod.java +++ b/src/main/java/com/tacz/guns/GunMod.java @@ -56,7 +56,6 @@ public GunMod() { registerDefaultExtraGunPack(); AttachmentPropertyManager.registerModifier(); - ShootBus shootBus = new ShootBus(); } private static void registerDefaultExtraGunPack() { diff --git a/src/main/java/com/tacz/guns/api/entity/IGunOperator.java b/src/main/java/com/tacz/guns/api/entity/IGunOperator.java index 0cbb260ae..948f44a4c 100644 --- a/src/main/java/com/tacz/guns/api/entity/IGunOperator.java +++ b/src/main/java/com/tacz/guns/api/entity/IGunOperator.java @@ -125,10 +125,10 @@ static IGunOperator fromLivingEntity(LivingEntity entity) { * @param yaw 开火方向的偏航角(即 yRot ) * @param timestamp 指定的时间戳,为偏移时间戳(相对于 base timestamp 的时间戳) * @param count 包含的弹药数 - * @param source true为来自服务器,false为来自客户端 + * @param fromServer true为来自服务器,false为来自客户端 * @return 本次射击的结果 */ - ShootResult shoot(Supplier pitch, Supplier yaw, long timestamp, int count, boolean source); + ShootResult shoot(Supplier pitch, Supplier yaw, long timestamp, int count, boolean fromServer); /** * 开始全自动射击 diff --git a/src/main/java/com/tacz/guns/client/gameplay/LocalPlayerShoot.java b/src/main/java/com/tacz/guns/client/gameplay/LocalPlayerShoot.java index 019b692bc..8610a4f2b 100644 --- a/src/main/java/com/tacz/guns/client/gameplay/LocalPlayerShoot.java +++ b/src/main/java/com/tacz/guns/client/gameplay/LocalPlayerShoot.java @@ -157,7 +157,7 @@ public ShootResult shoot() { private void doShoot(GunDisplayInstance display, IGun iGun, ItemStack mainHandItem, GunData gunData, long delay) { FireMode fireMode = iGun.getFireMode(mainHandItem); - //如果是全自动则按照射速应用后坐力,知道松开射击键或弹药耗尽由服务器调用stopFullAuto() + //如果是全自动则按照射速应用后坐力,直到松开射击键或弹药耗尽由服务器调用stopFullAuto() if(fireMode == FireMode.AUTO) { data.isShootRecorded = true; NetworkHandler.CHANNEL.sendToServer(new ClientMessagePlayerShootBegin(data.clientShootTimestamp - data.clientBaseTimestamp)); diff --git a/src/main/java/com/tacz/guns/util/ShootBus.java b/src/main/java/com/tacz/guns/util/ShootBus.java index 288182d89..d58344f6f 100644 --- a/src/main/java/com/tacz/guns/util/ShootBus.java +++ b/src/main/java/com/tacz/guns/util/ShootBus.java @@ -7,6 +7,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; @@ -16,29 +17,22 @@ import static com.tacz.guns.util.InputExtraCheck.isInGame; /** - * 增加了一条专门用于处理全自动射击的线 + * 增加了一个专门用于处理全自动射击的类 */ @Mod.EventBusSubscriber(modid = GunMod.MOD_ID) public class ShootBus { - private static ShootBus instance; - private final Object2IntOpenHashMap counter = new Object2IntOpenHashMap<>(); - - public ShootBus() { - if (instance == null) { - instance = this; - } - counter.defaultReturnValue(0); // 未找到时返回 0,而非 null - } - public static ShootBus getInstance() { - return instance; + private static final Object2IntOpenHashMap SHOT_COUNTER = new Object2IntOpenHashMap<>(); + static { + SHOT_COUNTER.defaultReturnValue(0); } + private ShootBus() {} /** * 射击一发子弹(只记录) * @param playerUUID 射击者的UUID */ public static void addShot(UUID playerUUID) { - ShootBus.getInstance().counter.addTo(playerUUID, 1); + ShootBus.SHOT_COUNTER.addTo(playerUUID, 1); } @SubscribeEvent /** @@ -48,13 +42,16 @@ public static void processAndClear(TickEvent.ServerTickEvent event) { if (event.phase != TickEvent.Phase.END && !isInGame()) { return; } - Iterator> it = ShootBus.getInstance().counter.object2IntEntrySet().iterator(); + Iterator> it = ShootBus.SHOT_COUNTER.object2IntEntrySet().iterator(); while (it.hasNext()) { Object2IntMap.Entry entry = it.next(); UUID playerUUID = entry.getKey(); int bulletCount = entry.getIntValue(); ServerPlayer player = event.getServer().getPlayerList().getPlayer(playerUUID); - if (player == null) continue; + if (player == null) { + it.remove(); + continue; + } IGunOperator shooter = IGunOperator.fromLivingEntity(player); ShooterDataHolder data = shooter.getDataHolder(); // 射击 @@ -63,5 +60,10 @@ public static void processAndClear(TickEvent.ServerTickEvent event) { it.remove(); } } - + @SubscribeEvent + public static void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) { + if (event.getEntity() instanceof ServerPlayer serverPlayer) { + SHOT_COUNTER.removeInt(serverPlayer.getUUID()); + } + } }