宏列表 第一弹
IMPORTANT
访问JS文件时出现中文乱码,请使用浏览器插件CharSet设置字符编码格式为UTF-8
。
1. 随机宏
<<prize 选项与其概率>> <</prize>>
随机出的选项可以通过 _result
或 $result
在宏内部使用,注意这两个变量只在宏内部有效。
<<prize `{"厕所": 0.9, "浴室": 0.1}`>>
<<print _result>>
<<print $result>>
<</prize>>
1.1 种子随机
WARNING
请保证Seed版本不低于 0.1.9
如果需要固定随机结果,可引入种子随机 seedrandom.js 与 seed.js。seed.js不涉及具体的随机逻辑,完全依赖于davidbau的seedrandom。
因此如果不使用本指南提供的随机宏,可以直接使用seedrandom.js,JS存放顺序如下 seedrandom.js、seed.js、random.js 。
引入后在 StoryInit
初始化后既可以正常使用。
:: 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. 打字机宏
该宏根据 SugarCube
的 type 的基础上增加了特定字符设定延迟时间以实现停顿的效果,与点击屏幕空白处跳过的效果。(尝试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>>
<<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 合并下载
可用功能:
- 随机生成NPC
TODO:
根据时间、地点、场景获取当前判断存在的NPC。
简易NPC的相关宏(包括声明、修改、创建、打印)。
在加入random.js
、macro-utils.js
、 generate-npc.js
后,在全局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。
<<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(返回数)
,随机后使用临时变量存储,就可以对该数组进行遍历以及其他操作了。
范围过滤(min, max),指定数字在指定范围内。
属性:力量 (strength)
- 条件: 在 50 到 100 之间
- 写法:
strength: { min: 50, max: 100 }
属性:敏捷 (agility)
- 条件: 必须大于或等于 60
- 写法:
agility: { min: 60 }
属性:速度 (speed)
- 条件: 必须小于或等于 30
- 写法:
speed: { max: 30 }
包含检查,检查数组是否包含指定值。
特质:
- 条件: NPC 必须拥有 "brave" 和 "loyal" 特质
- 写法:
traits: ['brave', 'loyal']
标签:
- 条件: NPC 必须拥有 "家人" 标签
- 写法:
tags: ['家人']
相等检查,检查值是否相等。
- 性别 (sex)
- 条件: 性别必须是 "男"
- 写法:
sex: '男'
- 性别 (sex)
3.2 示例代码
<!-- 初始化 -->
<<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。
INFO
4.1 新增配方
⚠️施工中
4.2 配方查询
⚠️施工中
4.3 制作配方
⚠️施工中
4.4 其他API
⚠️施工中
4.5 使用示例
INFO
点击使用示例右键另存为即可下载示例文件。
全局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点表达式解析
INFO
<<evelexpr 表达式>> <</evelexpr>>
表达式的计算结果可以通过 _result
或 $result
在宏内部使用,注意这两个变量只在宏内部有效。
<<evelexpr '2d6 + 10 + 1d6'>>
<<print _result>>
<!-- 单骰的结果合集 -->
<<print _rolls>>
<</evelexpr>>
6. 时钟宏
6.1 创建时钟
在StoryInit使用 <<newclock 年 月 日 时 分>>
初始化Clock时钟内,时钟默认的变量名为 $clock
可通过 Clock.varName
设置初始化的时钟名称。
<<newclock 2024 10 1 10 1>>
6.2 打印时间
<<ftime>>
该宏用于显示当前的时间,格式化为可读的字符串。
输出示例: 2024-09-25 14:30:00
<<fhour>>
该宏用于显示当前的小时。
输出示例: 14
<<fminute>>
该宏用于显示当前的分钟。
输出示例: 30
<<fday>>
该宏用于显示当前的日期。
输出示例: 25
<<fmonth>>
该宏用于显示当前的月份。
输出示例: 09
<<fyear>>
该宏用于显示当前的年份。
输出示例: 2024
<<weekday>>
该宏用于显示当前的星期。
可以通过设置在全局JS设置 Clock.weekdays
来覆盖默认的输出。例如: Clock.weekdays=['周天','周一','周二','周三','周四','周五','周六'];
。
输出示例: 星期三
$clock
获取相关数据
$clock.year
$clock.month
$clock.day
$clock.hour
$clock.minute
6.3 增加时间
<<addminutes N>>
该宏用于在当前时间基础上增加指定的分钟数N
。输出示例:
当前时间为2024-09-25 14:30:00
,执行<<addminutes 10>>
后的输出为2024-09-25 14:40:00
。
<<addhours N>>
该宏用于在当前时间基础上增加指定的小时数N
。输出示例:
当前时间为2024-09-25 14:30:00
,执行<<addhours 5>>
后的输出为2024-09-25 19:30:00
。
<<adddays N>>
该宏用于在当前时间基础上增加指定的天数N
。输出示例:
当前时间为2024-09-25 14:30:00
,执行<<adddays 5>>
后的输出为2024-09-30 14:30:00
。
<<addmonths N>>
该宏用于在当前时间基础上增加指定的月数N
。输出示例:
当前时间为2024-09-25 14:30:00
,执行<<addmonths 2>>
后的输出为2024-11-25 14:30:00
。
<<addyears N>>
该宏用于在当前时间基础上增加指定的年数N
。输出示例:
当前时间为2024-09-25 14:30:00
,执行<<addyears 10>>
后的输出为2034-09-25 14:30:00
。
6.4 其他API
$clock.getDaysInMonth(year, month)
该方法用于获取指定年份和月份的天数,未提供年份和月份默认使用当前的年份和月份。输出示例:
对于getDaysInMonth(2024, 2)
,返回29
(2024年是闰年)。
对于getDaysInMonth(2023, 4)
,返回30
。
$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
。
Clock.isLeapYear(year)
该方法用于判断指定年份是否为闰年。输出示例:
执行isLeapYear(2024)
返回true
。
执行isLeapYear(2023)
返回false
。
$clock.weekday
该方法用于获取当前日期是星期几返回值为数字。0
是星期天,1~6
是周一到周六。输出示例:
当前日期为2024-09-25
,执行weekday
返回3
。
$clock.weekdayChs
该方法用于获取当前日期是星期几,返回值为字符串。可以通过设置在全局JS设置
Clock.weekdays
来覆盖默认的输出。例如:Clock.weekdays=['周天','周一','周二','周三','周四','周五','周六'];
。输出示例:
当前日期为2024-09-25
,执行weekday
返回星期三
。
6.5 普通触发器
如果希望在时间变动后执行某些操作,可以使用Trigger.add(id,condition,action,single)
在全局JavaScript添加触发器。
id
是key
,是该触发器的唯一键。如果添加相同ID的触发器,会覆盖前一个触发器。condition
是执行条件,需要传递表达式。action
是触发时会执行的代码,同样支持上述变量。single
是布尔值,如果为true
单次执行后改触发器会被删除。可选项,默认为false
。
额外支持的变量与函数:
year
年month
月day
日hour
时minute
分weekday
周td
增加的时间,以分钟为单位dd
增加的天数,23:00
到24:00
也被视为一天isInRange(str)
使用方法参考上文的其他API- 支持以
_
,$
开头的方糖变量 lastUpdate
是缓存更新前的时间信息,对象结构{ year, month, day, hour, minute }
危险操作
Trigger.add
中通过 $.wiki()
使用方糖语法可能出现文本替换导致的异常。因此当大量的使用方糖语法时,请使用 addSugar
添加触发器。
此外,addSugar
中请使用 $clock.变量名
, 且不支持 td
与 dd
需要通过 $clock.timeDiff
与 $clock.daysDiff
获取。
// 增加的时间大于等于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初始化后可以添加触发器,并且会随着存档文件存储。
// 增加的时间大于等于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 固定时间
/* 每小时整点执行 */
<<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 固定间隔
/* 每5分钟执行一次 */
<<run CTrigger.add('every5min', '*/5 * * * * *', () => {
console.log('5分钟检查');
})>>
/* 每2小时执行一次 */
<<run CTrigger.add('every2hours', '0 */2 * * * *', () => {
console.log('2小时检查');
})>>
6.6.1.3 特定时间点
/* 每天早上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 范围与手动取值
/* 工作日(周一至周五)早上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 特定年份和季节
/* 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 其他示例
/* 商店每天早上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>>
指定标签重载
<<redo_radio player.sex "sex">> <<redo_list player.age "age">>
上下操作的实际效果相同,区别在于是否使用传入CSS选择器:
<<change "[name='radiobutton-playersex']" "sex">> <<change "[name='listbox-playerage']" "age">>
无差别重载
<!-- 可传入任意数量的参数 --> <<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']
。
8. Lang Marco-多语言支持
8.1 Lang Passages
创建不同语言的专属片段以储存不同语言文本。片段内的文本以换行区分不同的关键字,同一行以 :
前的文本作为关键字, 同一行以 :
后的文本作为值。
:: 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创建事件:
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的宏实现
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 事件触发与事件捕获
事件触发时可以被捕获:
$(document).on("sugarevent:追杀", ev=> {
console.log("追杀");
});
passage 和 content 默认不会输出, 除非在需要输出的页面增加一个 <<output>>
宏。如果事件内容固定在头部输出可以直接放在 PassageHeader
里。