diff --git a/src/startup_scripts/C4.js b/src/startup_scripts/C4.js index 2ad979b..1c13ce9 100644 --- a/src/startup_scripts/C4.js +++ b/src/startup_scripts/C4.js @@ -1,16 +1,46 @@ const KeyMapping = Java.loadClass("net.minecraft.client.KeyMapping"); -const C4_EXPLOSION_TIME = 3 * 20; -const C4_EXPLOSION_RANGE = 256; +const C4_EXPLOSION_TIME = 3 * 20; // 3 seconds in ticks +const C4_EXPLOSION_POWER = 4; // Explosion power (TNT is 4) + +// Tolerance for floating point comparison +const ANGLE_TOLERANCE = 0.001; +const POS_TOLERANCE = 0.01; /** * @type {{ [key: string]:{ * angle: {x: number, y:number, z:number}, - * pos: {x: number, y: number, z: number} + * pos: {x: number, y: number, z: number}, + * blockPos: {x: number, y: number, z: number} * } | undefined}} */ const lastPlayerInfoMap = {}; +/** + * Helper function to compare floating point numbers with tolerance + * @param {number} a + * @param {number} b + * @param {number} tolerance + * @returns {boolean} + */ +function isApproximatelyEqual(a, b, tolerance) { + return Math.abs(a - b) <= tolerance; +} + +/** + * Get the block position under the player + * @param {Internal.Player} player + * @returns {{x: number, y: number, z: number}} + */ +function getBlockUnderPlayer(player) { + const playerPos = player.position(); + return { + x: Math.floor(playerPos.x()), + y: Math.floor(playerPos.y()) - 1, + z: Math.floor(playerPos.z()), + }; +} + /** * @param {Internal.ItemStack} itemstack * @param {Internal.Level} level @@ -18,23 +48,60 @@ const lastPlayerInfoMap = {}; * @returns {boolean} */ function shouldActivateC4(itemstack, level, player) { - const playerPos = player.position(); - const block = level.getBlock( - playerPos.x() - 1, // Must subtract 1, is it a Bug ??? - playerPos.y() - 1, // The block under the player - playerPos.z(), - ); + const blockUnder = getBlockUnderPlayer(player); + const block = level.getBlock(blockUnder.x, blockUnder.y, blockUnder.z); const lookAngle = player.lookAngle; + const playerPos = player.position(); const lastPlayerInfo = lastPlayerInfoMap[player.uuid.toString()]; + if (lastPlayerInfo === undefined) return false; + + // Check if player moved (using block position for stability) + const currentBlockPos = getBlockUnderPlayer(player); + const isBlockPosChanged = + currentBlockPos.x !== lastPlayerInfo.blockPos.x || + currentBlockPos.y !== lastPlayerInfo.blockPos.y || + currentBlockPos.z !== lastPlayerInfo.blockPos.z; + + // Check if player moved within the same block (with tolerance) + const isPosChanged = + !isApproximatelyEqual( + playerPos.x(), + lastPlayerInfo.pos.x, + POS_TOLERANCE, + ) || + !isApproximatelyEqual( + playerPos.y(), + lastPlayerInfo.pos.y, + POS_TOLERANCE, + ) || + !isApproximatelyEqual( + playerPos.z(), + lastPlayerInfo.pos.z, + POS_TOLERANCE, + ); + + // Check if player rotated view (with tolerance) + const isAngleChanged = + !isApproximatelyEqual( + lookAngle.x(), + lastPlayerInfo.angle.x, + ANGLE_TOLERANCE, + ) || + !isApproximatelyEqual( + lookAngle.y(), + lastPlayerInfo.angle.y, + ANGLE_TOLERANCE, + ) || + !isApproximatelyEqual( + lookAngle.z(), + lastPlayerInfo.angle.z, + ANGLE_TOLERANCE, + ); + const isPlayerInfoChanged = - lookAngle.x() !== lastPlayerInfo.angle.x || - lookAngle.y() !== lastPlayerInfo.angle.y || - lookAngle.z() !== lastPlayerInfo.angle.z || - playerPos.x() !== lastPlayerInfo.pos.x || - playerPos.y() !== lastPlayerInfo.pos.y || - playerPos.z() !== lastPlayerInfo.pos.z; + isBlockPosChanged || isPosChanged || isAngleChanged; return ( /** @type {string} */ (block.id) === "kubejs:c4_target" && @@ -65,7 +132,6 @@ StartupEvents.registry("block", (event) => { .displayName(/** @type {any} */ ("C4")); // Set a custom name }); -let c4PlacedGameTime = 0; StartupEvents.registry("item", (event) => { event .create("c4_item") @@ -73,18 +139,18 @@ StartupEvents.registry("item", (event) => { .useAnimation("eat") .useDuration((_itemStack) => 100) // 5 Seconds .use((level, player, _hand) => { - const playerPos = player.position(); + const blockUnder = getBlockUnderPlayer(player); const block = level.getBlock( - playerPos.x() - 1, // Must subtract 1, is it a Bug ??? - playerPos.y() - 1, // The block under the player - playerPos.z(), + blockUnder.x, + blockUnder.y, + blockUnder.z, ); if (/** @type {string} */ (block.id) !== "kubejs:c4_target") { return false; } - // const itemstack = player.getUseItem(); + const playerPos = player.position(); const lookAngle = player.lookAngle; lastPlayerInfoMap[player.uuid.toString()] = { angle: { @@ -97,30 +163,44 @@ StartupEvents.registry("item", (event) => { y: playerPos.y(), z: playerPos.z(), }, + blockPos: blockUnder, }; - // console.log(`Map count: ${lastPlayerInfoMap.size}`); console.log(`Player UUID: ${player.uuid}`); console.log( - `Player Info: ${lastPlayerInfoMap[player.uuid.toString()]}`, + `Player Info: ${JSON.stringify(lastPlayerInfoMap[player.uuid.toString()])}`, ); const server = Utils.server; server.scheduleInTicks(5, (event) => { const itemstack = player.getUseItem(); + + // Check if player is still using the item + if ( + itemstack === undefined || + /** @type {string} */ (itemstack.id) !== "kubejs:c4_item" + ) { + return; + } + if (!shouldActivateC4(itemstack, level, player)) { player.stopUsingItem(); player.addItemCooldown(itemstack.getItem(), 20); itemstack.resetHoverName(); + delete lastPlayerInfoMap[player.uuid.toString()]; return; } - const useDuration = itemstack.useDuration; - if (useDuration <= 0) return; + // Get remaining ticks for this use + const ticksUsingItem = player.getTicksUsingItem(); + const remainingTicks = 100 - ticksUsingItem; // useDuration is 100 + + // if (remainingTicks <= 0) return; + itemstack.setHoverName( /** @type {any} */ ( Component.literal( - `C4 - ${(useDuration / 20.0).toFixed(2)}s`, + `C4 - ${(remainingTicks / 20.0).toFixed(2)}s`, ) ), ); @@ -136,46 +216,78 @@ StartupEvents.registry("item", (event) => { return itemstack; } - if ( - !shouldActivateC4(itemstack, level, /** @type {any} */ (entity)) - ) { + /** @type {Internal.Player} */ + const player = /** @type {any} */ (entity); + + if (!shouldActivateC4(itemstack, level, player)) { + itemstack.resetHoverName(); + delete lastPlayerInfoMap[player.uuid.toString()]; return itemstack; // Do nothing } - const playerPos = entity.position(); - const newBlock = level.getBlock(playerPos); + // Place C4 at player's feet + const playerPos = player.position(); + const c4BlockPos = { + x: Math.floor(playerPos.x()), + y: Math.floor(playerPos.y()), + z: Math.floor(playerPos.z()), + }; + const newBlock = level.getBlock( + c4BlockPos.x, + c4BlockPos.y, + c4BlockPos.z, + ); newBlock.set(/** @type {any} */ ("kubejs:c4")); itemstack.shrink(1); itemstack.resetHoverName(); + delete lastPlayerInfoMap[player.uuid.toString()]; const server = level.server; - c4PlacedGameTime = level.levelData.getGameTime(); + const c4PlacedGameTime = level.levelData.getGameTime(); + + // Store block position for explosion (capture in closure) + const explosionX = c4BlockPos.x; + const explosionY = c4BlockPos.y; + const explosionZ = c4BlockPos.z; + server.scheduleInTicks(20, (event) => { - const gameTime = level.levelData.getGameTime(); - const explosionRestTime = - C4_EXPLOSION_RANGE - gameTime - c4PlacedGameTime; + const currentGameTime = level.levelData.getGameTime(); + const elapsedTime = currentGameTime - c4PlacedGameTime; + const explosionRestTime = C4_EXPLOSION_TIME - elapsedTime; + if (explosionRestTime > 0) { - server.players.forEach((player) => { - player.tell( + server.players.forEach((p) => { + p.tell( /** @type {any} */ ( Component.literal( - `C4还剩 ${Math.floor(explosionRestTime / 20)} 秒爆炸`, + `C4还剩 ${Math.ceil(explosionRestTime / 20)} 秒爆炸`, ) ), ); }); - event.reschedule(20); + event.reschedule(); } else { - const blockPos = newBlock.pos; - level.explode( - /** @type {any} */ (null), - blockPos.x, - blockPos.y, - blockPos.z, - C4_EXPLOSION_RANGE, - "block", + // Check if C4 block is still there before exploding + const c4Block = level.getBlock( + explosionX, + explosionY, + explosionZ, ); + if (/** @type {string} */ (c4Block.id) === "kubejs:c4") { + // Remove the C4 block first + c4Block.set(/** @type {any} */ ("minecraft:air")); + + // Create explosion + level.explode( + /** @type {any} */ (null), + explosionX + 0.5, + explosionY + 0.5, + explosionZ + 0.5, + C4_EXPLOSION_POWER, + "block", + ); + } } });