Compare commits
3 Commits
e8ce7b7216
...
13007c8fa4
| Author | SHA1 | Date |
|---|---|---|
|
|
13007c8fa4 | |
|
|
df990d4453 | |
|
|
b737e9f4ea |
|
|
@ -0,0 +1,92 @@
|
||||||
|
# 要求
|
||||||
|
|
||||||
|
编写ttrpg冒险。冒险将要使用./system.md描述的规则运行。
|
||||||
|
|
||||||
|
每个冒险应该包含以下要素:
|
||||||
|
|
||||||
|
- 简报:地理、环境、当前态势。
|
||||||
|
- 后果:如果玩家什么都不做,将会默认发生的后果。
|
||||||
|
- 压力:一些随机遭遇,将在冒险中随机发生。
|
||||||
|
- 主线:一些逻辑上连续的遭遇。
|
||||||
|
- 支线:一些独立的支线遭遇,完成可以获得资源帮助。
|
||||||
|
|
||||||
|
## 简报
|
||||||
|
|
||||||
|
应当确立以下内容:
|
||||||
|
|
||||||
|
- 环境:地理特征,社会风貌,政治态势。
|
||||||
|
- 当前态势:冒险要解决的事件当前的状态。
|
||||||
|
- 势力:1-2种与事件相关的势力。
|
||||||
|
- 玩家角色:玩家的身份,以及初始任务目标。
|
||||||
|
|
||||||
|
设计势力时,应当确立以下内容:
|
||||||
|
|
||||||
|
- 1-2 名代表性NPC。
|
||||||
|
- 势力当前的计划以及障碍。
|
||||||
|
- 势力掌握的资源。
|
||||||
|
|
||||||
|
## 后果
|
||||||
|
|
||||||
|
随着时间的推移,如果玩家未能解决事件,则不好的事情会逐渐发生。
|
||||||
|
|
||||||
|
后果分三个阶段,每个阶段会引入新的规则,为玩家行动制造额外障碍。
|
||||||
|
|
||||||
|
在第三个阶段发生后,玩家行动将会遭受极大挑战,很可能很快失败。
|
||||||
|
|
||||||
|
## 压力
|
||||||
|
|
||||||
|
设计10条随机遭遇,用于玩家冒险中遇到的意外。
|
||||||
|
|
||||||
|
大部分应该为玩家制造危险。随着后果的发生,随机遭遇的危险程度也会随之提升。
|
||||||
|
|
||||||
|
## 主线
|
||||||
|
|
||||||
|
设计6条逻辑连续的遭遇,完成一条遭遇后会揭露下一条遭遇。
|
||||||
|
|
||||||
|
在完成最后一条遭遇后,玩家可以解决事件。
|
||||||
|
|
||||||
|
## 支线
|
||||||
|
|
||||||
|
设计6条逻辑独立的遭遇,每个遭遇会给玩家提供一些可能的资源。
|
||||||
|
|
||||||
|
每个遭遇位于一个独立地点,需要玩家投入时间探索或完成挑战。
|
||||||
|
|
||||||
|
## 地图设计
|
||||||
|
|
||||||
|
描述冒险发生的地理布局及各遭遇点之间的逻辑连接。
|
||||||
|
|
||||||
|
- 区域布局:描述主要地标及其空间关系。
|
||||||
|
- 路径与环境:描述连接地点的路径特征及旅行中的风险。
|
||||||
|
- 地点标注:明确主线遭遇(M1-M6)和支线遭遇(S1-S6)所在的地理位置。
|
||||||
|
|
||||||
|
使用`mermaid`语法绘制地图,并插入冒险文件中。
|
||||||
|
|
||||||
|
## 遭遇的设计
|
||||||
|
|
||||||
|
设计一项遭遇时,如果有NPC出现,则为出现的NPC设计动机、当前计划、当前遇到的障碍,以便玩家交互。
|
||||||
|
|
||||||
|
如果没有NPC,则设计2-4种可以交互的环境元素,尽量覆盖不同的玩家属性以及不同的交互性质。
|
||||||
|
|
||||||
|
每条遭遇都需要明确描述发生的场景地点。尽量设计不同风格的地点。
|
||||||
|
|
||||||
|
## 文本格式
|
||||||
|
|
||||||
|
输出时,使用markdown格式。
|
||||||
|
|
||||||
|
对于遭遇表而言,放在`csv`文件里:
|
||||||
|
|
||||||
|
```csv
|
||||||
|
label,body
|
||||||
|
1,遭遇内容的markdown文本
|
||||||
|
2,遭遇内容的markdown文本
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
在冒险本体的markdown里,使用以下语法引用遭遇表:
|
||||||
|
`:md-table[./encounters.csv]`
|
||||||
|
|
||||||
|
主线、支线、压力等每个类型,需要有独特的csv文件。
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
为每个冒险创建一个新文件夹。冒险本体以`adventure.md`保存。
|
||||||
|
|
@ -1,3 +1,15 @@
|
||||||
# Commander Test
|
# Commander Test
|
||||||
|
|
||||||
:md-commander[./commands.csv]
|
:md-commander[./commands.csv]
|
||||||
|
|
||||||
|
## Lonelog
|
||||||
|
|
||||||
|
`我` 走进酒吧。
|
||||||
|
|
||||||
|
`oracle` 酒吧里人多吗?
|
||||||
|
|
||||||
|
`roll` d20 = 18 `prompt` 是的
|
||||||
|
|
||||||
|
`fact` 酒吧里有很多人。
|
||||||
|
|
||||||
|
:[blah]
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
# 系统
|
||||||
|
|
||||||
|
## 玩家角色
|
||||||
|
|
||||||
|
只有一个玩家角色。
|
||||||
|
|
||||||
|
### 基本属性
|
||||||
|
|
||||||
|
玩家角色具有以下基本属性:
|
||||||
|
|
||||||
|
- 力量
|
||||||
|
- 体质
|
||||||
|
- 敏捷
|
||||||
|
- 感知
|
||||||
|
- 智力
|
||||||
|
- 魅力
|
||||||
|
|
||||||
|
每个基本属性范围1-19,会用于属性检定,掷D20小于等于属性则检定成功。
|
||||||
|
|
||||||
|
属性可能遭受损伤扣减,损伤需要通过休息来缓慢回复。
|
||||||
|
|
||||||
|
一次受到超过当前一半属性损伤时,会遭受严重创伤,该属性即使休息也不会回复损伤。
|
||||||
|
|
||||||
|
必须通过专家、专门地点或道具来解除严重创伤。
|
||||||
|
|
||||||
|
### 耐受
|
||||||
|
|
||||||
|
玩家具有一条耐受度属性,只需几分钟的调整即可完全恢复。
|
||||||
|
|
||||||
|
耐受度随玩家角色成长而提升,初始范围为1-6。
|
||||||
|
|
||||||
|
在玩家l做好准备时,可消耗耐受用于抵抗属性损伤,每点耐受抵抗1点损伤。
|
||||||
|
|
||||||
|
### 时间
|
||||||
|
|
||||||
|
每天有24小时,玩家的每次基本行动花费1小时。
|
||||||
|
|
||||||
|
白天6点到晚上0点属于行动时间:
|
||||||
|
|
||||||
|
- 在条件允许的情况下可以进行休息,每小时随机恢复1点属性损伤。
|
||||||
|
|
||||||
|
晚上0点到凌晨6点属于睡眠时间:
|
||||||
|
|
||||||
|
- 若睡觉休息,每小时随机恢复1点属性损伤。
|
||||||
|
- 若条件不允许,则即使休息也不能回复损伤。
|
||||||
|
- 若不休息,则每小时随机遭受1点属性损伤。
|
||||||
|
|
||||||
|
### 属性骰
|
||||||
|
|
||||||
|
随机回复/扣减属性时,使用D6属性骰来决定随机回复/扣减的属性。
|
||||||
|
|
||||||
|
若掷恢复时,掷出的属性已满,则本次随机恢复没有效果。
|
||||||
|
|
||||||
|
### 基本行动
|
||||||
|
|
||||||
|
除休息外,玩家可进行以下行动,每次花费1小时:
|
||||||
|
|
||||||
|
- 旅行:移动到另一地点。若旅途超过1小时,可能在半路遭遇随机遭遇。
|
||||||
|
- 调查:检查当前地点,揭露更多线索或交互对象。
|
||||||
|
- 交互:根据交互性质而定,可能有不同的检定、难度、机会。
|
||||||
|
|
||||||
|
### 交互行动
|
||||||
|
|
||||||
|
每条交互需要三种属性:
|
||||||
|
|
||||||
|
- 主属性:玩家需要使用什么属性来进行此交互。
|
||||||
|
- 难度:玩家需要积累多少成功进度来成功完成此交互。
|
||||||
|
- 机会:玩家积累多少失败进度后交互失败,且玩家不可再尝试。
|
||||||
|
|
||||||
|
如:`力量3/1`(力量,难度3,机会1)表示玩家使用力量属性检定,3成功进度后交互成功完成,1失败进度后交互失败且不可再尝试。
|
||||||
|
|
||||||
|
每次进行交互时,掷D20检定骰,以及D4进度骰。
|
||||||
|
若检定骰小于等于玩家当前主属性,则获得进度骰数量成功进度,否则获得对应数量失败进度。
|
||||||
|
|
||||||
|
根据交互的挑战性质设计效果:
|
||||||
|
|
||||||
|
- 应变:难度/机会都低,即使失败也能达到成功的效果,仅受到不可耐受的属性损伤。
|
||||||
|
- 挑战:难度高/机会低,失败后根据进度获取收益,且交互可再次尝试。
|
||||||
|
- 努力:难度/机会都高,失败无事发生。
|
||||||
|
- 风险:难度低/机会高,失败有较重大损失。
|
||||||
|
|
||||||
|
检定的难度/机会不会告知玩家。若没有触发成功也没有触发失败,则检定后仅告知当前成功/失败进度。
|
||||||
|
|
||||||
|
### 状态
|
||||||
|
|
||||||
|
玩家状态
|
||||||
|
|
||||||
|
- 属性损伤:属性类型+当前值
|
||||||
|
- 严重创伤:属性类型
|
||||||
|
- 标签:类型+持续事件,用于疾病、祝福、中毒等
|
||||||
|
- 资源:类型+数量,用于物品、金钱等
|
||||||
|
- 成就:类型+等级,用于声望、技能等
|
||||||
|
|
||||||
|
世界状态
|
||||||
|
|
||||||
|
- 进度:类型+id+成功进度+失败进度,用于势力计划、事件进展等。
|
||||||
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
|
|
@ -32,23 +32,25 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@solidjs/router": "^0.15.0",
|
"@solidjs/router": "^0.15.0",
|
||||||
"chokidar": "^5.0.0",
|
"chokidar": "^5.0.0",
|
||||||
"commander": "^12.1.0",
|
"commander": "^14.0.3",
|
||||||
"csv-parse": "^5.5.6",
|
"csv-parse": "^6.1.0",
|
||||||
"js-yaml": "^4.1.1",
|
"js-yaml": "^4.1.1",
|
||||||
"marked": "^14.1.0",
|
"marked": "^17.0.3",
|
||||||
"marked-alert": "^2.1.2",
|
"marked-alert": "^2.1.2",
|
||||||
"marked-directive": "^1.0.7",
|
"marked-directive": "^1.0.7",
|
||||||
|
"marked-gfm-heading-id": "^4.1.3",
|
||||||
|
"mermaid": "^11.0.0",
|
||||||
"solid-element": "^1.9.1",
|
"solid-element": "^1.9.1",
|
||||||
"solid-js": "^1.9.3"
|
"solid-js": "^1.9.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rsbuild/core": "^1.1.8",
|
"@rsbuild/core": "^1.1.8",
|
||||||
"@rsbuild/plugin-babel": "^1.1.0",
|
"@rsbuild/plugin-babel": "^1.1.0",
|
||||||
"@rsbuild/plugin-solid": "^1.0.7",
|
"@rsbuild/plugin-solid": "^1.1.0",
|
||||||
"@tailwindcss/postcss": "^4.2.1",
|
"@tailwindcss/postcss": "^4.2.1",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.19.13",
|
||||||
"tailwindcss": "^4.0.0",
|
"tailwindcss": "^4.0.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.7.2"
|
"typescript": "^5.7.2"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Component, createSignal, createEffect, onCleanup, Show, createResource } from 'solid-js';
|
import { Component, createSignal, createEffect, onCleanup, Show, createResource } from 'solid-js';
|
||||||
import { parseMarkdown } from '../markdown';
|
import { parseMarkdown } from '../markdown';
|
||||||
import { fetchData, extractSection } from '../data-loader';
|
import { fetchData, extractSection } from '../data-loader';
|
||||||
|
import mermaid from 'mermaid';
|
||||||
|
|
||||||
export interface ArticleProps {
|
export interface ArticleProps {
|
||||||
src: string;
|
src: string;
|
||||||
|
|
@ -30,6 +31,8 @@ export const Article: Component<ArticleProps> = (props) => {
|
||||||
const data = content();
|
const data = content();
|
||||||
if (data) {
|
if (data) {
|
||||||
props.onLoaded?.();
|
props.onLoaded?.();
|
||||||
|
// 内容加载完成后,渲染 mermaid 图表
|
||||||
|
void mermaid.run();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,16 @@ export const HeadingNode: Component<{
|
||||||
const handleClick = (e: MouseEvent) => {
|
const handleClick = (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
navigate(href);
|
navigate(href);
|
||||||
|
// 滚动到目标元素,考虑导航栏高度偏移
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const element = document.getElementById(anchor);
|
||||||
|
if (element) {
|
||||||
|
const navBarHeight = 80;
|
||||||
|
const elementPosition = element.getBoundingClientRect().top;
|
||||||
|
const offsetPosition = window.scrollY + elementPosition - navBarHeight;
|
||||||
|
window.scrollTo({ top: offsetPosition, behavior: "smooth" });
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const indent = props.depth * 12;
|
const indent = props.depth * 12;
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,14 @@ import { Marked } from 'marked';
|
||||||
import {createDirectives, presetDirectiveConfigs} from 'marked-directive';
|
import {createDirectives, presetDirectiveConfigs} from 'marked-directive';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import markedAlert from "marked-alert";
|
import markedAlert from "marked-alert";
|
||||||
|
import markedMermaid from "./mermaid";
|
||||||
|
import {gfmHeadingId} from "marked-gfm-heading-id";
|
||||||
|
|
||||||
// 使用 marked-directive 来支持指令语法
|
// 使用 marked-directive 来支持指令语法
|
||||||
const marked = new Marked()
|
const marked = new Marked()
|
||||||
|
.use(gfmHeadingId())
|
||||||
.use(markedAlert())
|
.use(markedAlert())
|
||||||
|
.use(markedMermaid())
|
||||||
.use(createDirectives([
|
.use(createDirectives([
|
||||||
...presetDirectiveConfigs,
|
...presetDirectiveConfigs,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import type {MarkedExtension} from "marked";
|
||||||
|
import mermaid from "mermaid";
|
||||||
|
|
||||||
|
mermaid.initialize({
|
||||||
|
theme: 'dark'
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function markedMermaid(): MarkedExtension {
|
||||||
|
return {
|
||||||
|
renderer: {
|
||||||
|
code: (code) => {
|
||||||
|
// Use default render for other languages
|
||||||
|
if (code.lang !== 'mermaid') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use Mermaid to render the diagram
|
||||||
|
return `<pre class="mermaid">${code.text}</pre>`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue