本篇教程由作者设定未经允许禁止转载。

开什么天?拿什么辟地?

本教程并不会教你写地形生成。
但是,作为世界的缔造者,你将能够约束世间的一切规则!
好吧,也不是一切。但是你可以看一眼目录,就在下面不远处。
开天辟地 —— CrT事件教学-第1张图片本教程基于1.12.2的CraftTweaker和ZenUtils。
如果你不打算使用1.12.2,这里面的很多思路也是可以借鉴的。
如果你想要借鉴代码,请保证你的模组版本是最新的。
ZenUtils提供了很多非常有用的功能,大大提升了CrT的能力。
此外,有的时候还会用到其它CrT的附属,包括:CrT UtilsModTweakerRandomTweakerContentTweaker
总之,全装上就对了!


此外,标有(旧)的代码,代码风格不建议参考。

以及,代码不允许商用。



前置知识点

import

这里以模组合成配方修改为例。

import mods.botania.RuneAltar;
import mods.bloodmagic.BloodAltar as BA;

第一句话应该不陌生吧?那么我们来说一下它的具体效果。
如果你不写这句import,也是可以的。不过你写的所有

RuneAltar.addRecipe(...)

都要变成

mods.botania.RuneAltar.addRecipe(...)

第二句话呢,我们知道没有“as BA”这几个字,就写

BloodAltar.addRecipe(...)

就好了。但是,有了“as BA”,我们甚至可以写得更加简短:C

BA.addRecipe(...)


此外,我们还可以引用自己写的脚本。不建议文件名里面带空格、汉字、'.' 等特殊字符就是这个原因。

比如说,在.minecraft/scripts/Chapter3/Misc.zs里面,我们定义了一个函数,非常好用:

(import crafttweaker.item.IIngredient;) //在前面导包,不然IIngreddient要写成crafttweaker.item.IIngredient

function orb(level as int)as IIngredient{
    //输入气血宝珠等级,输出对应的气血宝珠们组成的IIngredient
    var orbs as IIngredient[]=[
        <bloodmagic:blood_orb>.withTag({orb: "bloodmagic:weak"}),
        <bloodmagic:blood_orb>.withTag({orb: "bloodmagic:apprentice"}),
        <bloodmagic:blood_orb>.withTag({orb: "bloodmagic:magician"}),
        <bloodmagic:blood_orb>.withTag({orb: "bloodmagic:master"}),
        <bloodmagic:blood_orb>.withTag({orb: "bloodmagic:archmage"})
    ];
    var result as IIngredient=orbs[level- 1];
    for i in (level- 1) to 5{
        result=result|orbs[i];
    }
    return result;
}

那么,如果我们想要在其它的脚本里面使用这一个函数,要满足以下几个要求:
1、两者使用相同的加载器。不能一个是crattweaker reloadableevents一个不是。
2、被调用脚本先于另一个脚本加载。你可以提高被调用脚本的#priority来实现这一点。



如果你现在手上有一个程序意义上的“对象”,名字叫p,你知道它属于IPlayer类,那么怎么获取它的相关信息呢?
首先,"IPlayer extends IEntityLivingBase",也就是说IPlayer是特殊的IEntityLiving,IEntityLiving所拥有的所有特性,IPlayer都有。


那么先看一下IPlayer有哪些独有的东西吧:

开天辟地 —— CrT事件教学-第2张图片那么如果我们想要得到p的名字,就可以写p.uuid。这是一个"string"类,也就是字符串。
想要得到p手上的物品的CrT表示法呢,我们可以写:

p.currentItem.commandString

来解析一下:

currentItem是一个zengetter,返回的是IPlayer手上的物品,一格IItemStack类。

而p.currentItem就是我们要的IItemStack了。但是它还不是尖括号形式的<minecraft:apple>。
不过问题不大。

“IItemStack extends IIngredient”,而IIngredient有这么一格zenGetter:commandString,能够把矿辞、物品,转换成尖括号形式的字符串。

所以p.currentItem.commandString就是我们要的东西了!


ZenUtils

reloadableevents

首先,如果你要写一个“事件”相关的脚本,请在脚本最头上加上ZenUtils提供的:

#loader crattweaker reloadableevents

然后,每次修改好脚本,保存好,只需要在游戏内输入指令/ct reload,就可以直接运行新的脚本了!


CustomWorld/ChunckData

这是ZenUtils提供的一套非常靠谱的系统。它能够让你向世界存储数据。
直接看官方wiki吧,已经写得非常详细了!
ZenUtilsWorld · friendlyhj/ZenUtils Wiki (github.com)

需要注意的一点是,我们几乎不会用setCustomWorld/ChunkData。
因为它会清空别的内容。比如说我们写了一个关于火红莲的Data:{"endoFlameRestriction":{...}},这个时候再set一个新的IData上去,可能就会把无辜的火红莲相关Data覆盖掉。而如果用update,除非你写的信的IData也用了{"endoFlameRestriction":{...}},否则不会修改火红莲的相关IData。


几个小例子

调试信息(简单版)

#loader crafttweaker reloadableevents
#priority 114
import crafttweaker.command.ICommandManager;
import crafttweaker.player.IPlayer;
function executeCommand(s as string){
    server.commandManager.executeCommandSilent(server,s);
}
function say(s as string){
    executeCommand("say "~s);
}
function tell(p as IPlayer,s as string){
    p.sendChat(s);
}

下文中,所有L.say("...")都会调用上述函数,直接将信息输出至聊天栏。


禁止创建地狱门

events.onPortalSpawn(function (event as crafttweaker.event.PortalSpawnEvent){
    event.cancel();
    L.say(game.localize("quests.ch2.portalCancel.des"));    //输出警告。你需要在语言文件里写上对应的句子。
});

掉落物不消失!

events.onItemExpire(function(event as crafttweaker.event.ItemExpireEvent){
    if((["minecraft:apple","botania:terraplate"] as string[]) has event.item.item.definition.id){
        event.extraLife=1919810;
        event.cancel();
    }
});

指令惊吓

在玩家使用指令的时候,除去白名单指令,左下角显示泰拉瑞亚的Boss战预警消息。

events.onCommand(function(event as CommandEvent){
    if(event.command.name=="say")return;
    if(event.command.name=="tellraw")return;
    if(event.command.name=="forge")return;
    if(event.command.name=="ftbquests")return;
    if(event.command.name=="crafttweaker")return;
    if(event.commandSender instanceof IPlayer){
        val p as IPlayer=event.commandSender;
        var t as string[]=[
            "You feel an evil presence watching you",
            "A horrible chill goes down your spine...",
            //"Screams echo around you...",
            //"This is going to be a terrible night...",
            "You feel vibrations from deep below...",
            "The air is getting colder around you...",
            //"What a horrible night to have a curse.",
            "Your mind goes numb...",
            "You are overwhelmed with pain...",
            "Otherworldly voices linger around you...",
            "Impending doom approaches...",
            "The ancient spirits of light and dark have been released.",
            "The Moon Lord has been defeated !"
            ] as string[];
        var r as int=((Math.random()*10) as int)%10;    //这个Math.random是由(import mods.ctutils.utils.Math;)
        var s as string=t[r];
        p.server.commandManager.executeCommand(server,"tellraw @p [{\"text\":\""+s+"\",\"color\":\"dark_purple\",\"bold\":\"true\",\"italic\":\"true\"}]");
    }
});

