前言

mc版本1.20.1,写匠魂特性需要有头脑的工匠

需要注意的是,因为kjs不好评价的文档和几乎没有注释的源码,下文的内容中或许有更好的实现方法,此外,由于技术的不断精进,越考下的特性效果越成熟,代码越精简(可能?),复杂度越高(应该)

提示:大部分数据都放在nbt里,可以通过拆分nbt来获取值(用get函数),1.20.1的实体的id是数字,想要获取生物类型应该用type

使用方法:在startup_scripts文件夹下新建任意js文件,写入内容,在kubejs/data/kubejs/tinkering/materials/traits中找到你想要的材料json(没有请见其他用kjs向匠魂添加自定义材料的教程),写入(以第一个特性为例)

{
  "default": [
    {
  "level": 1,
      "name": "kubejs:remove\_phantom"
    }
  ]
}

此外,需要自行修改lang文件,否则书中不会正常显示

第一部分和第二部分是什么:
第一部分是直接在startevent中用kubejs本体和有头脑的工匠提供的方法,限定在startscript文件夹中
第二部分是一种曲线救国的方案,在其他地方定义具体逻辑,在startscript仅定义一个空的特性(或者一个正常的特性也行),一般依赖物品的NBT(特性也存在nbt里面),不知道这是不是作者留空特性的意义

如果后续我还发现一些其他的方法,我还会在此添加部分

第一部分

