渡己・Mlgdi

README (1)

2026-10-24 · 3461 字

# 文字暗黑 (Text Diablo) - 项目架构文档

本文档详细说明了项目的目录结构、文件作用以及核心架构设计。本项目是一个基于 React + TypeScript + Tailwind CSS 构建的纯文字 RPG 游戏。

## 1. 项目概览

* **核心框架**: React 18
* **构建工具**: Vite
* **样式库**: Tailwind CSS (通过 index.html CDN 引入)
* **图标库**: Lucide React
* **语言**: TypeScript
* **后端**: Node.js + Socket.io (集成)
* **当前版本**: 1.1.1

## 2. 目录结构

```text
/
├── index.html # HTML 入口文件,配置 Tailwind 和字体
├── index.tsx # React 挂载入口
├── App.tsx # 主控制器:包含游戏核心循环 (Loop)、状态管理 (State) 和路由逻辑
├── metadata.json # 项目元数据定义
├── types.ts # 全局 TypeScript 类型定义
├── constants.ts # 游戏常量配置(职业数据、数值平衡、音效配置)
├── config.ts # 全局游戏数值配置(倍率等)
├── effects.ts # 状态效果与传奇/暗金特效定义
├── zonesData.ts # 地图区域配置 (引用 bossRegistry)
├── bossRegistry.ts # 特殊怪物/首领注册表
│
├── components/ # UI 组件文件夹 (UI Logic)
│ ├── Sidebar.tsx # 左侧边栏:角色状态、聊天框、主菜单导航
│ ├── Town.tsx # 城镇中心视图 (Hub)
│ ├── WorldMap.tsx # 世界地图与副本选择视图
│ ├── Inventory.tsx # 背包与装备界面
│ ├── SkillTree.tsx # 技能树与技能装备界面
│ ├── Blacksmith.tsx # 铁匠铺界面(商店与精造)
│ ├── Social.tsx # 社交面板(队伍、排行榜、在线玩家)
│ ├── Statistics.tsx # 战术大厅(详细数据统计)
│ │
│ └── combat/ # 战斗界面相关组件
│ ├── CombatScene.tsx # 战斗场景:包含敌人渲染、区域信息、死亡/暂停遮罩
│ ├── LogSection.tsx # 中间区域:战斗日志与战利品拾取列表
│ ├── ControlBar.tsx # 底部区域:生命/能量球、技能栏、状态(Buff)栏
│ ├── CombatOverlays.tsx # 暂停、死亡、复活弹窗
│ └── SimulacrumWidget.tsx # 挂机状态悬浮窗
│
└── services/ # 核心逻辑服务文件夹 (Business Logic)
├── gameLogic.ts # 纯逻辑函数(伤害计算、掉落生成、属性重算)
├── audioService.ts # 音频管理服务(Web Audio API 合成器)
├── backendService.ts # 后端服务(API调用与Socket.io连接)
└── useSimulacrum.ts # 自动挂机逻辑 Hook
```

## 3. 详细文件说明与核心函数

### A. 根目录核心逻辑

* **`App.tsx`**
* **作用**: 应用程序的根组件,扮演“游戏引擎”的角色。它是唯一持有游戏核心状态(玩家、敌人、当前区域)的地方。
* **核心功能**:
* `handleCombatTick()`: 游戏主循环函数。处理战斗心跳(Tick),计算 DoT(持续伤害)、冷却时间、召唤物行为、敌人AI行动和刷新逻辑。
* `castSkill(skillId)`: 玩家施法逻辑。处理资源消耗、CD检查、目标选择、伤害计算和特效触发。
* `handleEquip/Unequip()`: 处理装备变更,并触发属性重算。
* **State**: 管理 `player` (玩家数据), `enemies` (怪物列表), `currentZone` (当前副本), `view` (当前视图路由)。

* **`types.ts`**
* **作用**: 定义游戏内的所有数据结构接口。
* **重点类型**: `Player`, `Enemy`, `Item`, `Skill`, `Zone`, `ChatMessage`, `Team`。

* **`constants.ts`**
* **作用**: 静态数据仓库。
* **重点导出**:
* `CLASS_DATA`: 定义各职业的基础属性、成长率和技能树结构。
* `LOOT_CONFIG`: 定义掉落率、金币倍率。
* `SOUND_LIBRARY`: 定义音效的频率和波形参数。

