前言
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 ""
}
后面不定期更新……