Skip to content

宏列表 第一弹

IMPORTANT

访问JS文件时出现中文乱码,请使用浏览器插件CharSet设置字符编码格式为UTF-8

1. 随机宏

INFO

链接: random.js prize.js 合并下载

  • 基本使用 已测试
  • 嵌套使用 已测试

使用前导入: random.js prize.js

<<prize 选项与其概率>> <</prize>>

随机出的选项可以通过 _result$result 在宏内部使用,注意这两个变量只在宏内部有效。

SugarCube
<<prize `{"厕所": 0.9, "浴室": 0.1}`>>
<<print _result>>
<<print $result>>
<</prize>>

1.1 种子随机

WARNING

请保证Seed版本不低于 0.1.9

如果需要固定随机结果,可引入种子随机 seedrandom.jsseed.js。seed.js不涉及具体的随机逻辑,完全依赖于davidbau的seedrandom。

因此如果不使用本指南提供的随机宏,可以直接使用seedrandom.js,JS存放顺序如下 seedrandom.jsseed.jsrandom.js

引入后在 StoryInit 初始化后既可以正常使用。

js
:: StoryInit
/* 参数一: 行动标签的最大存储数; 参数二: 回滚历史的最大存储数 */
<<initseed 10 20>>


:: 使用示例
/* 在1~100之间添加一个随机数,并在增加行动标签‘吃饭’ */
/* 行动标签可以影响随机结果,可以通过在添加行动标签来影响随机结果 */
/* 不同行动标签的随机数不同 */
<<= seedrandom(1,100, '吃饭')>>
<<= seedrandom(1,100)>>
<<= randomInt(1,10)>>
<<= randomInt(1,10)>>
<<prize `{"厕所": 0.9, "浴室": 0.1}`>>
    <<= _result>>
<</prize>>
/* 由于evelexpr无法添加行动标签,因此可以在执行前手动push行动标签 */
<<run $seed.push('roll', '吃饭', '睡觉')>>
/* 需引入roll点解析 */
<<evelexpr '2d4 + d6'>>
	<<= _rolls>>
	<<= _result>>
<</evelexpr>>
/* 回滚三次(即前三次投掷) */
<<run $seed.rollback(3)>>
<<evelexpr '2d4 + d6'>>
	<<= _rolls>>
	<<= _result>>
<</evelexpr>>

2. 打字机宏

INFO

链接:type-wp.js

  • 基本使用 已测试
  • 嵌套使用 已测试

