|
| 1 | +import { |
| 2 | + Block, |
| 3 | + BlockLocation, |
| 4 | + BlockPermutation, |
| 5 | + Entity, |
| 6 | + EntityDataDrivenTriggerEventOptions, |
| 7 | + Items, |
| 8 | + ItemStack, |
| 9 | + Location, |
| 10 | + MinecraftBlockTypes, |
| 11 | + Vector, |
| 12 | + world, |
| 13 | +} from "mojang-minecraft"; |
| 14 | +import { createEntityBlock, getGamerules, isInList } from "./utils.js"; |
| 15 | + |
| 16 | +import { explosionConfig } from "./explosion-config.js"; |
| 17 | +let { |
| 18 | + additionalVerticalVelocity, |
| 19 | + collideWithEntities, |
| 20 | + despawnTimer, |
| 21 | + dropWhenUnplaceable, |
| 22 | + enabled, |
| 23 | + horizontalVelocityModifier, |
| 24 | + ignoreBlocks, |
| 25 | + placeWhenHitGround, |
| 26 | + replaceBlocks, |
| 27 | + rotateWithVelocity, |
| 28 | + simpleMath, |
| 29 | + verticalVelocityModifier, |
| 30 | + minBlocksToSpawnPerTick, |
| 31 | +} = explosionConfig; |
| 32 | +ignoreBlocks = ignoreBlocks.map((v) => v.replace("minecraft:", "")); |
| 33 | +replaceBlocks = replaceBlocks.map((v) => v.replace("minecraft:", "")); |
| 34 | + |
| 35 | +const blockMap = new Map<Entity, BlockPermutation>(); |
| 36 | + |
| 37 | +let tick = 0; |
| 38 | +world.events.tick.subscribe(({ currentTick }) => (tick = currentTick)); |
| 39 | +if (enabled) { |
| 40 | + world.events.beforeExplosion.subscribe(async (evd) => { |
| 41 | + if (!evd.source) return; |
| 42 | + |
| 43 | + const dim = evd.dimension; |
| 44 | + const orgLoc = evd.source.location; |
| 45 | + const origin = new Vector(orgLoc.x, orgLoc.y + 0.5, orgLoc.z); |
| 46 | + |
| 47 | + const blocks = evd.impactedBlocks |
| 48 | + .map((v) => dim.getBlock(v)) |
| 49 | + .filter((v) => !isInList(ignoreBlocks, v.id.replace("minecraft:", ""))); |
| 50 | + |
| 51 | + const blockDist = new Map<Block, number>(); |
| 52 | + const blockId = new Map<Block, string>(); |
| 53 | + for (let block of blocks) { |
| 54 | + if (!simpleMath) { |
| 55 | + blockDist.set( |
| 56 | + block, |
| 57 | + Math.hypot( |
| 58 | + origin.x - block.x, |
| 59 | + origin.y - (block.y - 0.5), |
| 60 | + origin.z - block.z |
| 61 | + ) |
| 62 | + ); |
| 63 | + } |
| 64 | + blockId.set(block, block.id); |
| 65 | + } |
| 66 | + const range = Math.max(...blockDist.values()); |
| 67 | + |
| 68 | + let currentTick = tick; |
| 69 | + let blocksInTick = 0; |
| 70 | + |
| 71 | + for (let block of blocks) { |
| 72 | + const pos = new Vector(block.x + 0.5, block.y - 0.5, block.z + 0.5); |
| 73 | + |
| 74 | + let vel = Vector.subtract(pos, origin); |
| 75 | + const dist = Math.hypot(vel.x, vel.y, vel.z); |
| 76 | + vel = Vector.divide(vel, dist); |
| 77 | + |
| 78 | + if (!simpleMath) { |
| 79 | + const distMod = |
| 80 | + (range - Math.pow(dist, 1.5) + Math.pow(dist, 1.1)) / range; |
| 81 | + vel = Vector.multiply(vel, distMod * 2); |
| 82 | + } |
| 83 | + |
| 84 | + vel.y += additionalVerticalVelocity; |
| 85 | + vel = Vector.multiply( |
| 86 | + vel, |
| 87 | + new Vector( |
| 88 | + horizontalVelocityModifier, |
| 89 | + verticalVelocityModifier, |
| 90 | + horizontalVelocityModifier |
| 91 | + ) |
| 92 | + ); |
| 93 | + |
| 94 | + const id = blockId.get(block) ?? "minecraft:air"; |
| 95 | + const eBlock = createEntityBlock( |
| 96 | + id, |
| 97 | + block.dimension, |
| 98 | + new Location(block.x + 0.5, block.y + 0.5, block.z + 0.5) |
| 99 | + ); |
| 100 | + |
| 101 | + eBlock.addTag("$entityBlockFromTNT"); |
| 102 | + if (collideWithEntities) eBlock.triggerEvent("collision"); |
| 103 | + if (despawnTimer) eBlock.triggerEvent("despawn_timer"); |
| 104 | + if (placeWhenHitGround) blockMap.set(eBlock, block.permutation); |
| 105 | + if (rotateWithVelocity) eBlock.triggerEvent("rotate"); |
| 106 | + eBlock.setVelocity(vel); |
| 107 | + block.setType(MinecraftBlockTypes.air); |
| 108 | + if (currentTick != tick) { |
| 109 | + currentTick = tick; |
| 110 | + blocksInTick = 0; |
| 111 | + } |
| 112 | + blocksInTick++; |
| 113 | + if (blocksInTick > minBlocksToSpawnPerTick) await null; |
| 114 | + } |
| 115 | + }); |
| 116 | + |
| 117 | + if (placeWhenHitGround) { |
| 118 | + const options = new EntityDataDrivenTriggerEventOptions(); |
| 119 | + options.eventTypes = ["hit_ground"]; |
| 120 | + world.events.beforeDataDrivenEntityTriggerEvent.subscribe((evd) => { |
| 121 | + const entity = evd.entity; |
| 122 | + if (!entity.hasTag("$entityBlockFromTNT")) return; |
| 123 | + |
| 124 | + const perm = blockMap.get(entity); |
| 125 | + blockMap.delete(entity); |
| 126 | + if (!perm) return; |
| 127 | + |
| 128 | + const loc = entity.location; |
| 129 | + const block = entity.dimension.getBlock( |
| 130 | + new BlockLocation( |
| 131 | + Math.floor(loc.x), |
| 132 | + Math.floor(loc.y + 0.25), |
| 133 | + Math.floor(loc.z) |
| 134 | + ) |
| 135 | + ); |
| 136 | + if (isInList(replaceBlocks, block.id.replace("minecraft:", ""))) { |
| 137 | + block.setPermutation(perm); |
| 138 | + } else if (dropWhenUnplaceable) { |
| 139 | + if (getGamerules().dotiledrops) { |
| 140 | + const item = new ItemStack(Items.get(perm.type.id), 1); |
| 141 | + entity.dimension.spawnItem(item, loc); |
| 142 | + } |
| 143 | + } |
| 144 | + entity.triggerEvent("despawn"); |
| 145 | + }, options); |
| 146 | + } |
| 147 | +} |
0 commit comments