Skip to content

Instantly share code, notes, and snippets.

@lntricate1
Last active May 21, 2024 11:15
Show Gist options
  • Save lntricate1/e7a2c3f0e10eaa40ac932cab74619ea7 to your computer and use it in GitHub Desktop.
Save lntricate1/e7a2c3f0e10eaa40ac932cab74619ea7 to your computer and use it in GitHub Desktop.
TNT Archive Resources

TNT Archive Resources

This is a compilation of a bunch of knowledge about minecraft TNT and related mechanics.

Links to other resources

Our previous resources gist, made by Pwouik

Xcom's 3-part explanation series explains most of the basics of pearls, tnt, and chunkloading needed for cannons (accurate to mc1.12):

Two old Xcom videos explaining some of the basic concepts behind cannon:

Myren Eario's explanation of TNT duplication:

Methodzz's explanation of TNT movement (accurate to mc1.12):

Xcom's video on how to program an existing ender pearl cannon:

Explosions

Explosion powers

These are all floats

  • Fireball: 1
  • Wither Skull: 1
  • Creeper: 3
  • TNT: 4
  • Bed: 5
  • Respawn Anchor: 5
  • Charged Creeper: 6
  • End Crystal: 6
  • Wither spawning: 7
  • TNT Minecart: 4.0 + random.nextDouble() * 1.5 * Math.min(5.0, Math.sqrt(getDeltaMovement().lengthSqr())) (ranges from 4.0 to 11.5)

Exposure [source]

public static float getSeenPercent(Vec3 explosionPos, Entity entity)
{
  AABB aabb = entity.getBoundingBox();
  // Get the step size for all 3 dimenions
  // Will be used to iterate over a 3d grid of points in the entity
  double dx = 1.0 / ((aabb.maxX - aabb.minX) * 2.0 + 1.0);
  double dy = 1.0 / ((aabb.maxY - aabb.minY) * 2.0 + 1.0);
  double dz = 1.0 / ((aabb.maxZ - aabb.minZ) * 2.0 + 1.0);

  // Get the X and Z offset from the corner
  // Note that there is no Y offset, so the grid is always aligned to the bottom of the entity
  // Note that these are calculated wrong which makes the grid be directional, see https://bugs.mojang.com/browse/MC-232355
  double xOffset = (1.0 - Math.floor(1.0 / dx) * dx) / 2.0;
  double zOffset = (1.0 - Math.floor(1.0 / dz) * dz) / 2.0;

  // Return 0 if any of the step sizes is negative
  if(dx < 0.0 || dy < 0.0 || dz < 0.0)
    return 0F;

  int hitRays = 0;
  int totalRays = 0;
  for(double x = 0.0; x <= 1.0; x += dx)
    for(double y = 0.0; y <= 1.0; y += dy)
      for(double z = 0.0; z <= 1.0; z += dz)
      {
        // Calculate the position within the entity based on the current grid position
        Vec3 pos = new Vec3(
          Mth.lerp(x, aabb.minX, aabb.maxX) + xOffset,
          Mth.lerp(y, aabb.minY, aabb.maxY),
          Mth.lerp(z, aabb.minZ, aabb.maxZ) + zOffset);

        // If there are no blocks obstructing the ray from the pos to the explosion pos, increment hitRays
        if(entity.level().clip(new ClipContext(pos, explosionPos, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, entity)).getType() == HitResult.Type.MISS)
          ++hitRays;

        // Always increment totalRays
        ++totalRays;
      }
  // Return the ration between hitRays and totalRays (the seen percent)
  return (float)hitRays / (float)totalRays;
}

Block breaking [source]

