视频: https://www.bilibili.com/video/BV1zADtYGEuQ/
项目地址: https://github.com/gizmo-ds/taczjs-mod
MC 百科: https://www.mcmod.cn/class/16961.html
Modrinth: https://modrinth.com/mod/tacz-js
CurseForge: https://www.curseforge.com/minecraft/mc-mods/tacz-js

视频中使用的枪包是由 Rhodes_koei 创作的 机动军械师

例子可以在项目的 example 目录中找到, TypeScript 参考类型可以在 types 中找到.

金主爸爸们

赞助者

赞助我

如果你遇到了问题

你可以在上面的视频底下留言, 或加入我的 QQ 群 324617456 交流.

视频中使用到的代码

点击 Details 展开代码, 点击代码块右上角的复制按钮可以复制代码

左键攻击右键检视武器,启用原版交互

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// client_scripts/melee.js

const $HitResult$Type = Java.tryLoadClass("net.minecraft.world.phys.HitResult$Type");
const $IWrenchable = Java.tryLoadClass("com.simibubi.create.content.equipment.wrench.IWrenchable");

const MeleeWeapons = [
  "create_armorer:special_melee_wrench",
  "create_armorer:special_melee_atomic",
];

// 被视为扳手的枪械
const Wrenchs = ["create_armorer:special_melee_wrench"];

// 是否播放交互动画
const PlayInteractAnimation = true;

// https://github.com/Creators-of-Create/Create/blob/d48a504486311f3175f4ebef3b0649140e728fbb/src/generated/resources/data/create/tags/blocks/wrench_pickup.json
const WrenchPickup = [
  "create:andesite_bars",
  "create:brass_bars",
  "create:copper_bars",
  "create:industrial_iron_block",
  "minecraft:redstone_wire",
  "minecraft:redstone_torch",
  "minecraft:repeater",
  "minecraft:lever",
  "minecraft:comparator",
  "minecraft:observer",
  "minecraft:redstone_wall_torch",
  "minecraft:piston",
  "minecraft:sticky_piston",
  "minecraft:tripwire",
  "minecraft:tripwire_hook",
  "minecraft:daylight_detector",
  "minecraft:target",
  "minecraft:hopper",
  "#minecraft:buttons",
  "#minecraft:pressure_plates",
  "#minecraft:rails",
];

TaCZClientEvents.gunIndexLoad((event) => {
  if (MeleeWeapons.includes(event.getGunId().toString())) {
    // 为近战武器添加原版交互设置
    event.setVanillaInteract(true);
  }
});

TaCZClientEvents.playerAim((event) => {
  const gunId = event.getGunId().toString();
  if (MeleeWeapons.includes(gunId)) {
    // 只处理开始瞄准的事件
    if (!event.isAim()) return;

    // 如果是原版交互并且可以交互, 播放交互动画并取消瞄准
    if (event.isVanillaInteract() && event.canInteractEntity()) {
      if (PlayInteractAnimation) {
        TaCZJSUtils.SoundPlayManager.stopPlayGunSound();
        event.runMaimAnimation(
          "melee_bayonet_1",
          TaCZJSUtils.AnimationPlayType.PLAY_ONCE_STOP,
          0.2
        );
      }
      return event.cancelAim();
    }

    const blockHitResult = event.getBlockHitResult();
    if (
      Wrenchs.includes(gunId) &&
      blockHitResult !== null &&
      blockHitResult.getType() === $HitResult$Type.BLOCK
    ) {
      const state = event
        .getLevel()
        .getBlockState(blockHitResult.getBlockPos());
      const block = state.getBlock();

      // 如果是可旋转方块或者可拾取的方块, 播放交互动画并取消瞄准
      if (
        block instanceof $IWrenchable ||
        WrenchPickup.includes(state.getBlock().getIdLocation().toString())
      ) {
        if (PlayInteractAnimation) {
          TaCZJSUtils.SoundPlayManager.stopPlayGunSound();
          event.runMaimAnimation(
            "melee_bayonet_1",
            TaCZJSUtils.AnimationPlayType.PLAY_ONCE_STOP,
            0.2
          );
        }
        return event.cancelAim();
      }
    }

    event.cancelAim();
    const gunIndex = event.getGunIndex();
    const am = gunIndex.getAnimationStateMachine();
    if (am === null) return;
    if (am.isPlayingRunIntroOrLoop()) {
      // 如果正在跑步, 播放 run_start 动画
      event.runMovementAnimation(
        "run_start",
        TaCZJSUtils.AnimationPlayType.PLAY_ONCE_HOLD,
        0.2
      );
    } else if (am.isPlayingIdleAnimation() || am.isPlayingWalkAnimation()) {
      // 如果正在站立或者走路, 播放武器检视动画
      TaCZJSUtils.SoundPlayManager.stopPlayGunSound();
      TaCZJSUtils.SoundPlayManager.playInspectSound(event.getPlayer(), gunIndex, false);
      am.onGunInspect();
    }
  }
});

TaCZClientEvents.playerShoot((event) => {
  if (MeleeWeapons.includes(event.getGunId().toString())) {
    // 停止播放音效
    TaCZJSUtils.SoundPlayManager.stopPlayGunSound();
    // 如果近战武器进行射击, 取消射击并执行近战
    event.cancelShoot();
    event.getGunOperator().melee();
  }
});

机动军械师 的扳手拥有机械动力的扳手的功能

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
// server_scripts/wrench.js