拉普达碎片魔力量设置

有人想用拉普达碎片做发癫机,但可惜魔力量太低。但是我们可以做一点修改——只要每tick,寻找到拉普达脉冲就修改携带的魔力量就好。
当然初始魔力量也要修改,否则那个魔力脉冲就会非常胖。

events.onWorldTick(function(event as crafttweaker.event.WorldTickEvent){
    var MANA_AMOUNT as int=100;
    var world as IWorld=event.world;
    if(world.remote)return;
    for i in world.getEntities(){    //zenUtils提供的
        if(isNull(i.definition))continue;
        if(i.definition.id!="botania:mana_burst")continue;
        if(isNull(i.nbt))continue;
        if(isNull(i.nbt.lensStack))continue;
        if(isNull(i.nbt.lensStack.id))continue;
        if(i.nbt.lensStack.id=="botania:laputashard"){
            i.updateNBT({mana:MANA_AMOUNT,startingMana:MANA_AMOUNT});    //也是zenUtils提供的
        }
        else{
            //L.say(i.nbt);        在写代码的时候用来获取信息的代码
        }
    }
});

难度升温

火红莲数量限制(旧)

我们不希望玩家全靠火红莲产魔。所以限制玩家一个区块最多放8个。
那么“放置”、“拆除”火红莲会触发什么事件呢?

不管怎样,方块更新总会有。
那么接下来我们就要判断这个方块是不是火红莲了。
由于一些原因,我们可能得等到下一个Tick才能判断出来,所以这个时候我们要用到ZenUtils提供的延时系统:
https://www.mcmod.cn/post/2684.html
https://github.com/friendlyhj/ZenUtils/wiki/Catenation


这里的用法是:IWorldObj.catenation.run(function(IWorld, mods.zenutils.CatenationContext){...}).start();
第二个mods.zenutils.CatenationContext我们不用管。


由于拆除火红莲时删除相关数据比较麻烦,有一堆可能性。所以我们只管放的时候区块内的火红莲数量又没哟溢出就好。

接下来是代码:

events.onBlockNeighborNotify(function(event as BlockNeighborNotifyEvent){
    var world0 as IWorld=event.world;
    var p as IBlockPos=event.position;
    var data0 as IData=world0 .getBlock(p).data;
    if(world0 .isRemote())return;                                        //逻辑客户端干了活也没用,所以不干活。
    if(!isNull(data0) && data0  has "subTileName"){
        world0 .catenation().run(function(world,c){
            var data1=world.getBlock(p).data;
            if(!isNull(data1) && data1 has "subTileName" && data1.subTileName=="endoflame"){
                //知道是火红莲了
                var dd as IData=world.getCustomChunkData(p);
                if(!(dd has "EndoResV3")){
                    //如果之前都没用放过火红莲自然没有相关数据,直接把数据存进去就好了。
                    world.updateCustomChunkData({"EndoResV3":{"num":1,"x":[p.x]as int[],"y":[p.y]as int[],"z":[p.z]as int[]}},p);
                    return;
                }
                else{
                    var d as IData=dd.EndoResV3;
                    var x as int[]=[p.x];
                    var y as int[]=[p.y];
                    var z as int[]=[p.z];
                    var n as int=1;
                    for i in 0 to d.num{
                        //如果以前的坐标上还有火红莲,并且和现在的不重复,那么就继续保存。
                        var p1 as IBlockPos= crafttweaker.util.Position3f.create(d.x[i], d.y[i], d.z[i])as IBlockPos;
                        var d1 as IData=world.getBlock(p1).data;
                        if(!isNull(d1) && d1 has "subTileName" && d1.subTileName=="endoflame"&&((p1.x!=p.x)||(p1.y!=p.y)||(p1.z!=p.z))){
                            x+=p1.x;    y+=p1.y;    z+=p1.z;    n+=1;
                        }
                    }
                    if(n>8){
                        L.say(game.localize("info.crt.endoFlameRes.exceed"));
                        world.destroyBlock(p, true);    //这个也是ZenUtils提供的方法
                    }
                    else world.updateCustomChunkData({"EndoResV3":{"num":n,"x":x,"y":y,"z":z}},p);
                }
            }
        }).start();
    }
});

随想之茧保底哞菇(旧)

如果不是玩家放的当然就不保底了。

events.onBlockPlace(function(event as BlockPlaceEvent){
    var w0 as IWorld=event.world;
    var p as IBlockPos=event.position;
    var b0 as IBlock=w0.getBlock(p);
    var uuid as string=event.player.uuid;
    if(w0.isRemote())return;
    if(b0.definition.id=="botania:cocoon"){
        var num as int= -11 as int;
        if(w0.getCustomWorldData() has "cocoonDat"){
            var l as IData[]=w0.getCustomWorldData().cocoonDat.asList()as IData[];
            for d in l{
                if(uuid==d.uuid){
                    num=(d.num as int)+1;
                    break;
                }
            }
            if(num== -11){
                num=1;
            }
            var l1 as IData=[{"uuid":uuid,"num":num}as IData]as IData;
            for d1 in l{
                if(uuid!=d1.uuid)l1+=d1;
            }
            w0.updateCustomWorldData({"cocoonDat":l1 as IData});
        }
        else{
            num=1;
            w0.updateCustomWorldData({"cocoonDat":[{"uuid":uuid,"num":num}]});
        }
        if(num%100==0){
            w0.catenation().sleep(2398).run(function(w,c){
                if(w.getBlock(p).definition.id=="botania:cocoon"){
                    w.setBlockState(<blockstate:minecraft:air>, p);
                    var e as crafttweaker.entity.IEntityAgeable=
                        <entity:minecraft:mooshroom>.createEntity(w);
                    e.posX=0.5+p.x;
                    e.posY=0.5+p.y;
                    e.posZ=0.5+p.z;
                    e.growingAge=-24000;
                    w.spawnEntity(e);
                }
                else{
                    var n=(num/10)*10- 1;
                    var l1 as IData=[{"uuid":uuid,"num":n}as IData]as IData;
                    for d1 in w0.getCustomWorldData().cocoonDat.asList()as IData[]{
                        if(uuid!=d1.uuid)l1+=d1;
                    }
                    w0.updateCustomWorldData({"cocoonDat":l1 as IData});
                }
            }).start();
        }
    }
});