* **`config.ts`**
* **作用**: 全局调试配置。
* **功能**: 提供 `GLOBAL_GAME_CONFIG`,可实时调整经验、掉落、伤害倍率,用于 GM 命令或平衡性测试。

### B. 服务层 (Services)

* **`services/gameLogic.ts`**
* **作用**: 纯逻辑函数库(Pure Functions),负责数值计算。不直接操作 UI 或 React State。
* **核心函数**:
* `recalculatePlayerStats(player)`: 核心算法。根据基础属性、装备词缀、被动加成重新计算玩家面板(攻击力、护甲、暴击率等)。
* `calculateDamage(...)`: 伤害公式。计算 基础伤害 * 技能倍率 * 主属性 * 暴击 * 易伤 * 压制。
* `generateEnemy(...)`: 根据区域等级和稀有度生成怪物实例。
* `generateKillLoot(...)`: 击杀掉落计算(RNG)。
* `generateSingleItem(...)`: 物品生成器,处理词缀随机化、太古装备(GA)判定。
* `masterworkItem(...)`: 装备精造逻辑,处理每4级的词缀大幅提升。

* **`services/backendService.ts`**
* **作用**: 前后端通信层。
* **核心功能**:
* `connect/disconnect`: 管理 Socket.io 连接。
* `syncPlayer`: 定时向服务器同步存档。
* `emit/on`: 封装 WebSocket 事件的发送与监听。
* **Mock Mode**: 内置模拟数据生成器,在无后端环境下模拟聊天、排行榜和组队功能。

* **`services/audioService.ts`**
* **作用**: 基于 Web Audio API 的音频合成器。
* **核心函数**:
* `playSfx(type)`: 根据类型(如 `ATTACK_PHYSICAL`, `LOOT_DROP`)实时合成音效,无需加载 MP3 文件。
* `playSynthesizedSound`: 使用振荡器(Oscillator)生成波形。

* **`services/useSimulacrum.ts`**
* **作用**: 自动挂机(灵魂拟像)系统的自定义 Hook。
* **核心功能**:
* 维护一个独立的定时器循环(150ms),在后台执行索敌、施法、喝药、拾取和副本重置逻辑。

### C. UI 组件层 (Components)

#### 1. 核心视图
* **`components/Inventory.tsx`**
* **作用**: 玩家装备与背包界面。
* **重点**: 实现了物品的拖拽(Drag & Drop)逻辑、装备稀有度边框渲染、以及复杂的悬浮提示(Tooltip)组件 `ItemTooltip`。
* **`components/SkillTree.tsx`**
* **作用**: 技能加点与配置界面。
* **重点**: 根据 `CLASS_DATA` 动态渲染技能节点,处理技能升级与快捷栏装备逻辑。
* **`components/Blacksmith.tsx`**
* **作用**: 商店与强化界面。
* **重点**: 处理物品买卖、刷新商店以及装备精造(Masterworking)的 UI 交互。
* **`components/WorldMap.tsx`**
* **作用**: 副本选择界面。
* **重点**: 渲染不同类型的活动(地狱狂潮、世界首领、梦魇地下城),处理进入条件的锁定逻辑。
* **`components/Social.tsx`**
* **作用**: 社交中心。
* **重点**: 包含队伍管理(创建/邀请/踢出)、全服排行榜表格、在线玩家列表以及邀请链接生成。
* **`components/Statistics.tsx`**
* **作用**: 战术大厅。
* **重点**: 可视化展示本次游戏会话的详细数据(DPS、总承伤、掉落分布等)。

#### 2. 战斗相关 (Components/Combat)
* **`CombatScene.tsx`**
* **作用**: 战斗主画面。
* **重点**: 渲染怪物列表(普通/精英/首领),处理怪物血条、状态条(Stagger)以及飘字(Floating Text)动画。
* **`ControlBar.tsx`**
* **作用**: 底部 HUD。
* **重点**: 渲染动态液体效果的生命/能量球,展示技能冷却遮罩(Cooldown Overlay)和 Buff 状态栏。
* **`LogSection.tsx`**
* **作用**: 战斗日志与地面掉落。
* **重点**:
* 日志过滤:支持按类型(伤害、掉落、系统)筛选日志。
* 地面掉落:支持“列表模式”和“地面模式”(模拟暗黑类游戏的地面光柱效果)。
* **`AutoBattleModal.tsx` & `SimulacrumWidget.tsx`**
* **作用**: 挂机系统的配置弹窗与运行时悬浮窗。