const $Random = Java.tryLoadClass("java.util.Random");
const RANDOM = new $Random();
const $HitResult$Type = Java.tryLoadClass("net.minecraft.world.phys.HitResult$Type");
const $BlockHitResult = Java.tryLoadClass("net.minecraft.world.phys.BlockHitResult");
const $UseOnContext = Java.tryLoadClass("net.minecraft.world.item.context.UseOnContext");
const $AllSoundEvents = Java.tryLoadClass("com.simibubi.create.AllSoundEvents");
const $InteractionHand = Java.tryLoadClass("net.minecraft.world.InteractionHand");
const $IWrenchable = Java.tryLoadClass("com.simibubi.create.content.equipment.wrench.IWrenchable");
const $Block = Java.tryLoadClass("net.minecraft.world.level.block.Block")
const $ItemStack = Java.tryLoadClass("net.minecraft.world.item.ItemStack")

// https://github.com/Creators-of-Create/Create/blob/d48a504486311f3175f4ebef3b0649140e728fbb/src/generated/resources/data/create/tags/blocks/wrench_pickup.json
const WrenchPickup = [
  "create:andesite_bars",
  "create:brass_bars",
  "create:copper_bars",
  "create:industrial_iron_block",
  "minecraft:redstone_wire",
  "minecraft:redstone_torch",
  "minecraft:repeater",
  "minecraft:lever",
  "minecraft:comparator",
  "minecraft:observer",
  "minecraft:redstone_wall_torch",
  "minecraft:piston",
  "minecraft:sticky_piston",
  "minecraft:tripwire",
  "minecraft:tripwire_hook",
  "minecraft:daylight_detector",
  "minecraft:target",
  "minecraft:hopper",
  "#minecraft:buttons",
  "#minecraft:pressure_plates",
  "#minecraft:rails",
];

const Wrenchs = ["create_armorer:special_melee_wrench"];

ItemEvents.firstRightClicked((event) => {
  const item = event.getItem();
  if (item.equals(Item.of("minecraft:air"))) return;

  const target = event.getTarget();
  if (
    (target !== null && target.type.toString() !== $HitResult$Type.BLOCK.toString()) ||
    !(item.getId().toString() === "tacz:modern_kinetic_gun" && Wrenchs.includes(item.getNbt().getString("GunId")))
  ) {
    return;
  }

  const state = target.block.getBlockState();
  const block = state.getBlock();
  const world = event.getLevel();
  const player = event.getPlayer();
  const blockHitResult = new $BlockHitResult(
    target.hit,
    target.facing,
    target.block.getPos(),
    false
  );
  const context = new $UseOnContext(
    world,
    player,
    $InteractionHand.MAIN_HAND,
    item,
    blockHitResult
  );

  if (block instanceof $IWrenchable) {
    const actor = block;
    player.isShiftKeyDown()
      ? actor.onSneakWrenched(state, context)
      : actor.onWrenched(state, context);
  } else {
    player.isShiftKeyDown() &&
      WrenchPickup.includes(state.getBlock().getIdLocation().toString()) &&
      onItemUseOnOther(player, world, target, item);
  }
});

function onItemUseOnOther(player, world, target, item) {
  const state = target.block.getBlockState();
  const pos = target.block.getPos();
  if (player !== null && !player.isCreative()) {
    $Block
      .getDrops(state, world, pos, world.getBlockEntity(pos), player, item)
      .forEach((itemStack) => {
        player.getInventory().placeItemBackInInventory(itemStack);
      });
  }
  state.spawnAfterBreak(world, pos, $ItemStack.EMPTY, true);
  world.destroyBlock(pos, false);
  $AllSoundEvents.WRENCH_REMOVE.playOnServer(
    world,
    pos,
    1.0,
    RANDOM.nextFloat() * 0.5 + 0.5
  );
}

Hardcore Revival 的倒地后仅允许使用手枪射击

介绍视频: https://www.bilibili.com/video/BV19QDqY8EsE/

客户端脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// client_scripts/hardcore_revival.js

const $HardcoreRevival = Java.tryLoadClass("net.blay09.mods.hardcorerevival.HardcoreRevival");

function isKnockedOut(player) {
  return $HardcoreRevival.getRevivalData(player).isKnockedOut();
}

TaCZClientEvents.playerShoot((event) => {
  if (isKnockedOut(event.getGunOperator())) {
    // 倒地后仅允许使用手枪射击
    if (TaCZJSUtils.getGunIndex(event.getGunId()).type !== "pistol") {
      return event.cancelShoot();
    }
  }
});

TaCZClientEvents.playerMelee((event) => {
  // 倒地后禁用枪械近战
  isKnockedOut(event.getGunOperator()) && event.cancelMelee();
});

服务端脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// server_scripts/hardcore_revival.js

const $HardcoreRevival = Java.tryLoadClass("net.blay09.mods.hardcorerevival.HardcoreRevival");

function isKnockedOut(player) {
  return $HardcoreRevival.getRevivalData(player).isKnockedOut();
}

TaCZServerEvents.entityShoot((event) => {
  if (isKnockedOut(event.getShooter())) {
    // 倒地后仅允许使用手枪射击
    if (TaCZJSUtils.getGunIndex(event.getGunId()).type !== "pistol") {
      return event.cancelShoot();
    }
  }
});

TaCZServerEvents.entityMelee((event) => {
  // 倒地后禁用枪械近战
  isKnockedOut(event.getShooter()) && event.cancelMelee();
});