铁砧当锤子用!

获取神秘4的碎片——当然,使用ContentTweaker做的(就不细说怎么做的了)。

events.onWorldTick(function(event as crafttweaker.event.WorldTickEvent){
    var world as IWorld=event.world;
    if(world.remote)return;
    for i in world.getEntities(){
        if(isNull(i.definition))continue;
        if(i.definition.id!="minecraft:falling_block")continue;
        if(i.getNBT().Block!="minecraft:anvil")continue;
        var x1 as double=i.x+i.motionX;
        var y1 as double=i.y+i.motionY;
        var z1 as double=i.z+i.motionZ;
        var blockPos as IBlockPos=L.formBlockPos(x1,y1,z1);
        var block as IBlock=world.getBlock(blockPos);
        if(isNull(block))continue;
        if(isNull(block.definition))continue;
        var id as string=block.definition.id;
        val block_drop as IItemStack[string]={
            "minecraft:tnt":<contenttweaker:shard_perditio>,
            "minecraft:ice":<contenttweaker:shard_aqua>,
            "minecraft:grass":<contenttweaker:shard_terra>,
            "minecraft:nether_brick":<contenttweaker:shard_ignis>
        };
        var drop as IItemStack=null;
        if(id=="minecraft:double_stone_slab"){
            if(block.meta==0){
                drop=<contenttweaker:shard_ordo>;
            }
            if(block.meta==1){
                drop=<contenttweaker:shard_aer>;
            }
        }
        else if (block_drop has id){
            drop=block_drop[id];
        }
        if(isNull(drop))continue;
        world.destroyBlock(blockPos,false);
        world.spawnEntity(drop.createEntityItem(world,blockPos));
    }
});

某种将史莱姆变蓝的液体

代码中这个液体是用CoT做的。自行更改。

events.onEntityLivingUpdate(function(event as crafttweaker.event.EntityLivingUpdateEvent){
    var entity as crafttweaker.entity.IEntity=event.entity;
    var world as IWorld=entity.world;
    if(world.remote)return;
    if(isNull(entity))return;
    if(isNull(entity.definition))return;
    if(isNull(entity.definition.id))return;
    if(entity.definition.id=="minecraft:slime"){
        var pos as IBlockPos = entity.position;
        if(isNull(world.getBlock(pos))){
            //L.say("air");
            return;
        }
        if(isNull(world.getBlock(pos).fluid)){
            //L.say(world.getBlock(pos).definition.id);
            return;
        }
        if(world.getBlock(pos).fluid.name!="bot_mana")return;
        var command as string="summon tconstruct:blueslime "~entity.x~" "~entity.y~" "~entity.z~" "~entity.nbt;
        var commandFormalized as string="";
        var flag as int=0;
        // 指令中不能像CrT里面意义写 false as bool,单一个false就行。下面这个循环负责删除 "as xxx"
        // 因为写这段代码的时候还没有IEntity.updateNBT(...)
        for i in 0 to command.length{
            if(command[i] == " ")flag = flag - 1;
            if( ([",","{","}","[","]"]as string[]) has command[i])flag=0;
            if(command[i]=="a" && i>0 && i < command.length - 2 && command[i - 1]==" " && command[i+1]=="s" && command[i+2]==" "){
                flag=2;
            }
            if(flag > 0)continue;
            commandFormalized += command[i];
        }
        L.executeCommand(commandFormalized);
        world.removeEntity(entity);
    }
});

不稳定金属锭&伪逆徽章

ContentTweaker相关的东西就不放出来了。
不过,未激活的分割徽章不会像ExU1那样发光。

不稳定金属锭的合成

爆炸效果不能自己做。但是我们可以继续用ExU2自带的不稳定金属锭。怎么爆炸就是锭的nbt的决定的了。

#loader crafttweaker reloadableevents
#priority 114514
import crafttweaker.item.IItemStack;
import crafttweaker.player.IPlayer;
import crafttweaker.world.IWorld;
import mods.zenutils.PlayerStat;
import crafttweaker.data.IData;
static tagName as string="unstableIngotContainerCounter";
function setPlayerContainerCounter(n as int, player as IPlayer){
    var data as IData=IData.createEmptyMutableDataMap();
    data.memberSet(tagName,n);
    player.update(data);
}
function getPlayerContainerCounter(player as IPlayer)as int{
    if(player.data has tagName)return player.data.memberGet(tagName).asInt();
    else return 0;
}
events.onPlayerLoggedIn(function(event as crafttweaker.event.PlayerLoggedInEvent){
    setPlayerContainerCounter(0,event.player);
});
events.onPlayerRespawn(function(event as crafttweaker.event.PlayerRespawnEvent){
    setPlayerContainerCounter(0,event.player);
});
function getUnstableIngot(p as IPlayer,w as IWorld)as IItemStack{
    return <extrautils2:unstableingots>.withTag({
        "owner": {
            //不稳定金属锭首先要有正确的玩家信息
            UUIDL:  p.getUUIDObject().getLeastSignificantBits() as long,
            Name:   p.name,
            UUIDU:  p.getUUIDObject().getMostSignificantBits() as long
        },
        "container": getPlayerContainerCounter(p),    //这个是说玩家在这次游戏中(进入存档/复活以来)第几次开启容器
        "dim": w.getDimension(),    //维度
        "time": w.getWorldTime() as long    //时间,决定了什么时候爆炸。10s就这么写。
    });
}
events.onPlayerOpenContainer(function(event as crafttweaker.event.PlayerOpenContainerEvent){
    var p as IPlayer=event.player;
    var w as IWorld=p.world;
    if(w.remote)return;
    setPlayerContainerCounter(1+getPlayerContainerCounter(p),p);
    //p.give(getUnstableIngot(p,w));  测试用
});
//recipes.remove(<extrautils2:unstableingots>);    这个找个其它的zenScripts放,因为这里是reloadableevents
recipes.addShaped("unstable_ingot_unstable",<extrautils2:unstableingots>,[
    [null,<minecraft:diamond>,null],
    [null,<contenttweaker:division_sigil_activated>.anyDamage().transformDamage(),null],
    [null,<minecraft:iron_ingot>,null]],
    function(out,ins,info){
        if(isNull(info.player))return null;
        //return getUnstableIngot(info.player,info.player.world);      如果这里return的话,那么你可能没把它合成出来,它还在预览栏里就炸了
        return out;
    },function(out,cInfo,player){
        if(isNull(player))out.mutable().shrink(1);
        out.mutable().withTag(getUnstableIngot(player,player.world).tag);
        print(out.commandString);
    });

分割徽章激活祭坛