## 4. 后端集成说明

前端通过 `backendService` 与后端进行通信。

### WebSocket 事件监听 (在 App.tsx)

* `chat:world`: 接收世界频道消息。
* `chat:team`: 接收队伍频道消息。
* `team:invite`: 接收组队邀请。
* `team:updated`: 接收队伍状态更新(加入/离开/成员变化)。
* `user_online` / `user_offline`: 实时更新在线玩家列表。

### 数据同步

* **玩家数据**: `App.tsx` 中设置了定时器(每30秒)调用 `backend.syncPlayer()` 将本地玩家状态同步至服务器。
* **排行榜**: 打开排行榜标签页时实时拉取。

## 5. 版本更新记录

* **1.1.1**:
* **退出优化**: 增加了登出二次确认。
* **组队链接**: 增加了通过 URL 参数邀请加入队伍的功能。
* **挂机系统**: 新增 `Simulacrum` (灵魂拟像) 自动战斗功能。
* **1.1.0**:
* **后端接入**: 集成了 Node.js 后端服务。
* **社交系统**: 新增 `Social` 组件,支持组队(创建/加入/邀请)、排行榜查看和在线玩家列表。
* **实时聊天**: 侧边栏聊天框接入 Socket.io。

---

## 6. 后端接口设计规范 (Backend API Specification)

本游戏采用 **RESTful API (全 POST)** 与 **WebSocket** 混合架构。

### 6.1 核心数据模型 (Data Models)

* **PlayerProfile (玩家存档)**
* **Team (队伍)**
* **ChatMessage (聊天消息)**

*(详细字段定义见 `types.ts`)*

---

### 6.2 HTTP API 接口 (All POST)

所有接口统一使用 POST 方法,请求头 `Content-Type: application/json`。

#### A. 玩家与存档 (Player)

* **登录/加载存档**: `/api/auth/login`
* **Payload**: `{ "username": "Player1" }`
* **Response**: `PlayerProfile`
```typescript
// 示例代码
const res = await fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify({ username: 'Nephalem' })
});
const playerProfile = await res.json();
```

* **存档同步**: `/api/player/sync`
* **Payload**: `{ "username": "Player1", "data": PlayerProfile }`
* **Response**: `{ "status": "ok", "timestamp": 171500000 }`
```typescript
// 示例代码
backend.syncPlayer = async (player) => {
await fetch('/api/player/sync', {
method: 'POST',
body: JSON.stringify({ username: player.name, data: player })
});
};
```

* **查询排行榜**: `/api/leaderboard/query`
* **Payload**: `{ "type": "level" | "power", "limit": 50 }`
* **Response**: `LeaderboardEntry[]`
```typescript
// 示例代码
const res = await fetch('/api/leaderboard/query', {
method: 'POST',
body: JSON.stringify({ type: 'level', limit: 20 })
});
const topPlayers = await res.json();
```

#### B. 队伍管理 (Team)

* **创建队伍**: `/api/team/create`
* **Payload**: `{ "username": "Player1", "maxMembers": 4 }`
* **Response**: `Team`
```typescript
// 示例代码
const team = await fetch('/api/team/create', {
method: 'POST',
body: JSON.stringify({ username: player.name, maxMembers: 4 })
}).then(res => res.json());
```

* **加入队伍**: `/api/team/join`
* **Payload**: `{ "username": "Player2", "teamId": "uuid" }`
* **Response**: `Team`
```typescript
// 示例代码
try {
const team = await fetch('/api/team/join', {
method: 'POST',
body: JSON.stringify({ username: player.name, teamId: inviteCode })
}).then(res => res.json());
console.log("Joined team:", team.teamId);
} catch (e) { console.error("Team full or invalid"); }
```

* **离开队伍**: `/api/team/leave`
* **Payload**: `{ "username": "Player2" }`
* **Response**: `{ "status": "ok" }`
```typescript
// 示例代码
await fetch('/api/team/leave', {
method: 'POST',
body: JSON.stringify({ username: player.name })
});
```

