Compare commits

...

2 Commits

View File

@@ -1,13 +1,45 @@
const C4_EXPLOSION_TIME = 3 * 20; const KeyMapping = Java.loadClass("net.minecraft.client.KeyMapping");
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 {Map<Internal.UUID, { * @type {{ [key: string]:{
* angle: {x: number, y:number, z:number}, * 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 = new Map(); 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.ItemStack} itemstack
@@ -16,27 +48,65 @@ const lastPlayerInfoMap = new Map();
* @returns {boolean} * @returns {boolean}
*/ */
function shouldActivateC4(itemstack, level, player) { function shouldActivateC4(itemstack, level, player) {
const playerPos = player.position(); const blockUnder = getBlockUnderPlayer(player);
const block = level.getBlock( const block = level.getBlock(blockUnder.x, blockUnder.y, blockUnder.z);
playerPos.x() - 1, // Must subtract 1, is it a Bug ???
playerPos.y() - 1, // The block under the player
playerPos.z(),
);
const lookAngle = player.lookAngle; const lookAngle = player.lookAngle;
const lastPlayerInfo = lastPlayerInfoMap.get(player.UUID); const playerPos = player.position();
const lastPlayerInfo = lastPlayerInfoMap[player.uuid.toString()];
if (lastPlayerInfo === undefined) return false; 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 = const isPlayerInfoChanged =
lookAngle.x() !== lastPlayerInfo.angle.x || isBlockPosChanged || isPosChanged || isAngleChanged;
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;
return ( return (
/** @type {string} */ (block.id) === "kubejs:c4_target" && /** @type {string} */ (block.id) === "kubejs:c4_target" &&
!isPlayerInfoChanged !isPlayerInfoChanged &&
/** @type {string} */ (itemstack.id) === "kubejs:c4_item"
); );
} }
@@ -62,26 +132,27 @@ StartupEvents.registry("block", (event) => {
.displayName(/** @type {any} */ ("C4")); // Set a custom name .displayName(/** @type {any} */ ("C4")); // Set a custom name
}); });
let c4PlacedGameTime = 0;
StartupEvents.registry("item", (event) => { StartupEvents.registry("item", (event) => {
event event
.create("c4_item") .create("c4_item")
.unstackable() .unstackable()
.useAnimation("eat") .useAnimation("eat")
.useDuration((itemStack) => 100) // 5 Seconds .useDuration((_itemStack) => 100) // 5 Seconds
.use((level, player, hand) => { .use((level, player, _hand) => {
const playerPos = player.position(); const blockUnder = getBlockUnderPlayer(player);
const block = level.getBlock( const block = level.getBlock(
playerPos.x() - 1, // Must subtract 1, is it a Bug ??? blockUnder.x,
playerPos.y() - 1, // The block under the player blockUnder.y,
playerPos.z(), blockUnder.z,
); );
if (/** @type {string} */ (block.id) === "kubejs:c4_target") { if (/** @type {string} */ (block.id) !== "kubejs:c4_target") {
const itemstack = player.getUseItem(); return false;
}
const playerPos = player.position();
const lookAngle = player.lookAngle; const lookAngle = player.lookAngle;
lastPlayerInfoMap.set(player.UUID, { lastPlayerInfoMap[player.uuid.toString()] = {
angle: { angle: {
x: lookAngle.x(), x: lookAngle.x(),
y: lookAngle.y(), y: lookAngle.y(),
@@ -92,12 +163,52 @@ StartupEvents.registry("item", (event) => {
y: playerPos.y(), y: playerPos.y(),
z: playerPos.z(), z: playerPos.z(),
}, },
blockPos: blockUnder,
};
console.log(`Player UUID: ${player.uuid}`);
console.log(
`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;
}
// 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 - ${(remainingTicks / 20.0).toFixed(2)}s`,
)
),
);
event.reschedule();
}); });
return true; return true;
} else {
return false;
}
}) })
.finishUsing((itemstack, level, entity) => { .finishUsing((itemstack, level, entity) => {
if (!entity.isPlayer()) { if (!entity.isPlayer()) {
@@ -105,84 +216,99 @@ StartupEvents.registry("item", (event) => {
return itemstack; return itemstack;
} }
if ( /** @type {Internal.Player} */
!shouldActivateC4(itemstack, level, /** @type {any} */ (entity)) const player = /** @type {any} */ (entity);
) {
if (!shouldActivateC4(itemstack, level, player)) {
itemstack.resetHoverName();
delete lastPlayerInfoMap[player.uuid.toString()];
return itemstack; // Do nothing return itemstack; // Do nothing
} }
const playerPos = entity.position(); // Place C4 at player's feet
const newBlock = level.getBlock(playerPos); 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")); newBlock.set(/** @type {any} */ ("kubejs:c4"));
itemstack.shrink(1); itemstack.shrink(1);
itemstack.resetHoverName(); itemstack.resetHoverName();
delete lastPlayerInfoMap[player.uuid.toString()];
const server = level.server; 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) => { server.scheduleInTicks(20, (event) => {
const gameTime = level.levelData.getGameTime(); const currentGameTime = level.levelData.getGameTime();
const explosionRestTime = const elapsedTime = currentGameTime - c4PlacedGameTime;
C4_EXPLOSION_RANGE - gameTime - c4PlacedGameTime; const explosionRestTime = C4_EXPLOSION_TIME - elapsedTime;
if (explosionRestTime > 0) { if (explosionRestTime > 0) {
server.tell( server.players.forEach((p) => {
/** @type {Component} */ ( p.tell(
/** @type {any} */ (
Component.literal( Component.literal(
`C4还剩 ${Math.floor(explosionRestTime / 20)}`, `C4还剩 ${Math.ceil(explosionRestTime / 20)}爆炸`,
) )
), ),
); );
event.reschedule(20); });
event.reschedule();
} else { } else {
const blockPos = newBlock.pos; // 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( level.explode(
/** @type {any} */ (null), /** @type {any} */ (null),
blockPos.x, explosionX + 0.5,
blockPos.y, explosionY + 0.5,
blockPos.z, explosionZ + 0.5,
C4_EXPLOSION_RANGE, C4_EXPLOSION_POWER,
"block", "block",
); );
} }
}
}); });
return itemstack; return itemstack;
}) })
.releaseUsing((itemstack, level, entity, count) => { .releaseUsing((itemstack, _level, entity, _count) => {
itemstack.resetHoverName(); itemstack.resetHoverName();
if (!entity.isPlayer()) return; if (!entity.isPlayer()) return;
lastPlayerInfoMap.delete(entity.UUID); delete lastPlayerInfoMap[entity.uuid.toString()];
}) })
.displayName(/** @type {any} */ ("C4")); .displayName(/** @type {any} */ ("C4"));
}); });
let useItemTickCnt = 0; const EXAMPLE_MAPPING = new KeyMapping(
const useItemTickInterval = 5; "key.examplemod.example1", // Will be localized using this translation key
ForgeEvents.onEvent( 69, // Default key is E
"net.minecraftforge.event.entity.living.LivingEntityUseItemEvent$Tick", "key.categories.misc", // Mapping will be in the misc category
(event) => {
// Check every 5 ticks (0.25s)
if (useItemTickCnt++ % useItemTickInterval !== 0) return;
const itemstack = event.getItem();
if (/** @type {string} */ (itemstack.id) !== "kubejs:c4_item") return;
if (
!shouldActivateC4(
itemstack,
event.entity.level,
/** @type {any} */ (event.entity),
)
) {
event.setCanceled(true);
}
const useDuration = event.duration;
if (useDuration <= 0) return;
itemstack.setHoverName(
/** @type {any} */ (
Component.literal(`C4 - ${(useDuration / 20.0).toFixed(2)}s`)
),
); );
ForgeEvents.onEvent(
"net.minecraftforge.client.event.RegisterKeyMappingsEvent",
(event) => {
event.register(EXAMPLE_MAPPING);
}, },
); );