本篇教程由作者设定使用 CC BY-NC 协议。

主要模组就是CrT和Zen Utils(重载什么的)


功能大概是……大概是什么机制都可以做!比MM强大114514倍!
缺陷是……渲染动画比较谔谔,只能用粒子(通过指令,还没写进去,也不想写(恼)),不过也比MM好嘛!
还有一个缺陷……十分的面向过程,要求的代码能力和查询wiki程度的能力比较高。
可以先去看看可爱的火红莲限制


本文机器的激活方式是红石脉冲,如果想要其它的激活方式,还没研究呢……

本文目的是给大家看看新活,哦不,看看事件有多么的强大(


至于代码质量,咱才刚学CrT两三周,很差就是了。
小丑级代码,能跑就行。

赶紧开始看样例吧!


封面大图真正的自定义机器:事件-第1张图片

V1
在20220914制作,没有运行特效。不过逻辑比较简单。
效果
V2效果在[MC]音乐祭坛(CrT x 植物魔法, 1.12.2)_哔哩哔哩bilibili_Minecraft

真正的自定义机器:事件-第2张图片使用音符盒在祭坛核心所在区块内奏乐(八度是会被检测的)
并且祭坛(命令方块)拥有正确的物品
可以进行合成(得到泰拉镐)


可修改项:
可以拥有多个合成表。不过成千上万的合成表就别了。
当然也可以自己写乐曲。例:"F4+L4X4F4+L4X4L4F4+M4R4M4F4+R4"。

祭坛核心可以改成任何自己就会检测红石脉冲的家伙,比如发射器、命令方块

祭坛多方块可以随心变化。


模块1:判断红石信号。

可能比较玄学,但是这就是我整出来的正解。

实现

function checkStrongPower(w as IWorld,p as IBlockPos) as bool{
    val ALLFacing=[IFacing.north(),IFacing.east(),IFacing.south(),
        IFacing.west(),IFacing.down(),IFacing.up()] as IFacing[];
    for i in ALLFacing{
        if(w.getStrongPower(p.getOffset(i,1),i)>0)return true;
    }
    return false;
}

function checkRSWork(w as IWorld,p as IBlockPos) as bool{
    val ALLFacing=[IFacing.north(),IFacing.east(),IFacing.south(),
        IFacing.west(),IFacing.down(),IFacing.up()] as IFacing[];
    if(checkStrongPower(w,p))return true;
    for i in ALLFacing{
        if(checkStrongPower(w,p.getOffset(i,1)))return true;
    }
    return false;
}

使用

红石更新会触发BlockNeighborNotifyEvent。
我们打算使用本身就接收红石脉冲的方块作为祭坛核心。

当祭坛核心被激活时,checkRSWork,然后查看blockdata,如果powered<1就是之前未激活,那么就可以干活了。


模块2:检测多方块。

由于我只会用/ct blockinfo找BlockState的meta值,而不会找variant,所以我吧Block匹配和meta匹配分开写成两份了。
实现

function match_by_meta(a as IBlockState, b as IBlockState, c as int) as bool{
    var ret as bool=false;
    if(a.block.definition.id==b.block.definition.id){
        if(a.meta==c | c<0){
            ret=true;
        }
    }
    return ret;
}


function checkBlockStates(w as IWorld, p as IBlockPos, x_shift as int, y_shift as int, z_shift as int, MultB as IBlockState[][][], MultBMeta as int[][][]) as bool{
    for i in 0 to MultB.length for j in 0 to MultB[i].length for k in 0 to MultB[i][j].length{
        if(isNull(MultB[i][j][k]))continue;
        if(!(match_by_meta(w.getBlockState(Position3f.create(
                p.getX()+x_shift+i,
                p.getY()+y_shift+j,
                p.getZ()+z_shift+k).asBlockPos()),
            MultB[i][j][k],MultBMeta[i][j][k])))return false;
    }
    return true;
}

使用例

val altar as IBlockState=<blockstate:minecraft:repeating_command_block>;
val MultB as IBlockState[][][]=[
    [   [<blockstate:avaritia:block_resource>],
        [altar]]
    ] as IBlockState[][][];
val MultBMeta as int[][][]=[
    [   [2],
        [-1]]
    ] as int[][][];
    ......
    
//祭坛是一个水晶矩阵上面一个命令方块。命令方块是核心。
    checkBlockStates(world,pos,0,-1,0,MultB,MultBMeta)


合成表设计

注:还没有弄到JEI里……不过也不难呢。

包括:一些IIngredient,一个字符串代表乐曲,一个成品IItemStack。

实现

var muaRecipes=[] as IIngredient[][];
var Recipemusics=[] as string[];
var muaResults=[] as IItemStack[];

添加合成

muaRecipes+=[<botania:manaresource:4>*3,<botania:manaresource:3>*2] as IIngredient[];
Recipemusics+="F4+L4X4F4+L4X4L4F4+M4R4M4F4+R4";    //六兆年一夜物语选段,我忘了哪个物品的成就是六兆年了
muaResults+=<botania:terrapick>.withTag({mana: 2147483646});

合成检测

什么检测?等会再说


方块更新时干什么?

events.onBlockNeighborNotify(function(event as BlockNeighborNotifyEvent){
    var w as IWorld=event.world;
    var pos0 as IBlockPos = event.position;
    var poses=[pos0] as IBlockPos[];
    for tempv0 in event.notifiedSides{
        poses+=pos0.getOffset(tempv0,1);
    }
    //查看Notify的Block周围的所有Block
    for p in poses{
        if(isNull(w.getBlock(p)))continue;
        if(w.isAirBlock(p))continue;
        if(isNull(w.getBlock(p).data))continue;
        if(!(w.getBlock(p).data has "powered"))continue;
        //我们要管的要么是命令方块,要么是音符盒,都有power作为红石信号的记录。而我们也需要记录红石信号。
        if(w.getBlock(p).data.powered < 1 as byte){
            if(checkRSWork(w,p)){
                var Bl=w.getBlock(p);
                var dat as IData=w.getCustomChunkData(p);
                if(Bl.definition.id=="minecraft:noteblock"){
                    //得到音符。我们没有降号,升号用+。
                    //然后记录在一个CustomChunkData:chunkMus里
                        //如果dat已有这个chunkMus,那么读取出来,把新音符加在后面
                        //否则,先创建这个项,然后把新音符加在后面
                    //坏消息,我们不能监听音符盒事件。
                    //所以我们只能用粗劣的手段猜测音质对应的八度,即列举会造成音色变化的基座方块。
                }
                //////如果是完整的祭坛
                if(checkBlockStates(w,p,0,-1,0,MultB,MultBMeta)){
                    //通过IWorld.getEntitiesInArea寻找周边物品,并全部记录
                    //选取一个合成表,看看合成表里的物品是不是全都有,如果没有就再换一个合成表,如果全部合成表都找完了还是没有匹配的就开摆。如果找到了也不用管后面的合成表了。
                    //在匹配合成表的时候把编号记一下,我们还要用来寻找对应的乐曲和成品。
                    //找到了物品合成表,把chunkMus记录下来然后清空chunkMus
                    //看一下chunkMus的末尾是不是我们(上帝)想要的曲子,如果不是那就不理玩家
                    //如果是正确的曲目,那么把实体形式的材料消耗了,在祭坛上方生成物品。
                }
            }
        }
    }
});

音符盒

                if(Bl.definition.id=="minecraft:noteblock"){
                    var str="";
                    if(dat has "chunkMus"){
                        str=dat.chunkMus as string;
                    }
                    else{
                        w.setCustomChunkData({chunkMus:str},p);
                    }
                    if(!isNull(Bl.data)){if(Bl.data has "note"){
                        var noten=Bl.data.note as int;
                        var f=3 as int;
                        var Bname=w.getBlock(p.getOffset(IFacing.down(),1)).definition.id;
                        //对于音符盒事件音色的拙劣判断
                        if(Bname=="minecraft:planks" | Bname=="minecraft:log" | Bname=="minecraft:log2" ){
                            f=1;
                        }
                        if(Bname=="minecraft:wool"){
                            f=2;
                        }
                        if(Bname=="minecraft:clay"){
                            f=4;
                        }
                        if(Bname=="minecraaft:gold_block" | Bname=="minecraft:packed_ice"){
                            f=5;
                        }
                        if(Bname=="minecraft:sand" | Bname=="minecraft:glass" ){
                            f=114514;
                        }
                        if(Bname=="minecraft:stone" | Bname=="minecraft:cobblestone"){
                            f=114514;
                        }
                        var judg=f*12+noten as int;
                        var sn as string=" ";//敲鼓的结果:爆炸!
                        if(f<1000){
                            var t=judg%12;
                            if(t==0 | t==11)sn="F";
                            else if(t==1 | t==2)sn="S";
                            else if(t==3 | t==4)sn="L";
                            else if(t==5)sn="X";
                            else if(t==6 | t==7)sn="D";
                            else if(t==8 | t==9)sn="R";
                            else if(t==10)sn="M";
                            var p=(judg+6)/12;
                            sn+=('0' + p as byte)as byte;
                            if(t==0 | t==2 | t==4 | t==7 | t==9)sn+='+';
                            print("New Note recorded");print(sn);
                        }
                        str+=sn;
                        if(str.length>7000){
                            print("music overflow");
                            var temp=" " as string;
                            for i in (str.length - 3000) to str.length{
                                temp+=str[i];
                            }
                            str=temp;
                        }
                        w.updateCustomChunkData({chunkMus: str},p);
                        print("Existing Notes:");
                        print(str);
                    }}
                }

祭坛

祭坛是命令方块

                if(checkBlockStates(w,p,0,-1,0,MultB,MultBMeta)){
                    server.commandManager.executeCommand(server,"say Music altar is stimmulated");
                    if(p.getY()<192){
                        server.commandManager.executeCommand(server,"say The altar should be high in the sky");
                        server.commandManager.executeCommand(server,"say Enabling it to spread the music as far as possible");
                        continue;
                    }
                    if(!(dat has "chunkMus")){
                        server.commandManager.executeCommand(server,"say Charge the altar when notes are recorded");
                        continue;
                    }
                    
                    //获取物品列表
                    val r=2;
                    var p1=Position3f.create(p.getX()-r,p.getY()-r,p.getZ()-r);
                    var p2=Position3f.create(p.getX()+r,p.getY()+r,p.getZ()+r);
                    var ListOfIE=[] as IEntityItem[];
                    for e in w.getEntitiesInArea(p1,p2){
                        if (e instanceof IEntityItem){
                            var j as IEntityItem=e;
                            ListOfIE+=j;
                        }
                    }
                    if(ListOfIE.length<1){
                        server.commandManager.executeCommand(server,"say Throw items ont the altar after playing the music");
                    }
                    var flags=[] as bool[];
                    var itemsIn=[] as IItemStack[];
                    for i in ListOfIE{
                        print(i.item.name);
                        itemsIn+=i.item;
                        flags+=true;
                    }
                    server.commandManager.executeCommand(server,"say Checking recipes");
                    
                    //穷举合成表
                    if(muaRecipes.length<1){
                        server.commandManager.executeCommand(server,"say ERROR: No available recipes");
                        continue;
                    }
                    var recipeindex= (-1) as int;
                    var t=0;
                    for rnum in 0 to muaRecipes.length{
                        var match=true;
                        
                        for ingre in muaRecipes[rnum]{
                            print(rnum);
                            for i in ingre.items{
                                print(i.name);
                                break;
                            }
                            var found=false;
                            for it in itemsIn{
                                if(ingre.matches(it)){
                                    if(ingre.amount<=it.amount){
                                        found=true;
                                        server.commandManager.executeCommand(server,"say "+it.name);
                                    }
                                    break;
                                }
                            }
                            if(!found){
                                match=false;
                                break;
                            }
                        }
                        if(!match)continue;
                        t=t+1;
                        recipeindex=t;
                        break;
                    }
                    if(recipeindex<0)continue;
                    recipeindex=recipeindex- 1;
                    server.commandManager.executeCommand(server,"say recipes Found!");
                    
                    //记录、清楚chunkMus
                    var musicInput=dat.chunkMus as string;
                    w.updateCustomChunkData({chunkMus: ""},p);
                    print(recipeindex);
                    print(Recipemusics.length);
                    var requiredMus=Recipemusics[recipeindex];
                    if(musicInput.length<requiredMus.length)continue;
                    print(musicInput);
                    print(requiredMus);
                    if(musicInput.indexOf(requiredMus)!=
                        musicInput.length-requiredMus.length)continue;
                   
                    //消耗物品并生成成品。好吧我把所有物品全都吞了然后再把不要的吐出来。
                    for i in ListOfIE w.removeEntity(i);
                    var ret=[] as IItemStack[];
                    for ingre in muaRecipes[recipeindex]{
                        for it in 0 to itemsIn.length{
                            if(ingre.matches(itemsIn[it])){
                                if(ingre.amount<=itemsIn[it].amount)flags[it]=false;
                                if(ingre.amount<itemsIn[it].amount)
                                    ret+=itemsIn[it].withAmount(itemsIn[it].amount-ingre.amount);
                            }
                        }
                    }
                    for i in 0 to flags.length{
                        if(flags[i])w.spawnEntity(itemsIn[i].createEntityItem(w,p.getX(),p.getY()+1,p.getZ()));
                    }
                    for i in ret{
                        w.spawnEntity(i.createEntityItem(w,p.getX(),p.getY()+1,p.getZ()));
                    }
                    w.spawnEntity(muaResults[recipeindex].createEntityItem(w,p.getX(),p.getY()+1,p.getZ()));
                }


补丁

用JEI的“信息”功能,写个轮子往里面加铺子和解释
当然也可以写个函数来负责合成表的添加
如果你想让判别变得更加不严格那可能要好好想一想。

其它的话,如果想要把合成表变成五彩祭坛、工作台(、聚合核心)那种有序合成,可以试试给几坛里加一个类似于箱子的玩意,然后查看箱子的库存。
这样一来,什么流体聚合也都是可行的。
只不过是瞬间合成……


全部代码

import crafttweaker.block.IBlockState;
import crafttweaker.block.IBlock;

import crafttweaker.util.Position3f;
import crafttweaker.world.IBlockPos;
import crafttweaker.world.IFacing;

import crafttweaker.events.IEventManager;
import crafttweaker.event.BlockNeighborNotifyEvent;

import crafttweaker.server.IServer;
import crafttweaker.command.ICommandSender;
import crafttweaker.world.IWorld;
import crafttweaker.data.IData;
import crafttweaker.entity.IEntityItem;

import crafttweaker.item.IItemStack;
import crafttweaker.item.IIngredient;


function match_by_meta(a as IBlockState, b as IBlockState, c as int) as bool{
    var ret as bool=false;
    if(a.block.definition.id==b.block.definition.id){
        if(a.meta==c | c<0){
            ret=true;
        }
    }
    return ret;
}

function checkBlockStates(w as IWorld, p as IBlockPos, x_shift as int, y_shift as int, z_shift as int, MultB as IBlockState[][][], MultBMeta as int[][][]) as bool{
    for i in 0 to MultB.length for j in 0 to MultB[i].length for k in 0 to MultB[i][j].length{
        if(isNull(MultB[i][j][k]))continue;
        if(!(match_by_meta(w.getBlockState(Position3f.create(
                p.getX()+x_shift+i,
                p.getY()+y_shift+j,
                p.getZ()+z_shift+k).asBlockPos()),
            MultB[i][j][k],MultBMeta[i][j][k])))return false;
    }
    return true;
}

val altar as IBlockState=<blockstate:minecraft:repeating_command_block>;
val MultB as IBlockState[][][]=[
    [   [<blockstate:avaritia:block_resource>],
        [altar]]
    ] as IBlockState[][][];
val MultBMeta as int[][][]=[
    [   [2],
        [-1]]
    ] as int[][][];

var muaRecipes=[] as IIngredient[][];
var Recipemusics=[] as string[];
var muaResults=[] as IItemStack[];



muaRecipes+=[<botania:manaresource:4>*3,<botania:manaresource:3>*2] as IIngredient[];
Recipemusics+="F4+L4X4F4+L4X4L4F4+M4R4M4F4+R4";
muaResults+=<botania:terrapick>.withTag({mana: 2147483646});


val ALLFacing=[IFacing.north(),IFacing.east(),IFacing.south(),
    IFacing.west(),IFacing.down(),IFacing.up()] as IFacing[];

function checkStrongPower(w as IWorld,p as IBlockPos) as bool{
    val ALLFacing=[IFacing.north(),IFacing.east(),IFacing.south(),
        IFacing.west(),IFacing.down(),IFacing.up()] as IFacing[];
    for i in ALLFacing{
        if(w.getStrongPower(p.getOffset(i,1),i)>0)return true;
    }
    return false;
}
function checkRSWork(w as IWorld,p as IBlockPos) as bool{
    val ALLFacing=[IFacing.north(),IFacing.east(),IFacing.south(),
        IFacing.west(),IFacing.down(),IFacing.up()] as IFacing[];
    if(checkStrongPower(w,p))return true;
    for i in ALLFacing{
        if(checkStrongPower(w,p.getOffset(i,1)))return true;
    }
    return false;
}
events.onBlockNeighborNotify(function(event as BlockNeighborNotifyEvent){
    var w as IWorld=event.world;
    var pos0 as IBlockPos = event.position;
    var poses=[pos0] as IBlockPos[];
    for tempv0 in event.notifiedSides{
        poses+=pos0.getOffset(tempv0,1);
    }
    for p in poses{
        if(isNull(w.getBlock(p)))continue;
        if(w.isAirBlock(p))continue;
        if(isNull(w.getBlock(p).data))continue;
        if(!(w.getBlock(p).data has "powered"))continue;
        if(w.getBlock(p).data.powered < 1 as byte){
            if(checkRSWork(w,p)){
                print(w.getBlock(p).definition.id);
                print("Newly updated");
                var Bl=w.getBlock(p);
                var dat as IData=w.getCustomChunkData(p);
                if(Bl.definition.id=="minecraft:noteblock"){
                    var str="";
                    if(dat has "chunkMus"){
                        str=dat.chunkMus as string;
                    }
                    else{
                        w.setCustomChunkData({chunkMus:str},p);
                    }
                    if(!isNull(Bl.data)){if(Bl.data has "note"){
                        var noten=Bl.data.note as int;
                        var f=3 as int;
                        var Bname=w.getBlock(p.getOffset(IFacing.down(),1)).definition.id;
                        if(Bname=="minecraft:planks" | Bname=="minecraft:log" | Bname=="minecraft:log2" ){
                            f=1;
                        }
                        if(Bname=="minecraft:wool"){
                            f=2;
                        }
                        if(Bname=="minecraft:clay"){
                            f=4;
                        }
                        if(Bname=="minecraaft:gold_block" | Bname=="minecraft:packed_ice"){
                            f=5;
                        }
                        if(Bname=="minecraft:sand" | Bname=="minecraft:glass" ){
                            f=114514;
                        }
                        if(Bname=="minecraft:stone" | Bname=="minecraft:cobblestone"){
                            f=114514;
                        }
                        var judg=f*12+noten as int;
                        var sn as string=" ";
                        if(f<1000){
                            var t=judg%12;
                            if(t==0 | t==11)sn="F";
                            else if(t==1 | t==2)sn="S";
                            else if(t==3 | t==4)sn="L";
                            else if(t==5)sn="X";
                            else if(t==6 | t==7)sn="D";
                            else if(t==8 | t==9)sn="R";
                            else if(t==10)sn="M";
                            var p=(judg+6)/12;
                            sn+=('0' + p as byte)as byte;
                            if(t==0 | t==2 | t==4 | t==7 | t==9)sn+='+';
                            print("New Note recorded");print(sn);
                        }
                        str+=sn;
                        if(str.length>7000){
                            print("music overflow");
                            var temp=" " as string;
                            for i in (str.length - 3000) to str.length{
                                temp+=str[i];
                            }
                            str=temp;
                        }
                        w.updateCustomChunkData({chunkMus: str},p);
                        print("Existing Notes:");
                        print(str);
                    }}
                }
                //////
                if(checkBlockStates(w,p,0,-1,0,MultB,MultBMeta)){
                    server.commandManager.executeCommand(server,"say Music altar is stimmulated");
                    if(p.getY()<192){
                        server.commandManager.executeCommand(server,"say The altar should be high in the sky");
                        server.commandManager.executeCommand(server,"say Enabling it to spread the music as far as possible");
                        continue;
                    }
                    if(!(dat has "chunkMus")){
                        server.commandManager.executeCommand(server,"say Charge the altar when notes are recorded");
                        continue;
                    }
                    //Obtain the List of Entities
                    val r=2;
                    var p1=Position3f.create(p.getX()-r,p.getY()-r,p.getZ()-r);
                    var p2=Position3f.create(p.getX()+r,p.getY()+r,p.getZ()+r);
                    var ListOfIE=[] as IEntityItem[];
                    for e in w.getEntitiesInArea(p1,p2){
                        if (e instanceof IEntityItem){
                            var j as IEntityItem=e;
                            ListOfIE+=j;
                        }
                    }
                    if(ListOfIE.length<1){
                        server.commandManager.executeCommand(server,"say Throw items ont the altar after playing the music");
                    }
                    var flags=[] as bool[];
                    var itemsIn=[] as IItemStack[];
                    for i in ListOfIE{
                        print(i.item.name);
                        itemsIn+=i.item;
                        flags+=true;
                    }
                    server.commandManager.executeCommand(server,"say Checking recipes");
                    //Recipe check
                    if(muaRecipes.length<1){
                        server.commandManager.executeCommand(server,"say ERROR: No available recipes");
                        continue;
                    }
                    var recipeindex= (-1) as int;
                    var t=0;
                    for rnum in 0 to muaRecipes.length{
                        var match=true;
                        
                        for ingre in muaRecipes[rnum]{
                            print(rnum);
                            for i in ingre.items{
                                print(i.name);
                                break;
                            }
                            var found=false;
                            for it in itemsIn{
                                if(ingre.matches(it)){
                                    if(ingre.amount<=it.amount){
                                        found=true;
                                        server.commandManager.executeCommand(server,"say "+it.name);
                                    }
                                    break;
                                }
                            }
                            if(!found){
                                match=false;
                                break;
                            }
                        }
                        if(!match)continue;
                        t=t+1;
                        recipeindex=t;
                        break;
                    }
                    if(recipeindex<0)continue;
                    recipeindex=recipeindex- 1;
                    server.commandManager.executeCommand(server,"say recipes Found!");
                    //consume&check Music
                    var musicInput=dat.chunkMus as string;
                    w.updateCustomChunkData({chunkMus: ""},p);
                    print(recipeindex);
                    print(Recipemusics.length);
                    var requiredMus=Recipemusics[recipeindex];
                    if(musicInput.length<requiredMus.length)continue;
                    print(musicInput);
                    print(requiredMus);
                    if(musicInput.indexOf(requiredMus)!=
                        musicInput.length-requiredMus.length)continue;
                   
                    //consume stacks and give results
                    for i in ListOfIE w.removeEntity(i);
                    var ret=[] as IItemStack[];
                    for ingre in muaRecipes[recipeindex]{
                        for it in 0 to itemsIn.length{
                            if(ingre.matches(itemsIn[it])){
                                if(ingre.amount<=itemsIn[it].amount)flags[it]=false;
                                if(ingre.amount<itemsIn[it].amount)
                                    ret+=itemsIn[it].withAmount(itemsIn[it].amount-ingre.amount);
                            }
                        }
                    }
                    for i in 0 to flags.length{
                        if(flags[i])w.spawnEntity(itemsIn[i].createEntityItem(w,p.getX(),p.getY()+1,p.getZ()));
                    }
                    for i in ret{
                        w.spawnEntity(i.createEntityItem(w,p.getX(),p.getY()+1,p.getZ()));
                    }
                    w.spawnEntity(muaResults[recipeindex].createEntityItem(w,p.getX(),p.getY()+1,p.getZ()));
                }
            }
        }
    }
});


V2

[MC]音乐祭坛(CrT x 植物魔法, 1.12.2)_哔哩哔哩bilibili_Minecraft

借用泰拉凝聚板的运行动画。理论上允许直接使用泰拉凝聚板接收魔力合成,但是那样会耗费2.1B mana,十分昂贵,故此建议玩家使用音乐祭坛。
音乐祭坛基础结构如图,合成台是随着合成表变化而变化的:真正的自定义机器:事件-第3张图片

加入合成表的步骤,大概是:
1、给凝聚板加入合成表,给物品的jei说明加入乐谱,并在音符盒的jei说明加入乐谱
2、给zs文件里的static变量加入。
由于这个过程,我们不能用zenutil 提供的事件重载。

当然,我们应该写一个函数来运行这件事。


注意事项是,我们不能把信息存入各种TileEntity的BlockData,因为会被自动清理掉。
所以我把所有信息都存入了customChunkData:祭坛之前的乐谱编号,祭坛之前的位置,祭坛之前的进度。(一个区块只允许一个祭坛,否则后果玩家自负)


效果

弄一个长一点的铺子以及序列化的音符盒激活,你可以看到乐曲驱动着泰拉凝聚板。


可拓展性

祭坛结构可以更改。乐曲随便改。现在的乐曲是没有时间要求的,你也可以记录“上一次音符更新的WorldTime”来做一个时间要求。不过要怎么告诉玩家啊……


代码

import crafttweaker.events.IEventManager;

import crafttweaker.block.IBlockDefinition;
import crafttweaker.block.IBlockState;
import crafttweaker.block.IBlock;

import crafttweaker.world.IFacing;
import crafttweaker.util.Position3f;
import crafttweaker.world.IBlockPos;

import crafttweaker.world.IWorld;
import crafttweaker.data.IData;

import crafttweaker.server.IServer;
import crafttweaker.command.ICommandSender;

import crafttweaker.event.WorldTickEvent;
import crafttweaker.event.BlockNeighborNotifyEvent;

import crafttweaker.item.IItemStack;
import crafttweaker.item.IIngredient;
import crafttweaker.entity.IEntityItem;


//给音符盒加上音乐祭坛的说明
val nb=<minecraft:noteblock>;
mods.jei.JEI.addDescription(nb,"Music Altar requires: a repetitive command block under the terra plate, with the rest 5 faces surrounded by Avartia's Crystal Matrix.\nIf the command block is at (x,y,z),then Botania's 4 mana_pylons should be placed at (x+2,y,z), (x-2,y,z), (x,y,z+2), (x,y,z-2).");
mods.jei.JEI.addDescription(nb,"To use the Music Altar, first charge it, then play the NoteBlocks with correct notes and octaves in order, so that numerous mana could be offered to the plate. However, if the music went wrong, tiny explosion will take place above the plate, terminating the craft.");
mods.jei.JEI.addDescription(nb,"Note that shorter than 0.1s redstone pulse on NoteBlock may not be detected");
mods.jei.JEI.addDescription(nb,"Different from the voice you hear, the octave detected by the altar is determined as the following:");
mods.jei.JEI.addDescription(nb,"1~3: minecraft:planks minecraft:log minecraft:log2\n2~4 minecraft:wool\n4~6minecraft:clay5~7minecraaft:gold_block minecraft:packed_ice\n114514~114516:minecraft:sand minecraft:glass minecraft:stone minecraft:cobblestone\n3~5: All the rest blocks");
mods.jei.JEI.addDescription(nb,"If there are several Music Altar exists in 1 chunk, unexpected behaviour may take place.");


//乐谱本地存储,一个音符作为一个string。
//其实你应该想:muaRecipes是一个二维数组。
//但是由于static变量不能编辑,但是static数组里的元素是可以编辑的,我们就多加一维,万事大吉。
//不用static的话,函数里没法用外面的变量

static muaRecipes as IItemStack[][][]=[[]];
static muaResults as IItemStack[][]=[[]];
static muaRecipemusics as string[][][]=[[]];


//加入一个新的合成表
function addMuaRecipe(out as IItemStack, ins as IItemStack[], music as string,
    cornerIn as IIngredient = <minecraft:crafting_table>, cornerRet as IIngredient = <minecraft:crafting_table>,
    color1 as int=0x00FF88, color2 as int=0x0088FF) as void{

    //合成表默认四角都是工作台
    //锁死中心是重复型命令方块,四边是水晶矩阵
    //不用音乐祭坛的话,硬用21亿mana也是可以的
    if(music.length<1)return;
    muaRecipes[0]=muaRecipes[0]+ins;
    muaResults[0]=muaResults[0]+out;
    var s as string="";
    var musIn as string[]=[] as string[];
    for i in 0 to music.length{
        if(s.length<1 || music[i].toLowerCase==music[i].toUpperCase)s+=music[i];
        else{
            musIn+=s;
            s=music[i];
        }
    }
    musIn+=s;
    muaRecipemusics[0]=muaRecipemusics[0]+musIn;

   
    var mx as IIngredient=<avaritia:block_resource:2>;
    var muac as IIngredient=<minecraft:repeating_command_block>;
    mods.botaniatweaks.Agglomeration.addRecipe(out,ins,2147483640,color1,color2,muac,mx,cornerIn,muac,mx,cornerRet);
    mods.jei.JEI.addDescription(out,"This item should be crafted on the Music Altar. For further information, Check the JEI description for Note Boxes. Here is the music score:\n"+music);

}


//泰拉镐子=3泰拉钢锭+2活木枝+一小段六兆年
addMuaRecipe(<botania:terrapick>.withTag({mana: 2147483646}),
    [<botania:manaresource:3>*2,<botania:manaresource:4>*3],
    "F4+L4X4F4+L4X4L4F4+M4R4M4F4+R4");








//红石信号检测。但是如果像魔力检测器那样只有1gt的信号是检测不到的。不过音符盒说明里写了。
global ALLIBlockFacing as IFacing[]=[IFacing.north(),IFacing.east(),IFacing.south(),
    IFacing.west(),IFacing.down(),IFacing.up()];
function checkStrongPower(w as IWorld,p as IBlockPos) as bool{
    for i in ALLIBlockFacing{
        if(w.getStrongPower(p.getOffset(i,1),i)>0)return true;
    }
    return false;
}
function checkRSWork(w as IWorld,p as IBlockPos) as bool{
    if(checkStrongPower(w,p))return true;
    for i in ALLIBlockFacing{
        if(checkStrongPower(w,p.getOffset(i,1)))return true;
    }
    return false;
}


//多方块结构检测
//这个函数是检测一个Block
function match_by_meta(a as IBlockState, b as IBlockState, c as int) as bool{
    var ret as bool=false;
    if(isNull(b))return true;
    if(a.block.definition.id==b.block.definition.id){
        if(a.meta==c | c<0){
            ret=true;
        }
    }
    return ret;
}
//这个函数是输入一个结构和一个坐标,检测结构是否被完成。
function checkBlockStates(w as IWorld, p as IBlockPos, x_shift as int, y_shift as int, z_shift as int, MultB as IBlockState[][][], MultBMeta as int[][][]) as bool{
    for i in 0 to MultB.length for j in 0 to MultB[i].length for k in 0 to MultB[i][j].length{
        if(isNull(MultB[i][j][k]))continue;
        if(!(match_by_meta(w.getBlockState(Position3f.create(
                p.getX()+x_shift+i,
                p.getY()+y_shift+j,
                p.getZ()+z_shift+k).asBlockPos()),
            MultB[i][j][k],MultBMeta[i][j][k])))return false;
    }
    return true;
}
//这个函数是直接把祭坛结构写出来
function checkMuaStructure(w as IWorld, p as IBlockPos)as bool{
        var pl as IBlockState=<blockstate:botania:pylon>;
        var mx as IBlockState=<blockstate:avaritia:block_resource>;
        var muac as IBlockState=<blockstate:minecraft:repeating_command_block>;
        var trpl as IBlockState=<blockstate:botania:terraplate>;
        var z0 as IBlockState[]=[null,null,null,null,null];
        var z1 as IBlockState[]=[null,null,pl,null,null];
        var z2 as IBlockState[]=[null,null,mx,null,null];
        var z3 as IBlockState[]=[pl,mx,muac,mx,pl];
        var z4 as IBlockState[]=[null,null,trpl,null,null];
        var mz0 as int[]=[-1,-1,-1,-1,-1];
        var mz1 as int[]=[-1,-1,2,-1,-1];
        var mz2 as int[]=[-1,2,-1,2,-1];
        return checkBlockStates(w,p,-2,-1,-2,
            [[z0,z1,z0],[z0,z2,z0],[z2,z3,z4],[z0,z2,z0],[z0,z1,z0]],
            [[mz0,mz0,mz0],[mz0,mz1,mz0],[mz1,mz2,mz0],[mz0,mz1,mz0],[mz0,mz0,mz0]]);
}


//把音符盒的BlockData里面的Note转换为音符,便于与存储的信息比对
//NoteBlockConverter
function convert_note_block_data_to_string(dat as IData,base as IBlock)as string{
    var str="";
    if(dat has "note"){
        var noten=dat.note as int;
        var f=3 as int;
        var Bname=base.definition.id;
        if(Bname=="minecraft:planks" | Bname=="minecraft:log" | Bname=="minecraft:log2" ){
            f=1;
        }
        if(Bname=="minecraft:wool"){
            f=2;
        }
        if(Bname=="minecraft:clay"){
            f=4;
        }
        if(Bname=="minecraaft:gold_block" | Bname=="minecraft:packed_ice"){
            f=5;
        }
        if(Bname=="minecraft:sand" | Bname=="minecraft:glass" ){
            f=114514;
        }
        if(Bname=="minecraft:stone" | Bname=="minecraft:cobblestone"){
            f=114514;
        }
        var judg=f*12+noten as int;
        var sn as string=" ";
        if(f<1000){
            var t=judg%12;
            if(t==0 | t==11)sn="F";
            else if(t==1 | t==2)sn="S";
            else if(t==3 | t==4)sn="L";
            else if(t==5)sn="X";
            else if(t==6 | t==7)sn="D";
            else if(t==8 | t==9)sn="R";
            else if(t==10)sn="M";
            var p=(judg+6)/12;
            sn+=('0' + p as byte)as byte;
            if(t==0 | t==2 | t==4 | t==7 | t==9)sn+='+';
            print("New Note recorded");print(sn);
        }
        str+=sn;
        if(str.length>7000){
            print("music overflow");
            var temp=" " as string;
            for i in (str.length - 3000) to str.length{
                temp+=str[i];
            }
            str=temp;
        }
    }
    return str;
}





//命令方块被充能,检测是否完成祭坛结构。如果是则记录入ChunkData
//MusicAltarChecking
events.onBlockNeighborNotify(function(event as BlockNeighborNotifyEvent){
    var w as IWorld=event.world;
    var pos0 as IBlockPos = event.position;
   
    var poses=[pos0] as IBlockPos[];
    for tempv0 in event.notifiedSides{
        poses+=pos0.getOffset(tempv0,1);
    }

    for p in poses{
        if(isNull(w.getBlock(p)))continue;
        if(w.isAirBlock(p))continue;
        if(isNull(w.getBlock(p).data))continue;
        if(!(w.getBlock(p).data has "powered"))continue;
        if(w.getBlock(p).data.powered > 0 as byte)continue;
        if(!checkRSWork(w,p))continue;
        //checkStructure
        if(!checkMuaStructure(w,p))continue;

        server.commandManager.executeCommand(server,"say Music altar is stimmulated");

        //Record the Mua to the chunk
        var dat as IData=w.getCustomChunkData(p);
        if(true){
            w.updateCustomChunkData({"muaInfo":{
                "x":p.getX() as int,
                "y":p.getY() as int,
                "z":p.getZ() as int,
                "recipeIndex":-1 as int,
                "progress":-1 as int
            }},p);
        }
        server.commandManager.executeCommand(server,"say Succeed in registering the MuA");
    }
});


//音符盒被红石信号激活。
//NoteBlockDetection
events.onBlockNeighborNotify(function(event as BlockNeighborNotifyEvent){

    var w as IWorld=event.world;
    var pos0 as IBlockPos = event.position;
   
    var poses=[pos0] as IBlockPos[];
    for tempv0 in event.notifiedSides{
        poses+=pos0.getOffset(tempv0,1);
    }

    for p in poses{
        if(isNull(w.getBlock(p)))continue;
        if(w.isAirBlock(p))continue;
        if(isNull(w.getBlock(p).data))continue;
        if(!(w.getBlock(p).data has "powered"))continue;
        //server.commandManager.executeCommand(server,"say NoteBlock A");
        if(w.getBlock(p).data.powered > 0 as byte)continue;
        //server.commandManager.executeCommand(server,"say NoteBlock B");
        if(!checkRSWork(w,p))continue;
        //server.commandManager.executeCommand(server,"say NoteBlock C");
       
        var b=w.getBlock(p);
        if(b.definition.id!="minecraft:noteblock")continue;

        //server.commandManager.executeCommand(server,"say NoteBlock stimulated");
       
       //得到祭坛坐标
        //Obtain the Position of Mua
        var dat as IData=w.getCustomChunkData(p);
        if(!(dat has "muaInfo"))continue;
        if(!(dat.muaInfo has "y")){
            continue;
        }
        if(dat.muaInfo.y<0)continue;
        var ap as IBlockPos=Position3f.create(dat.muaInfo.x,dat.muaInfo.y,dat.muaInfo.z) as IBlockPos;
        var plp=ap.getOffset(IFacing.up(),1);
        
       //查看祭坛是否正常
        //Check if_ the altar is all right
        if(!checkMuaStructure(w,ap)){
            if(w.getBlock(plp).definition.id=="botania:terraplate"){
                w.setBlockState(w.getBlockState(plp),w.getBlock(plp).data+{"Mana":0}as IData,plp);
            }
            var tempData as IData=dat+{"y":-1};
            w.updateCustomChunkData({"muaInfo":tempData},p);
        }
        else{
           //祭坛正常。
           //查看实体列表以看看现在应该使用什么乐谱。
            //Obtain the Current Recipe Index from the Entities
            var pos1 as Position3f=Position3f.create(ap.getX(),ap.getY()+1,ap.getZ());
            var pos2 as Position3f=Position3f.create(ap.getX()+1,ap.getY()+2,ap.getZ()+1);
            var itemIn as IItemStack[]=[];
            for i in w.getEntitiesInArea(pos1,pos2){
                if(i instanceof IEntityItem){
                    var t as IEntityItem=i;
                    itemIn+=(t.item);
                }
            }
            //var test=scripts.exp.t;
            if(itemIn.length<0)continue;
            /**
            val muaRec=scripts.Altars.MusicAltarv2Recipe.muaRecipes[0];
            val muaRes=scripts.Altars.MusicAltarv2Recipe.muaResults[0];
            val muaMus=scripts.Altars.MusicAltarv2Recipe.muaRecipemusics[0];
            val muaRec as IItemStack[][]=muaRecipes[0];
            val muaRes as IItemStack[]=muaResults[0];
            val muaMus as string[][]=muaRecipemusics[0];
            if(muaRec.length<1)continue;
            var index as int= -1 as int;
            for i in 0 to muaRec.length{
                var flag as bool=true;
                if(muaRec[i].length!=itemIn.length)flag=false;
                else if(muaRec[i].length==0)flag=true;
                else{
                    var flags as bool[]=[]; //itemIn[j]is used
                    for j in 0 to itemIn.length{
                        flags+=false;
                    }
                    for j in 0 to muaRec[i].length{
                        var fl3=false;  //whether muaRec[i][j] is found
                        for k in 0 to itemIn.length{
                            if(flags[k])continue;
                            var a as IItemStack=itemIn[j];
                            var b as IItemStack=muaRec[i][j];
                            if(a.definition.id!=b.definition.id)continue;
                            if(a.amount!=b.amount)continue;
                            if(a.capNBT!=b.capNBT)continue;
                            fl3=true;
                            flags[k]=true;
                            break;
                        }
                    }
                }
                if(flag){
                    index=i;
                    break;
                }
            }
           
            //server.commandManager.executeCommand(server,"say RecipeNumber: "+(index as string));
            //server.commandManager.executeCommand(server,"say Old Data(Check): "+(w.getCustomChunkData(p).muaInfo as string));
            
            //如果实体列表没有对应的合成表,那么开摆
            if(index<0)continue;
           
           //找到了合成表,看看进度如何
            var progress1 as int=-1 as int;
            //如果和之前记录的合成表不同,或者之前的乐曲已经弹完了,那么该从零开始了。
            if(index!=(dat.muaInfo.recipeIndex as int) || (dat.muaInfo.progress as int)>muaMus[index].length)progress1=0;
            //不然就接着弹
            else progress1=dat.muaInfo.progress as int;
                //server.commandManager.executeCommand(server,"say progress1: "+(progress1 as string));
            
            //看看弹的音和乐曲对得上对不上
            if(convert_note_block_data_to_string(w.getBlock(p).data,w.getBlock(p.getOffset(IFacing.down(),1)))
                !=muaMus[index][progress1]){
                //Failure
                //对不上,制造爆炸(不会消耗物品,不会摧毁方块),业绩清零

                //server.commandManager.executeCommand(server,"say Detected Note: "+convert_note_block_data_to_string(w.getBlock(p).data,w.getBlock(p.getOffset(IFacing.down(),1))));
                //server.commandManager.executeCommand(server,"say Expected Note: "+muaMus[index][progress1]);

                w.setBlockState(w.getBlockState(plp),w.getBlock(plp).data+{"Mana":0}as IData,plp);
                w.performExplosion(null,ap.getX()+0.5,ap.getY()+0.7,ap.getZ()+0.5,progress1/300,false,false);
                var tempData as IData=dat.muaInfo+{"recipeIndex": -1};
                w.updateCustomChunkData({"muaInfo":tempData},p);
            }
            else{   //Success
                //成功了,修改泰拉凝聚板的内置魔力量
                var mana as int=((2147483641.0 as double)/muaMus[index].length*(progress1+1)) as int;
                w.setBlockState(w.getBlockState(plp),w.getBlock(plp).data+{"Mana":mana}as IData,plp);
                var tempData as IData=dat.muaInfo+{"progress": progress1+1 as int,"recipeIndex":index as int};
                //server.commandManager.executeCommand(server,"say New Data: "+(tempData as string));
                w.updateCustomChunkData({"muaInfo":tempData},p);
                //server.commandManager.executeCommand(server,"say New Data(Check): "+(w.getCustomChunkData(p).muaInfo as string));

                //Music Ends
                if(progress1+1>=muaMus[index].length){
                    var tempData as IData=dat.muaInfo+{"recipeIndex":-1 as int};
                    w.updateCustomChunkData({"muaInfo":tempData},p);
                }
            }
        }
    }
});

End