* **发送邀请**: `/api/team/invite/send`
* **Payload**: `{ "teamId": "...", "fromUser": "Player1", "toUser": "Player2" }`
* **Response**: `{ "status": "sent" }`
```typescript
// 示例代码
await fetch('/api/team/invite/send', {
method: 'POST',
body: JSON.stringify({ teamId: currentTeam.id, fromUser: player.name, toUser: targetUser })
});
```

---

### 6.3 WebSocket 事件定义

#### 客户端发送 (Client -> Server)

| 事件名 | Payload | 说明 | 示例代码 |
| :--- | :--- | :--- | :--- |
| `auth:identity` | `{ username: string }` | 鉴权 | `socket.emit('auth:identity', { username: 'Player1' });` |
| `chat:send` | `{ channel: "world", message: string }` | 发言 | `socket.emit('chat:send', { channel: 'world', message: 'Hello!' });` |
| `zone:enter` | `{ zoneId: string }` | 进入区域 | `socket.emit('zone:enter', { zoneId: 'zone_1' });` |

#### 服务器推送 (Server -> Client)

| 事件名 | Payload | 说明 | 示例代码 |
| :--- | :--- | :--- | :--- |
| `chat:broadcast` | `ChatMessage` | 收到消息 | `socket.on('chat:broadcast', (msg) => { addLog(msg.content); });` |
| `users:online` | `string[]` | 在线列表 | `socket.on('users:online', (list) => { setOnlineUsers(list); });` |
| `team:update` | `TeamObject` | 队伍变更 | `socket.on('team:update', (team) => { setTeam(team); });` |
| `invite:received` | `TeamInvite` | 收到邀请 | `socket.on('invite:received', (inv) => { showInviteModal(inv); });` |

---

### 6.4 组队战斗同步 (Team Combat Sync)

当队伍进入战斗区域时,启用高频 WebSocket 同步模式。

#### A. 战斗交互协议 (Socket Events)

**客户端发送 (Client -> Server)**

| 事件名 | Payload | 说明 | 示例代码 |
| :--- | :--- | :--- | :--- |
| `combat:start_zone` | `{ activityId: "nmd", level: 50 }` | 开启副本 | `socket.emit('combat:start_zone', { activityId: 'nmd', level: 100 });` |
| `combat:cast_skill` | `{ skillId: string, targetId: string }` | 施法 | `socket.emit('combat:cast_skill', { skillId: 'fireball', targetId: 'enemy_1' });` |
| `combat:use_potion` | `{}` | 喝药 | `socket.emit('combat:use_potion');` |
| `combat:revive_req` | `{ targetUsername: string }` | 复活 | `socket.emit('combat:revive_req', { targetUsername: 'DeadTeammate' });` |

**服务器推送 (Server -> Client)**

**1. 战斗快照同步 (`combat:sync_state`)**
服务器高频广播当前战场状态。
```typescript
socket.on('combat:sync_state', (snapshot: CombatSnapshot) => {
// 1. 更新怪物位置和血量
setEnemies(snapshot.enemies);

// 2. 更新队友状态 (血条、位置)
snapshot.players.forEach(pData => {
if (pData.username !== myPlayer.name) {
updateTeammateUI(pData);
}
});

// 3. 客户端平滑插值 (可选)
interpolatePositions(snapshot.tick);
});
```

**2. 战斗事件日志 (`combat:event_log`)**
用于飘字和日志显示。
```typescript
socket.on('combat:event_log', (event: CombatEvent) => {
if (event.type === 'damage') {
// 显示伤害飘字
showFloatingText(event.targetId, event.value, event.flags.isCrit ? 'crit' : 'normal');
} else if (event.type === 'death') {
// 播放死亡音效
playSfx('enemy_death');
}
});
```

**3. 掉落通知 (`combat:loot_drop`)**
```typescript
socket.on('combat:loot_drop', (data) => {
if (data.owner === myPlayer.name) {
// 只有自己能看到光柱
setLootOnGround(prev => [...prev, data.item]);
playSfx('LOOT_DROP');
}
});
```

---

### 6.5 通信加密协议 (Encryption Protocol - Draft)