public void explode()
{
  this.level.gameEvent(this.source, GameEvent.EXPLODE, new Vec3(this.x, this.y, this.z));
  HashSet<BlockPos> blocks = Sets.newHashSet();
  // Iterate over a 16x16x16 cube
  for(int x = 0; x < 16; ++x)
    for(int y = 0; y < 16; ++y)
      for(int z = 0; z < 16; ++z)
        // Check ifthe point is on the outside faces of the cube
        if(x == 0 || x == 15 || y == 0 || y == 15 || z == 0 || z == 15)
        {
          // Convert point into a direction vector, from the explosion to the outside of the cube, with length 0.3F
          double dx = (double)((float)x / 15.0F * 2.0F - 1.0F);
          double dy = (double)((float)y / 15.0F * 2.0F - 1.0F);
          double dz = (double)((float)z / 15.0F * 2.0F - 1.0F);
          double magnitude = Math.sqrt(dx * dx + dy * dy + dz * dz);
          dx = dx / magnitude * 0.3F;
          dy = dy / magnitude * 0.3F;
          dz = dz / magnitude * 0.3F;

          // Random.nextFloat is uniformly distributed from 0 to 1
          float rayStrength = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F);

          // March from the explosion position outwards following the direction vector
          double rayX = this.x;
          double rayY = this.y;
          double rayZ = this.z;
          for(; rayStrength > 0.0F; rayStrength -= 0.22500001F)
          {
            // Get the block at the current ray position, ignoring block shape
            BlockPos blockPos = BlockPos.containing(rayX, rayY, rayZ);
            BlockState blockState = this.level.getBlockState(blockPos);
            FluidState fluidState = this.level.getFluidState(blockPos);
            if(!this.level.isInWorldBounds(blockPos))
              break;

            // See net.minecraft.class_5362.method_29555
            Optional blastResistance = this.damageCalculator.getBlockExplosionResistance(this, this.level, blockPos, blockState, fluidState);

            // ifthe block has blast resistance, reduce the ray strength accordingly
            if(blastResistance.isPresent())
              rayStrength -= (blastResistance.get() + 0.3F) * 0.3F;

            // ifthe ray strength is still above 0 after reducing, mark the block for deletion
            if(rayStrength > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockPos, blockState, rayStrength))
              blocks.add(blockPos);

            // Advance the ray
            rayX += dx;
            rayY += dy;
            rayZ += dz;
          }
        }
  // Add marked blocks into the toBlow list
  // Note: this list will get shuffled before the blocks are actually broken
  this.toBlow.addAll(var1);

  /* Entity pushing happens here */
}

Entity pushing [source]

public void explode()
{
  /* Block breaking happens here */

  // Explosion power is double the radius.
  // Radius =
  // Fireball, wither skull: 1
  // Creeper: 3
  // TNT: 4
  // Bed: 5
  // Respawn anchor: 5
  // Charged creeper: 6
  // Wither spawning: 7
  // TNT minecart: see net.minecraft.class_1701.method_47305
  float power = this.radius * 2.0F;

  // Get entities around explosion
  int minX = Mth.floor(this.x - (double)power - 1.0);
  int maxX = Mth.floor(this.x + (double)power + 1.0);
  int minY = Mth.floor(this.y - (double)power - 1.0);
  int maxY = Mth.floor(this.y + (double)power + 1.0);
  int minZ = Mth.floor(this.z - (double)power - 1.0);
  int maxZ = Mth.floor(this.z + (double)power + 1.0);
  List<Entity> entities = this.level.getEntities(this.source, new AABB((double)minX, (double)minY, (double)minZ, (double)maxX, (double)maxY, (double)maxZ));

  Vec3 pos = new Vec3(this.x, this.y, this.z);
  for(Entity entity : entities)
  {
    if(entity.ignoreExplosion())
      continue;

    // Distance is measured from explosion to entity feet and divided by power
    double distance = Math.sqrt(entity.distanceToSqr(pos)) / (double)power;

    // ifentity is further than power, skip this entity
    if(distance > 1.0)
      continue;

    // Direction is measured from explosion entity eyes, unless entity is TNT, in which case it's feet
    double directionX = entity.getX() - this.x;
    double directionY = (entity instanceof PrimedTnt ? entity.getY() : entity.getEyeY()) - this.y;
    double directionZ = entity.getZ() - this.z;
    double directionMagnitude = Math.sqrt(directionX * directionX + directionY * directionY + directionZ * directionZ);

    // ifdirection magnitude is 0, skip this entity
    if(directionMagnitude == 0.0)
      continue;

    // See net.minecraft.class_1927.method_17752
    // Will always be 1F ifthere are no blocks between explosion and entity
    double exposure = (double)getSeenPercent(pos, entity);

    // Velocity magnitude is direction * (1 - distance) * exposure
    double knockback = (1.0 - distance) * exposure;

    entity.hurt(this.getDamageSource(), (float)((int)((knockback * knockback + knockback) / 2.0 * 7.0 * (double)power + 1.0)));

    // Explosion protection enchantment reduces knockback, see net.minecraft.class_1900.method_8237
    if(entity instanceof LivingEntity livingEntity)
      knockback = ProtectionEnchantment.getExplosionKnockbackAfterDampener(livingEntity, knockback);

    // Velocity is direction scaled to the size of knockback
    Vec3 deltaMovement = new Vec3(
      directionX / directionMagnitude * knockback,
      directionY / directionMagnitude * knockback,
      directionZ / directionMagnitude * knockback);
    entity.setDeltaMovement(entity.getDeltaMovement().add(deltaMovement));

    // ifthe entity is a player, add it to the list of hit players
    if(entity instanceof Player player && !player.isSpectator() && (!player.isCreative() || !player.getAbilities().flying))
      this.hitPlayers.put(player, deltaMovement);
  }
}