#loader crafttweaker reloadableevents
import crafttweaker.item.IItemStack;
import crafttweaker.world.IBlockPos;
import crafttweaker.player.IPlayer;
import scripts.LibReloadable as L;
import crafttweaker.block.IBlock;
import crafttweaker.world.IWorld;
import mods.zenutils.PlayerStat;
import crafttweaker.data.IData;
function isMidNight(world as IWorld)as bool{
    var time as int=(world.getProvider().getWorldTime()%(24000 as long))as int;
    return (time<=18500)&&(time>17500);
}
static BRIGHTNESS as int=4;
function getBrightness(world as IWorld, pos as IBlockPos)as int{
    return world.getBrightnessSubtracted(pos);
}
function checkRitual(world as IWorld, pos as IBlockPos)as int{
    var result as int=0;        //状态压缩,我喜欢
    if(world.getBlock(pos).definition.id!="minecraft:enchanting_table")result+=1;
    if(getBrightness(world,pos)>BRIGHTNESS)result=result|2;
    //泥土       
    for i in -2 as int to 3{
        for j in -2 as int to 3{
            var pos2 as IBlockPos=IBlockPos.create(pos.x+i,pos.y- 1,pos.z+j);
            if(getBrightness(world,pos2)>BRIGHTNESS)result=result|2;
            if(!(["minecraft:dirt","extrautils2:cursedearth"]as string[] has
                world.getBlock(pos2).definition.id))result=result|4;
        }
    }
    //红石
    for i in -1 as int to 2{
        for j in -1 as int to 2{
            if(i==0&&j==0)continue;
            var pos2 as IBlockPos=IBlockPos.create(pos.x+i,pos.y,pos.z+j);
            if(getBrightness(world,pos2)>BRIGHTNESS)result=result|2;
            if(world.getBlock(pos2).definition.id!="minecraft:redstone_wire")result=result|8;
        }
    }
    if(!isMidNight(world))result=result|16;
    return result;
}
//手持徽章检查祭坛
events.onPlayerInteractBlock(function(event as crafttweaker.event.PlayerInteractBlockEvent){
    var world as IWorld=event.world;
    if(world.remote)return;
    if(!isNull(event.block) && !isNull(event.item)){
        var pos as IBlockPos=event.position;
        var player as IPlayer=event.player;
        if(event.block.definition.id=="minecraft:enchanting_table"
            &&event.item.definition.id=="contenttweaker:division_sigil"){
            var check as int=checkRitual(world,pos);
            if(check<1){
                player.sendChat(game.localize("chat.crt.exu1sigil.prepared"));
                player.sendChat(game.localize("chat.crt.exu1sigil.sacrifice"));
            }
            else{
                if((check&2)>0)player.sendChat(game.localize("chat.crt.exu1sigil.bright"));
                if((check&4)>0)player.sendChat(game.localize("chat.crt.exu1sigil.dirt"));
                if((check&8)>0)player.sendChat(game.localize("chat.crt.exu1sigil.wire"));
                if((check&16)>0)player.sendChat(game.localize("chat.crt.exu1sigil.time"));
            }
        }
    }
});
//献祭动物
events.onEntityLivingDeath(function(event as crafttweaker.event.EntityLivingDeathEvent){
    var entity as crafttweaker.entity.IEntity=event.entity;
    var world as IWorld=entity.world;
    if(world.remote)return;
    if(entity instanceof crafttweaker.entity.IEntityAnimal) {
        if(event.damageSource.immediateSource instanceof IPlayer){
            var player as IPlayer=event.damageSource.immediateSource;
            for i in -1 as int to 2{
                for j in -1 as int to 2{
                    var pos as IBlockPos=IBlockPos.create(entity.x as int+i,entity.y as int,entity.z as int+j);
                    var check as int=checkRitual(world,pos);
                    if(check<1){
                        var flag=false;
                        for i in 0 to player.inventorySize{
                            if(!isNull(player.getInventoryStack(i))&&(player.getInventoryStack(i).definition.id=="contenttweaker:division_sigil")){
                                player.replaceItemInInventory(i,<contenttweaker:division_sigil_activated>);
                                flag=true;
                            }
                        }
                        if(flag){
                            //L.say("Suceess!");
                            world.addWeatherEffect(world.createLightningBolt(0.5+pos.x,0.5+pos.y,0.5+pos.z,true));
                            for i in -2 as int to 3{
                                for j in -2 as int to 3{
                                    var pos2 as IBlockPos=IBlockPos.create(pos.x+i,pos.y- 1,pos.z+j);
                                    world.setBlockState((<extrautils2:cursedearth>as IBlock).definition.defaultState,pos2);
                                }
                            }
                        }
                    }
                    else if(check%2==0){
                        if((check&2)>0)player.sendChat(game.localize("chat.crt.exu1sigil.bright"));
                        if((check&4)>0)player.sendChat(game.localize("chat.crt.exu1sigil.dirt"));
                        if((check&8)>0)player.sendChat(game.localize("chat.crt.exu1sigil.wire"));
                        if((check&16)>0)player.sendChat(game.localize("chat.crt.exu1sigil.time"));
                    }
                }
            }
        }
    }
});

伪逆徽章仪式