> **注意**: 本协议当前版本 **尚未启用** (Status: Disabled)。仅作为 V2.0 安全版本的预留设计,用于防止中间人攻击和数据篡改。

#### A. 设计目标
* **保密性**: 游戏操作指令和状态同步使用 AES 加密。
* **完整性**: 使用 GCM 模式防止数据包被篡改。
* **前向安全**: 每次会话生成新的临时密钥。

#### B. 握手与密钥交换 (Handshake)
采用 **ECDH (X25519)** 协商会话密钥,后续通信使用 **AES-256-GCM** 加密。

1. **Client Hello**: 客户端生成临时公私钥对,发送公钥。
2. **Server Hello**: 服务器生成临时公私钥对,计算共享密钥,返回服务器公钥。
3. **Session Established**: 双方派生出对称加密密钥 (Session Key)。

#### C. 数据包结构 (Packet Structure)
所有 WebSocket `payload` 将被封装在以下 JSON 结构中传输:

```json
{
"e": 1, // encryption version
"iv": "base64_iv_string", // 12 bytes unique IV per packet
"data": "base64_encrypted_payload", // Encrypted JSON string
"tag": "base64_auth_tag" // 16 bytes GMAC tag
}
```

#### D. 实现示例 (Hypothetical Implementation)

**1. 发送加密请求 (Client -> Server)**
```typescript
// 假设 cryptoUtil 已包含 AES-GCM 封装逻辑
import { encryptPayload } from './services/cryptoUtil';

// 获取握手阶段协商好的密钥 (Session Key)
const secretKey = sessionStorage.getItem('secure_session_key');

const payload = {
skillId: "fireball",
targetId: "enemy_1",
timestamp: Date.now()
};

// 加密载荷
const encryptedPacket = await encryptPayload(payload, secretKey);

// 发送加密包
socket.emit('combat:action_secure', encryptedPacket);
```

**2. 接收解密响应 (Server -> Client)**
```typescript
import { decryptPayload } from './services/cryptoUtil';

socket.on('combat:sync_state_secure', async (packet) => {
try {
const secretKey = sessionStorage.getItem('secure_session_key');

// 解密并验证完整性
const decryptedJson = await decryptPayload(packet, secretKey);

const snapshot: CombatSnapshot = JSON.parse(decryptedJson);

// 正常处理同步逻辑
setEnemies(snapshot.enemies);
} catch (e) {
console.error("Security Warning: Decryption failed or Tampering detected", e);
// 可能需要断开连接或重新握手
}
});
```

---

## 7. 安装与部署 (Installation & Deployment)

### 7.1 环境要求
* **Node.js**: v16.0.0 或更高版本
* **npm**: v8.0.0 或更高版本

### 7.2 快速启动 (开发模式)

1. **克隆项目**
```bash
git clone https://github.com/your-repo/text-diablo.git
cd text-diablo
```

2. **安装依赖**
```bash
npm install
```

3. **启动前端 (Standalone Mode)**
默认情况下,前端配置为使用 **Mock Backend** (模拟后端),无需运行真实服务器即可体验所有功能。
```bash
npm run dev
```
访问 `http://localhost:5173` 即可开始游戏。

### 7.3 后端服务部署 (可选)

如果你希望启用真实的多人在线功能(WebSocket 聊天、组队、排行榜),你需要部署 Node.js 后端。

1. **切换配置**
打开 `services/backendService.ts`,将 `USE_MOCK_BACKEND` 设置为 `false`。
```typescript
const USE_MOCK_BACKEND = false;
```

2. **实现后端**
根据 **第 6 节 (后端接口设计规范)** 实现对应的 REST API 和 Socket.io 服务。
*(注:本项目仓库当前仅包含前端代码,后端代码需单独实现或请求服务端仓库权限)*

3. **运行后端**
确保后端服务运行在 `http://localhost:3001` (或在 `backendService.ts` 中修改 `API_URL`)。

### 7.4 生产环境构建

1. **构建前端**
```bash
npm run build
```
构建产物位于 `dist/` 目录。

2. **部署**
你可以将 `dist/` 目录下的静态文件部署到 Vercel, Netlify, GitHub Pages 或 Nginx 服务器。

### 7.5 Docker 部署 (示例)

```dockerfile
# Dockerfile
FROM node:18-alpine as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```