Math formula for entity pushing (not float precise)

image

$$\overrightarrow{V} = \text{Explosion to eyes unit vector} \cdot \left(1 - \frac{\text{Distance to feet}}{2r}\right) \cdot \text{Exposure} \cdot \text{Count}$$

Note: TNT "Eye position" is the same as the feet position for this calculation

Explanation: https://pastebin.com/jFhrviNp

Entity movement

This section lists useful entity move code. Each entity will have the following:

  • Serverside movement code from the official Mojang mappings, with added explanation comments.
  • A mathematical formula for position and velocity, if applicable. Note that this won't be float accurate.
    • A desmos file showing the formula.

Entity [source]

public void move(MoverType moverType, Vec3 movement)
{
  // Skip all collision checks ifentity has noclip
  if(this.noPhysics)
  {
    this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
    return;
  }
  this.wasOnFire = this.isOnFire();
  // Adjust movement with piston limiter, then early exit ifthe limited movement is zero
  if(moverType == MoverType.PISTON && (movement = this.limitPistonMovement(movement)).equals(Vec3.ZERO))
    return;
  // stuckSpeedMultiplier gets set by cobwebs, powder snow, and berry bushes. Movement gets multiplied by the multiplier, then velocity is set to zero.
  // note: this also gets called by piston movement, which means you can adjust piston movement using different multipliers, and cancel velocity.
  if(this.stuckSpeedMultiplier.lengthSqr() > 1.0E-7)
  {
    movement = movement.multiply(this.stuckSpeedMultiplier);
    this.stuckSpeedMultiplier = Vec3.ZERO;
    this.setDeltaMovement(Vec3.ZERO);
  }
  // Mojang wrote this in a hard to read way;
  // First, movement is adjusted by maybeBackOffFromEdge.
  // Second, movementAdjusted is set to the movement after calculating block and hard entity collisions.
  // Third, lengthSqrAdjusted is set to the squared euclidean length of movementAdjusted.
  // Fourth, the ifchecks for lengthSqrAdjusted being larger than 1.0E-7.
  if((lengthSqrAdjusted = (movementAdjusted = this.collide(movement = this.maybeBackOffFromEdge(movement, moverType))).lengthSqr()) > 1.0E-7)
  {
    // First, check fallDistance != 0.
    // ifthat returned true, check lengthSqrAdjusted >= 1.
    // ifthat returned true, perform a raycast from this.position() to this.position() + movementAdjusted.
    // The raycast will be stopped early ifa block with the tag FALLDAMAGE_RESETTING or water is hit.
    // ifthe raycast gets stopped early, call this.resetFallDistance().
    // Note: this raycast loads chunks, causes lag, and could get called for any entity that calls Entity.move().
    // Placing FALLDAMAGE_RESETTING blocks or water in the path of big movements can help with the lag.
    if(this.fallDistance != 0.0f && lengthSqrAdjusted >= 1.0 && this.level.clip(new ClipContext(this.position(), this.position().add(movementAdjusted), ClipContext.Block.FALLDAMAGE_RESETTING, ClipContext.Fluid.WATER, this)).getType() != HitResult.Type.MISS)
      this.resetFallDistance();
    // Set position to this.position() + movementAdjusted
    this.setPos(this.getX() + movementAdjusted.x, this.getY() + movementAdjusted.y, this.getZ() + movementAdjusted.z);
  }

  boolean collidedX = !Mth.equal(movement.x, movementAdjusted.x);
  boolean collidedZ = !Mth.equal(movement.z, movementAdjusted.z);
  this.horizontalCollision = collidedX || collidedZ;
  this.verticalCollision = movement.y != movementAdjusted.y;
  this.verticalCollisionBelow = this.verticalCollision && movement.y < 0.0;
  this.minorHorizontalCollision = this.horizontalCollision ? this.isHorizontalCollisionMinor(movementAdjusted) : false;
  this.onGround = this.verticalCollisionBelow;
  BlockPos blockPos = this.getOnPos();
  BlockState blockState2 = this.level.getBlockState(blockPos);

  // Apply fall damage, this can kill the entity.
  this.checkFallDamage(movementAdjusted.y, this.onGround, blockState2, blockPos);
  // ifit died, early exit.
  if(this.isRemoved())
    return;

  // Cancel velocity on X and/or Z
  if(this.horizontalCollision)
  {
    Vec3 velocity = this.getDeltaMovement();
    this.setDeltaMovement(collidedX ? 0.0 : velocity.x, velocity.y, collidedZ ? 0.0 : velocity.z);
  }
  Block block = blockState2.getBlock();
  if(this.verticalCollision)
    block.updateEntityAfterFallOn(this.level, this);
  if(this.onGround && !this.isSteppingCarefully())
    block.stepOn(this.level, blockPos, blockState2, this);

  this.tryCheckInsideBlocks();
  // Get block friction
  float j = this.getBlockSpeedFactor();
  // Apply block friction
  this.setDeltaMovement(this.getDeltaMovement().multiply(j, 1.0, j));

  if(this.level.getBlockStatesIfLoaded(this.getBoundingBox().deflate(1.0E-6)).noneMatch(blockState -> blockState.is(BlockTags.FIRE) || blockState.is(Blocks.LAVA))) {
    if(this.remainingFireTicks <= 0) {
      this.setRemainingFireTicks(-this.getFireImmuneTicks());
    }
    if(this.wasOnFire && (this.isInPowderSnow || this.isInWaterRainOrBubble())) {
      this.playEntityOnFireExtinguishedSound();
    }
  }
  if(this.isOnFire() && (this.isInPowderSnow || this.isInWaterRainOrBubble())) {
    this.setRemainingFireTicks(-this.getFireImmuneTicks());
  }
}