#loader crafttweaker reloadableevents
import crafttweaker.entity.IEntityDefinition;
import crafttweaker.entity.IEntityLiving;
import crafttweaker.entity.IEntityMob;
import crafttweaker.item.IItemStack;
import crafttweaker.world.IBlockPos;
import crafttweaker.entity.IEntity;
import crafttweaker.player.IPlayer;
import scripts.LibReloadable as L;
import crafttweaker.block.IBlock;
import crafttweaker.world.IWorld;
import mods.zenutils.PlayerStat;
import mods.ctutils.utils.Math;
import crafttweaker.data.IData;
//east,south,west,north
//x+,z+,x-,z-
//一个小小的工具,用于搜寻方块
function shift(pos as IBlockPos, x as int, z as int)as IBlockPos{
    return IBlockPos.create(pos.x+x,pos.y,pos.z+z);
}
//所需要的物品
static CHESTS as [IItemStack][string]={
    "east":[
        <minecraft:potion>.withTag({Potion: "minecraft:long_night_vision"}),
        <minecraft:potion>.withTag({Potion: "minecraft:long_invisibility"}),
        <minecraft:potion>.withTag({Potion: "minecraft:strong_leaping"}),
        <minecraft:potion>.withTag({Potion: "minecraft:long_fire_resistance"}),
        <minecraft:potion>.withTag({Potion: "minecraft:strong_swiftness"}),
        <minecraft:potion>.withTag({Potion: "minecraft:long_slowness"}),
        <minecraft:potion>.withTag({Potion: "minecraft:long_water_breathing"}),
        <minecraft:potion>.withTag({Potion: "minecraft:strong_healing"}),
        <minecraft:potion>.withTag({Potion: "minecraft:strong_harming"}),
        <minecraft:potion>.withTag({Potion: "minecraft:strong_poison"}),
        <minecraft:potion>.withTag({Potion: "minecraft:strong_regeneration"}),
        <minecraft:potion>.withTag({Potion: "minecraft:strong_strength"}),
        <minecraft:potion>.withTag({Potion: "minecraft:long_weakness"}),
        <minecraft:potion>.withTag({Potion: "minecraft:luck"})
    ],
    "south":[
        <minecraft:dirt>,
        <minecraft:grass>,
        <minecraft:sand>,
        <minecraft:gravel>,
        <minecraft:clay>,
        <minecraft:gold_ore>,
        <minecraft:iron_ore>,
        <minecraft:coal_ore>,
        <minecraft:lapis_ore>,
        <minecraft:redstone_ore>,
        <minecraft:emerald_ore>,
        <minecraft:diamond_ore>
    ],
    "west":[
        <minecraft:record_13>,
        <minecraft:record_cat>,
        <minecraft:record_blocks>,
        <minecraft:record_chirp>,
        <minecraft:record_far>,
        <minecraft:record_mall>,
        <minecraft:record_mellohi>,
        <minecraft:record_stal>,
        <minecraft:record_strad>,
        <minecraft:record_ward>,
        <minecraft:record_11>,
        <minecraft:record_wait>
    ],
    "north":[
        <minecraft:stone>,
        <minecraft:hardened_clay>,
        <minecraft:glass>,
        <minecraft:coal:1>,
        <minecraft:gold_ingot>,
        <minecraft:iron_ingot>,
        <minecraft:dye:2>,
        <minecraft:cooked_porkchop>,
        <minecraft:cooked_fish>,
        <minecraft:cooked_beef>,
        <minecraft:cooked_chicken>,
        <minecraft:baked_potato>,
        <minecraft:cooked_mutton>,
        <minecraft:cooked_rabbit>
    ]
};
//比较箱子中的物品和要求
function stacksMatch(a as IItemStack[],b as IItemStack[])as bool{
    var f as bool[]=[];
    if(a.length!=b.length)return false;
    var n as int=a.length;
    for i in 0 to n{
        f+=true;
    }
    for i in 0 to n{
        var g=true;
        for j in 0 to n{
            if(f[j]&&b[j].commandString==a[i].commandString){
                f[j]=false;
                g=false;
                break;
            }
        }
        if(g)return false;
    }
    return true;
}
//检查线、红石线是否齐全
function countWire(world as IWorld, pos as IBlockPos)as bool{
    var pat as string[]=[
        "AAAAAAAAB",
        "BBBBBBBAB",
        "BAAAAABAB",
        "BABBBABAB",
        "BABAXABAB",
        "BABABBBAB",
        "BABAAAAAB",
        "BABBBBBBB",
        "BAAAAAAAA"
    ];
    for iii in 0 to 2{
        var mapper as string[string]={
            "A":"minecraft:tripwire",
            "B":"minecraft:redstone_wire"
        };
        for jjj in 0 to 2{
            var flag=false;
            for i in 0 to 9{
                for j in 0 to 9{
                    var p as string=pat[i][j];
                    if(p=="X")continue;
                    if(!L.isBlock(world,shift(pos,i- 4,j- 4),mapper[p]))flag=true;
                    if(flag)break;
                }
                if(flag)break;
            }
            if(!flag)return true;
            mapper={
                "B":"minecraft:tripwire",
                "A":"minecraft:redstone_wire"
            };
        }
        pat=[
            "BAAAAAAAA",
            "BABBBBBBB",
            "BABAAAAAB",
            "BABABBBAB",
            "BABAXABAB",
            "BABBBABAB",
            "BAAAAABAB",
            "BBBBBBBAB",
            "AAAAAAAAB"
        ];
    }
    return false;
}
//检查仪式
function checkRitual(world as IWorld, pos as IBlockPos)as string[]{
    var result as string[]=[];
    var flag=true;
    //没在末地
    if(world.getDimension()!=1 as int){
        //L.say(world.getDimension());
        return [game.localize("chat.crt.exu1sigil2.wrong_dimension")];
    }
    //核心方块不是信标
    if(!L.isBlock(world,pos,"minecraft:beacon")){
        result+="ERROR: The Block is not beacon. This should be a bug in the code. Report this to the author of the modpack";
        flag=false;
    }
    //检查箱子
    var xs as int[]=[5,0,-5 as int,0];
    var zs as int[]=[0,5,0,-5 as int];
    var facingMeta as int[]=[4,2,5,3];
    var direction as string[]=["east","south","west","north"];
    var color1 as string[]=["§b","§a","§e","§c"];
    var color2 as string[]=["§1","§2","§6","§4"];
    var reset as string="§r";
    for i in 0 to 4{
        var pos2 as IBlockPos=shift(pos,xs[i],zs[i]);
        var missing as string=game.localize(("chat.crt.exu1sigil2.missing."~direction[i])as string);
        var facing as string=game.localize(("chat.crt.exu1sigil2.facing."~direction[i])as string);
        var req0 as string=game.localize(("chat.crt.exu1sigil2.req."~direction[i])as string);
        var prepared as string=game.localize(("chat.crt.exu1sigil2.prepared."~direction[i])as string);
        //箱子没放
        if(!L.isBlock(world,pos2,"minecraft:chest")){
            result+=(color2[i]~missing~" ("~pos2.x~", "~pos2.y~", "~pos2.z~")§r")as string;
            flag=false;
        }
        //箱子没面对信标
        else if(facingMeta[i]!=world.getBlock(pos2).meta){
            result+=(color2[i]~facing~reset)as string;
            flag=false;
        }
        else{
            //箱子物品不对
            if(!stacksMatch(L.getItemsInChest(world.getBlock(pos2)),CHESTS[direction[i]])){
                var req as string=(color1[i]~req0)as string;
                for item in CHESTS[direction[i]]{
                    //var display as string=(item.hasTag&&(item.tag!={}))?item.commandString:item.displayName;
                    req=req~"\n      "~color1[i]~item.displayName~" §o( "~item.commandString~" )§r";
                }
                flag=false;
                result+=req;
            }
            //箱子没事了
            else{
                result+=(color1[i]~prepared~reset)as string;
            }
        }
    }
    //检查红石线、线
    if(countWire(world,pos)){
        result+=game.localize("chat.crt.exu1sigil2.complete_wire");
    }
    else{
        result+=game.localize("chat.crt.exu1sigil2.imcomplete_wire");
        flag=false;
    }
    //总结到底成功没成功
    if(flag){
        result+=game.localize("chat.crt.exu1sigil2.complete");
        result+=game.localize("chat.crt.exu1sigil2.sacrifice");
    }
    else{
        result+=game.localize("chat.crt.exu1sigil2.imcomplete");
    }
    return result;
}
//检查献祭条件
events.onPlayerInteractBlock(function(event as crafttweaker.event.PlayerInteractBlockEvent){
    if(event.player.world.remote)return;
    if(!isNull(event.block)&&event.block.definition.id=="minecraft:beacon"&&!isNull(event.item)&&event.item.definition.id=="contenttweaker:division_sigil_activated"){
        for i in checkRitual(event.player.world,event.position){
            event.player.sendChat(i);
        }
    }
});
static tagName as string="PseudoInversionRirualKill";
// 这个nbt在玩家身上记录击杀数,在怪物身上则作为一个标记,绑定到玩家身上。
// 设置击杀数
function setKill(player as IPlayer, n as int){
    var data as IData=IData.createEmptyMutableDataMap();
    data.memberSet(tagName,n);
    player.update(data);
}
//获取玩家击杀数。如果不在仪式中则击杀数为-1.
function getKill(player as IPlayer) as int{
    if(isNull(player))return -1 as int;
    if(player.data has tagName)return player.data.memberGet(tagName).asInt();
    else return -1 as int;
}
events.onEntityLivingDeath(function(event as crafttweaker.event.EntityLivingDeathEvent){
    var entity as crafttweaker.entity.IEntity=event.entity;
    var world as IWorld=entity.world;
    if(world.remote)return;
    if(event.damageSource.immediateSource instanceof IPlayer){
        var player as IPlayer=event.damageSource.immediateSource;
        //献祭铁傀儡
        if(entity.definition.id=="minecraft:villager_golem") {
            //寻找信标,判定祭坛
            for i in -5 as int to 6{
                for j in -3 as int to 4{
                    for k in -5 as int to 6{
                        //L.say("B");
                        var pos as IBlockPos=IBlockPos.create(entity.x as int+i,entity.y as int+j,entity.z as int+k);
                        if(L.isBlock(world,pos,"minecraft:beacon")){
                            //L.say("A");
                            var check as string[]=checkRitual(world,pos);
                            if(check[check.length- 1]==game.localize("chat.crt.exu1sigil2.sacrifice")){
                                player.sendChat(game.localize("chat.crt.exu1sigil2.start"));
                                setKill(player,0);
                                world.performExplosion(null, pos.x, pos.y, pos.z, 5, true, true);
                                world.performExplosion(null, pos.x+5, pos.y, pos.z, 2, true, true);
                                world.performExplosion(null, pos.x- 5, pos.y, pos.z, 2, true, true);
                                world.performExplosion(null, pos.x, pos.y, pos.z+5, 2, true, true);
                                world.performExplosion(null, pos.x, pos.y, pos.z- 5, 2, true, true);
                                world.catenation().run(function(w,c){
                                    w.performExplosion(null, pos.x+5, pos.y, pos.z, 2, true, true);
                                    w.performExplosion(null, pos.x- 5, pos.y, pos.z, 2, true, true);
                                    w.performExplosion(null, pos.x, pos.y, pos.z+5, 2, true, true);
                                    w.performExplosion(null, pos.x, pos.y, pos.z- 5, 2, true, true);
                                }).start();
                            }
                            else{
                                for i in check{
                                    player.sendChat(i);
                                }
                            }
                            return;
                        }
                    }
                }
            }
        }
        //如果是怪物
        else if(entity instanceof IEntityMob){
            //如果是有那个NBT的怪物,设置击杀数
            if(entity.nbt.ForgeData has tagName && getKill(player)>-1 && world.getDimension()==1){
                setKill(player,getKill(player)+1);
                player.sendChat("kill:"~getKill(player));
                if(getKill(player)>99){
                    player.sendChat(game.localize("chat.crt.exu1sigil2.end"));
                    for i in 0 to player.inventorySize{
                        if(!isNull(player.getInventoryStack(i))&&(player.getInventoryStack(i).definition.id=="contenttweaker:division_sigil_activated")){
                            player.replaceItemInInventory(i,<contenttweaker:psu_inver_sigil>);
                            break;
                        }
                    }
                    setKill(player,-1);
                    for i in world.getEntitiesInArea(IBlockPos.create((player.x- 130)as int,0,(player.z- 130) as int),IBlockPos.create((player.x+130)as int,255,(player.z+130) as int)){
                        if(isNull(i))continue;
                        if(i instanceof IEntityMob && i.getNBT().ForgeData has tagName){
                            world.removeEntity(i);
                        }
                    }
                }
            }
        }
    }
});

