Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@
* Class for hitting on bounding boxes.
*/
public class BoundingBoxHitResult {
public final BoundingBox box;
public final Point3D position;
public final Axis side;

public BoundingBoxHitResult(BoundingBox box, Point3D hitPosition, Axis side) {
this.box = box;
this.position = hitPosition;
this.side = side;
}
}
public final BoundingBox box;
public final Point3D position;
public final Point3D normal;
public final Axis side;

public BoundingBoxHitResult(BoundingBox box, Point3D hitPosition, Axis side) {
this(box, hitPosition, new Point3D(side.xOffset, side.yOffset, side.zOffset), side);
}

public BoundingBoxHitResult(BoundingBox box, Point3D hitPosition, Point3D normal, Axis side) {
this.box = box;
this.position = hitPosition;
this.normal = normal;
this.side = side;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import minecrafttransportsimulator.entities.instances.PartGun;
import minecrafttransportsimulator.jsondefs.JSONBullet;
import minecrafttransportsimulator.jsondefs.JSONBullet.BulletType;
import minecrafttransportsimulator.jsondefs.JSONCollisionGroup;
import minecrafttransportsimulator.jsondefs.JSONCollisionGroup.CollisionType;
import minecrafttransportsimulator.jsondefs.JSONPotionEffect;
import minecrafttransportsimulator.mcinterface.AWrapperWorld;
Expand Down Expand Up @@ -166,7 +167,8 @@ private void doEntityDamage() {
baseDamage = blastDamageVsGround != 0 ? blastDamageVsGround : (blastDamageVsVehicles != 0 ? blastDamageVsVehicles : blastDamage);
}

double damageAmount = baseDamage * blastHit.falloff;
double explosionResistance = getExplosionResistance(blastHit.hitPosition, multiparts);
double damageAmount = getBlastDamageAfterResistance(baseDamage, blastHit.falloff, explosionResistance);
Damage damage = new Damage(damageAmount, blastHit.box, gun, entityResponsible, deathLanguage).setExplosive().ignoreCooldown().bypassIgnoredExplosiveDamage();

//Apply knockback away from explosion center.
Expand Down Expand Up @@ -217,7 +219,8 @@ private void doEntityDamage() {
}

float baseDamage = blastDamageVsLiving != 0 ? blastDamageVsLiving : blastDamage;
double damageAmount = baseDamage * damageFalloff;
double explosionResistance = getExplosionResistance(entityBlastPoint, multiparts);
double damageAmount = getBlastDamageAfterResistance(baseDamage, damageFalloff, explosionResistance);
Damage damage = new Damage(damageAmount, blastBounds, gun, entityResponsible, deathLanguage).setExplosive().ignoreCooldown();

//Apply MC-style knockback: use entity eye height for the Y delta, creating natural upward arcs.
Expand Down Expand Up @@ -561,6 +564,36 @@ private static double getEllipticalFalloff(double dx, double dy, double dz, doub
return 1.0 - normDist;
}

private static double getBlastDamageAfterResistance(double baseDamage, double falloff, double explosionResistance) {
return Math.max(baseDamage - explosionResistance, 0) * falloff;
}

/**
* Gets the combined explosion resistance from collision groups between the blast source and target.
* Each group is counted once even if the ray intersects multiple boxes in that group.
*/
private double getExplosionResistance(Point3D targetPosition, List<AEntityF_Multipart<?>> multiparts) {
Point3D delta = targetPosition.copy().subtract(position);
double pathLength = delta.length();
if (pathLength < MIN_PATH_LENGTH) {
return 0;
}

double explosionResistance = 0;
Set<JSONCollisionGroup> resistedGroups = new HashSet<>();
Point3D rayEnd = targetPosition.copy().addScaled(delta, 0.01D / pathLength);
for (AEntityF_Multipart<?> multipart : multiparts) {
for (BoundingBox box : multipart.allCollisionBoxes) {
JSONCollisionGroup groupDef = box.groupDef;
if (groupDef != null && groupDef.explosionResistance > 0 && !resistedGroups.contains(groupDef) && box.getIntersection(position, rayEnd) != null) {
explosionResistance += groupDef.explosionResistance;
resistedGroups.add(groupDef);
}
}
}
return explosionResistance;
}

/**
* Returns the best exposed attack box on a vehicle for this explosion, or null if the vehicle
* is outside the damage ellipsoid or fully blocked by terrain.
Expand Down Expand Up @@ -593,9 +626,17 @@ private BlastHit getBestVehicleBlastHit(EntityVehicleF_Physics vehicle, Bounding
* apply to large vehicles by their hull hitboxes rather than only by their entity center.
*/
private static void getClosestPointOnBox(BoundingBox box, Point3D point, Point3D output) {
output.x = Math.max(box.globalCenter.x - box.widthRadius, Math.min(point.x, box.globalCenter.x + box.widthRadius));
output.y = Math.max(box.globalCenter.y - box.heightRadius, Math.min(point.y, box.globalCenter.y + box.heightRadius));
output.z = Math.max(box.globalCenter.z - box.depthRadius, Math.min(point.z, box.globalCenter.z + box.depthRadius));
if (box.isOBB()) {
output.set(point).subtract(box.globalCenter).reOrigin(box.orientation);
output.x = Math.max(-box.widthRadius, Math.min(output.x, box.widthRadius));
output.y = Math.max(-box.heightRadius, Math.min(output.y, box.heightRadius));
output.z = Math.max(-box.depthRadius, Math.min(output.z, box.depthRadius));
output.rotate(box.orientation).add(box.globalCenter);
} else {
output.x = Math.max(box.globalCenter.x - box.widthRadius, Math.min(point.x, box.globalCenter.x + box.widthRadius));
output.y = Math.max(box.globalCenter.y - box.heightRadius, Math.min(point.y, box.globalCenter.y + box.heightRadius));
output.z = Math.max(box.globalCenter.z - box.depthRadius, Math.min(point.z, box.globalCenter.z + box.depthRadius));
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,13 +434,13 @@ private boolean checkEntityCollisions(Point3D collisionMotion) {
for (BoundingBox box : otherVehicle.allCollisionBoxes) {
if (box.collisionTypes.contains(CollisionType.VEHICLE) && box.intersects(solidBox)) {
if (collisionMotion.y > 0) {
boxCollisionDepth = solidBox.globalCenter.y + solidBox.heightRadius - (box.globalCenter.y - box.heightRadius);
boxCollisionDepth = solidBox.globalCenter.y + solidBox.heightRadius - (box.globalCenter.y - box.getYRadius());
if (boxCollisionDepth > solidBox.currentCollisionDepth.y) {
solidBox.currentCollisionDepth.y = boxCollisionDepth;
collidingBox = box;
}
} else {
boxCollisionDepth = solidBox.globalCenter.y - solidBox.heightRadius - (box.globalCenter.y + box.heightRadius);
boxCollisionDepth = solidBox.globalCenter.y - solidBox.heightRadius - (box.globalCenter.y + box.getYRadius());
if (boxCollisionDepth < solidBox.currentCollisionDepth.y) {
solidBox.currentCollisionDepth.y = boxCollisionDepth;
collidingBox = box;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ protected void updateCollisionBoxes(boolean requiresDeltaUpdates) {
if (requiresDeltaUpdates) {
for (BoundingBox box : boxes) {
box.globalCenter.set(box.localCenter).transform(switchBox.netMatrix);
box.updateToEntity(this, box.globalCenter);
box.updateToEntity(this, box.globalCenter, switchBox.rotation);
}
}
} else {
Expand Down Expand Up @@ -312,9 +312,9 @@ protected void updateEncompassingBox() {
encompassingBox.heightRadius = 0;
encompassingBox.depthRadius = 0;
for (BoundingBox box : collisionBoxes) {
encompassingBox.widthRadius = (float) Math.max(encompassingBox.widthRadius, Math.abs(box.globalCenter.x - position.x) + box.widthRadius);
encompassingBox.heightRadius = (float) Math.max(encompassingBox.heightRadius, Math.abs(box.globalCenter.y - position.y) + box.heightRadius);
encompassingBox.depthRadius = (float) Math.max(encompassingBox.depthRadius, Math.abs(box.globalCenter.z - position.z) + box.depthRadius);
encompassingBox.widthRadius = (float) Math.max(encompassingBox.widthRadius, Math.abs(box.globalCenter.x - position.x) + box.getXRadius());
encompassingBox.heightRadius = (float) Math.max(encompassingBox.heightRadius, Math.abs(box.globalCenter.y - position.y) + box.getYRadius());
encompassingBox.depthRadius = (float) Math.max(encompassingBox.depthRadius, Math.abs(box.globalCenter.z - position.z) + box.getZRadius());
}
encompassingBox.updateToEntity(this, null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,9 @@ public Collection<BoundingBoxHitResult> getHitBoxes(Point3D pathStart, Point3D p
if (hitResult != null) {
double boxDistance = hitResult.position.distanceTo(pathStart);
boolean addBox = true;
if (box.groupDef != null) {
//Don't add boxes within the same group.
boolean groupHasArmor = box.groupDef != null && (box.groupDef.volumetricArmor || box.groupDef.armorThickness != 0 || box.groupDef.heatArmorThickness != 0);
if (box.groupDef != null && !groupHasArmor) {
//Don't add boxes within the same non-armor group.
Iterator<Entry<Double, BoundingBoxHitResult>> iterator = hitBoxes.entrySet().iterator();
while (iterator.hasNext()) {
Entry<Double, BoundingBoxHitResult> entry = iterator.next();
Expand Down Expand Up @@ -355,13 +356,18 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet,
}

//Check armor pen and see if we hit too much and need to stop processing.
if (hitEntry.box.groupDef != null && (hitEntry.box.groupDef.armorThickness != 0 || hitEntry.box.groupDef.heatArmorThickness != 0)) {
if (hitEntry.box.groupDef != null && (hitEntry.box.groupDef.volumetricArmor || 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);
Point3D bulletDirection = bullet.motion.copy().normalize();
double armorThickness = hitEntry.box.groupDef.volumetricArmor ? getPhysicalArmorThickness(hitEntry, bulletDirection) : (hitEntry.box.definition != null ? (bullet.definition.bullet.isHeat && hitEntry.box.groupDef.heatArmorThickness != 0 ? hitEntry.box.groupDef.heatArmorThickness : hitEntry.box.groupDef.armorThickness) : 0);
double effectiveArmorThickness = hitEntry.box.groupDef.volumetricArmor ? armorThickness : getEffectiveArmorThickness(hitEntry, bulletDirection, armorThickness);
double penetrationPotential = bullet.definition.bullet.armorPenetration;
if (!bullet.definition.bullet.isHeat && bullet.initialVelocity > 0) {
penetrationPotential *= Math.min(bullet.velocity / bullet.initialVelocity, 1D);
}
bullet.armorPenetrated += effectiveArmorThickness;
bullet.displayDebugMessage("HIT ARMOR OF: " + (int) armorThickness + " EFFECTIVE: " + (int) effectiveArmorThickness + " TOTAL: " + (int) bullet.armorPenetrated + " PEN: " + (int) penetrationPotential);

if (bullet.armorPenetrated > penetrationPotential) {
//Bullet hit too much armor.
Expand Down Expand Up @@ -426,6 +432,36 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet,
return null;
}

private double getEffectiveArmorThickness(BoundingBoxHitResult hitEntry, Point3D bulletDirection, double armorThickness) {
if (armorThickness == 0) {
return 0;
}
Point3D armorNormal = hitEntry.normal.copy().normalize();
if (bulletDirection.isZero() || armorNormal.isZero()) {
return armorThickness;
}
double armorImpactFactor = Math.max(Math.abs(armorNormal.dotProduct(bulletDirection, true)), 0.001D);
double effectiveThickness = armorThickness / armorImpactFactor;
double physicalThickness = getPhysicalArmorThickness(hitEntry, bulletDirection);
return Math.min(effectiveThickness, physicalThickness);
}

private double getPhysicalArmorThickness(BoundingBoxHitResult hitEntry, Point3D bulletDirection) {
BoundingBox box = hitEntry.box;
Point3D localDirection = box.isOBB() ? bulletDirection.copy().reOrigin(box.orientation) : bulletDirection.copy();
double physicalThickness = Double.MAX_VALUE;
if (Math.abs(localDirection.x) > 1.0E-7D) {
physicalThickness = Math.min(physicalThickness, 2D * box.widthRadius / Math.abs(localDirection.x));
}
if (Math.abs(localDirection.y) > 1.0E-7D) {
physicalThickness = Math.min(physicalThickness, 2D * box.heightRadius / Math.abs(localDirection.y));
}
if (Math.abs(localDirection.z) > 1.0E-7D) {
physicalThickness = Math.min(physicalThickness, 2D * box.depthRadius / Math.abs(localDirection.z));
}
return physicalThickness * 1000D;
}

@Override
public void remove() {
super.remove();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package minecrafttransportsimulator.jsondefs;

import minecrafttransportsimulator.baseclasses.Point3D;
import minecrafttransportsimulator.baseclasses.RotationMatrix;
import minecrafttransportsimulator.jsondefs.JSONAction.ActionType;
import minecrafttransportsimulator.packloading.JSONParser.JSONDescription;
import minecrafttransportsimulator.packloading.JSONParser.JSONRequired;
Expand All @@ -13,9 +14,15 @@ public class JSONCollisionBox {
@JSONDescription("The width of this collision box, in meters. Note that since the pos parameter is the center of the box the box will actually extend � the width in the X and Z direction.")
public float width;

@JSONDescription("The length of this collision box, in meters. If omitted, width will be used for the Z direction.")
public float length;

@JSONDescription("Same as width, just for the Y direction.")
public float height;

@JSONDescription("The rotation of this collision box. Only used if the owning collision group has isOBB set to true.")
public RotationMatrix rot;

@JSONDescription("If true, the collision box will behave like a ground device set to float. Note that if you make a boat that uses only these boxes, you'll need one for every corner like you would wheels on a car. Failing to do so will result in your boat doing a Titanic, just without an iceberg.")
public boolean collidesWithLiquids;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ public class JSONCollisionGroup {
@JSONDescription("Like normal armor thickness, but used only if a bullet is a HEAT bullet.")
public float heatArmorThickness;

@JSONDescription("If true, armor thickness is calculated from the actual path length through the collision box volume instead of armorThickness. If false, armorThickness is applied to the face that was hit and adjusted for the impact angle, with the box dimensions only limiting grazing impacts.")
public boolean volumetricArmor;

@JSONDescription("How much blast damage this group absorbs from explosions. This value reduces blastDamage before distance falloff is applied, and multiple groups between the explosion and target will stack.")
public float explosionResistance;

@JSONDescription("How much to multiply the damage for bullets that hit this group by. Is 1.0 if not specified.")
public float damageMultiplier;

Expand All @@ -32,6 +38,9 @@ public class JSONCollisionGroup {
@JSONDescription("If this is set, then this collision group will first use the animations for this object from the rendering section instead of the animations in this one. If the specified object has applyAfter on it itself, then the animations will be gotten recursively until an applyAfter is not found.")
public String applyAfter;

@JSONDescription("If true, this collision group will use oriented bounding box logic. This allows to specify rotations and dimensions along all three axes.")
public boolean isOBB;

@JSONRequired
@JSONDescription("The types of collision for this group.")
public Set<CollisionType> collisionTypes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public boolean intersects(double otherMinX, double otherMinY, double otherMinZ,
//CHeck super first, as that's the encompassing box.
if (super.intersects(otherMinX, otherMinY, otherMinZ, otherMaxX, otherMaxY, otherMaxZ)) {
for (BoundingBox testBox : getBoxes()) {
if (otherMaxX > testBox.globalCenter.x - testBox.widthRadius && otherMinX < testBox.globalCenter.x + testBox.widthRadius && otherMaxY > testBox.globalCenter.y - testBox.heightRadius && otherMinY < testBox.globalCenter.y + testBox.heightRadius && otherMaxZ > testBox.globalCenter.z - testBox.depthRadius && otherMinZ < testBox.globalCenter.z + testBox.depthRadius) {
if (testBox.intersects(otherMinX, otherMinY, otherMinZ, otherMaxX, otherMaxY, otherMaxZ)) {
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1021,7 +1021,7 @@ public void spawnExplosion(Point3D location, double strength, boolean flames, bo
* Helper method to convert a BoundingBox to an AxisAlignedBB.
*/
public static AxisAlignedBB convert(BoundingBox box) {
return new AxisAlignedBB(box.globalCenter.x - box.widthRadius, box.globalCenter.y - box.heightRadius, box.globalCenter.z - box.depthRadius, box.globalCenter.x + box.widthRadius, box.globalCenter.y + box.heightRadius, box.globalCenter.z + box.depthRadius);
return new AxisAlignedBB(box.getMinX(), box.getMinY(), box.getMinZ(), box.getMaxX(), box.getMaxY(), box.getMaxZ());
}

/**
Expand All @@ -1030,7 +1030,7 @@ public static AxisAlignedBB convert(BoundingBox box) {
* creating two AABBs (the conversion and the offset box).
*/
public static AxisAlignedBB convertWithOffset(BoundingBox box, double x, double y, double z) {
return new AxisAlignedBB(x + box.globalCenter.x - box.widthRadius, y + box.globalCenter.y - box.heightRadius, z + box.globalCenter.z - box.depthRadius, x + box.globalCenter.x + box.widthRadius, y + box.globalCenter.y + box.heightRadius, z + box.globalCenter.z + box.depthRadius);
return new AxisAlignedBB(x + box.getMinX(), y + box.getMinY(), z + box.getMinZ(), x + box.getMaxX(), y + box.getMaxY(), z + box.getMaxZ());
}

/**
Expand Down
Loading