该宏根据 SugarCubetype 的基础上增加了特定字符设定延迟时间以实现停顿的效果,与点击屏幕空白处跳过的效果。(尝试Pull Request,但方糖开发者不看PR#泪)

如果点击跳过只在文字显示的一小块生效,证明body只有这么大,请尝试设置 body 的宽高,例如 width: 100vw; height: 100vh;。或者使用 Ver.0.1.1 添加的 .type-click-skip class, 在指定的元素上添加该 class,用户点击该包含 .type-click-skip 的元素也会触发点击事件。

<<type-wp 打字速度 pause 停顿时间 symbols 停顿字符 element 元素标签>> <</type-wp>>

SugarCube
<<type-wp 100ms pause 500ms symbols "。" element "span">>
<!-- 测试文本,GPT生成 -->
在一个古老而宁静的小镇上,清晨的阳光透过薄薄的雾气洒在大地上,街道两旁的花坛中盛开着各种各样的花朵,空气中弥漫着淡淡的花香和新鲜泥土的气息。镇上的居民们早早地起床,开始了他们的一天。老李在家门口的木椅上坐下,端起一杯热腾腾的咖啡,享受着这片刻的宁静。他的目光扫过街道,看见孩子们在嬉戏打闹,年轻的情侣们手牵手走过小镇的石板路。镇上的小店也陆续开门营业,店主们忙碌地准备着新一天的生意。
<</type-wp>>

3. 简易NPC

WARNING

链接:random.js macro-utils.js expr-parser.js (可选) filter-utils.js (可选) generate-npc.js 合并下载

可用功能:
  1. 随机生成NPC
TODO:
  1. 根据时间、地点、场景获取当前判断存在的NPC。

  2. 简易NPC的相关宏(包括声明、修改、创建、打印)。

在加入random.jsmacro-utils.jsgenerate-npc.js 后,在全局JavaScript里增加随机需要的数据。

javascript
setup.profiles = {
    first: ['John', 'Jane', 'Alex', 'Emily'],  // 名字列表
    last: ['Smith', 'Doe', 'Johnson', 'Williams'],  // 姓氏列表
    favorite: ['蛋糕', '牛排', '香蕉'], // 喜欢的食物列表
    race: [
            {
                value: '巨人',
                prob: 0.02  // 巨人,用于测试expr
            },
            {
                value: "精灵",
                prob: 0.49, // 概率49%
                affects: {agility: 2}, // 增加2点敏捷
                limits: {agility: {min: 5, max: 10}, strength: {min: 0, max: 5}}  // 敏捷上下限5/10,力量上下限0/5
            },
            {
                value: "侏儒",
                prob: 0.49, // 概率49%
                affects: {strength: -2}, // 减少2点力量
                limits: {agility: {min: 0, max: 8}, strength: {min: -5, max: 5}} // 敏捷上下限0/8,力量上下限-5/5
            }
        ],
    itinerary: [
        'list', // 未支持格式,当前情况做占位符。如果缺少占位符,无法随机object。
        {AM0700: "食堂", AM0800: "商业街"},
        {AM0700: "酒馆", AM0800: "中央广场"}
    ]
};

setup.attrs = {
    strength: {expr: "3d6*5"}, // 骰3个6面骰其结果乘以5,无上下限限制,需要导入expr-parser.js
    intelligence: {
        min: 1,
        max: 10  // 智力上下限1/10
    },
    agility: {
        min: 1,
        max: 10,  // 敏捷上下限1/10
        ranges: [
            {min: 1, max: 3, prob: 0.3}, // 敏捷范围 1-3,概率 30%
            {min: 4, max: 8, prob: 0.6}, // 敏捷范围 4-8,概率 60%
            {min: 9, max: 10, prob: 0.1} // 敏捷范围 9-10,概率 10%
        ]
    }
};

setup.traits = [
    { name: '强壮', affects: { strength: 2 } },  // 特质"强壮"提升力量2点
    { name: '敏捷', affects: { agility: 2 } },   // 特质"敏捷"提升敏捷2点
    { name: '聪明', affects: { intelligence: 2 } },  // 特质"聪明"提升智力2点
    { name: '迟缓', affects: { agility: -2 }, exclusive: ['敏捷'] }  // 降低敏捷2点,且不能与"敏捷"共存
];

setup.defaults = {
    affection: 0 // 好感度,默认0
}

// maxtrait 最大的特质数
setup.config = {
    maxtrait: 3
};

配置好后使用set宏保存生成的NPC。没有设置默认值或最大特质数时,可以不传递config和defaults。

SugarCube
<<set $NPCA = generateNPC(setup.profiles, setup.attrs,setup.traits, setup.config, setup.defaults)>>

实际使用时可以用数组存取NPC,打印时建议使用for循环。

<<pushnpc 存放NPC的数组 个数 默认值(可空) 固定值(可空)>> <</pushnpc>>,内部可使用 _start_end 来获取第一个和最后一个添加的数组下标,需要注意的是如果设置固定值,需要先设置空的默认值列表。

<<filternpc 存放NPC的数组 过滤条件>> <</filternpc>>,内部可使用 _result 获取过滤后的NPC数组。

3.1 过滤条件编写

INFO

可通过 length 限制返回的个数。

如果希望随机返回指定个数,正常filter并使用方糖的randomMany函数 _result.randomMany(返回数),随机后使用临时变量存储,就可以对该数组进行遍历以及其他操作了。

  1. 范围过滤(min, max),指定数字在指定范围内。

    • 属性:力量 (strength)

      • 条件: 在 50 到 100 之间
      • 写法:strength: { min: 50, max: 100 }
    • 属性:敏捷 (agility)

      • 条件: 必须大于或等于 60
      • 写法:agility: { min: 60 }
    • 属性:速度 (speed)

      • 条件: 必须小于或等于 30
      • 写法:speed: { max: 30 }
  2. 包含检查,检查数组是否包含指定值。

    • 特质:

      • 条件: NPC 必须拥有 "brave" 和 "loyal" 特质
      • 写法:traits: ['brave', 'loyal']
    • 标签:

      • 条件: NPC 必须拥有 "家人" 标签
      • 写法:tags: ['家人']
  3. 相等检查,检查值是否相等。

    • 性别 (sex)
      • 条件: 性别必须是 "男"
      • 写法:sex: '男'

3.2 示例代码

SugarCube
<!-- 初始化 -->
<<set $npcs =[]>>

<!-- 增加1个随机NPC -->
<<run $npcs.push(generateNPC(setup.profiles, setup.attrs,setup.traits))>>

<!-- 增加5个随机NPC,起始下标_start,尾部下标_end -->
<<pushnpc $npcs 5>>_start,_end<</pushnpc>>
<!-- 增加5个随机NPC,Defaults为{}, 固定种族为精灵,该格式不支持固定特质 -->
<<pushnpc $npcs 5 `{}` 'race' '精灵'>>_start,_end<</pushnpc>>
<!-- 增加5个随机NPC,默认为家人,固定种族为精灵,固定特质为脆弱 -->
<<pushnpc $npcs 20 `{tags:['家人']}` `{traits: ['脆弱'], race: '精灵'}`>>_start,_end<</pushnpc>>
<!-- 获取符合条件的NPC -->
<!-- 属性 "strength" 在 50 到 100 之间 -->
<!-- 属性 "agility" 必须大于或等于 60 -->
<!-- 属性 "speed" 必须小于或等于 30 -->
<!-- NPC 必须拥有 "brave" 和 "loyal" 特性 -->
<!-- NPC 必须拥有 "家人" 标签 -->
<<filternpc $npcs `{ strength: { min: 50, max: 100 }, agility: { min: 60 }, speed: { max: 30 }, traits: ['brave', 'loyal'], tags: ['家人'] }`>> 
	<<print _result.length>>
<</filternpc>>

<!-- 获取一个tags包含家人的NPC -->
<<filternpc $npcs `{ tags: ['家人'], length: 1}`>> 
	<<print _result.length>>
<</filternpc>>
<!-- 打印 -->
<<for _i, _npc range $npcs>>
_npc.first _npc.last
特征:_npc.traits
<</for>>

4. 配方宏

基于简易库存实现,请先确保使用配方宏前已在全局JavaScript里导入了 simple-inventory

4.1 新增配方

⚠️施工中

4.2 配方查询

⚠️施工中

4.3 制作配方

⚠️施工中

4.4 其他API

⚠️施工中

4.5 使用示例

INFO

点击使用示例右键另存为即可下载示例文件。

全局JavaScript

JavaScript
// 添加配方,后续添加请使用 RecipeBook.insert()
RecipeBook.set("宫保鸡丁", {"鸡胸肉": 2, "花生": 1, "青椒": 1});
RecipeBook.set("鱼香肉丝", {"猪里脊": 1, "胡萝卜": 1, "木耳": 1});
RecipeBook.set("红烧茄子", {"茄子": 1, "蒜": 3, "酱油": 1});
RecipeBook.set("炒面", [
    { "面条": 1, "鸡蛋": 2, "青菜": 1 },
    { "面条": 1, "牛里脊": 1, "洋葱": 1 }
], {servings: 2}, '面');
RecipeBook.set("鸡腿炒饭", { "米": 1, "鸡蛋": 2, "鸡腿": 1 });
RecipeBook.update();

// 自定义输出
RecipeBook.success = '制作成功';
RecipeBook.failure = '制作失败';
RecipeBook.not_found = '配方不存在';
RecipeBook.not_unlocked = '配方未解锁';
RecipeBook.missing_materials = '缺少材料';
RecipeBook.unlocked_success = '配方成功解锁';
RecipeBook.default_no_recipe= '空配方';

// 为材料添加标签
Item.add('鸡胸肉',{},['鸡肉', '肉类']);
Item.add('鸡腿',{},['鸡肉', '肉类']);
Item.add('牛里脊',{}, ['牛肉', '肉类']);
Item.add('猪里脊',{}, ['猪肉', '肉类'])

// 指定自动扣除材料的背包名
RecipeBook.crafting_table= 'inv';

计算/制作

计算传入的材料可制作的菜谱及其份数:
<<calculate `{ "面条": 1, "鸡蛋": 2, "青菜": 1 }`>>
菜谱: _name
可制作份数: _servings 
<</calculate>>

传入材料并制作,制作成功后放入inv背包里:
<<cook `{"茄子": 1, "蒜": 3, "酱油": 1}` $inv>>
制作结果:
<<if _result.success>>
制作成功!
菜品:_result.id
份数: _result.servings
消息:_result.message
<<else>>
制作失败!
消息: _result.message
<</if>>
<</cook>>

<<pickup $inv '茄子' 1 '蒜' 3 '酱油' 1>>

从背包扣除对应材料,制作成功后放入背包:
<<cook '红烧茄子' $inv>>
制作结果:
<<if _result.success>>
制作成功!
菜品:_result.id
份数: _result.servings
消息:_result.message
<<else>>
制作失败!
消息: _result.message
<</if>>
<</cook>>

配方查询

获取配方的制作材料中,包含肉类标签的材料的配方:
<<filter_recipe `{ext: {tags: ['肉类']}}`>>
<<for _i,_item range _result>>
<<= _item.id>>
<</for>>
<</filter_recipe>>

获取配方的制作材料中,包含鸡肉、牛肉标签的材料的配方:
<<filter_recipe `{ext: {tags: ['鸡肉', '牛肉']}}`>>
<<for _i,_item range _result>>
<<= _item.id>>
<</for>>
<</filter_recipe>>

获取所有包含标签为面的配方:
<<filter_recipe `{tags: ['面']}`>>
<<for _i,_item range _result>>
<<= _item.id>>
<</for>>
<</filter_recipe>>

获取所有解锁的配方:
<<filter_recipe `{unlock: true}`>>
<<for _i,_item range _result>>
<<= _item.id>>
<</for>>
<</filter_recipe>>

5. ROLL点表达式解析

<<evelexpr 表达式>> <</evelexpr>>

表达式的计算结果可以通过 _result$result 在宏内部使用,注意这两个变量只在宏内部有效。

SugarCube
<<evelexpr '2d6 + 10 + 1d6'>>
<<print _result>>
<!-- 单骰的结果合集 -->
<<print _rolls>>
<</evelexpr>>

6. 时钟宏

INFO

❗ ❗ ❗ Ver.2.0.0 修复了重大BUG

链接: macro-utils.js clock.js 合并下载

6.1 创建时钟

在StoryInit使用 <<newclock 年 月 日 时 分>> 初始化Clock时钟内,时钟默认的变量名为 $clock 可通过 Clock.varName 设置初始化的时钟名称。

StoryInit
<<newclock 2024 10 1 10 1>>

6.2 打印时间

  1. <<ftime>> 该宏用于显示当前的时间,格式化为可读的字符串。

输出示例: 2024-09-25 14:30:00


  1. <<fhour>> 该宏用于显示当前的小时。

输出示例: 14


  1. <<fminute>> 该宏用于显示当前的分钟。

输出示例: 30


  1. <<fday>> 该宏用于显示当前的日期。

输出示例: 25


  1. <<fmonth>> 该宏用于显示当前的月份。

输出示例: 09


  1. <<fyear>> 该宏用于显示当前的年份。

输出示例: 2024


  1. <<weekday>> 该宏用于显示当前的星期。

可以通过设置在全局JS设置 Clock.weekdays 来覆盖默认的输出。例如: Clock.weekdays=['周天','周一','周二','周三','周四','周五','周六'];

输出示例: 星期三


  1. $clock 获取相关数据
  • $clock.year
  • $clock.month
  • $clock.day
  • $clock.hour
  • $clock.minute

6.3 增加时间

  1. <<addminutes N>> 该宏用于在当前时间基础上增加指定的分钟数 N

    输出示例:
    当前时间为 2024-09-25 14:30:00,执行 <<addminutes 10>> 后的输出为 2024-09-25 14:40:00


  1. <<addhours N>> 该宏用于在当前时间基础上增加指定的小时数 N

    输出示例:
    当前时间为 2024-09-25 14:30:00,执行 <<addhours 5>> 后的输出为 2024-09-25 19:30:00


  1. <<adddays N>> 该宏用于在当前时间基础上增加指定的天数 N

    输出示例:
    当前时间为 2024-09-25 14:30:00,执行 <<adddays 5>> 后的输出为 2024-09-30 14:30:00


  1. <<addmonths N>> 该宏用于在当前时间基础上增加指定的月数 N

    输出示例:
    当前时间为 2024-09-25 14:30:00,执行 <<addmonths 2>> 后的输出为 2024-11-25 14:30:00


  1. <<addyears N>> 该宏用于在当前时间基础上增加指定的年数 N

    输出示例:
    当前时间为 2024-09-25 14:30:00,执行 <<addyears 10>> 后的输出为 2034-09-25 14:30:00

6.4 其他API

  1. $clock.getDaysInMonth(year, month) 该方法用于获取指定年份和月份的天数,未提供年份和月份默认使用当前的年份和月份。

    输出示例:
    对于 getDaysInMonth(2024, 2),返回 29(2024年是闰年)。
    对于 getDaysInMonth(2023, 4),返回 30


  1. $clock.isInRange(rangeStr) 该方法用于判断当前时间是否在指定的时间范围内。rangeStr 是一个字符串,支持以下格式:

    • YYYY-MM-DD HH:MM to YYYY-MM-DD HH:MM
    • YYYY-MM-DD to YYYY-MM-DD
    • YYYY-MM-DD HH:MM to HH:MM
    • HH:MM to HH:MM(默认使用当前日期)

    输出示例:
    当前时间为 2024-09-25 14:30,执行 isInRange("2024-09-25 14:00 to 2024-09-25 15:00") 返回 true
    执行 isInRange("2024-09-25 15:00 to 2024-09-26") 返回 false


  1. Clock.isLeapYear(year) 该方法用于判断指定年份是否为闰年。

    输出示例:
    执行 isLeapYear(2024) 返回 true
    执行 isLeapYear(2023) 返回 false


  1. $clock.weekday 该方法用于获取当前日期是星期几返回值为数字。0 是星期天, 1~6 是周一到周六。

    输出示例:
    当前日期为 2024-09-25,执行 weekday 返回 3


  1. $clock.weekdayChs 该方法用于获取当前日期是星期几,返回值为字符串。

    可以通过设置在全局JS设置 Clock.weekdays 来覆盖默认的输出。例如: Clock.weekdays=['周天','周一','周二','周三','周四','周五','周六'];

    输出示例:
    当前日期为 2024-09-25,执行 weekday 返回 星期三

6.5 普通触发器

如果希望在时间变动后执行某些操作,可以使用Trigger.add(id,condition,action,single) 在全局JavaScript添加触发器。

  • idkey ,是该触发器的唯一键。如果添加相同ID的触发器,会覆盖前一个触发器。
  • condition 是执行条件,需要传递表达式。
  • action 是触发时会执行的代码,同样支持上述变量。
  • single 是布尔值,如果为 true 单次执行后改触发器会被删除。可选项,默认为 false

额外支持的变量与函数:

  • year
  • month
  • day
  • hour
  • minute
  • weekday
  • td 增加的时间,以分钟为单位
  • dd 增加的天数,23:0024:00 也被视为一天
  • isInRange(str) 使用方法参考上文的其他API
  • 支持以 _ ,$ 开头的方糖变量
  • lastUpdate 是缓存更新前的时间信息,对象结构 { year, month, day, hour, minute }

危险操作

Trigger.add 中通过 $.wiki() 使用方糖语法可能出现文本替换导致的异常。因此当大量的使用方糖语法时,请使用 addSugar 添加触发器。

此外,addSugar中请使用 $clock.变量名, 且不支持 tddd 需要通过 $clock.timeDiff$clock.daysDiff 获取。

JavaScript
// 增加的时间大于等于60分钟$index加1
GTrigger.add('增加索引','td >= 60', () => {
    $index += 1;
    $.wiki(`<<goto '特殊事件'>>`)
}, false);

GTrigger.addSugar('增加索引(方糖语法)', 'td >= 60', `
<<set $index += 1>>
`, false);

如果希望在 StoryInit 中添加触发器,请使用 <<run>><<script>> 包裹 Trigger.add

GTrigger.add是全局触发器。Trigger在Clock初始化后可以添加触发器,并且会随着存档文件存储。

JavaScript
// 增加的时间大于等于60分钟$index加1
<<run Trigger.add('增加索引','td >= 60', () => {
    $index += 1;
}, false);>>

6.6 Cron触发器

Cron 表达式由6个字段组成,按顺序分别是:

分钟 小时 日期 月份 星期 年份

取值范围:

  • 分钟:0-59
  • 小时:0-23
  • 日期:1-31
  • 月份:1-12
  • 星期:0-6(0=星期日)
  • 年份:如 2024, 2025 等

特殊取值:

  • *:表示任意值
  • */n:表示每n个单位(步长)
  • a,b,c:表示列表值
  • a-b:表示范围值(如:2024-2025)

6.6.1 基本示例

CTrigger 同样支持 addSugar ,用法请参考上文。

危险操作

非调试请勿在 Cron 触发器中使用 console.log 以及其余代码在控制台输出文本,此操作会导致执行时间显著变慢。

请勿在Cron触发器中使用诸如redo的带有页面渲染、页面重绘效果的方法、函数或宏,此操作可能导致巨量无用的重复渲染从而使浏览器卡顿、崩溃。

6.6.1.1 固定时间
SugarCube
/* 每小时整点执行 */
<<run CTrigger.add('hourly', '0 * * * * *', () => {
    console.log('整点报时');
})>>

/* 每天早上8点执行 */
<<run CTrigger.add('morning', '0 8 * * * *', () => {
    console.log('早上好');
})>>

/* 每天晚上10点执行(仅2024年) */
<<run CTrigger.add('night2024', '0 22 * * * 2024', () => {
    console.log('晚上好');
})>>
6.6.1.2 固定间隔
SugarCube
/* 每5分钟执行一次 */
<<run CTrigger.add('every5min', '*/5 * * * * *', () => {
    console.log('5分钟检查');
})>>

/* 每2小时执行一次 */
<<run CTrigger.add('every2hours', '0 */2 * * * *', () => {
    console.log('2小时检查');
})>>
6.6.1.3 特定时间点
SugarCube
/* 每天早上8点和下午2点执行 */
<<run CTrigger.add('twiceDaily', '0 8,14 * * * *', () => {
    console.log('定时检查');
})>>

/* 每周一早上9点执行(2024-2025年) */
<<run CTrigger.add('weeklyMeeting', '0 9 * * 1 2024-2025', () => {
    console.log('周一早会');
})>>

/* 2024年每月1日零点执行 */
<<run CTrigger.add('monthlyTask', '0 0 1 * * 2024', () => {
    console.log('月初任务');
})>>
6.6.1.4 范围与手动取值
SugarCube
/* 工作日(周一至周五)早上9点和下午6点 */
<<run CTrigger.add('workHours', '0 9,18 * * 1-5 *', () => {
    console.log('工作时间');
})>>

/* 每月1日和15日的中午12点 */
<<run CTrigger.add('biMonthly', '0 12 1,15 * * *', () => {
    console.log('半月检查');
})>>
6.6.1.5 特定年份和季节
SugarCube
/* 2024年夏季(6-8月)每天早上6点 */
<<run CTrigger.add('summer2024', '0 6 * 6-8 * 2024', () => {
    console.log('夏日清晨');
})>>

/* 2024-2025年冬季(12-2月)每天晚上9点 */
<<run CTrigger.add('winter', '0 21 * 12,1,2 * 2024-2025', () => {
    console.log('冬夜');
})>>
6.6.1.6 其他示例
SugarCube
/* 商店每天早上9点开门(2024年) */
<<run CTrigger.add('shopOpen', '0 9 * * * 2024', () => {
    $shopIsOpen = true;
})>>

/* 商店每天晚上9点关门(2024年) */
<<run CTrigger.add('shopClose', '0 21 * * * 2024', () => {
    $shopIsOpen = false;
})>>

/* NPC每3小时改变位置 */
<<run CTrigger.add('npcMove', '0 */3 * * * *', () => {
    // 函数不存在,仅做示范
    $npcLocation = randomLocation();
})>>

/* 夏季(6-8月)白天时段(6:00-18:00)每小时检查中暑风险 */
<<run CTrigger.add('heatstroke', '0 6-18 * 6-8 * *', () => {
    if ($isOutdoor) {
        // 函数不存在,仅做示范
        checkHeatstrokeRisk();
    }
})>>

/* 冬季(12-2月)夜间(18:00-6:00)每小时检查保暖状态 */
<<run CTrigger.add('coldWeather', '0 18-23,0-5 * 12,1,2 * *', () => {
    if ($isOutdoor && !$hasWarmClothes) {
        // 函数不存在,仅做示范
        applyColDamage();
    }
})>>

7. 重载宏

INFO

链接: redo-utils.js

为指定元素添加change事件的监听器,并在元素修改时触发 redo 事件,从而使得页面动态更新。

7.1 使用方式

性别:
男<<radiobutton "$player.sex" "男" autocheck>>
女<<radiobutton "$player.sex" "女" autocheck>>

<br>

<<do tag 'sex'>>
<<if $player.sex === '男'>>
123
<<else>>
456
<</if>>
<</do>>

年龄: 
<<listbox "$player.age" autoselect>>
    <<optionsfrom Array.from({ length: 100 - 1 + 1 }, (_, i) => 1 + i);>>
<</listbox>>

<br>

<<do tag 'age'>>
<<if $player.age >= 18>>
    成年
<<else>>
    未成年
<</if>>
<</do>>

  1. 指定标签重载

    <<redo_radio player.sex "sex">>
    <<redo_list player.age "age">>

    上下操作的实际效果相同,区别在于是否使用传入CSS选择器:

    <<change "[name='radiobutton-playersex']" "sex">>
    <<change "[name='listbox-playerage']" "age">>
  2. 无差别重载

    <!-- 可传入任意数量的参数 -->
    <<redos_radio player.sex>>
    <<redos_list player.age>>

    上下操作的实际效果相同,区别在于是否使用传入CSS选择器:

    <<changes "[name='radiobutton-playersex']" "[name='listbox-playerage']">>

善用F12审查元素可以查看标签的具体信息。例如下图两个单选选项的 name 都为 radiobutton-playersex,因此我们需要获取 name 等于 radiobutton-playersex 的所有元素。 实际写法: [name='radiobutton-playersex']

alt text

8. Lang Marco-多语言支持

INFO

链接: lang.js

模板: 多语言支持_0.2.1

8.1 Lang Passages

创建不同语言的专属片段以储存不同语言文本。片段内的文本以换行区分不同的关键字,同一行以 : 前的文本作为关键字, 同一行以 : 后的文本作为值。

SugarCube
:: zh-cn
页面一文本: 你好世界
页面一选项一: 吃饭

使用 <<lang 'zh-cn'>> 可以切换语言,需要注意的是,在StoryInit中起码得有一个 <<lang>> 否则文本无法显示。

// TODO 正在施工

9. SugarEvent-无用的事件系统

进入片段时, 执行SugarEvent会遍历所有激活的事件并判断事件是否应该执行。

9.1 基本结构

SugarEvent 是最主要的数据类,其中有用的字段有:

  • id: 事件ID
  • name: 事件名称。如果事件名称不存在,则与事件ID相同
  • trigger: 触发片段。仅在当前片段包含在数组中,或者数组为空时触发
  • passage: 满足条件后需要执行的片段名
  • condition: 触发条件,如果不满足条件则不执行
  • count: 最大触发次数
  • prob: 触发概率
  • random: 随机触发的事件列表。从事件列表中按照权重随机一个事件。例如:{"抓小偷": 100, "追捕行动": 20}
  • sequential: 顺序触发的事件列表。按顺序触发数组中的所有事件。["午夜降临", "死亡判定"]
  • content: 执行内容。与passage相似,这里是具体内容
  • alwaysActive: 是否默认激活(想不到合适的变量名,存在一定歧义性)。默认为true。为flase时需要手动添加进激活事件列表。
  • description: 事件描述

9.2 创建事件

SugarEvent.create(id, passage, opts, tags)SugarEvent.child(id, passage, opts, tags) 使用JS创建事件:

javascript
SugarEvent.create("发现果树", "果树事件", {
  	trigger: "森林",
    prob: 0.7, // 0.7的概率触发
    random: {
        "采集+1": 100,
        "采集+2": 50,
        "采集+3": 10
    },
    condition: "$backpack.length < 5"
});

// 子事件,默认不在固定触发列表中
SugarEvent.child("采集+1", "", {
  	content: "<<set $gatherExp += 1>>",
  	count: 100 // 最大触发100次
});

SugarEvent.child("采集+2", "", {
  	content: "<<set $gatherExp += 2>>",
  	count: 30 // 最大触发30次
});

SugarEvent.child("采集+3", "", {
  	content: "<<set $gatherExp += 3>>",
  	count: 15 // 最大触发15次
});

方糖中创建该事件需要放置在 StoryInit 中,亦或者能保证在需要时引入也是可以的。

/* 普通事件 */
<<sugarevent "发现果树" "果树事件" 0.7>>
	<<trigger 森林>>
 	<<condition $backpack.length < 10>>
 	<<count 15>>
    /* 提供数字为随机触发,不提供数字是顺序触发 */
	<<child 采集+1 100 采集+2 20 采集+3 10>> 
<</sugarevent>>
/* 子事件 */
<<sugarchild "采集+1" 1.0>>
	<<set $gatherExp += 1>>
    <<count 5>>
<</sugarchild>>

/* 默认不激活的事件 */
<<sugarevent "追杀" "杀手事件" 0.6>>
	<<trigger 豪宅>>
    /* 只有在添加进事件列表后才会触发 */
    <<unactive>>
<</sugarevent>>
SugarEvent的宏实现
javascript
Macro.add(["sugarchild", "sugarevent"], {
    tags: ["condition", "count", "opts", "description", "child", "tags", "trigger", "unactive"],
    handler() {
        if (this.args.length < 1) {
            return this.error("事件名不能为空");
        }

        const id = this.args[0];
        const [prob, passage] = swap(this.args[1], this.args[2]);
        const opts = {};
        let tags;
        
        opts.prob = prob;
        opts.content = this.payload[0].contents.trim() || null;

        for(let it of this.payload.slice(1)) {
            switch(it.name) {
                case "unactive":
                    opts.alwaysActive = false;
                    break;
                case "description":
                    opts.description = it.contents.trim();
                    break;
                case "count":
                    opts.count = it.args[0] ?? -1;
                    break;
                case "condition":
                    opts.condition = it.arguments;
                    break;
                case "opts":
                    if (typeof it.args[0] === "object") {
                        Object.assign(opts, it.args[0]);
                    }
                    break;
                case "trigger":
                    opts.trigger = it.args;
                    break;
                case "tags":
                    tags = it.args;
                    break;
                case "child":
                    const events = parseEvents(it.args);
                    if (Array.isArray(events)) {
                        opts.sequential = events;
                    } else {
                        opts.random = events;
                    }
                    break;
            }
        }
        
        const method = this.name === "sugarevent" ? "create" : "child";
        SugarEvent[method](id, passage, opts, tags, "replace");
    }
});

9.3 事件触发与事件捕获

事件触发时可以被捕获:

javascript
$(document).on("sugarevent:追杀", ev=> {
	console.log("追杀");
});

passage 和 content 默认不会输出, 除非在需要输出的页面增加一个 <<output>> 宏。如果事件内容固定在头部输出可以直接放在 PassageHeader 里。

PassageHeader