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


喜报!

你是否还在为你自定义函数需要传一堆感觉没必要传的参数而烦恼?

你是否还在为你自定义的函数库需要一次又一次跨脚本调用而痛苦?

选择¥expand¥!啊不对用错符号了...选择$expand$(Expansion Method)解决这个问题!

自定义类成员函数,从根本上解决你的烦恼!


 · 前言

    这是一篇进阶教程, 如果你是一个CrT的新手,或者说你只是刚刚接触了事件,对“类”、“函数”这几个名词都没有很好理解的前提下建议你退出,这篇教程的内容过深,不适合你。

    笔者在搓隔壁那篇NBT与动态信息显示(传送门)的时候自定义了一堆函数,但总是感觉用着不顺手(写jvav写的):很多自定义函数都可以被设计成ZenMethod的形式(换言之某些类的成员函数,比如说笔者自定义的getItemCertainNBT()),从而减少后续传入参数的个数,明确函数用途。恰好ZenScript给出了能额外拓展某个类成员函数的$expand$用法,能够将自定义的函数做成对应类的成员函数,从而避免麻烦的跨脚本调用,以及解决部分魔改者的强迫症。

    当然如果你并没有笔者这种不顺手的感受也欢迎看下去,至少它能让你的魔改更加顺手。

    很遗憾,这个部分Zentutorial并没有相关说明,但up吕不才有相关视频说明,这里贴链接:Official / BiliBili


    (由于部分原因笔者不会为这篇教程添加封面,也请各位读者不要点击“推荐”这篇教程)


 · 格式与用法

    格式如下:

$expand <ZSClassName>$<fucntionName>(para1 as <paraType>,para2 as <paraType>, ...) as <returnType>{
    //Code
    //这里使用this指代ZSClassName对应的对象
    return <returnTypeObj> ;
}

    我们来举个例子:(导包略)不会吧不会吧,不会有人到这里都不懂怎么导包吧(

    我需要获取某个物品的特定NBT,在笔者以往的教程中写过这样的函数,将“没有NBT”、“没有对应NBT”都给做了判断:

function getItemCertainNBT(item as IItemStack, nbtString as string) as IData {
    if(!isNull(item)&&!isNull(nbtString)&&!isNull(item.tag)&&!isNull(item.tag.memberGet(nbtString))) {
        return item.tag.memberGet(nbtString);
    }
    else return null;
}

    既然都是对物品获取特定的NBT,那我们能不能这样想:直接通过item.XXXX(xxx)的形式(ZenMethod)来获取,而不需要额外传一个参数呢?$expand$就是来干这个的,看代码:

#priority 999
//这种脚本最好优先加载

import ......

$expand IItemStack$getCertainNBT(nbtString as string) as IData{
    //这里我们指明了是要对IItemStack这个对象来添加扩展方法的
    //所以我们可以少传一个IItemStack参数
    //而改用this指代这个参数
    //同时this不可能为空,略去空判断
    if(isNull(nbtString)||isNull(this.tag)||isNull(this.tag.memberGet(nbtString))) return null;
    return this.tag.memberGet(nbtString);
}

    然后我们就可以愉快地使用item.getCertainNBT(...)来获取特定NBT,而不需要再传一遍IItemStack参数了。

    当然你也可以把你所有的函数库都给改造成这种拓展函数,这是笔者自己设计的几个拓展方法:

//以数组的形式检测某个物品是否有某个NBT
//支持多层NBT
$expand IItemStack$getCertainNBT(nbtString as string[]) as IData{
    if(isNull(this.tag;)) return null;
    var returnNBT as IData = this.tag;
    if(isNull(nbtString)||nbtString.length == 0) return returnNBT;
    for i in 0 .. nbtString.length{
        if(isNull(returnNBT.memberGet(nbtString[i]))) return null;
        returnNBT = returnNBT.memberGet(nbtString[i]);
    }
    return returnNBT;
}

//获取物品某个特定附魔
$expand IItemStack$getCertainEnchantment(targetEnch as IEnchantmentDefinition) as IEnchantment{
    if(!this.isEnchanted)return null;
    for ench in this.enchantments{
        if(ench.definition==targetEnch){
            return ench;
        }
    }
    return null;
}

//物品是否有某个匠魂的特性
$expand IItemStack$hasCertainTConTrait(traitName as string) as bool{
    if(!isNull(this)&&!isNull(traitName)&&!isNull(this.getCertainNBT(["Traits"]))){
        if(this.getCertainNBT(["Traits"]).asString().contains("\""~traitName~"\"")) return true;
    }
    return false;
}

//物品某个匠魂特性是否能被触发
$expand IItemStack$certainTConTraitActive(traitName as string) as bool{
    if(
        this.hasCertainTConTrait(traitName)&&
        !isNull(this.getCertainNBT(["Stats"]))&&
        (isNull(this.getCertainNBT(["Stats","Broken"])))||
        (this.getCertainNBT(["Stats","Broken"]).asInt()==0)
    )return true;
    return false;
}

//获取以某个IEntity为中心、边长为2*range的正方体范围内的所有IEntityLivingBase
$expand IEntity$getEntityLivingBasesInCube(range as float) as IEntityLivingBase[]{
    var r as double = range as double;
    var Xt = this.getX() as double;
    var Yt = this.getY() as double;
    var Zt = this.getZ() as double;
    var areaStart as Position3f = Position3f.create( ( Xt + r ) , ( Yt + r ) , ( Zt + r ) );
    var areaEnd as Position3f = Position3f.create( ( Xt - r + 1.0d ) , ( Yt - r + 1.0d ) , ( Zt - r + 1.0d ) );
    var entityLivingBaseArr as IEntityLivingBase[] = [];
    var entityNearArr as IEntity[] = this.world.getEntitiesInArea( areaStart , areaEnd );
    for entity in this.world.getEntitiesInArea( areaStart , areaEnd ){
        if(entity instanceof IEntityLivingBase){
        var entityLivingBase as IEntityLivingBase = entity;
            entityLivingBaseArr += entityLivingBase;
        }
    }
    return entityLivingBaseArr;
}

//默认发出“Check!”的调试函数
$expand IPlayer$debugMessage() as void{
    this.sendChat("Check!");
}

    然后来说说这种操作的意义:

    从编程的角度来说,这种操作相当于给对应的类添加了自定义的成员函数。将一个函数写成成员函数相当于给这个函数作一个“归类”:相比于对于一个函数扔几个地位平等的参数而言,用成员函数的形式调用可以强调“这个函数是依赖于/作用于这个对象所属的类的”,将其类对象这个参数与其他参数“区分”开,用ZenMethod的形式进行调用。尽管会有人说“这不和自定义一个函数然后扔参数是一回事嘛”,但上述操作注重的是“函数的归类”,背后则体现了面向对象编程的思维逻辑。



========================================================================

⚠!!!警告!!!⚠

以下内容仅作教程举例,仅适用于你自己希望闭源 的整合包代码!!!

在其他场合中使用后果自负!!!

笔者不推荐任何交流场合中使用带有混淆性质的自定义ZenMethod!!!

========================================================================

    当然这种操作还有其他用法,其中比较典型的一个便是代码的混淆。部分魔改者对自己的魔改脚本的版权问题比较敏感(笔者就有一点),那使用代码混淆。如果说跨脚本调用对于有经验的魔改者而言各种调用依旧有迹可循的话,那Expansion Method则是在对方无法找到使用$expand$的脚本之前,完全对自定义的ZenMethod无能为力(特别是当scrpits文件夹中被塞了无数个文件夹以对脚本进行归类的前提下)。

    这里简单举个例子,比如笔者在路径为scripts\customedTconstructContents\materials\srparasites\yelloweyeBone(人话:scripts\自定义匠魂内容归类\自定义材料归类\SRP模组归类\露骨的双马尾美少女(划去)的材料,对于每一个文件夹都还有很多其他脚本/文件夹)下加了名为“CustomedRecipes.zs”的文件,然后里边塞以下脚本:

#priority 999
import ............   //不含自定义脚本

$expand IWord$nextDouble() as double{
    return this.random.nextDouble();
}

///其他随机数取用如上处理

$expand IWorld$isServer() as bool{
    return !this.remote;
}

$expand IEntity$isServerExecuted() as bool{
    return this.world.isServer();
}

    并且在你的其他脚本里把所有的随机数取用都改成<worldObj>.nextDouble()的形式、事件remote判定改成isServer()的形式,那对于任何想无脑“借鉴”的“借鉴者”,直接copy原来的代码将会得到无数“找不到方法”的报错,很大程度上能够防止自己的代码被直接剽窃抄袭。

    或者你也可以写一堆名字毫无意义、和执行内容毫无关联的ZenMethod,然后把执行函数本体放进去。

    当然如果别人只是抄你代码的思路那就没办法了(

========================================================================

⚠!!!再次警告!!!⚠

以上内容仅作教程举例,仅适用于你自己希望闭源 的整合包代码!!!

在其他场合中使用后果自负!!!

笔者不推荐任何交流场合中使用带有混淆性质的自定义ZenMethod!!!

========================================================================


    (教程完)