//字面意思
function canEntitySpawn(world as IWorld, pos as IBlockPos, mob as IEntityDefinition)as bool{
    if(!world.getBlockState(pos).isSideSolid(world, pos, crafttweaker.world.IFacing.up()))return false;
    var entity0 as IEntity=mob.createEntity(world);
    if(!(entity0 instanceof IEntityLiving))return false;
    var entity as IEntityLiving=entity0;
    entity.posX=0.5+pos.x;
    entity.posY=1.0+pos.y;
    entity.posZ=0.5+pos.z;
    if(!world.getBlockState(pos).canEntitySpawn(entity))return false;
    if(!entity.canSpawnHere)return false;
    if(entity.isColliding)return false;
    return true;
}
function getTopBlock(world as IWorld, pos as IBlockPos, mob as IEntityDefinition)as IBlockPos{
    var y as int=pos.y;
    //print(y);
    if(y>255)y=255;
    while(y>-1){
        var pos2=IBlockPos.create(pos.x,y,pos.z);
        if(canEntitySpawn(world,pos2,mob))return pos2;
        y-=1;
    }
    if(pos.y<255)return getTopBlock(world,IBlockPos.create(pos.x,255,pos.z),mob);
    return null;
}
//刷怪列表
static MobsSpawning as IEntityDefinition[]=[
    <entity:minecraft:enderman>,
    <entity:minecraft:evocation_illager>,
    <entity:minecraft:skeleton>,
    <entity:minecraft:spider>,
    <entity:minecraft:stray>,
    <entity:minecraft:zombie>,
    <entity:minecraft:wither_skeleton>,
    <entity:minecraft:witch>,
    <entity:minecraft:vindication_illager>,
    <entity:minecraft:cave_spider>,
    <entity:minecraft:blaze>
];
static MAX_ENEMY_SPAWN as int=300;
//疯狂刷怪
events.onPlayerTick(function(event as crafttweaker.event.PlayerTickEvent){
    var player as IPlayer=event.player;
    var world as IWorld=player.world;
    if(world.remote)return;
    var pos as IBlockPos=IBlockPos.create(player.x as int,player.y as int,player.z as int);
    if(getKill(player)>-1 && world.getDimension()==1){
        var counter as int=0;
        for i in world.getEntitiesInArea(IBlockPos.create((player.x- 130)as int,0,(player.z- 130) as int),IBlockPos.create((player.x+130)as int,255,(player.z+130) as int)){
            if(isNull(i))continue;
            if(i instanceof IEntityMob)counter+=1;
            //怪物太多就不刷了
            if(counter>MAX_ENEMY_SPAWN)return;
        }
        //每tick3只怪
        for iii in 0 to 3{
            var entityDef as IEntityDefinition=MobsSpawning[
                (0.9999*MobsSpawning.length*Math.random())as int
            ];
            for jjj in 0 to 5{
                //尝试5个坐标
                var r as double=24.0+90.0*Math.random();
                var theta as double=3.1416*2*Math.random();
                var y as int=30+player.y;
                var pos as IBlockPos=getTopBlock(
                    world,IBlockPos.create(
                        (player.x+r*Math.cos(theta))as int, y,
                        (player.z+r*Math.sin(theta))as int
                    ),entityDef);
                if(isNull(pos))continue;
                var entity0 as IEntity=entityDef.createEntity(world);
                    var entity as IEntityLiving=entity0;
                    entity.posX=0.5+pos.x;
                    entity.posY=1.0+pos.y;
                    entity.posZ=0.5+pos.z;
                    var data as IData=IData.createEmptyMutableDataMap();
                    data.memberSet(tagName,player.uuid);
                    entity.setNBT(data);
                    world.spawnEntity(entity);
                    entity.addPotionEffect(<potion:minecraft:speed>.makePotionEffect(2000000000,4));
                    break;
            }
        }
    }
});
//如果玩家完成了仪式,生成的怪物就消失
events.onEntityLivingUpdate(function(event as crafttweaker.event.EntityLivingUpdateEvent){
    if(event.entity instanceof IEntityLiving){
        var entity as IEntityLiving=event.entity;
        var world as IWorld=entity.world;
        if(world.remote)return;
        if(entity.getNBT().ForgeData has tagName){
            var uuid as string=entity.getNBT().ForgeData.memberGet(tagName).asString();
            if(getKill(world.getPlayerByUUID(mods.zenutils.UUID.fromString(uuid)))<0)world.removeEntity(entity);
        }
    }
});

