docs: add design document workspace
This commit is contained in:
parent
19cf0ae820
commit
792081cb82
14
DOC/README.md
Normal file
14
DOC/README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# DOC
|
||||
|
||||
`DOC` stores project operation data and documents that are consumed by the local dashboard.
|
||||
|
||||
Keep these data files here unless `Tools/Dashboard/serve.py` is updated at the same time:
|
||||
|
||||
- `bugs.json`
|
||||
- `devplan.json`
|
||||
- `marketing/*.json`
|
||||
- `sns/*.json`
|
||||
|
||||
Marketing drafts, Steam announcement copies, public-opinion incident notes, and dashboard-backed task data also belong here.
|
||||
|
||||
Gameplay design documents should live under `Design/gameplay/`. Architecture and engineering documents should live under `MD/`.
|
||||
13
Design/README.md
Normal file
13
Design/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Design
|
||||
|
||||
`Design` is the source area for TH1 design documents. Prefer HTML for durable design documents so they can be opened directly and embedded in the Dashboard.
|
||||
|
||||
## Structure
|
||||
|
||||
- `final/mechanics/`: completed or accepted game-mechanic designs.
|
||||
- `final/narrative/`: completed or accepted worldbuilding, story, copywriting, and flavor text designs.
|
||||
- `drafts/`: work in progress, discussion notes, temporary records, and planning material.
|
||||
- `art/`: art direction and asset planning.
|
||||
- `audio/`: music and sound direction.
|
||||
|
||||
Dashboard-backed operational data belongs in `DOC/`. Engineering architecture belongs in `MD/`.
|
||||
37
Design/final/mechanics/core-loop.html
Normal file
37
Design/final/mechanics/core-loop.html
Normal file
@ -0,0 +1,37 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 核心循环</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell narrow">
|
||||
<header class="doc-hero compact">
|
||||
<p class="eyebrow">Mechanics / Core</p>
|
||||
<h1>核心循环</h1>
|
||||
<p class="lead">核心循环必须同时服务玩家、AI、联机和回放。任何能改变战局的设计,最终都要落到可验证的行动、数据变更和表现反馈。</p>
|
||||
</header>
|
||||
<section class="doc-section">
|
||||
<h2>回合结构</h2>
|
||||
<ol class="rule-list">
|
||||
<li><strong>回合开始:</strong>刷新城市产出、技能状态、可行动单位和回合事件。</li>
|
||||
<li><strong>玩家决策:</strong>移动、攻击、训练、建造、研发、外交、城市升级。</li>
|
||||
<li><strong>行动执行:</strong>构造参数、校验合法性、进入 ActionLogic 执行并广播事件。</li>
|
||||
<li><strong>表现播放:</strong>动画、特效、UI 和 Renderer 根据事件与 Presentation 队列更新。</li>
|
||||
<li><strong>回合结束:</strong>处理持续状态、AI 队列、胜利条件、存档/上传等外围逻辑。</li>
|
||||
</ol>
|
||||
</section>
|
||||
<section class="doc-section">
|
||||
<h2>设计约束</h2>
|
||||
<div class="callout-grid">
|
||||
<article><h3>规则要短</h3><p>每个行动最好能用一句话解释,同时允许技能层叠形成深度。</p></article>
|
||||
<article><h3>状态要可见</h3><p>恐惧、满月、金刚身、粗糙身等状态必须在 UI、伤害预览和战斗表现中一致。</p></article>
|
||||
<article><h3>行为要同步</h3><p>联机、AI 和玩家输入都不应绕过共享 Action 层。</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="doc-footer"><a href="index.html">返回机制总览</a></footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
36
Design/final/mechanics/design-principles.html
Normal file
36
Design/final/mechanics/design-principles.html
Normal file
@ -0,0 +1,36 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 设计原则</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell principles-shell">
|
||||
<section class="principles-page">
|
||||
<header class="principles-header">
|
||||
<p class="eyebrow">Design Principles</p>
|
||||
<h1>产品原则</h1>
|
||||
</header>
|
||||
|
||||
<ol class="principles-list">
|
||||
<li>
|
||||
<span class="principle-index">01</span>
|
||||
<strong>东方 Project 角色展示优先</strong>
|
||||
</li>
|
||||
<li>
|
||||
<span class="principle-index">02</span>
|
||||
<strong>保守设计,以 Polytopia 为基本原则,小步迭代,不在一次设计内做大幅度创新</strong>
|
||||
</li>
|
||||
<li>
|
||||
<span class="principle-index">03</span>
|
||||
<strong>极简,任意时刻让玩家保持 3 个及以下的决策选项</strong>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<footer class="doc-footer"><a href="index.html">返回设计树</a></footer>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
33
Design/final/mechanics/faction-system.html
Normal file
33
Design/final/mechanics/faction-system.html
Normal file
@ -0,0 +1,33 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 阵营与文明系统</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell narrow">
|
||||
<header class="doc-hero compact">
|
||||
<p class="eyebrow">Mechanics / Factions</p>
|
||||
<h1>阵营与文明系统</h1>
|
||||
<p class="lead">TH1 的文明基底继承 Polytopia 的地图、城市和科技结构;势力层负责东方主题、英雄名单、特色单位和专属机制。</p>
|
||||
</header>
|
||||
<section class="doc-section">
|
||||
<h2>两层模型</h2>
|
||||
<table class="doc-table">
|
||||
<thead><tr><th>层级</th><th>负责内容</th><th>设计风险</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>文明 Civ</td><td>城市名、基础资源倾向、起始科技、地图适应性。</td><td>如果差异过大,会破坏 Polytopia 式简洁。</td></tr>
|
||||
<tr><td>势力 Force</td><td>东方角色、阵营主题、英雄、特色单位、专属建筑和技能。</td><td>如果技能过多,会造成不可读和难平衡。</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<section class="doc-section">
|
||||
<h2>当前规模</h2>
|
||||
<p>Dashboard 摘要导出显示当前包含 17 个玩家阵营、20 名英雄、55 项科技。第一版设计框架先记录系统关系,后续再逐个阵营补全完整设计页。</p>
|
||||
</section>
|
||||
<footer class="doc-footer"><a href="index.html">返回机制总览</a></footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
183
Design/final/mechanics/hero-foundation.html
Normal file
183
Design/final/mechanics/hero-foundation.html
Normal file
@ -0,0 +1,183 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 英雄基础定位</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell hero-foundation-shell">
|
||||
<header class="doc-hero compact">
|
||||
<p class="eyebrow">Game Mechanics / Foundation / Heroes</p>
|
||||
<h1>英雄</h1>
|
||||
</header>
|
||||
|
||||
<section class="doc-section">
|
||||
<h2>1. 基础定位:职阶设计</h2>
|
||||
<div class="class-grid">
|
||||
<article class="class-card class-xiang">
|
||||
<div class="class-rank">相</div>
|
||||
<h3>探索型</h3>
|
||||
<p>开局任务:探索。核心收益在探索信息和机动展开,适合无防主发展。</p>
|
||||
</article>
|
||||
<article class="class-card class-hou">
|
||||
<div class="class-rank">后</div>
|
||||
<h3>防守探索型</h3>
|
||||
<p>开局任务:探索。兼具探索与防御,能支撑无防或有防主发展。</p>
|
||||
</article>
|
||||
<article class="class-card class-wang">
|
||||
<div class="class-rank">王</div>
|
||||
<h3>发展防御型</h3>
|
||||
<p>开局任务:特色。发展能力高,适合有防主发展或防御型主战争。</p>
|
||||
</article>
|
||||
<article class="class-card class-che">
|
||||
<div class="class-rank">车</div>
|
||||
<h3>正面战争型</h3>
|
||||
<p>开局任务:伤害。防御和进攻都强,适合主战争策略。</p>
|
||||
</article>
|
||||
<article class="class-card class-ma">
|
||||
<div class="class-rank">马</div>
|
||||
<h3>扩张突击型</h3>
|
||||
<p>开局任务:扩张。进攻与移动突出,适合主动扩张和压迫。</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc-section">
|
||||
<h2>职阶定位矩阵</h2>
|
||||
<div class="table-wrap">
|
||||
<table class="doc-table role-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2">职阶</th>
|
||||
<th colspan="4">开局整体表现收益</th>
|
||||
<th colspan="4">开局策略</th>
|
||||
<th rowspan="2">开局任务</th>
|
||||
<th colspan="5">开局属性</th>
|
||||
<th colspan="5">满级属性</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>发展</th><th>探索</th><th>防御</th><th>进攻</th>
|
||||
<th>主发展<br>无防</th><th>主发展<br>有防</th><th>主战争<br>防御</th><th>主战争<br>进攻</th>
|
||||
<th>生命</th><th>防御</th><th>攻击</th><th>射程</th><th>移动</th>
|
||||
<th>生命</th><th>防御</th><th>攻击</th><th>射程</th><th>移动</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>相</strong></td>
|
||||
<td>C</td><td class="grade-s">S</td><td>C</td><td>C</td>
|
||||
<td class="check">√</td><td></td><td></td><td></td>
|
||||
<td>探索</td>
|
||||
<td>A(15)</td><td>B(2)</td><td>C(1)</td><td>C(1)</td><td>A(2)</td>
|
||||
<td>A(45)</td><td>B(3)</td><td>B(3)</td><td>A(2)</td><td>B(2)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>后</strong></td>
|
||||
<td>C</td><td>A</td><td>A</td><td>C</td>
|
||||
<td class="check">√</td><td class="check">√</td><td></td><td></td>
|
||||
<td>探索</td>
|
||||
<td>C(10)</td><td class="grade-s">S(3)</td><td>B(2)</td><td>A(2)</td><td>B(1.5)</td>
|
||||
<td>C(30)</td><td>B(3)</td><td>B(3)</td><td class="grade-s">S(3)</td><td>B(2)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>王</strong></td>
|
||||
<td>A</td><td>C</td><td>B</td><td>B</td>
|
||||
<td></td><td class="check">√</td><td class="check">√</td><td></td>
|
||||
<td>特色</td>
|
||||
<td class="grade-s">S(20)</td><td>B(2)</td><td>B(2)</td><td>C(1)</td><td>C(1)</td>
|
||||
<td>B(35)</td><td class="grade-s">S(4)</td><td>B(3)</td><td>C(1)</td><td>C(1)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>车</strong></td>
|
||||
<td>C</td><td>C</td><td class="grade-s">S</td><td class="grade-s">S</td>
|
||||
<td></td><td></td><td class="check">√</td><td class="check">√</td>
|
||||
<td>伤害</td>
|
||||
<td class="grade-s">S(20)</td><td class="grade-s">S(3)</td><td class="grade-s">S(3)</td><td>C(1)</td><td>C(1)</td>
|
||||
<td class="grade-s">S(50)</td><td class="grade-s">S(4)</td><td class="grade-s">S(5)</td><td>C(1)</td><td>C(1)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>马</strong></td>
|
||||
<td>C</td><td>C</td><td>C</td><td>A</td>
|
||||
<td></td><td></td><td></td><td class="check">√</td>
|
||||
<td>扩张</td>
|
||||
<td>C(10)</td><td>C(1)</td><td class="grade-s">S(3)</td><td>C(1)</td><td class="grade-s">S(2.5)</td>
|
||||
<td>C(30)</td><td>B(3)</td><td>A(4)</td><td>C(1)</td><td class="grade-s">S(3)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="table-note">√ 表示该职阶能满足对应开局策略,并能顺势升级。</p>
|
||||
</section>
|
||||
|
||||
<section class="doc-section">
|
||||
<h2>五阵营英雄职阶配置</h2>
|
||||
<div class="table-wrap">
|
||||
<table class="doc-table faction-class-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>职阶</th>
|
||||
<th>基础定位</th>
|
||||
<th>红魔馆</th>
|
||||
<th>永远亭</th>
|
||||
<th>守矢神社</th>
|
||||
<th>地灵殿</th>
|
||||
<th>博丽神社</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>王</strong></td>
|
||||
<td>发展防御型,承担阵营特色任务。</td>
|
||||
<td>蕾米莉亚</td>
|
||||
<td>辉夜</td>
|
||||
<td>神奈子</td>
|
||||
<td>觉</td>
|
||||
<td class="muted-cell">待补</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>后</strong></td>
|
||||
<td>防守探索型,兼顾安全开图和阵地保护。</td>
|
||||
<td>帕秋莉</td>
|
||||
<td>永琳</td>
|
||||
<td>诹访子</td>
|
||||
<td>恋</td>
|
||||
<td class="muted-cell">待补</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>相</strong></td>
|
||||
<td>探索型,承担开图、信息和机动收益。</td>
|
||||
<td>咲夜</td>
|
||||
<td>因幡帝</td>
|
||||
<td>早苗</td>
|
||||
<td>燐</td>
|
||||
<td class="muted-cell">待补</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>马</strong></td>
|
||||
<td>扩张突击型,负责压迫、追击和地图推进。</td>
|
||||
<td>芙兰朵露</td>
|
||||
<td>铃仙</td>
|
||||
<td>射命丸文</td>
|
||||
<td>空</td>
|
||||
<td class="muted-cell">待补</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>车</strong></td>
|
||||
<td>正面战争型,承担伤害、防线和硬碰硬作战。</td>
|
||||
<td>美铃</td>
|
||||
<td>妹红</td>
|
||||
<td>犬走椛</td>
|
||||
<td>勇仪</td>
|
||||
<td class="muted-cell">待补</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="doc-footer"><a href="index.html">返回设计树</a></footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
35
Design/final/mechanics/hero-system.html
Normal file
35
Design/final/mechanics/hero-system.html
Normal file
@ -0,0 +1,35 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 东方英雄系统</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell narrow">
|
||||
<header class="doc-hero compact">
|
||||
<p class="eyebrow">Mechanics / Heroes</p>
|
||||
<h1>东方英雄系统</h1>
|
||||
<p class="lead">英雄是 TH1 相对 Polytopia 最大的机制扩展:它把东方角色设定转成高价值单位、技能触发器和阵营节奏。</p>
|
||||
</header>
|
||||
<section class="doc-section">
|
||||
<h2>英雄的三层职责</h2>
|
||||
<div class="flow-grid three">
|
||||
<article><h3>战术核心</h3><p>英雄通常具备更高属性、独特移动/攻击方式或强触发技能,是一局游戏中的局部战场焦点。</p></article>
|
||||
<article><h3>阵营识别</h3><p>每个势力通过英雄组合表达主题,例如红魔馆、永远亭、守矢、地灵殿等。</p></article>
|
||||
<article><h3>成长目标</h3><p>英雄等级、文化消耗、出战位和技能解锁构成中长期投入目标。</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<section class="doc-section">
|
||||
<h2>设计原则</h2>
|
||||
<ul class="rule-list">
|
||||
<li><strong>强但不万能:</strong>英雄应改变局部最优解,而不是替代城市、科技和普通单位。</li>
|
||||
<li><strong>角色设定先转规则:</strong>先明确角色幻想,再落成移动、伤害、状态、召唤、复活、转换等规则动词。</li>
|
||||
<li><strong>技能描述必须可验证:</strong>文案里的关键词要能追到技能类型、触发时机和代码实现。</li>
|
||||
</ul>
|
||||
</section>
|
||||
<footer class="doc-footer"><a href="index.html">返回机制总览</a></footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
98
Design/final/mechanics/index.html
Normal file
98
Design/final/mechanics/index.html
Normal file
@ -0,0 +1,98 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 设计树</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell tree-shell">
|
||||
<section class="design-tree" aria-label="TH1 设计树">
|
||||
<a class="tree-node tree-root" href="design-principles.html">设计原则</a>
|
||||
|
||||
<div class="tree-branches two">
|
||||
<section class="tree-card">
|
||||
<a class="tree-node tree-l1" href="design-principles.html#mechanics">1. 游戏机制</a>
|
||||
|
||||
<div class="tree-branches three">
|
||||
<section class="tree-card compact">
|
||||
<a class="tree-node tree-l2" href="design-principles.html#foundation">基础</a>
|
||||
<div class="tree-leaves">
|
||||
<a href="design-principles.html#farming">种田</a>
|
||||
<a href="design-principles.html#combat">战斗</a>
|
||||
<a href="design-principles.html#tempo">节奏</a>
|
||||
<a href="hero-foundation.html">英雄</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tree-card compact">
|
||||
<a class="tree-node tree-l2" href="design-principles.html#battlefield">战场</a>
|
||||
<div class="tree-group">
|
||||
<div class="tree-label">地形</div>
|
||||
<div class="tree-leaves inline">
|
||||
<a href="design-principles.html#dryland">旱地</a>
|
||||
<a href="design-principles.html#standard-map">标准</a>
|
||||
<a href="design-principles.html#sea-map">海图</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-group">
|
||||
<div class="tree-label">战场熵</div>
|
||||
<div class="tree-leaves inline">
|
||||
<a href="design-principles.html#duel">1v1</a>
|
||||
<a href="design-principles.html#ffa">FFA</a>
|
||||
<a href="design-principles.html#nvn">NvN</a>
|
||||
<a href="design-principles.html#conquest">征服</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tree-card compact">
|
||||
<a class="tree-node tree-l2" href="design-principles.html#factions">阵营</a>
|
||||
<div class="tree-leaves">
|
||||
<a href="design-principles.html#remilia">红魔馆</a>
|
||||
<a href="design-principles.html#kaguya">永远亭</a>
|
||||
<a href="design-principles.html#kanako">守矢神社</a>
|
||||
<a href="design-principles.html#satori">地灵殿</a>
|
||||
<a href="design-principles.html#reimu">博丽神社</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tree-card">
|
||||
<a class="tree-node tree-l1" href="design-principles.html#narrative">2. 文案剧情</a>
|
||||
|
||||
<div class="tree-branches three">
|
||||
<section class="tree-card compact">
|
||||
<a class="tree-node tree-l2" href="design-principles.html#story">剧情</a>
|
||||
</section>
|
||||
|
||||
<section class="tree-card compact">
|
||||
<a class="tree-node tree-l2" href="/api/design/narrative/heroes.html">英雄</a>
|
||||
<div class="tree-leaves">
|
||||
<a href="/api/design/narrative/heroes.html#remilia">红魔馆</a>
|
||||
<a href="/api/design/narrative/heroes.html#kaguya">永远亭</a>
|
||||
<a href="/api/design/narrative/heroes.html#kanako">守矢神社</a>
|
||||
<a href="/api/design/narrative/heroes.html#satori">地灵殿</a>
|
||||
<a href="/api/design/narrative/heroes.html#reimu">博丽神社</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tree-card compact">
|
||||
<a class="tree-node tree-l2" href="/api/design/narrative/civilizations.html">文明</a>
|
||||
<div class="tree-leaves">
|
||||
<a href="/api/design/narrative/civilizations.html#remilia">红魔馆</a>
|
||||
<a href="/api/design/narrative/civilizations.html#kaguya">永远亭</a>
|
||||
<a href="/api/design/narrative/civilizations.html#kanako">守矢神社</a>
|
||||
<a href="/api/design/narrative/civilizations.html#satori">地灵殿</a>
|
||||
<a href="/api/design/narrative/civilizations.html#reimu">博丽神社</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
370
Design/final/mechanics/ley-line-mechanism.md
Normal file
370
Design/final/mechanics/ley-line-mechanism.md
Normal file
@ -0,0 +1,370 @@
|
||||
# 地脉(Ley Line)机制 — 策划·程序综合开发文档
|
||||
|
||||
> 文档目录:`Design/final/mechanics/ley-line-mechanism.md`
|
||||
> 版本:v1.1 日期:2026-03-09 作者:待填写
|
||||
|
||||
---
|
||||
|
||||
## 一、策划需求
|
||||
|
||||
### 1.1 概述
|
||||
|
||||
地脉(Ley Line)是一种特殊地格属性(SpType),在地图生成阶段随机散布于地图各处。
|
||||
建造在地脉附近的建筑将获得初始等级加成,代表"灵气汇聚"对建筑的增幅效果。
|
||||
|
||||
**阵营限制**:地脉机制**仅对古明地帝国(Satori / Indian Empire)阵营生效**。
|
||||
- 只有 `CivEnum.Indian` 的玩家才能获得地脉建筑加成
|
||||
- 只有 `CivEnum.Indian` 的玩家才能在地图上看到地脉格的深紫色标识
|
||||
- 其他所有阵营(Egyptian、French、Germany等)的玩家**无法看到地脉,也不会获得任何地脉加成**
|
||||
- 地脉数据仍然存在于地图中(所有阵营共用同一张地图),只是非 Indian 阵营在渲染层和逻辑层都被跳过
|
||||
|
||||
### 1.2 建筑加成规则
|
||||
|
||||
| 条件 | 加成 | 备注 |
|
||||
|------|------|------|
|
||||
| 建筑建在地脉格上(isOnLeyLine) | 初始 buildingLevel **+3** | 冶炼厂(Forge)仅 **+2** |
|
||||
| 建筑建在地脉周围 1 格内(isAdjacentToLeyLine) | 初始 buildingLevel **+1** | 所有建筑类型相同 |
|
||||
| 两个条件叠加 | 不叠加,优先取在地脉上的规则 | 在地脉上不再享受周边加成 |
|
||||
|
||||
### 1.3 适用建筑范围
|
||||
|
||||
适用所有 `HasLevel = true` 的建筑,即当前的:
|
||||
- 风车(Windmill)
|
||||
- 冶炼厂(Forge)
|
||||
- 锯木厂(Sawmill)
|
||||
- 集市(Market)
|
||||
- 学院(Academy)
|
||||
- 保护区(Preserve)
|
||||
- 卡古亚法式农庄(KaguyaFrenchYard)
|
||||
- 埃及灌溉(EgyptianIrrigation)
|
||||
|
||||
> 等级上限(`resourceInfo.MaxLevel`)不变,地脉加成后仍受上限约束。
|
||||
|
||||
### 1.4 地脉格子的生成规则
|
||||
|
||||
地脉在 `MapGenerator.GenerateMap()` 的**最后阶段**随机生成,在所有地形与资源数据都确定之后进行。
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| 生成时机 | 在 `InitGridMapData()` 和 `CivTerrainFeatureCountControl()` 之后,所有资源生成完毕后执行 |
|
||||
| 禁止位置 | 不得生成在 **CityCenter 格本身**上(无论首都还是村庄) |
|
||||
| 排除区 | 不得生成在**首都**(`PlayerCivOri`)的 **1 格范围内**;非首都村庄 1 格范围内**允许**生成 |
|
||||
| 生成数量 | 总数量 = 地图上城市中心总数(`_tribes.Count`)的 **1/3**(取整) |
|
||||
| 地形限制 | 仅限陆地格(`Terrain == TerrainType.Land`) |
|
||||
| 文明保底 | **古明地帝国**(`CivEnum.Indian`)首都周围 **2 格内必定有至少 1 个地脉**,随机放置未满足时强制补充 |
|
||||
| 地脉持久性 | 地脉属于地形属性层,建造建筑后依然保留(持久型 SpType,不消耗) |
|
||||
|
||||
### 1.5 视觉表现规则
|
||||
|
||||
地脉格子在视觉上**通过表格配置**,不再使用代码硬编码颜色。
|
||||
|
||||
- 地脉格子的 Sprite 和颜色走 `GridAndResourceDataAssets` 表格配置
|
||||
- `GetTerrainSprite()` 方法已支持根据 `GridSpType.LeyLine` 返回对应配置的 Sprite
|
||||
- 在 `TerrainInfo.SpriteList` 中添加 `IsGridSpType = true` 且 `GridSpType = LeyLine` 的配置项即可定义地脉格的地块外观
|
||||
- 视觉表现与 `RemiliaGrid`、`KaguyaGrid` 机制一致,完全由策划在表格中配置
|
||||
- **仅当本地玩家(SelfPlayer)为 `CivEnum.Indian` 时才渲染地脉特殊外观**;其他阵营玩家看到的地脉格显示为普通陆地地块
|
||||
|
||||
---
|
||||
|
||||
## 二、程序功能设计
|
||||
|
||||
### 2.1 涉及文件清单
|
||||
|
||||
| 文件路径 | 修改类型 | 说明 |
|
||||
|----------|----------|------|
|
||||
| `Assets/Scripts/TH1_Data/GridData.cs` | **枚举新增** | `GridSpType` 中加入 `LeyLine` |
|
||||
| `Assets/Scripts/TH1_Logic/City/CityLogic.cs` | **逻辑修改** | `CalcGridBuildingLevel` 中注入地脉加成 |
|
||||
| `Assets/Scripts/TH1_Logic/Map/MapGenerator.cs` | **新增方法** | `GenerateLeyLines()` 随机生成地脉格,在 `GenerateMap()` 末尾调用 |
|
||||
| `Assets/Scripts/TH1_Renderer/GridRenderer.cs` | **无需修改** | 地脉视觉走 `GetTerrainSprite()` 表格配置,自动生效 |
|
||||
| `Assets/Resources/DataAssets/GridAndResourceDataAssets.asset` | **表格配置** | 在 `TerrainInfo.SpriteList` 中添加 `LeyLine` 对应的美术资源配置(仅 Indian 阵营) |
|
||||
| `Assets/Scripts/TH1_Logic/Action/BuildActionLogic.cs` | 无需修改 | 建造流程已调用 `CalcGridBuildingLevel`,自动生效 |
|
||||
|
||||
---
|
||||
|
||||
### 2.2 第一步:枚举扩展(GridData.cs)
|
||||
|
||||
**文件**:`Assets/Scripts/TH1_Data/GridData.cs`,第 313 行
|
||||
|
||||
**当前代码**:
|
||||
```csharp
|
||||
public enum GridSpType { None, RemiliaGrid, KaguyaGrid, RemiliaGridDark, Max }
|
||||
```
|
||||
|
||||
**修改为**:
|
||||
```csharp
|
||||
public enum GridSpType { None, RemiliaGrid, KaguyaGrid, RemiliaGridDark, LeyLine, Max }
|
||||
```
|
||||
|
||||
> `Max` 始终放最后,`LeyLine` 插在 `RemiliaGridDark` 之后。
|
||||
> MemoryPack 对枚举按整数值序列化,新增枚举值放在 Max 之前不影响旧存档读取,完全向后兼容。
|
||||
|
||||
---
|
||||
|
||||
### 2.3 第二步:地脉加成注入(CityLogic.cs)
|
||||
|
||||
**文件**:`Assets/Scripts/TH1_Logic/City/CityLogic.cs`
|
||||
**函数**:`CalcGridBuildingLevel`(约第 317 行)
|
||||
|
||||
在函数末尾 `if (level > resourceInfo.MaxLevel)` **之前**注入:
|
||||
|
||||
```csharp
|
||||
// ===== 地脉加成(仅 Indian 阵营生效) =====
|
||||
if (mapData.GetPlayerDataByTerritoryGridId(gridData.Id, out var leyLinePlayer)
|
||||
&& leyLinePlayer.CivEnum == CivEnum.Indian)
|
||||
{
|
||||
// 规则1:建筑就建在地脉格上
|
||||
if (gridData.HasSpType(GridSpType.LeyLine))
|
||||
{
|
||||
int bonus = (buildingType == ResourceType.Forge) ? 2 : 3;
|
||||
level += bonus;
|
||||
ret = true;
|
||||
}
|
||||
// 规则2:建筑周围1格存在地脉格(自身不是地脉格时才检查)
|
||||
else
|
||||
{
|
||||
foreach (var grid in aroundGridList)
|
||||
{
|
||||
if (grid == gridData) continue;
|
||||
if (grid.HasSpType(GridSpType.LeyLine))
|
||||
{
|
||||
level += 1;
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// ====================
|
||||
|
||||
if (level > resourceInfo.MaxLevel)
|
||||
level = resourceInfo.MaxLevel;
|
||||
return ret;
|
||||
```
|
||||
|
||||
**关键设计说明**:
|
||||
1. **阵营检查**——通过 `GetPlayerDataByTerritoryGridId` 获取该格所属玩家,仅 `CivEnum.Indian` 时才进入地脉加成逻辑
|
||||
2. `else` 保证互斥——在地脉上时不再检查周边,防止同格加成叠加
|
||||
3. `break` 保证邻格不叠加——相邻多个地脉格只加一次 +1
|
||||
4. 上限保护——地脉加成后仍受 `resourceInfo.MaxLevel` 约束
|
||||
5. `aroundGridList` 复用——函数顶部已初始化(`GetAroundGridData(1,1,gridData)`),含 center,无需额外查表
|
||||
|
||||
---
|
||||
|
||||
### 2.4 第三步:地脉地图生成(MapGenerator.cs)
|
||||
|
||||
**文件**:`Assets/Scripts/TH1_Logic/Map/MapGenerator.cs`
|
||||
|
||||
#### 2.4.1 新增私有方法
|
||||
|
||||
```csharp
|
||||
private void GenerateLeyLines(MapData mapData)
|
||||
{
|
||||
// Step #1 计算目标数量:地脉总数 = 城市中心总数 / 3(取整)
|
||||
int leyLineCount = _tribes.Count / 3;
|
||||
if (leyLineCount <= 0) return;
|
||||
|
||||
// Step #2 构建首都排除区(仅首都±1范围,非首都村庄不排除)
|
||||
var cradleExclude = new HashSet<uint>();
|
||||
foreach (var cradle in PlayerCivOri)
|
||||
for (int x = cradle.X - 1; x <= cradle.X + 1; x++)
|
||||
for (int y = cradle.Y - 1; y <= cradle.Y + 1; y++)
|
||||
cradleExclude.Add(MapPosition.CalculatePosId(x, y));
|
||||
|
||||
// Step #3 收集所有合法候选格子
|
||||
// 条件:陆地格 + 不是CityCenter + 不在首都1格范围内
|
||||
var candidates = new List<GridData>();
|
||||
foreach (var gridData in mapData.GridMap.GridList)
|
||||
{
|
||||
if (gridData.Terrain != TerrainType.Land) continue;
|
||||
if (gridData.Resource == ResourceType.CityCenter) continue;
|
||||
if (cradleExclude.Contains(gridData.Pos.PosId)) continue;
|
||||
candidates.Add(gridData);
|
||||
}
|
||||
|
||||
// Step #4 随机打乱候选列表(Fisher-Yates shuffle)
|
||||
for (int i = candidates.Count - 1; i > 0; i--)
|
||||
{
|
||||
int j = Random.Range(0, i + 1);
|
||||
(candidates[i], candidates[j]) = (candidates[j], candidates[i]);
|
||||
}
|
||||
|
||||
// Step #5 取前N个格子,标记为地脉
|
||||
int placed = 0;
|
||||
foreach (var grid in candidates)
|
||||
{
|
||||
if (placed >= leyLineCount) break;
|
||||
grid.SpTypeList.Add(GridSpType.LeyLine);
|
||||
placed++;
|
||||
}
|
||||
|
||||
// Step #6 古明地帝国保底:首都2格内必定有地脉
|
||||
for (int rk = 0; rk < mapData.PlayerMap.PlayerDataList.Count && rk < PlayerCivOri.Count; rk++)
|
||||
{
|
||||
var player = mapData.PlayerMap.PlayerDataList[rk];
|
||||
if (player.CivEnum != CivEnum.Indian) continue;
|
||||
if (!mapData.GridMap.GetGridDataByPos(PlayerCivOri[rk].X, PlayerCivOri[rk].Y, out var cradleGrid)) continue;
|
||||
|
||||
// 检查2格内是否已有地脉
|
||||
var aroundGrids = mapData.GridMap.GetAroundGridData(2, 2, cradleGrid);
|
||||
bool hasLeyLine = false;
|
||||
foreach (var g in aroundGrids)
|
||||
if (g.HasSpType(GridSpType.LeyLine)) { hasLeyLine = true; break; }
|
||||
if (hasLeyLine) continue;
|
||||
|
||||
// 没有则强制在2格内随机选一个合法格子放置
|
||||
for (int i = aroundGrids.Count - 1; i > 0; i--)
|
||||
{
|
||||
int j = Random.Range(0, i + 1);
|
||||
(aroundGrids[i], aroundGrids[j]) = (aroundGrids[j], aroundGrids[i]);
|
||||
}
|
||||
foreach (var g in aroundGrids)
|
||||
{
|
||||
if (g.Terrain != TerrainType.Land) continue;
|
||||
if (g.Resource == ResourceType.CityCenter) continue;
|
||||
if (cradleExclude.Contains(g.Pos.PosId)) continue;
|
||||
g.SpTypeList.Add(GridSpType.LeyLine);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4.2 调用时机
|
||||
|
||||
在 `GenerateMap()` 中,**`CivTerrainFeatureCountControl(mapData)` 之后**、`InitGeoData` 之前调用:
|
||||
|
||||
```csharp
|
||||
public void GenerateMap(MapData mapData)
|
||||
{
|
||||
// ... 原有流程 ...
|
||||
InitGridMapData(mapData);
|
||||
CivTerrainFeatureCountControl(mapData);
|
||||
|
||||
// ===== 新增:生成地脉 =====
|
||||
GenerateLeyLines(mapData);
|
||||
// =========================
|
||||
|
||||
Table.Instance.GeoDataAssets.InitGeoData(mapData);
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4.3 排除区说明:仅排除首都,不排除村庄
|
||||
|
||||
`PlayerCivOri` 是玩家摇篮城市(首都)的坐标列表,是 `_tribes` 的子集。
|
||||
`GenerateLeyLines` 在运行时动态构建首都 ±1 的排除区 `HashSet<uint> cradleExclude`,
|
||||
使用 `MapPosition.CalculatePosId(x, y)` 作为 key,与 `gridData.Pos.PosId` 直接比较,无需 `Equals` 重载。
|
||||
|
||||
非首都城市(无人占领的村庄)1 格范围内允许生成地脉,使得村庄附近可以出现地脉,为玩家的扩张选择提供策略价值。但 CityCenter 格本身始终禁止生成地脉(`Resource == ResourceType.CityCenter` 被排除)。
|
||||
|
||||
#### 2.4.4 古明地帝国保底说明
|
||||
|
||||
随机放置完毕后,遍历 `PlayerDataList` 查找 `CivEnum.Indian` 的玩家,通过索引 `rk` 对应 `PlayerCivOri[rk]` 获取首都坐标。
|
||||
调用 `GetAroundGridData(2, 2, cradleGrid)` 获取 2 格范围内所有格子,若其中无地脉则随机选取一个合法格子强制放置。
|
||||
强制放置仍遵守所有约束(陆地、非 CityCenter、不在首都 1 格排除区内),因此保底地脉最近出现在首都切比雪夫距离 = 2 的位置。
|
||||
|
||||
---
|
||||
|
||||
### 2.5 第四步:地脉渲染(GridRenderer.cs,走表格配置)
|
||||
|
||||
**文件**:`Assets/Scripts/TH1_Renderer/GridRenderer.cs`
|
||||
**函数**:`UpdateLand()`(约第 425 行)
|
||||
|
||||
地脉格子的视觉表现**完全由表格配置**,代码层面**不需要**像 RemiliaGrid/KaguyaGrid 那样添加特殊颜色覆写逻辑。
|
||||
|
||||
#### 当前实现
|
||||
|
||||
`GetTerrainSprite()` 已自动支持 `GridSpType.LeyLine`:
|
||||
|
||||
```csharp
|
||||
private void UpdateLand()
|
||||
{
|
||||
if(_land == null) return;
|
||||
var com = _land.GetComponent<SpriteRenderer>();
|
||||
if (com == null) return;
|
||||
var oldSprite = com.sprite;
|
||||
if (oldSprite == null) return;
|
||||
|
||||
com.color = _gridData.Player(Main.MapData, out var _) && _gridData.Terrain == TerrainType.Land
|
||||
? terriColor : Color.white;
|
||||
|
||||
var newSprite = Table.Instance.GridAndResourceDataAssets.GetTerrainSprite(_gridData);
|
||||
com.sprite = newSprite;
|
||||
if (oldSprite != newSprite && _gridData.HasSpType(GridSpType.RemiliaGrid))
|
||||
PlayVFXInSight(new GridVFXParams(GridVFXType.RedMistCreate));
|
||||
}
|
||||
```
|
||||
|
||||
**设计说明**:
|
||||
- **无需代码修改**——`GetTerrainSprite()` 会自动根据 `GridSpType.LeyLine` 返回表格中配置的 Sprite
|
||||
- **阵营限制在表格层实现**——配置 `EmpireGridSpInfoPack` 时指定 `CivId` 为 Indian 对应的 CivId,非 Indian 玩家将匹配不到对应 Sprite
|
||||
- 颜色/图案完全由 `TerrainInfo.SpriteList` 中的美术资源决定
|
||||
|
||||
---
|
||||
|
||||
### 2.6 第五步:建造流程回顾(BuildActionLogic.cs,无需修改)
|
||||
|
||||
建造流程的核心路径如下:
|
||||
|
||||
```
|
||||
BuildAction.Execute()
|
||||
└─ Main.CityLogic.CalcGridBuildingLevel(...) // ← 地脉加成已在此注入,自动生效
|
||||
gridData.buildingLevel = buildingLevel
|
||||
└─ Main.PlayerLogic.UpdateTerritoryAllBuildingLevel(...)
|
||||
└─ UpdateGridBuildingData_LogicView(...)
|
||||
└─ CalcGridBuildingLevel(...) // ← 全量刷新时同样生效
|
||||
```
|
||||
|
||||
第 62-64 行的 cityExp 计算使用最终的 `buildingLevel`,地脉加成后 cityExp 自动跟随正确数值,无需额外修改。
|
||||
|
||||
---
|
||||
|
||||
## 三、数值示例
|
||||
|
||||
Forge(冶炼厂)示例(周围有 Mine,基础 level = 2,MaxLevel = 4):
|
||||
|
||||
| 格子情况 | 基础 level | 地脉加成 | 最终 level |
|
||||
|----------|-----------|---------|-----------|
|
||||
| 无地脉 | 2 | 0 | **2** |
|
||||
| 建在地脉上 | 2 | +2(Forge专用) | **4**(触上限) |
|
||||
| 周边1格有地脉 | 2 | +1 | **3** |
|
||||
|
||||
Windmill(风车)示例(周围有 Farm,基础 level = 1,MaxLevel = 4):
|
||||
|
||||
| 格子情况 | 基础 level | 地脉加成 | 最终 level |
|
||||
|----------|-----------|---------|-----------|
|
||||
| 无地脉 | 1 | 0 | **1** |
|
||||
| 建在地脉上 | 1 | +3 | **4**(触上限) |
|
||||
| 周边1格有地脉 | 1 | +1 | **2** |
|
||||
|
||||
---
|
||||
|
||||
## 四、开发任务拆解
|
||||
|
||||
### 必做
|
||||
- [ ] `GridData.cs`:`GridSpType` 枚举加入 `LeyLine`
|
||||
- [ ] `CityLogic.cs`:`CalcGridBuildingLevel` 注入地脉加成逻辑
|
||||
- [ ] `MapGenerator.cs`:新增 `GenerateLeyLines()` 方法,并在 `GenerateMap()` 末尾调用
|
||||
- [ ] `GridRenderer.cs`:**无需修改**,地脉视觉走表格配置
|
||||
- [ ] `GridAndResourceDataAssets.asset`:在 `TerrainInfoList` → `Land` → `SpriteList` 中添加 `LeyLine` 对应的 `EmpireGridSpInfoPack` 配置(指定印度阵营对应的 CivId)
|
||||
|
||||
### 可选(视需求)
|
||||
- [ ] UI Tooltip:建造时预先显示地脉加成数值(需在 `CheckCan` 或 UI Controller 调用 `CalcGridBuildingLevel` 做预算)
|
||||
- [ ] `AddSpType`:为 `LeyLine` 添加专属校验规则(如只能在陆地格)
|
||||
|
||||
---
|
||||
|
||||
## 五、风险与注意事项
|
||||
|
||||
1. **MemoryPack 兼容性**:`GridSpType.LeyLine` 是新枚举值,旧存档不含此值,反序列化时 `SpTypeList` 不包含 `LeyLine` 即视为无地脉,**完全向后兼容**。
|
||||
|
||||
2. **`_innerCityGrid` 含中心格**:`GenerateTribes()` 中 `_innerCityGrid` 的循环范围是 `±1`,已包含城市中心本身(x==cent.X, y==cent.Y),因此 Contains 检查已覆盖中心格,无需额外处理。
|
||||
|
||||
3. **`_innerCityGrid` 使用 `MapPosition` 相等比较**:`MapPosition` 已重载 `Equals`/`GetHashCode`/`==`(见 GridData.cs 第 873-901 行),`List<MapPosition>.Contains` 语义正确,可放心使用。
|
||||
|
||||
4. **地脉生成在 `CivTerrainFeatureCountControl` 之后**:保底地形修正可能会改变某些格子的 Terrain,`GenerateLeyLines` 在其之后执行,读取的 `Terrain` 是最终值,不存在顺序问题。
|
||||
|
||||
5. **`aroundGridList` 含 center**:`CalcGridBuildingLevel` 中 `GetAroundGridData(1,1,gridData)` 返回含 center 的列表,邻格检查时已用 `grid == gridData` 跳过,逻辑正确。
|
||||
|
||||
6. **UpdateTerritoryAllBuildingLevel 刷新**:占领城市、新建城市等场景会触发全量刷新,地脉加成会被自动重新计算,无需额外处理。
|
||||
|
||||
7. **AI 评分**:AI 在 `AITechScoreCalculator` / `AIActionBase` 中评估建筑价值,若需 AI 感知地脉优势,需额外修改 AI 评估逻辑(属于后续优化,非核心需求)。
|
||||
37
Design/final/mechanics/map-city-tech.html
Normal file
37
Design/final/mechanics/map-city-tech.html
Normal file
@ -0,0 +1,37 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 地图、城市与科技</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell narrow">
|
||||
<header class="doc-hero compact">
|
||||
<p class="eyebrow">Mechanics / Economy</p>
|
||||
<h1>地图、城市与科技</h1>
|
||||
<p class="lead">经营层负责把地形、资源、城市升级和科技选择串成清晰的长期规划。东方机制应尽量附着在这条主线上,而不是另开一套难以理解的资源系统。</p>
|
||||
</header>
|
||||
<section class="doc-section">
|
||||
<h2>经营层主线</h2>
|
||||
<ol class="rule-list">
|
||||
<li><strong>地块:</strong>提供移动、建造和资源限制。</li>
|
||||
<li><strong>资源:</strong>通过科技解锁采集或建筑转换。</li>
|
||||
<li><strong>城市:</strong>将资源转化为发展度、生产能力和升级奖励。</li>
|
||||
<li><strong>科技:</strong>决定玩家能对地图做什么,也决定单位与建筑的可用集合。</li>
|
||||
<li><strong>文化卡:</strong>提供中后期方向选择,可承担部分东方化的国家制度表达。</li>
|
||||
</ol>
|
||||
</section>
|
||||
<section class="doc-section">
|
||||
<h2>设计检查项</h2>
|
||||
<div class="callout-grid">
|
||||
<article><h3>资源是否可读</h3><p>玩家看到地块时,应能判断它当前能做什么、研究科技后能做什么。</p></article>
|
||||
<article><h3>城市升级是否有选择</h3><p>升级奖励不能只是数值堆叠,应提供扩张、经济、军事或英雄成长方向。</p></article>
|
||||
<article><h3>科技是否改变行动</h3><p>科技最好解锁新的地图操作或战术路径,而不只是属性加成。</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="doc-footer"><a href="index.html">返回机制总览</a></footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
39
Design/final/mechanics/unit-skill-system.html
Normal file
39
Design/final/mechanics/unit-skill-system.html
Normal file
@ -0,0 +1,39 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 单位与技能系统</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell narrow">
|
||||
<header class="doc-hero compact">
|
||||
<p class="eyebrow">Mechanics / Units & Skills</p>
|
||||
<h1>单位与技能系统</h1>
|
||||
<p class="lead">单位提供可被玩家直接操作的棋子,技能提供规则差异。TH1 的深度主要来自单位模板、英雄等级、状态技能和触发技能的组合。</p>
|
||||
</header>
|
||||
<section class="doc-section">
|
||||
<h2>单位分层</h2>
|
||||
<div class="flow-grid three">
|
||||
<article><h3>基础单位</h3><p>战士、骑兵、弓兵、船只等低复杂度单位,支撑 Polytopia 式基础战斗。</p></article>
|
||||
<article><h3>特色单位</h3><p>阵营替换或专属训练单位,用少量规则表达势力主题。</p></article>
|
||||
<article><h3>英雄/巨人单位</h3><p>拥有高权重技能和成长价值,是东方扩展层的主要承载者。</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<section class="doc-section">
|
||||
<h2>技能分类建议</h2>
|
||||
<table class="doc-table">
|
||||
<thead><tr><th>分类</th><th>例子</th><th>文档需要记录</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>基础能力</td><td>移动、反击、射程、航海。</td><td>触发条件、是否影响 AI 评分。</td></tr>
|
||||
<tr><td>状态能力</td><td>恐惧、满月、金刚身、粗糙身。</td><td>持续时间、层数、刷新规则、显示方式。</td></tr>
|
||||
<tr><td>触发能力</td><td>攻击后、受伤后、死亡后、回合开始。</td><td>生命周期时机、动画表现、联机同步。</td></tr>
|
||||
<tr><td>转换/召唤</td><td>单位变体、生成单位、资源转换。</td><td>目标选择、合法性、失败回滚。</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<footer class="doc-footer"><a href="index.html">返回机制总览</a></footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
149
Design/final/narrative/civilizations.html
Normal file
149
Design/final/narrative/civilizations.html
Normal file
@ -0,0 +1,149 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>文案剧情 / 文明</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell narrative-shell">
|
||||
<header class="doc-hero compact">
|
||||
<p class="eyebrow">Narrative / Civilizations</p>
|
||||
<h1>文案剧情:文明</h1>
|
||||
<p class="lead">按当前 PlayerDataAssets 与 CivDataAssets 归类阵营文案。这里把东方势力层和 Polytopia 文明底板并列展示,方便后续决定哪些文案保留、替换或扩写。</p>
|
||||
<div class="source-strip" aria-label="DataAsset 来源">
|
||||
<span>PlayerDataAssets:CivName / ForceName / LeaderName / EmpireDesc / LeaderDesc</span>
|
||||
<span>CivDataAssets:CivName / CityInfoList / CityDescription</span>
|
||||
<span>LibraryDataAssets:奇观图鉴与英雄图鉴补充语气</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav class="faction-nav" aria-label="阵营导航">
|
||||
<a href="#remilia">红魔馆</a>
|
||||
<a href="#kaguya">永远亭</a>
|
||||
<a href="#kanako">守矢神社</a>
|
||||
<a href="#satori">地灵殿</a>
|
||||
<a href="#reimu">博丽神社</a>
|
||||
</nav>
|
||||
|
||||
<section id="remilia" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>红魔馆</h2>
|
||||
<p>ForceName:斯卡雷特帝国 / LeaderName:蕾米莉亚 / CivName:埃及</p>
|
||||
</div>
|
||||
<div class="civilization-grid">
|
||||
<article class="copy-card">
|
||||
<h3>阵营文案定位 <small>PlayerInfo</small></h3>
|
||||
<p class="copy-desc">红雾笼罩尼罗河两岸,生死、智慧与禁忌分别由咲夜、帕秋莉、芙兰等红魔馆成员承接。领袖形象是立于金字塔之巅的永生法老蕾米莉亚。</p>
|
||||
<p class="copy-diag">高傲的红魔馆大小姐抽到了埃及帝国卡。</p>
|
||||
<div class="copy-meta"><span>红雾</span><span>法老</span><span>馆主权威</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>文明底板 <small>CivDataAssets</small></h3>
|
||||
<p class="copy-desc">埃及文明城市名已经完整配置,城市描述目前是通用占位句。</p>
|
||||
<div class="city-list">
|
||||
<span>开罗</span><span>亚历山大</span><span>孟斐斯</span><span>底比斯</span><span>卢克索</span><span>阿斯旺</span><span>吉萨</span><span>西奈</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="kaguya" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>永远亭</h2>
|
||||
<p>ForceName:蓬莱山帝国 / LeaderName:蓬莱山辉夜 / CivName:法兰西</p>
|
||||
</div>
|
||||
<div class="civilization-grid">
|
||||
<article class="copy-card">
|
||||
<h3>阵营文案定位 <small>PlayerInfo</small></h3>
|
||||
<p class="copy-desc">妖怪兔近卫队、竹林狼上校与帝国元帅构成战队化军团叙事。辉夜以月之公主与电竞高手双重身份,站在凯旋门前写下最强陆军传说。</p>
|
||||
<p class="copy-diag">优雅而怠惰的月之公主抽到了法兰西帝国卡。</p>
|
||||
<div class="copy-meta"><span>永夜</span><span>法兰西荣耀</span><span>战队训练</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>文明底板 <small>CivDataAssets</small></h3>
|
||||
<p class="copy-desc">法兰西文明城市名已经完整配置,城市描述目前是通用占位句。</p>
|
||||
<div class="city-list">
|
||||
<span>巴黎</span><span>凡尔赛</span><span>里昂</span><span>马赛</span><span>波尔多</span><span>图卢兹</span><span>里尔</span><span>南特</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="kanako" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>守矢神社</h2>
|
||||
<p>ForceName:守矢帝国 / LeaderName:八坂神奈子 / CivName:德意志</p>
|
||||
</div>
|
||||
<div class="civilization-grid">
|
||||
<article class="copy-card">
|
||||
<h3>阵营文案定位 <small>PlayerInfo</small></h3>
|
||||
<p class="copy-desc">土著神奠基,风雨神推动工业齿轮。神奈子把技术与信仰熔铸成铁与血,整体调性是产业化、技术化、商业化的守矢集团。</p>
|
||||
<p class="copy-diag">野心勃勃的山神殿下抽到了德意志帝国卡。守矢神社已是比赛最大的冠名赞助商。</p>
|
||||
<div class="copy-meta"><span>信仰即流量</span><span>钢铁王座</span><span>技术变革</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>文明底板 <small>CivDataAssets</small></h3>
|
||||
<p class="copy-desc">德意志文明城市名已经完整配置,城市描述目前是通用占位句。</p>
|
||||
<div class="city-list">
|
||||
<span>柏林</span><span>哥尼斯堡</span><span>波茨坦</span><span>马格德堡</span><span>汉堡</span><span>不来梅</span><span>德累斯顿</span><span>莱比锡</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="satori" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>地灵殿</h2>
|
||||
<p>ForceName:古明地帝国 / LeaderName:古明地觉 / CivName:印度</p>
|
||||
</div>
|
||||
<div class="civilization-grid">
|
||||
<article class="copy-card">
|
||||
<h3>阵营文案定位 <small>PlayerInfo</small></h3>
|
||||
<p class="copy-desc">雷霆鬼王、苏利耶神子、无心神明与旧地狱业火共同拼成吠陀史诗式的地底帝国。觉的核心形象是洞彻万心的第三只眼。</p>
|
||||
<p class="copy-diag">沉静而可怖的地灵殿主人抽到了印度帝国卡。</p>
|
||||
<div class="copy-meta"><span>旧地狱</span><span>吠陀史诗</span><span>读心</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>文明底板 <small>CivDataAssets</small></h3>
|
||||
<p class="copy-desc">印度文明城市名已经完整配置,城市描述目前是通用占位句。</p>
|
||||
<div class="city-list">
|
||||
<span>德里</span><span>新德里</span><span>瓦拉纳西</span><span>巴特那</span><span>华氏城</span><span>阿约提亚</span><span>阿格拉</span><span>斋浦尔</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="reimu" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>博丽神社</h2>
|
||||
<p>ForceName:博丽帝国 / LeaderName:博丽灵梦 / CivName:维京</p>
|
||||
</div>
|
||||
<div class="civilization-grid">
|
||||
<article class="copy-card">
|
||||
<h3>阵营文案定位 <small>PlayerInfo</small></h3>
|
||||
<p class="copy-desc empty-copy">EmpireDesc 与 LeaderDesc 当前为空。LibraryDataAssets 中灵梦还是“代理木偶”占位,因此博丽神社暂时只有阵营壳和文明底板,没有正式剧情定位。</p>
|
||||
<div class="copy-meta"><span>待补</span><span>博丽结界</span><span>幻想乡中心</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>文明底板 <small>CivDataAssets</small></h3>
|
||||
<p class="copy-desc">维京文明城市名已经完整配置,城市描述目前是通用占位句。</p>
|
||||
<div class="city-list">
|
||||
<span>奥斯陆</span><span>卑尔根</span><span>特隆赫姆</span><span>斯塔万格</span><span>特罗姆瑟</span><span>克里斯蒂安桑</span><span>德拉门</span><span>博德</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc-section note">
|
||||
<h2>下一轮整理重点</h2>
|
||||
<ol class="rule-list">
|
||||
<li>博丽神社需要补齐 EmpireDesc、LeaderDesc、正式英雄组和图鉴文案。</li>
|
||||
<li>CivDataAssets 的 CityDescription 目前基本是通用占位句,可以后续按文明或阵营改写。</li>
|
||||
<li>LibraryDataAssets 的奇观文案现在更偏传统文明,需要决定是否要映射成东方阵营风格。</li>
|
||||
</ol>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
245
Design/final/narrative/heroes.html
Normal file
245
Design/final/narrative/heroes.html
Normal file
@ -0,0 +1,245 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>文案剧情 / 英雄</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell narrative-shell">
|
||||
<header class="doc-hero compact">
|
||||
<p class="eyebrow">Narrative / Heroes</p>
|
||||
<h1>文案剧情:英雄</h1>
|
||||
<p class="lead">按阵营归档 Library、Hero、Player 三类 DataAsset 中已经存在的英雄文案。当前页只做归类和设计入口,不在这里重写正式文案。</p>
|
||||
<div class="source-strip" aria-label="DataAsset 来源">
|
||||
<span>LibraryDataAssets:Name / SubTitle / Desc / Diag</span>
|
||||
<span>HeroDataAssets:TaskList / Desc</span>
|
||||
<span>PlayerDataAssets:ForceName / LeaderName</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav class="faction-nav" aria-label="阵营导航">
|
||||
<a href="#remilia">红魔馆</a>
|
||||
<a href="#kaguya">永远亭</a>
|
||||
<a href="#kanako">守矢神社</a>
|
||||
<a href="#satori">地灵殿</a>
|
||||
<a href="#reimu">博丽神社</a>
|
||||
</nav>
|
||||
|
||||
<section id="remilia" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>红魔馆</h2>
|
||||
<p>PlayerInfo:斯卡雷特帝国 / 领袖 蕾米莉亚 / 文明底板 埃及</p>
|
||||
</div>
|
||||
<div class="hero-copy-grid">
|
||||
<article class="copy-card">
|
||||
<h3>蕾米莉亚 · 斯卡雷特 <small>GiantType 2</small></h3>
|
||||
<p class="copy-subtitle">帝国的血之君主</p>
|
||||
<p class="copy-desc">饰演奥西里斯的鲜红幼月。红魔馆至高无上的主人,命运与威严的化身,为了那座“高雅”的冠军奖杯而参赛。</p>
|
||||
<p class="copy-diag">哼哼,这可是我重金买通裁判选到的最强阵营……你说我被骗了?不、不可能!</p>
|
||||
<p class="copy-task">任务文案线索:帝国累计金币、创造红雾领地。</p>
|
||||
<div class="copy-meta"><span>领袖</span><span>王</span><span>红雾</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>帕秋莉 · 诺蕾姬 <small>GiantType 5</small></h3>
|
||||
<p class="copy-subtitle">帝国的魔女</p>
|
||||
<p class="copy-desc">饰演克里欧帕特拉的魔女。红魔馆地下大图书馆的贤者,对纷争兴趣不高,以扩建图书馆为条件答应参赛。</p>
|
||||
<p class="copy-diag">……用“魅力”交涉的女王??那真是找错人了呢……看在皇家图书馆的份上,我勉强接受。</p>
|
||||
<p class="copy-task">任务文案线索:探索迷雾、消耗魔力石。</p>
|
||||
<div class="copy-meta"><span>后</span><span>知识</span><span>资源消耗</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>十六夜咲夜 <small>GiantType 3</small></h3>
|
||||
<p class="copy-subtitle">帝国的女仆长</p>
|
||||
<p class="copy-desc">饰演阿努比斯的人类少女。红魔馆女仆长,陪任性的大小姐一同加入游戏。</p>
|
||||
<p class="copy-diag">亡者引路人吗?不过大小姐是不会死的吧。我变得多余了呢。</p>
|
||||
<p class="copy-task">任务文案线索:探索迷雾、承受伤害、击杀敌方单位。</p>
|
||||
<div class="copy-meta"><span>相</span><span>时间</span><span>护卫</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>芙兰朵露 · 斯卡雷特 <small>GiantType 1</small></h3>
|
||||
<p class="copy-subtitle">帝国的禁忌</p>
|
||||
<p class="copy-desc">饰演赛特的恶魔之妹。被囚禁于红魔馆地下室的终极秘密,纯粹的破坏化身。</p>
|
||||
<p class="copy-diag">诶!这个叫赛特的剧本上,好像说要把姐姐大人干掉啊!嘻嘻……</p>
|
||||
<p class="copy-task">任务文案线索:占领村庄或敌城、累计击杀单位。</p>
|
||||
<div class="copy-meta"><span>马</span><span>破坏</span><span>扩张</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>红美铃 <small>GiantType 4</small></h3>
|
||||
<p class="copy-subtitle">帝国之龙</p>
|
||||
<p class="copy-desc">饰演荷鲁斯的武术家。红魔馆忠诚的门卫,因工作期间睡觉,醒来时发现自己已经在比赛现场。</p>
|
||||
<p class="copy-diag">为大小姐复仇?赴汤蹈……你说复仇的对象是妹妹大人?!我、我还得去看门……</p>
|
||||
<p class="copy-task">任务文案线索:累计造成伤害。</p>
|
||||
<div class="copy-meta"><span>车</span><span>门卫</span><span>正面战斗</span></div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="kaguya" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>永远亭</h2>
|
||||
<p>PlayerInfo:蓬莱山帝国 / 领袖 蓬莱山辉夜 / 文明底板 法兰西</p>
|
||||
</div>
|
||||
<div class="hero-copy-grid">
|
||||
<article class="copy-card">
|
||||
<h3>蓬莱山辉夜 <small>GiantType 6</small></h3>
|
||||
<p class="copy-subtitle">帝国的永远</p>
|
||||
<p class="copy-desc">饰演拿破仑的月之公主。永远亭主人,也是一位电竞高手,正在训练自家宠物打造战队。</p>
|
||||
<p class="copy-diag">这游戏目前的阵营太少了,平衡性非常糟糕,我的介绍页面也不够华丽,勉强给个好评吧。</p>
|
||||
<p class="copy-task">任务文案线索:累计造成伤害、施加状态。</p>
|
||||
<div class="copy-meta"><span>王</span><span>领袖</span><span>战队叙事</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>八意永琳 <small>GiantType 9</small></h3>
|
||||
<p class="copy-subtitle">帝国的月之头脑</p>
|
||||
<p class="copy-desc">饰演贝尔蒂埃元帅的月之头脑。永远亭最强大的贤者,配合辉夜参赛的同时借机推销新药。</p>
|
||||
<p class="copy-diag">拿破仑与贝尔蒂埃……这结局还真是令人唏嘘。突然有点怀念与公主的过往呢。</p>
|
||||
<p class="copy-task">任务文案线索:探索迷雾、提供生命回复。</p>
|
||||
<div class="copy-meta"><span>后</span><span>医疗</span><span>月都贤者</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>因幡帝 <small>GiantType 8</small></h3>
|
||||
<p class="copy-subtitle">帝国的欺诈师</p>
|
||||
<p class="copy-desc">饰演红衣主教的妖怪兔。迷途竹林的主人,奸诈而狡猾,不久前刚冒充比赛裁判敲了一笔。</p>
|
||||
<p class="copy-diag">全体目光向我看齐!“幸运豁免券”都能让你的罪孽一笔勾销!</p>
|
||||
<p class="copy-task">任务文案线索:挖掘遗迹、累计获得金币。</p>
|
||||
<div class="copy-meta"><span>相</span><span>幸运</span><span>欺诈</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>铃仙·优昙华院·因幡 <small>GiantType 7</small></h3>
|
||||
<p class="copy-subtitle">帝国的狂气之瞳</p>
|
||||
<p class="copy-desc">饰演让·拉纳元帅的月兔。作为辉夜的宠物,正在接受严格的电竞训练。</p>
|
||||
<p class="copy-diag">呜呜……我、我不会再输了!</p>
|
||||
<p class="copy-task">任务文案线索:占领村庄或敌城、创造月兔幻象、施加状态。</p>
|
||||
<div class="copy-meta"><span>马</span><span>幻象</span><span>狂气</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>藤原妹红 <small>GiantType 10</small></h3>
|
||||
<p class="copy-subtitle">帝国的不死鸟</p>
|
||||
<p class="copy-desc">饰演贝尔纳多特元帅的不死鸟。辉夜的仇敌,因为找辉夜决斗时被哄骗着参赛。</p>
|
||||
<p class="copy-diag">不管辉夜你耍什么把戏,我都会正面把你……哈?不能攻击队友?我们是队友?</p>
|
||||
<p class="copy-task">任务文案线索:累计造成伤害。</p>
|
||||
<div class="copy-meta"><span>车</span><span>不死</span><span>正面战斗</span></div>
|
||||
</article>
|
||||
</div>
|
||||
<p class="table-note">数据问题:永远亭多名英雄的 EnglishName 字段当前仍是 HONG MEILING,需要在 LibraryDataAssets 中单独修正。</p>
|
||||
</section>
|
||||
|
||||
<section id="kanako" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>守矢神社</h2>
|
||||
<p>PlayerInfo:守矢帝国 / 领袖 八坂神奈子 / 文明底板 德意志</p>
|
||||
</div>
|
||||
<div class="hero-copy-grid">
|
||||
<article class="copy-card">
|
||||
<h3>八坂神奈子 <small>GiantType 11</small></h3>
|
||||
<p class="copy-subtitle">帝国的全能神</p>
|
||||
<p class="copy-desc">饰演腓特烈二世的全能之神。风神、山神、战神与科技变革之神,正试图将比赛权利转入守矢产业名下。</p>
|
||||
<p class="copy-diag">留存和日活都很完美。流量便是信仰,游戏市场就是新的战场,是时候让守矢集团接管了。</p>
|
||||
<p class="copy-task">任务文案线索:完成科技研发、累计造成伤害。</p>
|
||||
<div class="copy-meta"><span>王</span><span>科技</span><span>信仰商业化</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>洩矢诹访子 <small>GiantType 12</small></h3>
|
||||
<p class="copy-subtitle">帝国的土著神</p>
|
||||
<p class="copy-desc">饰演路易斯·冯·梅克伦堡王后的土著神。守矢神社供奉的神之一,看到比赛后擅自给队友报名。</p>
|
||||
<p class="copy-diag">虽说路易斯王后和神奈子的角色并非一对……不过我和她确实也只是商业伙伴的关系嘛。</p>
|
||||
<p class="copy-task">任务文案线索:探索迷雾、召唤物等级、召唤物击杀。</p>
|
||||
<div class="copy-meta"><span>后</span><span>召唤</span><span>土著神</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>东风谷早苗 <small>GiantType 13</small></h3>
|
||||
<p class="copy-subtitle">帝国的现人神</p>
|
||||
<p class="copy-desc">饰演俾斯麦的现人神。守矢神社巫女、风祝与现人神,因为冠军限定机甲而参赛。</p>
|
||||
<p class="copy-diag">为了冠军机甲……咳,为了帝国的荣耀,从今天起,我就是“铁血风祝”了!</p>
|
||||
<p class="copy-task">任务文案线索:探索迷雾、施加乘风、掷出大吉或大凶。</p>
|
||||
<div class="copy-meta"><span>相</span><span>风祝</span><span>随机</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>射命丸文 <small>GiantType 14</small></h3>
|
||||
<p class="copy-subtitle">帝国的最速记者</p>
|
||||
<p class="copy-desc">饰演克劳塞维茨的鸦天狗。妖怪之山新闻记者,似乎与神奈子达成交易后加入比赛。</p>
|
||||
<p class="copy-diag">黑哨操纵!权钱交易!想看更多比赛的惊天大瓜?请订阅《文文。新闻》特刊!</p>
|
||||
<p class="copy-task">任务文案线索:占领村庄或敌城、累计造成溅射伤害。</p>
|
||||
<div class="copy-meta"><span>马</span><span>高速</span><span>媒体</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>犬走椛 <small>GiantType 15</small></h3>
|
||||
<p class="copy-subtitle">帝国的狂犬</p>
|
||||
<p class="copy-desc">饰演条顿骑士团长的白狼天狗。妖怪之山巡逻警卫,受文邀请后来参赛。</p>
|
||||
<p class="copy-diag">这个“帝国”棋似乎和将棋有几分类似……应该也能磨练思维和意志吧。</p>
|
||||
<p class="copy-task">任务文案线索:累计造成伤害、异端死亡数量。</p>
|
||||
<div class="copy-meta"><span>车</span><span>巡逻</span><span>纪律</span></div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="satori" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>地灵殿</h2>
|
||||
<p>PlayerInfo:古明地帝国 / 领袖 古明地觉 / 文明底板 印度</p>
|
||||
</div>
|
||||
<div class="hero-copy-grid">
|
||||
<article class="copy-card">
|
||||
<h3>古明地觉 <small>GiantType 16</small></h3>
|
||||
<p class="copy-subtitle">帝国的第三只眼</p>
|
||||
<p class="copy-desc">饰演罗摩的读心妖怪。原本在家看书,却发现家人突然出现在电视上,于是踏上“寻亲”之旅。</p>
|
||||
<p class="copy-diag">找回被魔王劫走的妻子悉多吗……恋是那个魔王才对吧?</p>
|
||||
<p class="copy-task">任务文案线索:施加恐惧、施加心理创伤。</p>
|
||||
<div class="copy-meta"><span>王</span><span>读心</span><span>恐惧</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>古明地恋 <small>GiantType 17</small></h3>
|
||||
<p class="copy-subtitle">帝国的本我</p>
|
||||
<p class="copy-desc">饰演奎师那的无意识妖怪。正在野外排练“大变活猫”,突然出现在比赛舞台中央。</p>
|
||||
<p class="copy-diag">…诶?大家为什么一副见鬼的表情呀?出场魔术太成功了吗,呼呼呼♪</p>
|
||||
<p class="copy-task">任务文案线索:探索迷雾、累计造成伤害。</p>
|
||||
<div class="copy-meta"><span>后</span><span>隐身</span><span>无意识</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>火焰猫燐 <small>GiantType 20</small></h3>
|
||||
<p class="copy-subtitle">帝国的地狱火车</p>
|
||||
<p class="copy-desc">饰演阿周那的猫车。陪恋排练魔术节目,被装进魔术箱后出现在比赛现场聚光灯下。</p>
|
||||
<p class="copy-diag">恋大人!这个宴会好像要“报名”才能参加哦,交给我来搞定吧!</p>
|
||||
<p class="copy-task">任务文案线索:探索迷雾、获得粗钝身/金刚身层数。</p>
|
||||
<div class="copy-meta"><span>相</span><span>猫车</span><span>承伤</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>灵乌路空 <small>GiantType 18</small></h3>
|
||||
<p class="copy-subtitle">帝国的神之火</p>
|
||||
<p class="copy-desc">饰演迦尔纳的地狱鸦。陪恋排练魔术时睡在帽子里,醒来后已成为比赛队伍成员。</p>
|
||||
<p class="copy-diag">只要把这里烧得一干二净,我们的魔术就算大成功了吧!</p>
|
||||
<p class="copy-task">任务文案线索:占领村庄或敌城、累计击杀单位。</p>
|
||||
<div class="copy-meta"><span>马</span><span>核热</span><span>破坏</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>星熊勇仪 <small>GiantType 19</small></h3>
|
||||
<p class="copy-subtitle">帝国的雷霆鬼王</p>
|
||||
<p class="copy-desc">饰演因陀罗的鬼王。意外收到地灵殿主人的请求,还没听清楚就豪爽答应。</p>
|
||||
<p class="copy-diag">交给我吧!保护三个小姑娘罢了!呃……你是说保护比赛现场的观众是嘛……</p>
|
||||
<p class="copy-task">任务文案线索:累计造成伤害。</p>
|
||||
<div class="copy-meta"><span>车</span><span>鬼王</span><span>正面战斗</span></div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="reimu" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>博丽神社</h2>
|
||||
<p>PlayerInfo:博丽帝国 / 领袖 博丽灵梦 / 文明底板 维京</p>
|
||||
</div>
|
||||
<div class="hero-copy-grid">
|
||||
<article class="copy-card">
|
||||
<h3>博丽灵梦(木偶) <small>GiantType 21</small></h3>
|
||||
<p class="copy-subtitle empty-copy">正式英雄文案待补</p>
|
||||
<p class="copy-desc">LibraryDataAssets 当前只有占位说明:大赛主办方提供的代理木偶。</p>
|
||||
<p class="copy-task">HeroDataAssets 当前未归档到正式任务线。PlayerDataAssets 的 EmpireDesc / LeaderDesc 为空。</p>
|
||||
<div class="copy-meta"><span>待设计</span><span>代理木偶</span></div>
|
||||
</article>
|
||||
</div>
|
||||
<p class="table-note">博丽神社目前只建立阵营入口,正式英雄组、图鉴描述和领袖剧情需要后续补齐。</p>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
646
Design/shared/design-doc.css
Normal file
646
Design/shared/design-doc.css
Normal file
@ -0,0 +1,646 @@
|
||||
:root {
|
||||
--bg: #f5f7fb;
|
||||
--paper: #ffffff;
|
||||
--ink: #1f2937;
|
||||
--muted: #64748b;
|
||||
--line: #dbe3ef;
|
||||
--blue: #2563eb;
|
||||
--cyan: #0891b2;
|
||||
--green: #059669;
|
||||
--orange: #d97706;
|
||||
--red: #dc2626;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Segoe UI", "Microsoft YaHei", Arial, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
a { color: var(--blue); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
.doc-shell {
|
||||
width: min(1180px, calc(100vw - 40px));
|
||||
margin: 0 auto;
|
||||
padding: 28px 0 56px;
|
||||
}
|
||||
|
||||
.doc-shell.narrow { width: min(920px, calc(100vw - 40px)); }
|
||||
|
||||
.doc-hero {
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.doc-hero.compact { padding: 24px 28px; }
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 8px;
|
||||
color: var(--cyan);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .08em;
|
||||
}
|
||||
|
||||
h1, h2, h3 { line-height: 1.25; margin: 0; }
|
||||
h1 { font-size: 34px; }
|
||||
h2 { font-size: 22px; margin-bottom: 12px; }
|
||||
h3 { font-size: 16px; margin-bottom: 8px; }
|
||||
|
||||
.lead {
|
||||
margin: 14px 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.meta-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.meta-grid div {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.meta-grid span {
|
||||
display: block;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.meta-grid strong { font-size: 14px; }
|
||||
|
||||
.doc-toc {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.doc-toc a {
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
color: var(--ink);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.doc-section {
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.doc-section.note {
|
||||
border-left: 4px solid var(--orange);
|
||||
}
|
||||
|
||||
.flow-grid,
|
||||
.callout-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.flow-grid.three,
|
||||
.callout-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.flow-grid article,
|
||||
.callout-grid article,
|
||||
.system-map a {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
background: #fbfdff;
|
||||
}
|
||||
|
||||
.flow-grid p,
|
||||
.callout-grid p { margin: 0; color: var(--muted); font-size: 14px; }
|
||||
|
||||
.split {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.split p { color: var(--muted); margin-bottom: 0; }
|
||||
|
||||
.pill-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.pill-list span {
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
border: 1px solid #bfdbfe;
|
||||
border-radius: 999px;
|
||||
padding: 4px 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.system-map {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.system-map strong,
|
||||
.system-map span { display: block; }
|
||||
.system-map span { color: var(--muted); font-size: 13px; margin-top: 4px; }
|
||||
|
||||
.doc-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.doc-table th,
|
||||
.doc-table td {
|
||||
border-bottom: 1px solid var(--line);
|
||||
padding: 10px 12px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.doc-table th {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.rule-list {
|
||||
margin: 0;
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.rule-list li { margin: 8px 0; }
|
||||
|
||||
.doc-footer {
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
padding: 8px 0 0;
|
||||
}
|
||||
|
||||
/* Design tree landing page */
|
||||
.tree-shell {
|
||||
width: min(1280px, calc(100vw - 36px));
|
||||
padding-top: 22px;
|
||||
}
|
||||
|
||||
.design-tree {
|
||||
min-height: calc(100vh - 44px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.tree-node {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
font-weight: 800;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tree-node:hover {
|
||||
text-decoration: none;
|
||||
border-color: #93c5fd;
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.tree-root {
|
||||
align-self: center;
|
||||
min-width: 220px;
|
||||
padding: 16px 26px;
|
||||
font-size: 26px;
|
||||
border-color: #93c5fd;
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.tree-l1 {
|
||||
width: 100%;
|
||||
padding: 12px 18px;
|
||||
font-size: 20px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.tree-l2 {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tree-branches {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tree-branches.two {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.tree-branches.three {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.tree-card {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: rgba(255,255,255,.74);
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.tree-card.compact {
|
||||
background: var(--paper);
|
||||
}
|
||||
|
||||
.tree-leaves {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.tree-leaves.inline {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 7px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.tree-leaves a {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 7px;
|
||||
padding: 7px 10px;
|
||||
background: #fbfdff;
|
||||
color: var(--ink);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tree-leaves a:hover {
|
||||
border-color: #bfdbfe;
|
||||
background: #eff6ff;
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.tree-group {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.tree-label {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
|
||||
/* Product principles */
|
||||
.principles-shell {
|
||||
width: min(980px, calc(100vw - 40px));
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.principles-page {
|
||||
width: 100%;
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.principles-header {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.principles-header h1 {
|
||||
font-size: 34px;
|
||||
}
|
||||
|
||||
.principles-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 0 20px;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.principles-list li {
|
||||
display: grid;
|
||||
grid-template-columns: 72px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
min-height: 86px;
|
||||
padding: 18px 22px;
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.principle-index {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 8px;
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
font-size: 18px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.principles-list strong {
|
||||
font-size: 20px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
/* Hero foundation */
|
||||
.hero-foundation-shell {
|
||||
width: min(1280px, calc(100vw - 36px));
|
||||
}
|
||||
|
||||
.class-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.class-card {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
background: #fbfdff;
|
||||
}
|
||||
|
||||
.class-rank {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 12px;
|
||||
color: #ffffff;
|
||||
font-size: 20px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.class-card p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.class-xiang .class-rank { background: #0891b2; }
|
||||
.class-hou .class-rank { background: #db2777; }
|
||||
.class-wang .class-rank { background: #ca8a04; }
|
||||
.class-che .class-rank { background: #dc2626; }
|
||||
.class-ma .class-rank { background: #7c3aed; }
|
||||
|
||||
.role-table th,
|
||||
.role-table td,
|
||||
.faction-class-table th,
|
||||
.faction-class-table td {
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.role-table td:first-child,
|
||||
.faction-class-table td:first-child {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.grade-s {
|
||||
color: #eab308;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.check {
|
||||
background: #86d47c;
|
||||
color: #ffffff;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.table-note {
|
||||
margin: 10px 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.muted-cell {
|
||||
color: var(--muted);
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
/* Narrative design archive */
|
||||
.narrative-shell {
|
||||
width: min(1320px, calc(100vw - 36px));
|
||||
}
|
||||
|
||||
.source-strip {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.source-strip span {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
background: #f8fafc;
|
||||
color: var(--muted);
|
||||
padding: 5px 9px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.faction-nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.faction-nav a {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.faction-nav a:hover {
|
||||
border-color: #bfdbfe;
|
||||
background: #eff6ff;
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.faction-section {
|
||||
scroll-margin-top: 18px;
|
||||
}
|
||||
|
||||
.faction-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.faction-head p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.hero-copy-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.copy-card {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: #fbfdff;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.copy-card h3 {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.copy-card small {
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.copy-subtitle {
|
||||
margin: 0 0 8px;
|
||||
color: var(--cyan);
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.copy-desc,
|
||||
.copy-diag,
|
||||
.copy-task {
|
||||
margin: 8px 0 0;
|
||||
color: var(--ink);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.copy-diag {
|
||||
border-left: 3px solid var(--line);
|
||||
color: #475569;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.copy-task {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.copy-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.copy-meta span {
|
||||
border-radius: 999px;
|
||||
background: #eef2ff;
|
||||
color: #3730a3;
|
||||
padding: 3px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.civilization-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(260px, .9fr) minmax(0, 1.1fr);
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.city-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 7px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.city-list span {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
padding: 5px 8px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.empty-copy {
|
||||
color: var(--muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.doc-shell,
|
||||
.doc-shell.narrow { width: min(100vw - 24px, 1180px); padding-top: 14px; }
|
||||
.doc-hero,
|
||||
.doc-section { padding: 18px; }
|
||||
h1 { font-size: 26px; }
|
||||
.meta-grid,
|
||||
.flow-grid,
|
||||
.flow-grid.three,
|
||||
.callout-grid,
|
||||
.split,
|
||||
.tree-branches.two,
|
||||
.tree-branches.three { grid-template-columns: 1fr; }
|
||||
.tree-root { width: 100%; font-size: 22px; }
|
||||
.principles-list li {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
.principles-list strong { font-size: 18px; }
|
||||
.class-grid { grid-template-columns: 1fr; }
|
||||
.civilization-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
@ -645,6 +645,125 @@ body::after {
|
||||
.filter-select { cursor: pointer; }
|
||||
.filter-select option { background: var(--bg-card); color: var(--text-primary); }
|
||||
|
||||
/* ========== Mechanics Documents ========== */
|
||||
.mechanics-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 310px minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
height: calc(100vh - 100px);
|
||||
min-height: 620px;
|
||||
}
|
||||
|
||||
.mechanics-sidebar,
|
||||
.mechanics-reader {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mechanics-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mechanics-sidebar-head {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.mechanics-title {
|
||||
font-size: 15px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.mechanics-subtitle {
|
||||
margin-top: 3px;
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.mechanics-doc-list {
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mechanics-doc-btn {
|
||||
width: 100%;
|
||||
display: block;
|
||||
text-align: left;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
padding: 11px 12px;
|
||||
margin-bottom: 8px;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.mechanics-doc-btn:hover {
|
||||
background: rgba(59,130,246,0.05);
|
||||
}
|
||||
|
||||
.mechanics-doc-btn.active {
|
||||
border-color: rgba(59,130,246,0.35);
|
||||
background: rgba(59,130,246,0.09);
|
||||
}
|
||||
|
||||
.mechanics-doc-btn.disabled {
|
||||
opacity: .45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.mechanics-doc-name,
|
||||
.mechanics-doc-summary,
|
||||
.mechanics-doc-meta {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mechanics-doc-name {
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.mechanics-doc-summary {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.mechanics-doc-meta {
|
||||
margin-top: 6px;
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.mechanics-reader {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mechanics-frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
background: #f5f7fb;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.mechanics-layout {
|
||||
grid-template-columns: 1fr;
|
||||
height: auto;
|
||||
}
|
||||
.mechanics-sidebar {
|
||||
max-height: 320px;
|
||||
}
|
||||
.mechanics-reader {
|
||||
height: 720px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== Story Module ========== */
|
||||
.story-hero-card {
|
||||
background: var(--bg-card); border: 1px solid var(--border-color);
|
||||
|
||||
@ -66,6 +66,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="20" x2="12" y2="10"/><line x1="18" y1="20" x2="18" y2="4"/><line x1="6" y1="20" x2="6" y2="16"/></svg>
|
||||
数值设计
|
||||
</button>
|
||||
<button class="sidebar-tab" data-tab="mechanics">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16v16H4z"/><path d="M9 4v16M4 9h16"/><path d="M14 14h3v3h-3z"/></svg>
|
||||
游戏机制
|
||||
</button>
|
||||
<button class="sidebar-tab" data-tab="story">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>
|
||||
文案剧情
|
||||
@ -246,6 +250,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="panel-mechanics" class="tab-panel">
|
||||
<div class="mechanics-layout">
|
||||
<aside class="mechanics-sidebar">
|
||||
<div class="mechanics-sidebar-head">
|
||||
<div class="mechanics-title">游戏机制文档</div>
|
||||
<div class="mechanics-subtitle">Design/final/mechanics</div>
|
||||
</div>
|
||||
<div id="mechanics-doc-list" class="mechanics-doc-list">
|
||||
<div class="loading-inline">正在加载机制文档...</div>
|
||||
</div>
|
||||
</aside>
|
||||
<section class="mechanics-reader">
|
||||
<iframe id="mechanics-frame" class="mechanics-frame" title="游戏机制文档"></iframe>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="panel-story" class="tab-panel">
|
||||
<!-- Sub-tabs -->
|
||||
<div class="sub-tabs" id="story-sub-tabs">
|
||||
@ -775,6 +796,7 @@
|
||||
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/balance.js"></script>
|
||||
<script src="js/mechanics.js"></script>
|
||||
<script src="js/story.js"></script>
|
||||
<script src="js/art.js"></script>
|
||||
<script src="js/sentiment.js"></script>
|
||||
|
||||
@ -683,6 +683,9 @@ function initTabs() {
|
||||
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
||||
const target = document.getElementById('panel-' + tab.dataset.tab);
|
||||
if (target) target.classList.add('active');
|
||||
if (tab.dataset.tab === 'mechanics' && typeof mechanicsLoad === 'function') {
|
||||
mechanicsLoad();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
62
Tools/Dashboard/js/mechanics.js
Normal file
62
Tools/Dashboard/js/mechanics.js
Normal file
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* TH1 Dashboard - Mechanics Design Documents
|
||||
*/
|
||||
|
||||
let mechanicsLoaded = false;
|
||||
let mechanicsDocs = [];
|
||||
|
||||
async function mechanicsLoad() {
|
||||
if (mechanicsLoaded) return;
|
||||
mechanicsLoaded = true;
|
||||
|
||||
const list = document.getElementById('mechanics-doc-list');
|
||||
const frame = document.getElementById('mechanics-frame');
|
||||
if (!list || !frame) return;
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/design/mechanics/list?t=' + Date.now());
|
||||
if (!resp.ok) throw new Error(resp.status);
|
||||
const data = await resp.json();
|
||||
mechanicsDocs = data.docs || [];
|
||||
mechanicsRenderList();
|
||||
const first = mechanicsDocs.find(d => d.exists) || mechanicsDocs[0];
|
||||
if (first) mechanicsOpen(first.file);
|
||||
} catch (e) {
|
||||
list.innerHTML = '<div class="loading-inline">机制文档加载失败</div>';
|
||||
console.warn('mechanicsLoad failed:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function mechanicsRenderList() {
|
||||
const list = document.getElementById('mechanics-doc-list');
|
||||
if (!list) return;
|
||||
if (!mechanicsDocs.length) {
|
||||
list.innerHTML = '<div class="loading-inline">暂无机制文档</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = mechanicsDocs.map((doc, index) => {
|
||||
const disabled = doc.exists ? '' : ' disabled';
|
||||
const active = index === 0 ? ' active' : '';
|
||||
const updated = doc.updatedAt ? new Date(doc.updatedAt * 1000).toLocaleDateString() : '';
|
||||
return `<button class="mechanics-doc-btn${active}${disabled}" data-file="${escHtml(doc.file)}" ${doc.exists ? '' : 'disabled'}>
|
||||
<span class="mechanics-doc-name">${escHtml(doc.title)}</span>
|
||||
<span class="mechanics-doc-summary">${escHtml(doc.summary || '')}</span>
|
||||
<span class="mechanics-doc-meta">${escHtml(doc.status || '')}${updated ? ' / ' + escHtml(updated) : ''}</span>
|
||||
</button>`;
|
||||
}).join('');
|
||||
|
||||
list.querySelectorAll('.mechanics-doc-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
list.querySelectorAll('.mechanics-doc-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
mechanicsOpen(btn.dataset.file);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function mechanicsOpen(file) {
|
||||
const frame = document.getElementById('mechanics-frame');
|
||||
if (!frame || !file) return;
|
||||
frame.src = '/api/design/mechanics/' + encodeURIComponent(file);
|
||||
}
|
||||
@ -40,6 +40,7 @@ import uuid
|
||||
import webbrowser
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
from html.parser import HTMLParser
|
||||
|
||||
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
|
||||
@ -58,6 +59,67 @@ MKT_TODOS_FILE = os.path.join(SCRIPT_DIR, '..', '..', 'DOC', 'marketing', 'todos
|
||||
OSS_DATA_DIR = os.path.join(SCRIPT_DIR, 'data', 'oss')
|
||||
SNS_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..', 'DOC', 'sns'))
|
||||
QUICK_REPLIES_FILE = os.path.join(SNS_DIR, 'quick_replies.json')
|
||||
DESIGN_MECHANICS_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..', 'Design', 'final', 'mechanics'))
|
||||
DESIGN_NARRATIVE_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..', 'Design', 'final', 'narrative'))
|
||||
DESIGN_SHARED_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..', 'Design', 'shared'))
|
||||
DESIGN_DOCS = [
|
||||
{
|
||||
'id': 'overview',
|
||||
'title': '设计树',
|
||||
'file': 'index.html',
|
||||
'status': '首版',
|
||||
'summary': '设计原则、游戏机制、文案剧情的树形入口。',
|
||||
},
|
||||
{
|
||||
'id': 'design-principles',
|
||||
'title': '设计原则',
|
||||
'file': 'design-principles.html',
|
||||
'status': '草案',
|
||||
'summary': '设计树根节点,后续承载基础原则正文。',
|
||||
},
|
||||
{
|
||||
'id': 'hero-foundation',
|
||||
'title': '基础 / 英雄',
|
||||
'file': 'hero-foundation.html',
|
||||
'status': '草案',
|
||||
'summary': '英雄基础定位与五职阶设计。',
|
||||
},
|
||||
{
|
||||
'id': 'core-loop',
|
||||
'title': '核心循环',
|
||||
'file': 'core-loop.html',
|
||||
'status': '首版',
|
||||
'summary': '回合、行动、表现和结算的设计骨架。',
|
||||
},
|
||||
{
|
||||
'id': 'hero-system',
|
||||
'title': '东方英雄系统',
|
||||
'file': 'hero-system.html',
|
||||
'status': '首版',
|
||||
'summary': '英雄作为战术核心、阵营识别和成长目标的设计说明。',
|
||||
},
|
||||
{
|
||||
'id': 'faction-system',
|
||||
'title': '阵营与文明系统',
|
||||
'file': 'faction-system.html',
|
||||
'status': '首版',
|
||||
'summary': '文明基底与东方势力层的边界。',
|
||||
},
|
||||
{
|
||||
'id': 'unit-skill-system',
|
||||
'title': '单位与技能系统',
|
||||
'file': 'unit-skill-system.html',
|
||||
'status': '首版',
|
||||
'summary': '基础单位、特色单位、英雄单位和技能分类。',
|
||||
},
|
||||
{
|
||||
'id': 'map-city-tech',
|
||||
'title': '地图、城市与科技',
|
||||
'file': 'map-city-tech.html',
|
||||
'status': '首版',
|
||||
'summary': '经营层中地块、资源、城市、科技和文化卡的关系。',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def _load_bugs():
|
||||
@ -207,6 +269,21 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
|
||||
}
|
||||
|
||||
def do_GET(self):
|
||||
if self.path.startswith('/api/design/mechanics/list'):
|
||||
self._send_json(self._get_design_mechanics_docs())
|
||||
return
|
||||
if self.path.startswith('/api/design/mechanics/doc'):
|
||||
self._handle_design_mechanics_doc()
|
||||
return
|
||||
if self.path.startswith('/api/design/mechanics/'):
|
||||
self._handle_design_mechanics_static()
|
||||
return
|
||||
if self.path.startswith('/api/design/narrative/'):
|
||||
self._handle_design_narrative_static()
|
||||
return
|
||||
if self.path.startswith('/api/design/shared/'):
|
||||
self._handle_design_shared_asset()
|
||||
return
|
||||
if self.path.startswith('/api/bugs/list'):
|
||||
self._send_json(_load_bugs())
|
||||
return
|
||||
@ -252,6 +329,91 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
|
||||
return
|
||||
super().do_GET()
|
||||
|
||||
def _get_design_mechanics_docs(self):
|
||||
docs = []
|
||||
for doc in DESIGN_DOCS:
|
||||
path = os.path.join(DESIGN_MECHANICS_DIR, doc['file'])
|
||||
item = dict(doc)
|
||||
item['exists'] = os.path.exists(path)
|
||||
item['updatedAt'] = os.path.getmtime(path) if item['exists'] else None
|
||||
docs.append(item)
|
||||
return {'base': 'Design/final/mechanics', 'docs': docs}
|
||||
|
||||
def _handle_design_mechanics_doc(self):
|
||||
parsed = urlparse(self.path)
|
||||
query = parse_qs(parsed.query)
|
||||
file_name = query.get('file', ['index.html'])[0]
|
||||
if os.path.basename(file_name) != file_name or not file_name.endswith('.html'):
|
||||
self.send_error(400, 'Invalid design document path')
|
||||
return
|
||||
path = os.path.normpath(os.path.join(DESIGN_MECHANICS_DIR, file_name))
|
||||
if not path.startswith(DESIGN_MECHANICS_DIR) or not os.path.exists(path):
|
||||
self.send_error(404, 'Design document not found')
|
||||
return
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'text/html; charset=utf-8')
|
||||
self.send_header('Content-Length', str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def _handle_design_mechanics_static(self):
|
||||
parsed = urlparse(self.path)
|
||||
file_name = parsed.path[len('/api/design/mechanics/'):]
|
||||
if os.path.basename(file_name) != file_name or not file_name.endswith(('.html', '.md')):
|
||||
self.send_error(400, 'Invalid design document path')
|
||||
return
|
||||
path = os.path.normpath(os.path.join(DESIGN_MECHANICS_DIR, file_name))
|
||||
if not path.startswith(DESIGN_MECHANICS_DIR) or not os.path.exists(path):
|
||||
self.send_error(404, 'Design document not found')
|
||||
return
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
content_type = 'text/html; charset=utf-8' if path.endswith('.html') else 'text/plain; charset=utf-8'
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', content_type)
|
||||
self.send_header('Content-Length', str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def _handle_design_narrative_static(self):
|
||||
parsed = urlparse(self.path)
|
||||
file_name = parsed.path[len('/api/design/narrative/'):]
|
||||
if os.path.basename(file_name) != file_name or not file_name.endswith('.html'):
|
||||
self.send_error(400, 'Invalid narrative document path')
|
||||
return
|
||||
path = os.path.normpath(os.path.join(DESIGN_NARRATIVE_DIR, file_name))
|
||||
if not path.startswith(DESIGN_NARRATIVE_DIR) or not os.path.exists(path):
|
||||
self.send_error(404, 'Narrative document not found')
|
||||
return
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'text/html; charset=utf-8')
|
||||
self.send_header('Content-Length', str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def _handle_design_shared_asset(self):
|
||||
parsed = urlparse(self.path)
|
||||
rel_path = parsed.path[len('/api/design/shared/'):]
|
||||
if not rel_path or '..' in rel_path.replace('\\', '/').split('/'):
|
||||
self.send_error(400, 'Invalid shared design asset path')
|
||||
return
|
||||
path = os.path.normpath(os.path.join(DESIGN_SHARED_DIR, rel_path))
|
||||
if not path.startswith(DESIGN_SHARED_DIR) or not os.path.exists(path):
|
||||
self.send_error(404, 'Shared design asset not found')
|
||||
return
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
content_type = 'text/css; charset=utf-8' if path.endswith('.css') else 'application/octet-stream'
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', content_type)
|
||||
self.send_header('Content-Length', str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def do_POST(self):
|
||||
if self.path == '/api/refresh':
|
||||
self.handle_refresh()
|
||||
@ -1669,7 +1831,7 @@ os.chdir(SCRIPT_DIR)
|
||||
print(f'Checking port {PORT} for stale processes...')
|
||||
kill_stale_servers(PORT)
|
||||
|
||||
with http.server.HTTPServer(('', PORT), DashboardHandler) as httpd:
|
||||
with http.server.ThreadingHTTPServer(('', PORT), DashboardHandler) as httpd:
|
||||
url = f'http://localhost:{PORT}'
|
||||
print(f'TH1 Dashboard serving at {url}')
|
||||
print('Press Ctrl+C to stop.')
|
||||
|
||||
@ -1,5 +1,32 @@
|
||||
@echo off
|
||||
title TH1 Dashboard
|
||||
cd /d "%~dp0"
|
||||
python serve.py
|
||||
|
||||
set "PYTHON_EXE="
|
||||
|
||||
for %%P in (
|
||||
"%LocalAppData%\Programs\Python\Python313\python.exe"
|
||||
"%LocalAppData%\Programs\Python\Python312\python.exe"
|
||||
"%LocalAppData%\Programs\Python\Python311\python.exe"
|
||||
) do (
|
||||
if not defined PYTHON_EXE if exist "%%~P" set "PYTHON_EXE=%%~P"
|
||||
)
|
||||
|
||||
if not defined PYTHON_EXE (
|
||||
for /f "delims=" %%P in ('where python 2^>nul') do (
|
||||
echo %%P | findstr /I "\\WindowsApps\\python.exe" >nul
|
||||
if errorlevel 1 if not defined PYTHON_EXE set "PYTHON_EXE=%%P"
|
||||
)
|
||||
)
|
||||
|
||||
if not defined PYTHON_EXE (
|
||||
echo Could not find a real Python installation.
|
||||
echo The WindowsApps python.exe launcher is not enough to run this dashboard.
|
||||
echo Install Python or add Python to PATH, then run this file again.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Using Python: "%PYTHON_EXE%"
|
||||
"%PYTHON_EXE%" serve.py
|
||||
pause
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user