-
Notifications
You must be signed in to change notification settings - Fork 0
Refactor bullet mechanics: add HEAT/SUBCALIBER/FRAG enum types, penetration loss, and fragmentation damage #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
83d2245
76ec997
4e365aa
71f20eb
bd72b02
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -42,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; | ||
|
|
@@ -357,9 +359,19 @@ 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; | ||
| 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); | ||
|
|
||
| if (bullet.armorPenetrated > penetrationPotential) { | ||
|
|
@@ -373,6 +385,105 @@ 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 / 10.0; | ||
| bullet.displayDebugMessage("FRAG ACTIVATED"); | ||
|
|
||
| //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) { | ||
| //Check if part is within the cone angle from bullet's motion direction. | ||
| Point3D toPartVector = part.position.copy().subtract(hitEntry.position); | ||
| 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); | ||
| } | ||
|
Comment on lines
+413
to
+416
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This branch applies damage to Useful? React with 👍 / 👎. |
||
| bullet.displayDebugMessage("FRAG HIT PART: " + part.definition.systemName + " FOR " + (int) fragDmg); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| //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) { | ||
| Point3D toRiderVector = partRider.getPosition().copy().subtract(hitEntry.position); | ||
| 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); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| //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); | ||
| } | ||
|
|
||
| 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"); | ||
| return EntityBullet.HitType.ARMOR; | ||
| } | ||
| } else { | ||
| //Not a bullet, but hit armor, 100% stopping power with no damage. | ||
| return EntityBullet.HitType.ARMOR; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
> 0to decide whether to apply a default makesfragHitProbability: 0.0impossible: it is treated as "unset" and replaced with0.5, so fragments still hit 50% of the time. That breaks the documented0.0–1.0behavior and can cause unexpected internal/crew damage when pack authors intentionally configure zero chance. Defaulting should use a distinct sentinel (for example negative values) or clamp the provided value directly.Useful? React with 👍 / 👎.