彩虹莲

使用RandomTweaker的自定义花朵功能。
产能花,当然想要更多效果也是可以的,只需要在下面某个函数里添点东西。

顾名思义,和彩虹发电机一样。不过我这里写的是附近3*3*3内必须13种原版产魔花同时工作。
同时工作的意思……看代码吧!

CoT:

#loader contenttweaker
import mods.contenttweaker.VanillaFactory;
import mods.randomtweaker.cote.ISubTileEntityGenerating;
val flower as ISubTileEntityGenerating = VanillaFactory.createSubTileGenerating("irisotos");
flower.range = 1;
flower.maxMana = 10000000;
flower.register();

运行逻辑:

#loader crafttweaker reloadableevents
import crafttweaker.event.BlockNeighborNotifyEvent;
import mods.randomtweaker.botania.IBotaniaFXHelper;
import crafttweaker.command.ICommandManager;
import mods.zenutils.ICatenationBuilder;
import crafttweaker.item.IIngredient;
import crafttweaker.item.IItemStack;
import crafttweaker.world.IBlockPos;
import mods.zenutils.NetworkHandler;
import crafttweaker.player.IPlayer;
import scripts.LibReloadable as L;
import crafttweaker.world.IWorld;
import crafttweaker.block.IBlock;
import crafttweaker.data.IData;
import mods.ctutils.utils.Math;
import mods.zenutils.IByteBuf;
//用于判断瞬时产魔花的工作
static MANA_DRAIN as double=160;
static IRISOTOS_RADIUS as int=1;
//给花一格编号
static flowers as int[string] = {
    "hydroangeas":0,
    "endoflame":1,
    "thermalily":2,
    "arcanerose":3,
    "munchdew":4,
    "entropinnyum":5,
    "kekimurus":6,
    "gourmaryllis":7,
    "narslimmus":8,
    "spectrolus":9,
    "dandelifeon":10,
    "rafflowsia":11,
    "shulk_me_not":12
};
//粒子效果颜色。如果花工作了,给它上粒子效果
function getColor(name as string)as int{
    if(name=="hydroangeas")return 0x8888FF;
    if(name=="endoflame")return 0xFF8800;
    if(name=="thermalily")return 0xFF2222;
    if(name=="arcanerose")return 0xCC7777;
    if(name=="munchdew")return 0x77FF77;
    if(name=="entropinnyum")return 0xAA0000;
    if(name=="kekimurus"){
        if(Math.random()<0.7)return 0xFFFFFF;
        return 0xFF0000;
    }
    if(name=="gourmaryllis")return 0xFFFF00;
    if(name=="narslimmus")return 0x77CC77;
    if(name=="spectrolus"){
        var t=Math.random()*3.1416*2;
        var r=128+127*Math.sin(t);
        var g=128+127*Math.sin(t+120);
        var b=128+127*Math.sin(t+240);
        return b+256*(g+256*r);
    }
    if(name=="rafflowsia")return 0xFF44FF;
    if(name=="shulk_me_not")return 0xCC00CC;
    if(name=="dandelifeon"){
        if(Math.random()<0.5)return 0xFF7777;
        return 0x55FF55;
    }
}
function getFlowerName(w as IWorld, p as IBlockPos)as string{
    var d as IData=w.getBlock(p).data;
    if(!isNull(d) && d has "subTileName")return d.subTileName.asString();
    return "";
}
function getFlowerMana(w as IWorld,p as IBlockPos)as int{
    var d as IData=w.getBlock(p).data;
    if(!isNull(d) && d has "subTileCmp")return d.subTileCmp.mana.asInt();
    return 0;
}
function pow(a as int, b as int)as int{
    if(b==1)return a;
    if(b<1)return 1;
    var t=pow(a,b/2);
    if(b%2==1)return t*t*a;
    return t*t;
}
function irisotosWork(world as IWorld,pos as IBlockPos){
    //这里面你可以加点你想要的效果,比如说把附近活石变成“彩虹石”之类的
}
<cotSubTile:irisotos>.onUpdate = function(tile, world, pos) {
    var r as int=IRISOTOS_RADIUS;         //radius
    var d as int=r*2+1;     //diameter
    //两个彩虹莲不能相隔太近
    for i in 0 to d*2+1{
        for j in 0 to d*2+1{
            for k in 0 to d*2+1{
                var pos1 as IBlockPos=IBlockPos.create(pos.x+i-d,pos.y+j-d,pos.z+k-d);
                var name as string=getFlowerName(world,pos1);
                if(pos1.x!=pos.x||pos1.y!=pos.y||pos1.z!=pos.z)if(name=="irisotos"){
                    //world.setBlockState(<blockstate:minecraft:air>,pos1);
                    if(!world.remote)//world.spawnEntity(<botania:specialflower>.withTag({type: "irisotos"}).createEntityItem(world,pos1));
                        world.destroyBlock(pos1, true);
                    else{
                        //在逻辑客户端生成植魔粒子效果,由RandomTweaker友情提供
                        for ii in 0 to 20{
                            var rat=0.1*ii;
                            var v=0.03;
                            IBotaniaFXHelper.wispFX(
                                0.5+pos1.x+(0.4-rat)*(i-d),0.5+pos1.y+(0.4-rat)*(j-d),0.5+pos1.z+(0.4-rat)*(k-d),
                                1,0,0,0.3,
                                v*(i-d)*(0.1+rat),v*(-0.1+j-d)*(0.1+rat),v*(k-d)*(0.1+rat),2
                            );
                            IBotaniaFXHelper.wispFX(
                                0.5+pos.x+(0.4-rat)*(d-i),0.5+pos.y+(0.4-rat)*(d-j),0.5+pos.z+(0.4-rat)*(d-k),
                                1,0,0,0.3,
                                v*(d-i)*(0.1+rat),v*(-0.1+d-j)*(0.1+rat),v*(d-k)*(0.1+rat),2
                            );
                        }
                    }
                }
            }
        }
    }
    if(world.remote)return;
    
    //运行逻辑
    var flags as int=0;    //还记得说花朵运行要用粒子效果连接花和彩虹莲吗,这里flags起到存储数据的效果。int有32位,而我们要存储26个bool,够了。
    var flagsForClient as int=0;
    var t1 as IData=tile.getCustomData();
    var manas as IData=(!isNull(t1)&&t1 has "manas")?t1.manas:IData.createEmptyMutableDataMap();
    var timers as IData=(!isNull(t1)&&t1 has "timers")?t1.timers:IData.createEmptyMutableDataMap();
    for i in 0 to d{
        for j in 0 to d{
            for k in 0 to d{
                var pos1 as IBlockPos=IBlockPos.create(pos.x+i-r,pos.y+j-r,pos.z+k-r);
                var compressedIndex as int=(d*(d*i+j)+k);
                var ci as string=""~compressedIndex; //compressed index
                var dat as IData=world.getBlock(pos1).data;
                var name as string=getFlowerName(world,pos1);
                dat=(isNull(dat)||!(dat has"subTileCmp"))?IData.createEmptyMutableDataMap():dat.subTileCmp;
                //计时器和魔力统计
                var lastMana=((manas has ci)?manas.memberGet(ci).asInt():0)as double;
                var mana as int=getFlowerMana(world,pos1);
                var time as int=(timers has ci)?timers.memberGet(ci).asInt():0;
                var toUpdate=IData.createEmptyMutableDataMap();
                toUpdate.memberSet(ci,mana);
                manas=manas+toUpdate;
                //检查花朵是否允许
                if(flowers has name){
                    var id as int=flowers[name]as int;
                    var working=false;
                    if(id==0||id==2)working=(dat.burnTime.asInt()>0)||(dat.cooldown.asInt()>0);      //水绣球&炽玫瑰
                    else if(id==1)working=(dat.burnTime.asInt()>0);    //火红莲
                    else if(id==7)working=(dat.cooldown>0);    //彼方兰
                    else if(id==4)working=dat.ateOnce.asBool();    //咀叶花
                    else{
                        //瞬时产魔花判定
                        var ratio as double=1.0;
                        if(id==6)ratio=40.0;
                        if(id==11)ratio=90.0;
                        if(id==10)ratio=2.0;
                        if(id==5)ratio=10.0;
                        //具体逻辑
                        if(lastMana<mana)time=((ratio*mana-lastMana)/MANA_DRAIN+time)as int;
                        if(id==3){
                            //阿卡纳蔷薇比较特殊
                            if(dat.collectorY>=0){
                                var pos2 as IBlockPos=IBlockPos.create(
                                    dat.collectorX.asInt(),dat.collectorY.asInt(),dat.collectorZ.asInt()
                                );
                                var spreader as IBlock=world.getBlock(pos2);
                                var spreaderDat as IData=spreader.data;
                                if(!isNull(spreaderDat)&&spreaderDat has"mana"&&spreaderDat.id=="botania:spreader"){
                                    var mana1=spreaderDat.mana;
                                    var manaCap=(spreader.meta>2)?6400:1000;
                                    time=time+1;
                                }
                            }
                            working=working||(time>0);
                        }else working=(time>0);
                    }
                    if(working){
                        flags=flags|pow(2,id);
                        flagsForClient=flagsForClient|pow(2,compressedIndex);
                    }
                    if(id!=10&&id!=12){
                        if(time>200)time=200;
                    }else if(time>5000)time=5000;
                }
                else time=0;
                toUpdate.memberSet(ci,(time<1)?0:time- 1);
                timers=timers+toUpdate;
            }
        }
    }
    if(!world.remote){
        if(!isNull(world.getBlock(pos).data)&&world.getBlock(pos).data.subTileCmp.ticksExisted>1){
            tile.updateCustomData({"timers":timers,"manas":manas});
            if(flags==pow(2,flowers.keys.length)- 1){
                irisotosWork(world,pos);
                L.say("Hooray!!!! Irisotos is finally working!");
                tile.addMana(50000);
            }
        }
        else{
            tile.updateCustomData({"manas":manas});
        }
        //ZenUtils,向逻辑客户端通信。因为服务端扔不了粒子。周围10格内的玩家可以收到信息
        NetworkHandler.sendToAllAround("IrisotosBotFXDat",
            pos.x,pos.y,pos.z,10,world.getDimension(),function(b){
                b.writeBlockPos(pos);
                b.writeInt(flagsForClient);
            });
    }
};
//客户端收到信息,扔粒子
NetworkHandler.registerServer2ClientMessage("IrisotosBotFXDat",function(p,b){
    var world as IWorld=p.world;
    var pos as IBlockPos=b.readBlockPos();
    var flags as int=b.readInt();
    //L.say(flags);
    var r as int=IRISOTOS_RADIUS;         //radius
    var d as int=r*2+1;     //diameter
    for i in 0 to d{
        for j in 0 to d{
            for k in 0 to d{
                var ci as int=k+d*(j+i*d);
                var pos1 as IBlockPos=IBlockPos.create(pos.x+i-r,pos.y+j-r,pos.z+k-r);
                var name as string=getFlowerName(world,pos1);
                if((flags/pow(2,ci))%2==1){
                    var v=0.03;
                    var rgbr=1.0/255;
                    if(Math.random()>0)IBotaniaFXHelper.wispFX(
                        0.5+pos1.x,0.5+pos1.y,0.5+pos1.z,
                        rgbr*(getColor(name)/256/256),rgbr*(getColor(name)/256%256),rgbr*(getColor(name)%256),0.2,
                        v*(r-i),v*(-0.3+r-j),v*(r-k),1
                    );
                }
            }
        }
    }
});