TNT [source]

Code
public void tick()
{
  // ifTNT has NoGravity NBT tag set to 0, add [0D, -0.04D, 0D] to its motion
  if(!this.isNoGravity())
    this.setDeltaMovement(this.getDeltaMovement().add(0.0, -0.04, 0.0));

  // Call the Entity.move method
  this.move(MoverType.SELF, this.getDeltaMovement());

  // Multiply motion by 0.98D
  this.setDeltaMovement(this.getDeltaMovement().scale(0.98));

  // ifTNT is on ground (AKA ended Entity.move on ground), multiply motion by [0.7D, -0.5D, 0.7D]
  if(this.onGround)
    this.setDeltaMovement(this.getDeltaMovement().multiply(0.7, -0.5, 0.7));

  // Decrement fuse timer
  int i = this.getFuse() - 1;
  this.setFuse(i);

  // iffuse timer is smaller or equal to 0, remove entity and explode
  if(i <= 0)
  {
    this.discard();
    this.explode();
  }
  else
    this.updateInWaterStateAndDoFluidPushing();
}
Formula

https://www.desmos.com/calculator/r33a6enfn5

$$\overrightarrow{V_t} = 0.98^t\left(\overrightarrow{V_0} + \begin{bmatrix} 0\\1.96\\0 \end{bmatrix}\right) - \begin{bmatrix} 0\\1.96\\0 \end{bmatrix}$$ $$\overrightarrow{P_t} = \overrightarrow{P_0} + 50\left(1 - 0.98^t\right)\left(\overrightarrow{V_0} + \begin{bmatrix} 0\\1.96\\0 \end{bmatrix}\right) - \begin{bmatrix} 0\\2t\\0 \end{bmatrix}$$
Code
public void tick()
{
  // Call the Projectile.tick method
  super.tick();
  
  // Perform a raycast that hits block collision boxes and entities
  // Raycast extends from entity feet to entity feet + deltaMovement
  // See net.minecraft.class_1675.method_49997 for details
  // Some entities are skipped: spectators, Projectiles, entities in the same vehicle as the entity that threw the projectile, etc:
  // See net.minecraft.class_1676.method_26958 for details
  HitResult hitResult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);

  boolean handled = false;
  if(hitResult.getType() == HitResult.Type.BLOCK)
  {
    // Get the block hit by the raycast
    BlockPos blockPos = ((BlockHitResult)hitResult).getBlockPos();
    BlockState blockState = this.level().getBlockState(blockPos);

    // ifblock is a nether portal, set entity inside portal. See net.minecraft.class_1297.method_5717 for details.
    if(blockState.is(Blocks.NETHER_PORtal))
    {
      this.handleInsidePortal(blockPos);
      handled = true;
    }

    // ifblock is an end gateway, attempt to teleport
    else if(blockState.is(Blocks.END_GATEWAY))
    {
      BlockEntity blockEntity = this.level().getBlockEntity(blockPos);
      if(blockEntity instanceof TheEndGatewayBlockEntity endGatewayBlockEntity && TheEndGatewayBlockEntity.canEntityTeleport(this))
        TheEndGatewayBlockEntity.teleportEntity(this.level(), blockPos, blockState, this, endGatewayBlockEntity);
      handled = true;
    }
  }

  // ifthe raycast didn't miss and it hasn't already been handled, execute this.onHit.
  // For ender pearls it will teleport, for snowballs, it'll damage, for eggs it'll try spawning chickens, for potions it'll break the potion.
  if(hitResult.getType() != HitResult.Type.MISS && !handled)
    this.onHit(hitResult);

  // See net.minecraft.class_1297.method_5852
  this.checkInsideBlocks();

  // Calculate new pos
  Vec3 deltaMovement = this.getDeltaMovement();
  double x = this.getX() + deltaMovement.x;
  double y = this.getY() + deltaMovement.y;
  double z = this.getZ() + deltaMovement.z;

  this.updateRotation();

  // Apply drag
  float drag = this.isInWater() ? 0.8f : 0.99f;
  this.setDeltaMovement(deltaMovement.scale((double)drag));

  // Apply gravity. this.getGravity() is 0.03F for all ThrowableProjectiles except potions which are 0.05F and xp bottles which are 0.07F
  if(!this.isNoGravity())
  {
    deltaMovement = this.getDeltaMovement();
    this.setDeltaMovement(deltaMovement.x, deltaMovement.y - (double)this.getGravity(), deltaMovement.z);
  }

  // Apply new pos
  this.setPos(x, y, z);
}
Formula

https://www.desmos.com/calculator/s2lwhidtgt

$$\overrightarrow{V_t} = 0.99^t\left(\overrightarrow{V_0} + \begin{bmatrix} 0\\100g\\0 \end{bmatrix}\right) - \begin{bmatrix} 0\\100g\\0 \end{bmatrix}$$ $$\overrightarrow{P_t} = \overrightarrow{P_0} + 100\left(1 - 0.99^t\right)\left(\overrightarrow{V_0} + \begin{bmatrix} 0\\100g\\0 \end{bmatrix}\right) - \begin{bmatrix} 0\\100gt\\0 \end{bmatrix}$$
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment