# 自定义符卡

本教程适用于最新版 Touhou Little Maid 模组。

# 1. 需要的条件

  • 基本的数据结构和计算机编程知识;
  • 基本的 JavaScript 知识,熟悉简单的 JavaScript 语法;
  • 高中及其以上的数学知识,熟悉各种基础函数的表达式;
  • 必须的文本编辑工具,推荐 VSCodeAtom 等工具;

# 2. 如何开始

  1. 游戏启动后,会在游戏主目录下的config\touhou_little_maid\custom_spell_card文件夹下生成默认的符卡文件;
  2. 在此文件夹内创建新的文件,文件需要为 JavaScript 脚本文件,并按照后文介绍的格式书写;
  3. 在游戏内使用 /spell reload 指令重载所有的符卡脚本;
  4. 打开本模组的符卡标签页找到所书写的符卡。

# 3. 详细介绍

# 1. 创建文件

新建一个后缀名为 .js 的文件即可,其他部分无硬性要求,但为了规范统一,建议采用如下格式命名:

  • 先找到想要还原符卡的英文名

    通过 https://en.touhouwiki.net/wiki/List_of_Spell_Cards 可获取官方游戏中所有符卡的英文名

  • 此处我们用 秋符「オータムスカイ」举例,其英文名为 Autumn Sign "Autumn Sky

  • 脚本命名就推荐书写为 autumn_sign.autumn_sky.js

# 2. 书写脚本

脚本中部分内容属于模式化的,可以直接复制粘贴进行使用,此处给出模板:

// 延迟执行逻辑的工具类
var Task = Java.type("com.github.tartaricacid.touhoulittlemaid.util.DelayedTask");
// 弹幕颜色枚举类
var Color = Java.type("com.github.tartaricacid.touhoulittlemaid.danmaku.DanmakuColor");
// 弹幕类型枚举类
var Type = Java.type("com.github.tartaricacid.touhoulittlemaid.danmaku.DanmakuType");
// 弹幕类
var Danmaku = Java.type("com.github.tartaricacid.touhoulittlemaid.danmaku.script.EntityDanmakuWrapper");
// 三元双精度浮点向量类
var Vec3d = Java.type("com.github.tartaricacid.touhoulittlemaid.danmaku.script.Vec3dWrapper");

Java.asJSONCompatible({
    // 符卡的 id,字符串,必需参数,不允许和其他符卡重名
    // 推荐格式:资源域:某某符.符卡名,全部采用符卡英文名小写下划线格式书写
    id: "demo:autumn_sign.autumn_sky",
    // 非必须参数
    // 如果没有此字段,则会自动依据符卡 id 生成语言文件的 Key
    nameKey: "spell_card.demo:autumn_sign.autumn_sky.name"
    // 非必须参数
    // 可以通过此 key 书写语言文件,进而提供符卡的描述性文字
    descriptionKey: "spell_card.demo:autumn_sign.autumn_sky.desc"
    // 非必须参数
    // 作者名称,字符串
    author: "tartaric_acid",
    // 非必须参数
    // 版本,字符串
    version: "1.0.0",
    // 非必须参数
    // 使用符卡后的冷却时间,整型数
    cooldown: 250,
    /**
     * 执行的符卡逻辑,函数签名固定,会直接调用
     * @param world 当前所处的世界
     * @param entity 释放符卡的实体
     */
    spellCard: function (world, entity) {
        // 此处书写释放符卡后的具体逻辑,后面会细讲
    }
});

作为示例,我们先写一个简单的朝视线方向发射的单发弹幕,这里仅给出符卡逻辑部分代码:

spellCard: function (world, entity) {
    // 构建一个弹幕实例
    // 其参数分别为:世界,发射它的实体,弹幕伤害值,弹幕重力值,弹幕类型,弹幕颜色
    var danmaku = new Danmaku(world, entity, 2.0, 0.0, Type.PETAL, Color.MAGENTA);
    // 弹幕的发射路径
    // 其参数分别为:发射它的实体,发射的 Pitch(俯仰),发射的 Yaw(偏航)
    // Pitch(俯仰)的偏移值,弹幕运动速度,弹幕不准确度
    danmaku.shoot(entity, 0, entity.getYaw(), 0, 0.4, 0);
    // 生成该弹幕
    world.spawnDanmaku(danmaku);
}

书写好此代码后,游戏内运行重载符卡指令,即可在创造模式标签页找到该符卡。

现在我们稍微改下此符卡,想让其每间隔半秒发射一发单发弹幕,总共发射 10 次。此时我们需要用到 Task 类,在原来单发弹幕基础上进行些许修改,即可实现此功能:

spellCard: function (world, entity) {
    // for 循环 10 次
    for (var i = 0; i < 10; i++) {
        // Task.add() 方法,有两个参数
        // 第一个参数:一个无参函数,这里书写的是具体上个案例中弹幕弹幕的逻辑
        // 第二个参数:延迟时间,这里写 i * 10 代表每 10 tick,也就是半秒执行一次该逻辑
        Task.add(function () {
            var danmaku = new Danmaku(world, entity, 2.0, 0.0, Type.PETAL, Color.MAGENTA);
            danmaku.shoot(entity, 0, entity.getYaw(), 0, 0.4, 0);
            world.spawnDanmaku(danmaku);
        }, i * 10);
    }
}

# 3. 方法列表

这里给出目前可用的所有方法的文档:

# Danmaku

弹幕本体,使用时需进行实例化,调用 World.spawnDanmaku(Danmaku danmaku) 方法进行实际的生成。

  • 构造方法:

    Danmaku(World world, EntityLivingBase thrower, Type type, Color color)

    参数名 类型 说明
    world World 弹幕实体所处的世界
    thrower EntityLivingBase 弹幕的发射者
    type Type 弹幕类型,枚举类
    color Color 弹幕颜色,枚举类

    Danmaku(World world, EntityLivingBase thrower,float damage, float gravity, Type type, Color color)

    参数名 类型 说明
    world World 弹幕实体所处的世界
    thrower EntityLivingBase 弹幕的发射者
    damage float 弹幕的伤害值
    gravity float 弹幕的重力值,设置为 0,弹幕不受重力影响
    type Type 弹幕类型,枚举类
    color Color 弹幕颜色,枚举类
  • 实例方法

    void shoot(EntityLivingBase thrower, float pitch, float yaw, float pitchOffset, float velocity, float inaccuracy):弹幕发射方向的设置,无返回值

参数名 类型 说明
thrower EntityLivingBase 弹幕的发射者
pitch float 弹幕的俯仰,单位为度
yaw float 弹幕的偏航,单位为度
pitchOffset float 弹幕的俯仰偏移量,最终弹幕的俯仰会按照 pitch + pitchOffset 角度进行运动
velocity float 弹幕运动速度
inaccuracy float 弹幕的不准确度,弹幕将会按照设置的方向,以高斯随机变量进行偏移,可用于设计一些泛自机狙弹幕

什么是俯仰(pitch)和偏航(yaw)?

来自 https://en.wikipedia.org/wiki/Aircraft_principal_axes 词条配图

001.png

void shoot(double x, double y, double z, float velocity, float inaccuracy):弹幕发射方向的设置,无返回值

参数名 类型 说明
x y z double 弹幕射向的 x y z 坐标
velocity float 弹幕运动速度
inaccuracy float 弹幕的不准确度,弹幕将会按照设置的方向,以高斯随机变量进行偏移,可用于设计一些泛自机狙弹幕

void setType(Type type):设置弹幕类型,无返回值

参数名 类型 说明
type Type 弹幕类型,枚举类

void setColor(Color color):设置弹幕颜色,无返回值

参数名 类型 说明
color Color 弹幕颜色,枚举类

void setTicksExisted(int ticksExisted):设置弹幕存活时间,不设置时默认为 200 tick,无返回值

参数名 类型 说明
ticksExisted int 弹幕存活时间

void setPosition(Vec3d vec3d):设置弹幕的位置,无返回值

参数名 类型 说明
vec3d Vec3d 弹幕坐标

void setDamagesTerrain(boolean canDamages):设置魔炮弹幕是否会破坏地形,该参数仅对魔炮弹幕有效,无返回值

参数名 类型 说明
canDamages boolean 默认为 false,设置为 true,会造成地形破坏和卡顿,谨慎使用

# Type枚举类

弹幕的类型枚举

枚举名 说明 备注
PELLET 点弹
BALL 小玉
ORBS 环玉
BIG_BALL 中玉
BUBBLE 大玉
HEART 心弹
JELLYBEAN 椭弹
AMULET 札弹
STAR 星弹
BIG_STAR 大星弹
PETAL 米弹
KNIFE 刀弹
MASTER_SPARK 魔炮 会不停的释放爆炸效果
BULLET 铳弹
KUNAI 苦无弹
RAINDROP 滴弹
ARROWHEAD 鳞弹

# Color枚举类

弹幕的颜色枚举

枚举名 说明
RED 红色
ORANGE 橙色
YELLOW 黄色
LIME 黄绿色
LIGHT_GREEN 淡绿色
GREEN 绿色
CYAN 青色
LIGHT_BLUE 淡蓝色
BLUE 蓝色
PURPLE 紫色
MAGENTA 品红色
PINK 粉色
GRAY 灰色

# World

spellCard: function (world, entity) 里面传入的 world 变量所属的类型

void spawnDanmaku(Danmaku danmaku):在世界上生成传入的弹幕实例,无返回值

参数名 类型 说明
danmaku Danmaku 想要生成的弹幕实例

# EntityLivingBase

spellCard: function (world, entity) 里面传入的 entity 变量所属的类型

float getYaw():获取该实体的偏航,单位为度,返回值为浮点数;

float getPitch():获取该实体的俯仰,单位为度,返回值为浮点数;

Vec3d getPos():获取该实体的坐标,返回值为 Vec3d 类;

# Task

一个用于延迟执行逻辑的类

void add(Function function, int delayedTick):依据设定的 tick 延时执行传入的函数式,无返回值

参数名 类型 说明
function Function 无参函数式,函数式内仅能调用 worldentity 变量,或者创建内部变量,其他外部变量无法调用
delayedTick int 延迟的 tick 数

因为目前所用的 JavaScript 脚本解析器对新版本语法还存在一定的支持问题,function 变量可使用匿名函数来书写,其实例如下:

Task.add(function () {
    // 具体的弹幕逻辑
}, delayedTick) // 变量 delayedTick 应为你想要设定的延迟时间

void add(Function<Integer> function, int delayedTick,int times):依据设定的 tick 延时执行传入的函数式,可以从外部额外传入一个 int 数据用于内部的函数式逻辑实现,无返回值

参数名 类型 说明
function Function<Integer> int 参数函数式,函数式内仅能调用 worldentitytimes 变量,或者创建内部变量,其他外部变量无法调用
delayedTick int 延迟的 tick 数
times int 外部传入的 int 数据,主要用于 for 循环传递次数

该方法实例如下:

Task.add(function (times) {
    // 具体的弹幕逻辑
    // 此时可额外使用 times 变量,该变量由下面的 times 参数传入
}, delayedTick, times) // 变量 delayedTick 应为你想要设定的延迟时间

# Vec3d

一个三元的浮点型向量类,主要用于一些简单的向量变换

  • 构造方法

    Vec3d(double x, double y, double z):依据传入的 x y z 构建向量

  • 实例方法

    double getX():获取该向量的 x 坐标,返回值为双精度浮点值

    double getY():获取该向量的 y 坐标,返回值为双精度浮点值

    double getZ():获取该向量的 z 坐标,返回值为双精度浮点值

  • 静态方法

    Vec3d getRotationVector(double x, double y, double z, float yaw, double yOffset, EntityLivingBase entity):把以实体为原点的相对向量(x,y,z) 转换成绝对向量

参数名 类型 说明
x y z double x y z 构建的相对向量
yaw float 向量的偏航,单位为度
yOffset double 该向量在 y 方向的偏移值
entity EntityLivingBase 偏转时参考的实体对象,最后向量的构建会参考该实体的朝向和坐标,从而构建成一个绝对向量