第一部分的所有内容都需要以
TConJSEvents.modifierRegistry((event) => {
开头,下面仅记录在这个大括号下的内容(看不懂可以去看mod作者在github留的例子)

清除玩家半径内的指定生物

这里以幻翼为例

event.createNew("kubejs:remove_phantom", (builder) => {
        builder.onInventoryTick((view, lvl, level, entity, slot, inMainHand, inAvailableSlot, itemStack) => {
            // 只对主手工具生效
            if (!inMainHand) return

            // 每20 tick(1秒)执行一次
            if (entity.age % 20 !== 0) return

            //半径为3+等级*2
            let distance = 3 + (lvl * 2)
            let area = entity.getBoundingBox().inflate(distance)
            let entities = entity.level.getEntitiesWithin(area)

            entities.forEach((entity) => {
                if (entity.type === 'minecraft:phantom') {//这里可以换其他生物
                    entity.kill(); //有一个entity.remove(),但是用的时候会卡死,直接kill省事
                }
            });
        })
    })

dash

右键向前方突进一段距离,二级有y轴移动(但不多)然后少2秒cd
本来想做成瞬间加速的,但是现在效果像是瞬移(会被方块遮挡)
这个技能有cd,而且会导致所有同类武器都进cd(我尝试用nbt匹配但是失败了)

//dash
    event.createNew("kubejs:dash", builder => {
        builder.onUseTool((view, lvl, player, hand, source) => {
            player.addItemCooldown(view.getItem(), lvl > 1 ? 160 : 200)
            player.move("player", player.lookAngle.multiply(5 * lvl, (lvl - 1) * 10, 5 * lvl))
            return true;
        });
    })

第二部分

需要在

TConJSEvents.modifierRegistry((event) => {

})

中定义特性,可以是空特性,也可以是具体特性,下面是空特性的代码
event.createEmpty(具体id);
此处的细则都是完整代码,需要自行拆分

额外击杀掉落

写在startup_scripts的js任意文件中

TConJSEvents.modifierRegistry((event) => {
    event.createEmpty("kubejs:add\_drop");
    })

写在server_scripts的任意js文件中

// kubejs:add\_drop
EntityEvents.death((event) => {
    let entity = event.entity; // 被击杀的实体
    let source = event.source; // 伤害来源
    let attacker = source.getActual(); // 实际攻击者

    // 检查攻击者是否为玩家
    if (attacker && attacker.isPlayer()) {
        let player = attacker; // 直接使用 attacker,因为 isPlayer() 确认其为玩家
        let handSlots = player.getHandSlots(); // 获取玩家手中的物品槽位(主手和副手)
        let mainHandItem = null;
        
        // 获取主手物品(通常是第一个槽位)
        let iterator = handSlots.iterator();//这里是一个迭代器
        if (iterator.hasNext())
            mainHandItem = iterator.next(); // 主手物品是第一个槽位
            
        if (mainHandItem.hasTag('tconstruct:modifiable')) {// 检查主手物品是否为匠魂工具
            let modifiers = mainHandItem.getNbt().getAsString(); // 获取工具的NBT
            let hasAddDropModifier = false;
            // 检查NBT中是否包含 "kubejs:add_drop"
            if (modifiers.includes("kubejs:add_drop"))
                hasAddDropModifier = true;
                
            // 如果工具具有 "kubejs:add_drop" 修饰符
            if (hasAddDropModifier) {
                let world = entity.level; // 获取实体所在的世界
                let x = entity.x; // 实体 X 坐标
                let y = entity.y + 1; // 实体 Y 坐标上方 1 格
                let z = entity.z; // 实体 Z 坐标
                
                // 生成掉落物,如果需要可以多写几句生成多种,这里以钻石为例
                world.runCommandSilent(`/summon minecraft:item ${x} ${y} ${z} {Item:{id:"minecraft:diamond",Count:1b}}`);
            }
        }
    }
});

上文实现了仅击杀掉落
当然如果你不想那么麻烦可以直接使用

builder.processLoot((view, lvl, items, context) => {

            items.add(Item.of("minecraft:diamond"))

        });

同时实现击杀掉落和挖掘掉落

腐败

效果是 每个部件提供10%攻击力加成,受到的伤害加10%,不在主手或第一个格子时耐久每分钟-1*等级,使被攻击的目标失明[部件等级]秒,每三次攻击造成一次凋零效果(跟你挂钩不跟怪挂钩)持续 5*[部件等级] 秒,凋零效果每20秒只能触发一次
受到的伤害加10%的效果实现的较为原始,可能有避免的办法
(失明效果对怪其实意义不大)

写在startup_scripts的js任意文件中

TConJSEvents.modifierRegistry((event) => {
//腐败:每个部件提供10%攻击力加成,受到的伤害加10%,不在主手或第一个格子时耐久每分钟-1*等级,使被攻击的目标失明,凋零2s
    event.createNew("kubejs:corruption", (builder) => {

        //10%攻击力加成

        builder.addToolStats((view, lvl, statsBuilder) => {

            TinkerToolStats.ATTACK_DAMAGE.multiply(statsBuilder, 1 + lvl * 0.1);

            TinkerToolStats.PROJECTILE_DAMAGE.multiply(statsBuilder, 1 + lvl * 0.1);

        })

            //如果不在主手或第一个格子,则每分钟减少 1*等级 耐久 

            .onInventoryTick((view, lvl, level, entity, slot, inMainHand, inAvailableSlot, itemStack) => {



                if (inMainHand || slot == 0) return

                else {

                    // 每1200刻(1分钟)执行一次

                    if (entity.age % 1200 == 0)

                        itemStack.setDamageValue(view.damage + 1 * lvl);

                }

            })

        })

    }

写在server_scripts的任意js文件中

const corruptionCounter = 0;
const corruptionTimer = -1

//part of kubejs:corruption
EntityEvents.hurt((event) => {
    //受到的伤害加10%
    if (event.entity && event.entity.isPlayer()) {
        let player = event.entity
        let iterator = player.getHandSlots().iterator()
        let mainHandItem = null;

        if (iterator.hasNext())
            mainHandItem = iterator.next(); // 主手物品是第一个槽位
        if (mainHandItem.hasTag('tconstruct:modifiable')) {// 检查主手物品是否为匠魂工具
            let modifiers = mainHandItem.getNbt().getAsString(); // 获取工具的NBT

            //如果有腐败特性,则受到0.1*等级的同样伤害
            if (matchModifiers(modifiers, "corruption")) {
                let lvl = getModifierLevel(modifiers, "corruption")
                player.attack(event.damage  0.1  lvl)
            }
        }
    }

    //使被攻击的目标失明1~3s,凋零5~15s
    let attacker = event.source.getActual();

    if (attacker && attacker.isPlayer()) {
        let player = attacker;
        let entity = event.entity;
        let world = entity.level; // 获取实体所在的世界
        let currentTime = world.getTime(); // 获取当前游戏刻(tick)

        // 初始化玩家数据(攻击次数和凋零冷却时间)
        if (!player.data.corruptionAttackCounter) {
            player.data.corruptionAttackCounter = 0; // 攻击计数
        }

        if (!player.data.lastWitherTime) {
            player.data.lastWitherTime = 0; // 上次触发凋零的时间
        }

        // 检查攻击者的主手物品是否有腐败特性
        let attackerMainHandItem = null;
        let iteratorAttacker = player.getHandSlots().iterator();
        if (iteratorAttacker.hasNext()) {
            attackerMainHandItem = iteratorAttacker.next(); // 攻击者的主手物品
        }

        let effectLevel = 0;
        if (attackerMainHandItem && attackerMainHandItem.hasTag('tconstruct:modifiable')) {
            let attackerModifiers = attackerMainHandItem.getNbt().getAsString();
            if (matchModifiers(attackerModifiers, "corruption")) {
                effectLevel = getModifierLevel(attackerModifiers, "corruption"); // 获取攻击者武器的腐败等级
            }
        }
        //没等级直接退出
        if (effectLevel <= 0) return

        //每次攻击都施加失明效果
        //指令执行的单位是秒
        world.runCommandSilent(`/execute as ${entity.uuid} run effect give @s minecraft:blindness ${effectLevel} ${effectLevel}`);
        player.data.corruptionAttackCounter++;
        
        //检查是否达到3次攻击且冷却时间已过
        let cooldownTicks = 400; // 20秒=400tick
        if (player.data.corruptionAttackCounter >= 3 && currentTime - player.data.lastWitherTime >= cooldownTicks) {

            //施加凋零效果(持续 5*lvl 秒,等级1)
            world.runCommandSilent(`/execute as ${entity.uuid} run effect give @s minecraft:wither ${5 * effectLevel} ${effectLevel}`);
            player.data.corruptionAttackCounter = 0;
            player.data.lastWitherTime = currentTime;
        }
    }

各种龙钢

最先写的是龙霜钢,后面都是以其为蓝本做的,仿的匠魂2的龙钢(自定义的龙霆钢)
原版描述龙钢有什么特殊效果,这里我直接用了源码中龙的伤害类型(虽然最后还是会被判定为玩家伤害),但总之是能打出伤害的

potionEffects.add可以代替执行effect命令,效果看上去一样
runCommandSilent看上去有权限要求,要用的话建议全挂在玩家或者维度上

实际上伤害不去除也一样……这里是实验的部分,完成后会再修改

写在startup_scripts的js任意文件中

TConJSEvents.modifierRegistry((event) => {
//凛冰:冻结敌人10(I)/15(II)秒,附加龙霜(魔法)伤害
    event.createNew("kubejs:blizzard", (builder) => {
 
        builder.onBeforeMeleeHit((view, lvl, context, damage, baseKnockback, finalKnockback) => {
            return finalKnockback * (1 + lvl);
        });
        builder.getMeleeDamage((view, lvl, context, baseDamage, finalDamage) => {
            //将武器的伤害去除,在Server里处理
            return 1
        });
    })
    //炽火:灼烧敌人10(I)/15(II)秒,附加龙炎伤害并将敌人击退得更远。
    event.createNew("kubejs:inferno", builder => {
        builder.onBeforeMeleeHit((view, lvl, context, damage, baseKnockback, finalKnockback) => {
            return finalKnockback * (1 + lvl);
        });
    })
    //司霆:直接打雷
    event.createNew("kubejs:thunder", builder => {
        builder.onBeforeMeleeHit((view, lvl, context, damage, baseKnockback, finalKnockback) => {
            return finalKnockback * (1 + lvl);
        });
    })
})

写在server_scripts的任意js文件中

下文中流血效果来自

//凛冰
//冻结敌人10(I)/15(II)秒,附加龙霜(魔法)伤害

EntityEvents.hurt(event => {
    if (event.source.getActual() && event.source.getActual().isPlayer()) {
        let player = event.source.getActual()
        let mob = event.entity
        let lvl = getModifierLevel(getNBT(player), "blizzard")
        
        //如果手上有 凛冰 特性的匠魂武器,且伤害类型为玩家
        // (否则所有伤害都会吃到buff,包括流血
        //会出现第一刀24,第二刀触发流血3秒扣100)
        if (matchModifiers(getNBT(player), "blizzard") && event.source.getType().includes("player")) {
            //一级10s2级缓慢+1级瞬间伤害,二级15s4级缓慢(基本上走不动了)+2级瞬间伤害
            //龙霜钢的顶部直接是两级,没有其他获取办法
            mob.potionEffects.add("slowness", 100 + 100  lvl, 2  lvl)
            mob.potionEffects.add("instant_damage", 1, lvl)

        }
    }
})

轮子

附赠一些轮子,可以放在需要的文件中的任意地方,根据你的需要修改,上文在部分地方用到了

/**

 * 

 * @param {string} str 传入匠魂工具的完整nbt

 * @param {string} tofind 要匹配的特性id,加不加modid都可

 * @returns 该匠魂工具中是否有所需特性

 */

function matchModifiers(str, tofind) {

    const startKeyword = 'tic_modifiers:[';

    const startIdx = str.indexOf(startKeyword);

    const endIdx = str.indexOf(']', startIdx + startKeyword.length);

    if (startIdx != -1 && endIdx != -1) {

        const result = str.substring(startIdx, endIdx + 1);// 输出: tic_modifiers:[{level:1,name:"A"},{level:2,name:"B"}]

        if (result.includes(tofind)) return true

    }

    return false

}


/**

 * 

 * @param {string} str 传入匠魂工具的完整nbt

 * @returns 以<level:1,name:"A">形式返回的string,不包含书名号

 */

function getAllModifiers(str) {

    // 定位到数组内容部分

    const start = str.indexOf('[{') + 1; // 跳过 [

    const end = str.lastIndexOf('}]');

    const arrayContent = str.substring(start, end); // 得到 "{...},{...}"



    // 分割并清理每个对象

    const result = arrayContent.split('},\\{')

        .map(item => item

            .replace('/^{|}$/g', '') // 去掉首尾可能残留的 {

            .trim()

        );

    return result

}

/**

 * 

 * @param {string} str 传入匠魂工具的完整nbt

 * @param {string} tofind 要匹配的特性id,加不加modid都可

 * @returns 特性等级,-1意味着没有这种特性

 */

function getModifierLevel(str, tofind) {

    // 定位到数组内容部分

    const start = str.indexOf('[{') + 1; // 跳过 [

    const end = str.lastIndexOf('}]');

    const arrayContent = str.substring(start, end); // 得到 "{...},{...}"



    // 拆分数组元素(利用 },{ 分割)

    const items = arrayContent.split('},\\{')

        .map(item => item.replace(/^\s*\\{|\}\s*$/g, '').trim());



    // 查找目标元素并提取等级

    for (const item of items) {

        if (!item.includes(tofind)) continue;

        return parseInt(item.split(",")[0].split(":")[1]);

    }



    return -1;

}


/**

 *

 * @param {Internal.Entity} player event.source.getActual()或者event.entity

 * @returns 完整NBT

 */

function getNBT(player) {

    let mainHandItem = null;

    if (player.getHandSlots().iterator().hasNext())

        mainHandItem = player.getHandSlots().iterator().next(); // 主手物品是第一个槽位

    // 检查主手物品是否为匠魂工具

    if (mainHandItem.hasTag('tconstruct:modifiable')) {

        return mainHandItem.getNbt().getAsString(); // 获取工具的NBT

    }

    return ""

}

后面不定期更新……