From 83d2245385dad34c10f258e27b912765935c59b7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Apr 2026 09:31:46 +0000 Subject: [PATCH 1/5] Refactor bullet mechanics: add HEAT/SUBCALIBER/FRAG enum types, penetration loss, and fragmentation damage - Add HEAT, SUBCALIBER, and FRAG to BulletType enum, deprecate isHeat boolean (backwards compatible) - Add fragmentation parameters: fragConeAngle, fragHitProbability, fragDamage to JSONBullet - Implement penetration loss for kinetic (non-HEAT) rounds based on armor thickness ratio - Implement fragmentation damage to internal parts and crew when FRAG bullet penetrates armor https://claude.ai/code/session_01L7GeMnAYtRwxzvzDwnTyRH --- .../components/AEntityF_Multipart.java | 73 ++++++++++++++++++- .../entities/instances/EntityBullet.java | 3 +- .../jsondefs/JSONBullet.java | 17 ++++- 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java index 890b10653..281fb1fe1 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java +++ b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java @@ -24,6 +24,7 @@ import minecrafttransportsimulator.entities.instances.APart; import minecrafttransportsimulator.entities.instances.EntityBullet; import minecrafttransportsimulator.entities.instances.EntityBullet.HitType; +import minecrafttransportsimulator.jsondefs.JSONBullet.BulletType; import minecrafttransportsimulator.entities.instances.EntityPlacedPart; import minecrafttransportsimulator.items.components.AItemBase; import minecrafttransportsimulator.items.components.AItemPart; @@ -357,10 +358,20 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, if (hitEntry.box.groupDef != null && (hitEntry.box.groupDef.armorThickness != 0 || hitEntry.box.groupDef.heatArmorThickness != 0)) { hitOperationalHitbox = true; if (bullet != null) { - double armorThickness = hitEntry.box.definition != null ? (bullet.definition.bullet.isHeat && hitEntry.box.groupDef.heatArmorThickness != 0 ? hitEntry.box.groupDef.heatArmorThickness : hitEntry.box.groupDef.armorThickness) : 0; - double penetrationPotential = bullet.definition.bullet.isHeat ? bullet.definition.bullet.armorPenetration : (bullet.definition.bullet.armorPenetration * bullet.velocity / bullet.initialVelocity); - bullet.armorPenetrated += armorThickness; - bullet.displayDebugMessage("HIT ARMOR OF: " + (int) armorThickness); + boolean bulletIsHeat = bullet.definition.bullet.types.contains(BulletType.HEAT) || bullet.definition.bullet.isHeat; + double armorThickness = hitEntry.box.definition != null ? (bulletIsHeat && hitEntry.box.groupDef.heatArmorThickness != 0 ? hitEntry.box.groupDef.heatArmorThickness : hitEntry.box.groupDef.armorThickness) : 0; + double penetrationPotential = bulletIsHeat ? bullet.definition.bullet.armorPenetration : (bullet.definition.bullet.armorPenetration * bullet.velocity / bullet.initialVelocity); + + //For non-HEAT kinetic rounds (AP and subcaliber), reduce remaining penetration based on armor thickness. + //Penetration loss is proportional to (armorThickness / penetrationPotential)^2, simulating energy loss. + if (!bulletIsHeat && armorThickness > 0 && penetrationPotential > 0) { + double ratio = armorThickness / penetrationPotential; + double penetrationLoss = armorThickness * (1.0 + ratio); + bullet.armorPenetrated += penetrationLoss; + } else { + bullet.armorPenetrated += armorThickness; + } + bullet.displayDebugMessage("HIT ARMOR OF: " + (int) armorThickness + " CUMULATIVE PEN USED: " + (int) bullet.armorPenetrated); if (bullet.armorPenetrated > penetrationPotential) { //Bullet hit too much armor. @@ -373,6 +384,60 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, bullet.displayDebugMessage("HIT TOO MUCH ARMOR. MAX PEN: " + (int) penetrationPotential); return EntityBullet.HitType.ARMOR; } + + //If the bullet has FRAG type and penetrated armor, deal fragmentation damage to internals. + if (bullet.definition.bullet.types.contains(BulletType.FRAG) && bullet.definition.bullet.fragDamage > 0) { + float coneAngle = bullet.definition.bullet.fragConeAngle > 0 ? bullet.definition.bullet.fragConeAngle : 45.0f; + float hitProbability = bullet.definition.bullet.fragHitProbability > 0 ? bullet.definition.bullet.fragHitProbability : 0.5f; + float fragDmg = bullet.definition.bullet.fragDamage; + double coneAngleRad = Math.toRadians(coneAngle / 2.0); + double coneRange = bullet.definition.bullet.diameter / 100.0; + + //Check all parts on this entity for fragmentation hits within the cone. + for (APart part : allParts) { + double distToPart = hitEntry.position.distanceTo(part.position); + if (distToPart <= coneRange && distToPart > 0) { + //Check if part is within the cone angle from bullet's motion direction. + Point3D toPartVector = part.position.copy().subtract(hitEntry.position); + double angleToPart = Math.acos(toPartVector.dotProduct(bullet.motion) / (toPartVector.length() * bullet.motion.length())); + if (angleToPart <= coneAngleRad) { + //Roll for hit probability. + if (Math.random() <= hitProbability) { + Damage fragDamage = new Damage(fragDmg, part.boundingBox, bullet.gun, bullet.gun.lastController, null); + fragDamage.ignoreCooldown = true; + if (world.isClient()) { + InterfaceManager.packetInterface.sendToServer(new PacketEntityBulletHitEntity(bullet.gun, part, fragDamage)); + } else { + EntityBullet.performEntityHitLogic(part, fragDamage); + } + bullet.displayDebugMessage("FRAG HIT PART: " + part.definition.systemName + " FOR " + (int) fragDmg + " DAMAGE"); + } + } + } + } + + //Also check riders (crew) on all parts for fragmentation damage. + for (APart part : allParts) { + if (part.rider != null) { + IWrapperEntity partRider = part.rider; + double distToRider = hitEntry.position.distanceTo(partRider.getPosition()); + if (distToRider <= coneRange && distToRider > 0) { + Point3D toRiderVector = partRider.getPosition().copy().subtract(hitEntry.position); + double angleToRider = Math.acos(toRiderVector.dotProduct(bullet.motion) / (toRiderVector.length() * bullet.motion.length())); + if (angleToRider <= coneAngleRad) { + if (Math.random() <= hitProbability) { + Damage fragDamage = new Damage(fragDmg, part.boundingBox, bullet.gun, bullet.gun.lastController, null); + fragDamage.ignoreCooldown = true; + if (!world.isClient()) { + partRider.attack(fragDamage); + } + bullet.displayDebugMessage("FRAG HIT CREW MEMBER: " + partRider.getName() + " FOR " + (int) fragDmg + " DAMAGE"); + } + } + } + } + } + } } else { //Not a bullet, but hit armor, 100% stopping power with no damage. return EntityBullet.HitType.ARMOR; diff --git a/mccore/src/main/java/minecrafttransportsimulator/entities/instances/EntityBullet.java b/mccore/src/main/java/minecrafttransportsimulator/entities/instances/EntityBullet.java index b4060595b..7678a5d7e 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/entities/instances/EntityBullet.java +++ b/mccore/src/main/java/minecrafttransportsimulator/entities/instances/EntityBullet.java @@ -314,7 +314,8 @@ public void update() { if (((definition.bullet.isLongRange || !(gun.lastController instanceof IWrapperPlayer)) ^ world.isClient()) && (!world.isClient() || InterfaceManager.clientInterface.getClientPlayer().getID().equals(gun.lastController.getID()))) { //Now that we have an accurate motion, check for collisions. //First get a damage object to try to attack entities with. - double amount = definition.bullet.isHeat ? definition.bullet.damage : (velocity / initialVelocity) * definition.bullet.damage * ConfigSystem.settings.damage.bulletDamageFactor.value * ConfigSystem.settings.damage.packBulletDamageFactors.value.get(gun.lastLoadedBullet.definition.packID); + boolean bulletIsHeat = definition.bullet.types.contains(BulletType.HEAT) || definition.bullet.isHeat; + double amount = bulletIsHeat ? definition.bullet.damage : (velocity / initialVelocity) * definition.bullet.damage * ConfigSystem.settings.damage.bulletDamageFactor.value * ConfigSystem.settings.damage.packBulletDamageFactors.value.get(gun.lastLoadedBullet.definition.packID); Damage damage = new Damage(gun, boundingBox, amount); //Declare variables that may be used for hit logic. diff --git a/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONBullet.java b/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONBullet.java index 889d5a7d7..89d685a0e 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONBullet.java +++ b/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONBullet.java @@ -29,9 +29,18 @@ public static class Bullet { @JSONDescription("Set this to true to make this bullet not spawn, but consume ammo.") public boolean isBlank; - @JSONDescription("If true, then this bullet will be considered a HEAT bullet and will use the HEAT armor value on any collision boxes it finds. If that value isn't defined, it will just use the normal armor value.") + @Deprecated public boolean isHeat; + @JSONDescription("The cone angle, in degrees, for fragmentation spread when a FRAG-type bullet penetrates armor. Fragments are scattered within this cone behind the point of penetration.") + public float fragConeAngle; + + @JSONDescription("The probability (0.0 to 1.0) that a fragment will hit an internal component or crew member within the fragmentation cone.") + public float fragHitProbability; + + @JSONDescription("The damage each fragment deals when it hits an internal component or crew member.") + public float fragDamage; + @JSONDescription("Normally, bullet checks are handled only on the client that spawned them. This client then sends the info to the server when it sees a hit. This works best for most bullets, since it prevents the firing player from 'missing' something they hit due to lag. However, this prevents bullets from hitting things that aren't loaded. Setting this to true will make the bullet do checks on the server, which will let them hit anything loaded on the server, but will result in de-syncs between hit position seen and actual hit position if the gun is moving at any significant speed when fired.") public boolean isLongRange; @@ -120,6 +129,12 @@ public enum BulletType { WATER, @JSONDescription("A bullet that pierces player armor. Useful for pesky super-suits.") ARMOR_PIERCING, + @JSONDescription("A HEAT (High-Explosive Anti-Tank) bullet. Uses the HEAT armor value on collision boxes if defined, otherwise uses normal armor. Penetration is not affected by velocity loss.") + HEAT, + @JSONDescription("A subcaliber (sabot) round. Uses the same penetration mechanics as armor-piercing rounds, but is a distinct type for categorization purposes.") + SUBCALIBER, + @JSONDescription("A bullet that produces fragmentation upon penetrating armor. Fragments deal damage to internal components and crew based on the bullet's fragConeAngle, fragHitProbability, and fragDamage parameters.") + FRAG, @JSONDescription("A bullet that has a custom function defined in code. Useful for integration with a variety of mods, regardless of version.") CUSTOM } From 76ec9974f4a412898bfd16e6829f3dd09470b8ee Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 15:51:06 +0000 Subject: [PATCH 2/5] Fix dotProduct calls to include required normalized parameter https://claude.ai/code/session_01L7GeMnAYtRwxzvzDwnTyRH --- .../entities/components/AEntityF_Multipart.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java index 281fb1fe1..249f70990 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java +++ b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java @@ -399,7 +399,7 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, if (distToPart <= coneRange && distToPart > 0) { //Check if part is within the cone angle from bullet's motion direction. Point3D toPartVector = part.position.copy().subtract(hitEntry.position); - double angleToPart = Math.acos(toPartVector.dotProduct(bullet.motion) / (toPartVector.length() * bullet.motion.length())); + double angleToPart = Math.acos(toPartVector.dotProduct(bullet.motion, false) / (toPartVector.length() * bullet.motion.length())); if (angleToPart <= coneAngleRad) { //Roll for hit probability. if (Math.random() <= hitProbability) { @@ -423,7 +423,7 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, double distToRider = hitEntry.position.distanceTo(partRider.getPosition()); if (distToRider <= coneRange && distToRider > 0) { Point3D toRiderVector = partRider.getPosition().copy().subtract(hitEntry.position); - double angleToRider = Math.acos(toRiderVector.dotProduct(bullet.motion) / (toRiderVector.length() * bullet.motion.length())); + double angleToRider = Math.acos(toRiderVector.dotProduct(bullet.motion, false) / (toRiderVector.length() * bullet.motion.length())); if (angleToRider <= coneAngleRad) { if (Math.random() <= hitProbability) { Damage fragDamage = new Damage(fragDmg, part.boundingBox, bullet.gun, bullet.gun.lastController, null); From 4e365aaa7130b1dcd08c903382c2a07b8494f22d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 16:21:22 +0000 Subject: [PATCH 3/5] Fix frag crew damage and adapt HEAT detonation/post-pen from heat-shell-rework - Fix frag crew damage: use PacketEntityBulletHitExternalEntity for proper client/server handling (was using server-only rider.attack() which never executed since bullet checks run on client) - Adapt HEAT shell behavior from heat-shell-rework branch: - HEAT shells always detonate on armor contact (return ARMOR, stop flying) - On successful penetration, apply post-pen damage proportional to residual penetration ratio to both vehicle entity and all crew members - Add missing PacketEntityBulletHitExternalEntity import https://claude.ai/code/session_01L7GeMnAYtRwxzvzDwnTyRH --- .../components/AEntityF_Multipart.java | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java index 249f70990..748ed881d 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java +++ b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java @@ -43,6 +43,7 @@ import minecrafttransportsimulator.mcinterface.InterfaceManager; import minecrafttransportsimulator.packets.instances.PacketEntityBulletHitCollision; import minecrafttransportsimulator.packets.instances.PacketEntityBulletHitEntity; +import minecrafttransportsimulator.packets.instances.PacketEntityBulletHitExternalEntity; import minecrafttransportsimulator.packets.instances.PacketEntityBulletHitGeneric; import minecrafttransportsimulator.packets.instances.PacketPartChange_Add; import minecrafttransportsimulator.packets.instances.PacketPartChange_Remove; @@ -428,8 +429,10 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, if (Math.random() <= hitProbability) { Damage fragDamage = new Damage(fragDmg, part.boundingBox, bullet.gun, bullet.gun.lastController, null); fragDamage.ignoreCooldown = true; - if (!world.isClient()) { - partRider.attack(fragDamage); + if (world.isClient()) { + InterfaceManager.packetInterface.sendToServer(new PacketEntityBulletHitExternalEntity(partRider, fragDamage)); + } else { + EntityBullet.performExternalEntityHitLogic(partRider, fragDamage); } bullet.displayDebugMessage("FRAG HIT CREW MEMBER: " + partRider.getName() + " FOR " + (int) fragDmg + " DAMAGE"); } @@ -438,6 +441,42 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, } } } + + //HEAT shells detonate on armor contact and do not continue flying. + //If penetration was successful, apply post-penetration damage to the vehicle and crew + //proportional to the residual penetration potential. + if (bulletIsHeat) { + double residualRatio = (penetrationPotential - bullet.armorPenetrated) / penetrationPotential; + if (residualRatio > 0) { + double postPenDamage = bullet.definition.bullet.damage * residualRatio; + Damage heatDamage = new Damage(bullet.gun, hitEntry.box, postPenDamage); + if (world.isClient()) { + InterfaceManager.packetInterface.sendToServer(new PacketEntityBulletHitEntity(bullet.gun, hitEntity, heatDamage)); + for (APart part : allParts) { + if (part.rider != null) { + InterfaceManager.packetInterface.sendToServer(new PacketEntityBulletHitExternalEntity(part.rider, heatDamage)); + } + } + } else { + EntityBullet.performEntityHitLogic(hitEntity, heatDamage); + for (APart part : allParts) { + if (part.rider != null) { + EntityBullet.performExternalEntityHitLogic(part.rider, heatDamage); + } + } + } + bullet.displayDebugMessage("HEAT POST-PEN DAMAGE: " + (int) postPenDamage + " RESIDUAL: " + (int) (residualRatio * 100) + "%"); + } + + if (world.isClient()) { + InterfaceManager.packetInterface.sendToServer(new PacketEntityBulletHitGeneric(bullet.gun, bullet.bulletNumber, hitEntry.position, hitEntry.side, HitType.ARMOR)); + bullet.waitingOnActionPacket = true; + } else { + EntityBullet.performGenericHitLogic(bullet.gun, bullet.bulletNumber, hitEntry.position, hitEntry.side, HitType.ARMOR); + } + bullet.displayDebugMessage("HEAT DETONATED ON ARMOR. PEN: " + (int) penetrationPotential + " ARMOR: " + (int) bullet.armorPenetrated); + return EntityBullet.HitType.ARMOR; + } } else { //Not a bullet, but hit armor, 100% stopping power with no damage. return EntityBullet.HitType.ARMOR; From 71f20eb8bd1b45233df04e446360bcdbef7b092f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 16:52:55 +0000 Subject: [PATCH 4/5] Fix frag not triggering: increase cone range and add diagnostic debug messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - coneRange was diameter/100 (0.75m for 75mm) — far too small to reach any internal parts. Changed to diameter/10 (7.5m for 75mm) - Removed distToPart > 0 check that filtered parts at the hit point - Added zero-length vector guard to prevent NaN from acos - Added FRAG ACTIVATED debug message at block entry to confirm code is reached - Enhanced hit debug messages with distance and angle info for diagnostics https://claude.ai/code/session_01L7GeMnAYtRwxzvzDwnTyRH --- .../components/AEntityF_Multipart.java | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java index 748ed881d..13b668469 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java +++ b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java @@ -392,26 +392,30 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, float hitProbability = bullet.definition.bullet.fragHitProbability > 0 ? bullet.definition.bullet.fragHitProbability : 0.5f; float fragDmg = bullet.definition.bullet.fragDamage; double coneAngleRad = Math.toRadians(coneAngle / 2.0); - double coneRange = bullet.definition.bullet.diameter / 100.0; + double coneRange = bullet.definition.bullet.diameter / 10.0; + bullet.displayDebugMessage("FRAG ACTIVATED: CONE=" + (int) coneAngle + "deg RANGE=" + String.format("%.1f", coneRange) + "m PROB=" + hitProbability + " PARTS=" + allParts.size()); //Check all parts on this entity for fragmentation hits within the cone. for (APart part : allParts) { double distToPart = hitEntry.position.distanceTo(part.position); - if (distToPart <= coneRange && distToPart > 0) { + if (distToPart <= coneRange) { //Check if part is within the cone angle from bullet's motion direction. Point3D toPartVector = part.position.copy().subtract(hitEntry.position); - double angleToPart = Math.acos(toPartVector.dotProduct(bullet.motion, false) / (toPartVector.length() * bullet.motion.length())); - if (angleToPart <= coneAngleRad) { - //Roll for hit probability. - if (Math.random() <= hitProbability) { - Damage fragDamage = new Damage(fragDmg, part.boundingBox, bullet.gun, bullet.gun.lastController, null); - fragDamage.ignoreCooldown = true; - if (world.isClient()) { - InterfaceManager.packetInterface.sendToServer(new PacketEntityBulletHitEntity(bullet.gun, part, fragDamage)); - } else { - EntityBullet.performEntityHitLogic(part, fragDamage); + double toPartLen = toPartVector.length(); + if (toPartLen > 0) { + double angleToPart = Math.acos(toPartVector.dotProduct(bullet.motion, false) / (toPartLen * bullet.motion.length())); + if (angleToPart <= coneAngleRad) { + //Roll for hit probability. + if (Math.random() <= hitProbability) { + Damage fragDamage = new Damage(fragDmg, part.boundingBox, bullet.gun, bullet.gun.lastController, null); + fragDamage.ignoreCooldown = true; + if (world.isClient()) { + InterfaceManager.packetInterface.sendToServer(new PacketEntityBulletHitEntity(bullet.gun, part, fragDamage)); + } else { + EntityBullet.performEntityHitLogic(part, fragDamage); + } + bullet.displayDebugMessage("FRAG HIT PART: " + part.definition.systemName + " FOR " + (int) fragDmg + " DMG (dist=" + String.format("%.1f", distToPart) + " angle=" + (int) Math.toDegrees(angleToPart) + "deg)"); } - bullet.displayDebugMessage("FRAG HIT PART: " + part.definition.systemName + " FOR " + (int) fragDmg + " DAMAGE"); } } } @@ -422,19 +426,22 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, if (part.rider != null) { IWrapperEntity partRider = part.rider; double distToRider = hitEntry.position.distanceTo(partRider.getPosition()); - if (distToRider <= coneRange && distToRider > 0) { + if (distToRider <= coneRange) { Point3D toRiderVector = partRider.getPosition().copy().subtract(hitEntry.position); - double angleToRider = Math.acos(toRiderVector.dotProduct(bullet.motion, false) / (toRiderVector.length() * bullet.motion.length())); - if (angleToRider <= coneAngleRad) { - if (Math.random() <= hitProbability) { - Damage fragDamage = new Damage(fragDmg, part.boundingBox, bullet.gun, bullet.gun.lastController, null); - fragDamage.ignoreCooldown = true; - if (world.isClient()) { - InterfaceManager.packetInterface.sendToServer(new PacketEntityBulletHitExternalEntity(partRider, fragDamage)); - } else { - EntityBullet.performExternalEntityHitLogic(partRider, fragDamage); + double toRiderLen = toRiderVector.length(); + if (toRiderLen > 0) { + double angleToRider = Math.acos(toRiderVector.dotProduct(bullet.motion, false) / (toRiderLen * bullet.motion.length())); + if (angleToRider <= coneAngleRad) { + if (Math.random() <= hitProbability) { + Damage fragDamage = new Damage(fragDmg, part.boundingBox, bullet.gun, bullet.gun.lastController, null); + fragDamage.ignoreCooldown = true; + if (world.isClient()) { + InterfaceManager.packetInterface.sendToServer(new PacketEntityBulletHitExternalEntity(partRider, fragDamage)); + } else { + EntityBullet.performExternalEntityHitLogic(partRider, fragDamage); + } + bullet.displayDebugMessage("FRAG HIT CREW: " + partRider.getName() + " FOR " + (int) fragDmg + " DMG (dist=" + String.format("%.1f", distToRider) + " angle=" + (int) Math.toDegrees(angleToRider) + "deg)"); } - bullet.displayDebugMessage("FRAG HIT CREW MEMBER: " + partRider.getName() + " FOR " + (int) fragDmg + " DAMAGE"); } } } From bd72b0275f1f2cd58ba8723fff55f2d4c1b0cec8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 9 Apr 2026 05:22:07 +0000 Subject: [PATCH 5/5] Add MISSILE and BOMB types to BulletType enum, simplify debug messages - Add MISSILE type for guided projectiles and BOMB type for unguided drops - Simplify all new debug messages to match mod's terse style https://claude.ai/code/session_01L7GeMnAYtRwxzvzDwnTyRH --- .../entities/components/AEntityF_Multipart.java | 12 ++++++------ .../jsondefs/JSONBullet.java | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java index 13b668469..24f0ba546 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java +++ b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java @@ -372,7 +372,7 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, } else { bullet.armorPenetrated += armorThickness; } - bullet.displayDebugMessage("HIT ARMOR OF: " + (int) armorThickness + " CUMULATIVE PEN USED: " + (int) bullet.armorPenetrated); + bullet.displayDebugMessage("HIT ARMOR OF: " + (int) armorThickness); if (bullet.armorPenetrated > penetrationPotential) { //Bullet hit too much armor. @@ -393,7 +393,7 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, float fragDmg = bullet.definition.bullet.fragDamage; double coneAngleRad = Math.toRadians(coneAngle / 2.0); double coneRange = bullet.definition.bullet.diameter / 10.0; - bullet.displayDebugMessage("FRAG ACTIVATED: CONE=" + (int) coneAngle + "deg RANGE=" + String.format("%.1f", coneRange) + "m PROB=" + hitProbability + " PARTS=" + allParts.size()); + bullet.displayDebugMessage("FRAG ACTIVATED"); //Check all parts on this entity for fragmentation hits within the cone. for (APart part : allParts) { @@ -414,7 +414,7 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, } else { EntityBullet.performEntityHitLogic(part, fragDamage); } - bullet.displayDebugMessage("FRAG HIT PART: " + part.definition.systemName + " FOR " + (int) fragDmg + " DMG (dist=" + String.format("%.1f", distToPart) + " angle=" + (int) Math.toDegrees(angleToPart) + "deg)"); + bullet.displayDebugMessage("FRAG HIT PART: " + part.definition.systemName + " FOR " + (int) fragDmg); } } } @@ -440,7 +440,7 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, } else { EntityBullet.performExternalEntityHitLogic(partRider, fragDamage); } - bullet.displayDebugMessage("FRAG HIT CREW: " + partRider.getName() + " FOR " + (int) fragDmg + " DMG (dist=" + String.format("%.1f", distToRider) + " angle=" + (int) Math.toDegrees(angleToRider) + "deg)"); + bullet.displayDebugMessage("FRAG HIT CREW: " + partRider.getName() + " FOR " + (int) fragDmg); } } } @@ -472,7 +472,7 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, } } } - bullet.displayDebugMessage("HEAT POST-PEN DAMAGE: " + (int) postPenDamage + " RESIDUAL: " + (int) (residualRatio * 100) + "%"); + bullet.displayDebugMessage("HEAT POST-PEN DAMAGE: " + (int) postPenDamage); } if (world.isClient()) { @@ -481,7 +481,7 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, } else { EntityBullet.performGenericHitLogic(bullet.gun, bullet.bulletNumber, hitEntry.position, hitEntry.side, HitType.ARMOR); } - bullet.displayDebugMessage("HEAT DETONATED ON ARMOR. PEN: " + (int) penetrationPotential + " ARMOR: " + (int) bullet.armorPenetrated); + bullet.displayDebugMessage("HEAT DETONATED ON ARMOR"); return EntityBullet.HitType.ARMOR; } } else { diff --git a/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONBullet.java b/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONBullet.java index 89d685a0e..e2a24ba36 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONBullet.java +++ b/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONBullet.java @@ -135,6 +135,10 @@ public enum BulletType { SUBCALIBER, @JSONDescription("A bullet that produces fragmentation upon penetrating armor. Fragments deal damage to internal components and crew based on the bullet's fragConeAngle, fragHitProbability, and fragDamage parameters.") FRAG, + @JSONDescription("A guided missile. Uses guidance and turn rate parameters for tracking targets.") + MISSILE, + @JSONDescription("An unguided bomb. Dropped from a gun with zero muzzle velocity, affected by gravity.") + BOMB, @JSONDescription("A bullet that has a custom function defined in code. Useful for integration with a variety of mods, regardless of version.") CUSTOM }