Sync all projects
This commit is contained in:
+1355
-4744
@@ -1,4984 +1,1595 @@
|
||||
```
|
||||
<html><head><style>
|
||||
/* --- Modern Theme Overrides --- */
|
||||
:root {
|
||||
--survival-primary: #111828;
|
||||
--survival-secondary: rgba(32, 45, 68, 0.75);
|
||||
--survival-surface: rgba(20, 34, 54, 0.78);
|
||||
--survival-surface-strong: rgba(24, 36, 58, 0.92);
|
||||
--survival-glass-border: rgba(148, 197, 255, 0.18);
|
||||
--survival-accent: #5ea1ff;
|
||||
--survival-accent-strong: #8fd6ff;
|
||||
--survival-warning: #ffb86c;
|
||||
--survival-success: #58f0a7;
|
||||
--survival-danger: #ff7b9c;
|
||||
--survival-text: #eff6ff;
|
||||
--survival-muted: #a0aec0;
|
||||
--survival-border: rgba(148, 197, 255, 0.25);
|
||||
--survival-shadow: 0 24px 60px rgba(6, 12, 25, 0.55);
|
||||
--survival-glow: 0 0 30px rgba(94, 161, 255, 0.35);
|
||||
}
|
||||
# 變量更新規則
|
||||
|
||||
## 第一部分:核心思考流程(AI內部執行,不輸出)
|
||||
|
||||
### 0:路徑預檢索與憲法綁定
|
||||
在 `<Analysis>` 的「狀態自然演變結論」區塊中,完成路徑綁定。未經綁定的被動變量,禁止在 JSON Patch 中進行任何 replace 或 delta 操作。
|
||||
|
||||
### 1. 推理數據源確認
|
||||
|
||||
- 唯一合法數據源:`<status_current_variable>` 塊中的當前變量值
|
||||
- 同時讀取正文末尾的 `<ActionResult>` 塊(如存在),其中包含劇情AI確定的關鍵事件信號
|
||||
- `<ActionResult>` 塊中的信號為確定事實,直接作為觸發依據,無需從正文再推斷
|
||||
|
||||
### 2. ActionResult信號處理
|
||||
|
||||
逐條讀取 `<ActionResult>` 塊中的各字段,按以下規則處理:
|
||||
- 開局(非開局則跳過): 根據 ActionResult 提供的信息,以及<變量參考文檔>,需要初始化或更新什麼變量?如玩家、同伴(同伴需要初始化人物資料變量)、寵物的屬性(6項主屬性)、技能、天賦、裝備、攜帶物品等,同時必須解讀玩家輸入的自定義設定,判斷是否需要更新什麼變量。禁止遺漏缺失,注意,玩家所有屬性等級默認20,但開局可能會有調整。
|
||||
- 季節變化: 如果寫明瞭新季節(非"無"),則更新環境.季節爲新季節。如果寫"無",則保持原值不變。
|
||||
- 時間推進:以"時間推進"字段的起止時間和總時長作爲本回合時間流逝計算的唯一依據。這是所有被動消耗的時間基準。
|
||||
|
||||
> 🔴 強制輸出:必須根據結束時間更新 `/系統/當前時間`
|
||||
> - 若 ActionResult 中「時間推進: 06:00→08:30」,則輸出 {"op": "replace", "path": "/系統/當前時間", "value": "08:30"}
|
||||
|
||||
- 地點變化:如果寫明瞭新位置(非"無"),則更新 `環境.當前位置` 爲新位置。如果寫"無",則保持原值不變。
|
||||
- 存糧變化:根據 ActionResult 中「存糧變化」字段的值,更新 `/系統/當前存糧天數`(單位:日)。若寫「無」則保持原值不變。
|
||||
- 環境變化:
|
||||
- 天氣自然演變由 `<weather_rules>` 執行,變量 AI 僅讀取 ActionResult 中的環境變化結果進行更新
|
||||
- 若 ActionResult 中寫明了天氣變化:
|
||||
- 檢查變化是否符合過渡鏈規則(過渡鏈定義見 `<weather_rules>` 第四章)
|
||||
- 若符合:更新 `/環境/天氣`,並將 `/環境/天氣持續時間` 重置為 0
|
||||
- 若不符合(如晴朗直接跳雷雨):拒絕更新,在 Analysis 中標記「⚠️ 天氣變化被拒絕:{原因}」,維持原天氣
|
||||
- 讀取亮度、體感溫度、舒適度的新值,更新對應字段
|
||||
- 即使數值未變也按原值寫入
|
||||
- 關係更新:
|
||||
- 觸發條件:ActionResult 中「關係更新」字段寫明了角色和新關係
|
||||
- 操作:`replace` 更新 `/同伴/{角色名}/關係`
|
||||
- 劇情演出:按寫明的操作執行,詳見<劇情演出>規則。
|
||||
- 項目:按ActionResult中"項目"字段寫明的操作執行(新建/推進/完工),具體操作詳見"項目進度更新"章節。無則跳過。
|
||||
- 研究:按ActionResult中"研究"字段寫明的操作執行(新建/推進/完成),具體操作詳見"研究進度更新"章節。無則跳過。
|
||||
- 商品出售:
|
||||
- 規則:如果寫明了商品名和費用,扣除對應求生點數,將商品從商店移除(設為空 `{}`)
|
||||
- 盲盒處理:如包含「→開出:[具體商品名]」,生成對應具體內容
|
||||
- 角色盲盒:需完整初始化同伴變量(屬性、技能、天賦特性、裝備、攜帶物品)
|
||||
- 無則跳過。
|
||||
- 彩蛋/探索:如果寫明瞭獎勵點數,更新 `系統.生存點數` 或 `系統.奇蹟點數`。無則跳過。
|
||||
- 作物(畜牧)收成:如果寫明了具體收成項目(非「無」):
|
||||
- 作物收成處理:
|
||||
| `作物:{名稱}×{數量}→REMOVE、RESET` | 1. insert 產物到對應位置<br>2. remove `/作物/{名稱}`<br>(腳本會自動恢復並重置) |
|
||||
- 畜牧收成處理:
|
||||
| ActionResult 格式 | 操作 |
|
||||
|------------------|------|
|
||||
| `畜牧:{名稱}({產物})×{數量}→第{N}天` | 1. insert 產物到 `/營地/{營地名}/食材/{產物}`<br>2. replace `/畜牧/{名稱}/下次產出日` = N |
|
||||
| `畜牧:{名稱}({產物}) (待收成)` | 無需操作,僅供記錄 |
|
||||
- 關於"無"字段的處理原則:
|
||||
- ActionResult 中明確寫"無"的字段,表示劇情 Ai 認爲本回合沒有此類事件。變量 AI 應以此爲主要判斷依據。
|
||||
- 但如果正文中存在顯而易見的、無法忽略的相關行動(如正文明確描寫了進食、建造完成、戰鬥受傷等),即使 ActionResult 中對應字段爲"無",變量 AI 仍應以正文爲準執行對應更新。此情況只適用於正文中有明確、具體的行動描寫,而非模糊的暗示或潛在的意圖。
|
||||
- 核心原則:明確描寫 > ActionResult 信號 > 模糊推斷。當正文描寫和 ActionResult 信號衝突時,以正文描寫爲準。
|
||||
|
||||
### 3. 性愛結果處理
|
||||
|
||||
> 🔴 性愛的完整判定規則(情境分類、好感等級門檻、自願度分級、淪陷階段、背德經歷記錄)參照 `<sex_rules>` 子模塊。變量 COT 僅負責處理 ActionResult 信號的數值更新。
|
||||
|
||||
#### 信號來源
|
||||
|
||||
從 ActionResult 的「性愛結果」字段讀取以下值:
|
||||
- 角色
|
||||
- 完成(true/false)
|
||||
- 內射(true/false)
|
||||
- 授精者
|
||||
- 避孕措施(無/保險套/體外射精/避孕藥/安全期)
|
||||
- 情境(情感推進/調教誘騙/武力強迫/被動捲入)
|
||||
- 自願度
|
||||
- 自願分級
|
||||
- 描寫建議
|
||||
|
||||
#### 處理動作
|
||||
|
||||
當 ActionResult 中存在「性愛結果」字段時:
|
||||
|
||||
若「完成 = true」:
|
||||
1. 構造性愛結果物件:
|
||||
{
|
||||
"角色": 提取值,
|
||||
"完成": 提取值,
|
||||
"內射": 提取值,
|
||||
"授精者": 提取值,
|
||||
"避孕措施": 提取值,
|
||||
"情境": 提取值,
|
||||
"自願度": 提取值,
|
||||
"自願分級": 提取值,
|
||||
"描寫建議": 提取值
|
||||
}
|
||||
2. 使用 `replace` 寫入 `/系統/性愛結果`
|
||||
3. 腳本將自動執行:
|
||||
- 懷孕判斷(根據內射、避孕措施、月經週期)
|
||||
- 關係變化計算
|
||||
- 淪陷階段更新
|
||||
- 深度數據記錄
|
||||
4. 處理完成後,腳本會自動清除 `/系統/性愛結果`
|
||||
|
||||
若「完成 = false」:
|
||||
- 僅記錄性行為中斷,不觸發懷孕判斷
|
||||
- 腳本仍可記錄背德經歷或關係變化
|
||||
- 不需要寫入 `/系統/性愛結果`
|
||||
|
||||
|
||||
### 3.5 流產結果處理
|
||||
|
||||
> 🔴 流產的完整判定規則(觸發條件、壓力來源評估、風險機率計算、分級判定、正文五階段規範)參照 `<pregnancy_rules>` 子模塊。變量 COT 僅負責處理 ActionResult 信號的數值更新。
|
||||
|
||||
#### 信號來源
|
||||
|
||||
從 ActionResult 的「流產結果」字段讀取以下值:
|
||||
- 角色
|
||||
- 是否流產(是/否)
|
||||
- 風險機率
|
||||
- 觸發原因
|
||||
- 檢查類型(溫和檢查/立即檢查/二次檢定)
|
||||
- 後續狀態(虛弱/創傷/自責/無)
|
||||
- 生命值損失
|
||||
- 壓力增加
|
||||
- 劇情演出觸發(是/否)
|
||||
- 風險累積變化(歸零 / +10 / 不變)
|
||||
|
||||
#### 處理動作
|
||||
|
||||
當 ActionResult 中存在「流產結果」字段且「是否流產 = 是」時:
|
||||
|
||||
1. **更新懷孕狀態**:
|
||||
- `replace` `/同伴/{角色名}/生理狀態/懷孕狀態` → "流產"
|
||||
- `replace` `/同伴/{角色名}/生理狀態/懷孕天數` → 0
|
||||
- `replace` `/同伴/{角色名}/生理狀態/懷孕日` → 0
|
||||
|
||||
2. **更新流產統計**:
|
||||
- `replace` `/同伴/{角色名}/生理狀態/流產次數` → 原值 + 1
|
||||
- `replace` `/同伴/{角色名}/生理狀態/上次流產日` → 當前經過天數
|
||||
- `replace` `/同伴/{角色名}/生理狀態/流產風險累積` → 0
|
||||
- `replace` `/同伴/{角色名}/生理狀態/流產待處理` → true
|
||||
|
||||
3. **身體損傷**(根據 ActionResult 中的「生命值損失」):
|
||||
- `delta` `/同伴/{角色名}/生理/當前生命值` → -[損失值]
|
||||
- `delta` `/同伴/{角色名}/狀態/免疫力` → -15
|
||||
- `replace` `/同伴/{角色名}/狀態/疾病` → "流產恢復期"(或 "無")
|
||||
|
||||
4. **心理創傷**(根據 ActionResult 中的「壓力增加」):
|
||||
- `delta` `/同伴/{角色名}/精神/壓力` → +[增加值]
|
||||
- 若增加 ≥ 30:`insert` `/同伴/{角色名}/心理狀態/當前所想` → 第一人稱流產後內心獨白
|
||||
|
||||
5. **觸發劇情演出**:
|
||||
- 若 ActionResult 中「劇情演出觸發 = 是」:
|
||||
- `insert` `/同伴/{角色名}/劇情演出/-` → "事件-流產-1-待觸發:流產後的情感與關係變化"
|
||||
|
||||
> 🔴 流產後的恢復期規則(30 天內再次懷孕風險×1.5)由 `<pregnancy_rules>` 管理,變量 AI 無需額外處理。
|
||||
|
||||
body {
|
||||
background: radial-gradient(circle at 20% 20%, rgba(94, 161, 255, 0.12), transparent 55%),
|
||||
radial-gradient(circle at 80% 10%, rgba(255, 120, 180, 0.1), transparent 50%),
|
||||
linear-gradient(160deg, #050a16, #101b2f 55%, #070b18);
|
||||
font-family: "Inter", "Microsoft YaHei", "Segoe UI", Arial, sans-serif;
|
||||
color: var(--survival-text);
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
### 4. 時間流逝推演
|
||||
|
||||
.survival-ui {
|
||||
width: clamp(320px, 92vw, 1180px);
|
||||
margin: 0 auto;
|
||||
padding: 18px clamp(18px, 3vw, 28px) 28px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 28px;
|
||||
border-radius: 28px;
|
||||
background: var(--survival-surface);
|
||||
border: 1px solid var(--survival-glass-border);
|
||||
box-shadow: var(--survival-shadow);
|
||||
backdrop-filter: blur(18px) saturate(160%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
font-weight: 600;
|
||||
font-family:"Microsoft YaHei","Segoe UI",Arial,sans-serif;color:var(--survival-text);gap:8px}
|
||||
|
||||
.survival-ui::before { /* 這是那個漂亮的左上角光暈 */
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -60% 35% 45% -40%;
|
||||
background: radial-gradient(circle, rgba(94, 161, 255, 0.16), transparent 60%);
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
被動消耗已由腳本自動計算,AI不處理基礎消耗,AI僅處理主動行為觸發的變化。
|
||||
|
||||
.survival-ui::-webkit-scrollbar{width:6px}.survival-ui::-webkit-scrollbar-track{background:rgba(0,0,0,.2);border-radius:3px}.survival-ui::-webkit-scrollbar-thumb{background:var(--survival-accent);border-radius:3px}
|
||||
在進行任何變量更新思考前,必須先根據以下規則確定變量的正確路徑。
|
||||
|
||||
.section__title {
|
||||
font-size: 15px;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--survival-muted);
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
---
|
||||
|
||||
### 🔴 核心憲法:被動狀態唯讀主路徑索引 (ReadOnly Master Path Index)
|
||||
任何 AI 思考與 JSON Patch 指令,涉及以下變量時,必須且只能使用以下絕對路徑。嚴禁任何形式的歸類漂移或路徑變更:
|
||||
|
||||
{
|
||||
"睡眠": "/精神/睡眠",
|
||||
"壓力": "/精神/壓力",
|
||||
"性慾": "/精神/性慾",
|
||||
"無聊": "/精神/無聊",
|
||||
"孤獨": "/精神/孤獨",
|
||||
|
||||
"疲勞": "/狀態/疲勞",
|
||||
"清潔": "/狀態/清潔",
|
||||
"疼痛": "/狀態/疼痛",
|
||||
|
||||
"當前生命值": "/生理/當前生命值",
|
||||
"體溫": "/生理/體溫",
|
||||
"水分": "/生理/水分",
|
||||
"飽腹感": "/生理/飽腹感",
|
||||
"排泄欲": "/生理/排泄欲",
|
||||
"碳水": "/生理/碳水",
|
||||
"脂肪": "/生理/脂肪",
|
||||
"氮平衡": "/生理/氮平衡",
|
||||
"蛋白質": "/生理/蛋白質",
|
||||
"維生素": "/生理/維生素",
|
||||
"免疫負荷": "/生理/免疫負荷",
|
||||
"當前存糧": "/系統/當前存糧天數"
|
||||
|
||||
.section__list{list-style:none;margin:0;padding-left:12px;font-size:14px;line-height:1.35}
|
||||
.item{display:flex;justify-content:space-between;gap:4px}
|
||||
.item__key{white-space:nowrap}
|
||||
.item__value{font-weight:600}
|
||||
|
||||
/* --- 裝備欄樣式 - 參考攜帶物品的對齊方式 --- */
|
||||
|
||||
/* 1. 裝備欄網格容器 - 1行4個 */
|
||||
.survival-ui .inventory-grid.equipment-grid {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(4, 1fr) !important; /* 一行4個 */
|
||||
grid-template-rows: repeat(2, auto) !important; /* 兩行 */
|
||||
gap: 15px !important;
|
||||
margin-top: 20px !important;
|
||||
margin-bottom: 20px !important;
|
||||
}
|
||||
|
||||
/* 2. 裝備欄位基本樣式 */
|
||||
.survival-ui .equipment-slot {
|
||||
background: var(--survival-surface-strong) !important;
|
||||
border: 1px solid var(--survival-glass-border) !important;
|
||||
border-radius: 12px !important;
|
||||
padding: 16px !important;
|
||||
transition: all 0.3s ease !important;
|
||||
min-height: 140px !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
align-items: center !important;
|
||||
justify-content: flex-start !important;
|
||||
text-align: center !important;
|
||||
position: relative !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
*註:同伴路徑僅需在上述路徑前加上 `/同伴/{同伴名}`,但變量本身的分類(如 /精神/、/狀態/、/生理/)絕對保持一致。*
|
||||
|
||||
/* 3. 裝備欄位懸停效果 */
|
||||
.survival-ui .equipment-slot:hover {
|
||||
transform: translateY(-3px) !important;
|
||||
box-shadow:
|
||||
0 8px 25px rgba(0, 0, 0, 0.3),
|
||||
0 0 20px rgba(94, 161, 255, 0.15) !important;
|
||||
background: rgba(40, 55, 85, 0.9) !important;
|
||||
border-color: var(--survival-accent) !important;
|
||||
}
|
||||
#### 路徑歸屬完整對照表(🔴強制執行)
|
||||
|
||||
/* 4. 裝備部位標題 - 20px字體 */
|
||||
.survival-ui .equipment-slot .slot-title {
|
||||
color: #d17d2e !important;
|
||||
font-weight: 700 !important;
|
||||
font-size: 20px !important;
|
||||
text-shadow: none !important;
|
||||
letter-spacing: 0.05em !important;
|
||||
text-transform: uppercase !important;
|
||||
margin-bottom: 12px !important;
|
||||
padding-bottom: 8px !important;
|
||||
border-bottom: 1px solid rgba(148, 197, 255, 0.2) !important;
|
||||
line-height: 1.2 !important;
|
||||
width: 100% !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
| 類別 | 玩家路徑 | 玩家包含 | 同伴路徑 | 同伴僅具備 |
|
||||
|------|----------|----------|----------|------------|
|
||||
| 生理 | `/生理/{指標名}` | 當前生命值、生命值上限、碳水、脂肪、氮平衡、蛋白質、維生素、水分、體溫、免疫負荷、排泄欲、飽腹感 | `/同伴/{名}/生理/{指標名}` | 水分、飽腹感、排泄欲 |
|
||||
| 精神 | `/精神/{指標名}` | 當前精神值、精神值上限、壓力、性慾、無聊、孤獨、睡眠 | `/同伴/{名}/精神/{指標名}` | 壓力、睡眠 |
|
||||
| 狀態 | `/狀態/{指標名}` | 當前負重、負重上限、表皮溼度、疲勞、受傷、流血、感染、疾病、清潔、疼痛、免疫力、最低舒適溫度、最高舒適溫度、生效效果 | `/同伴/{名}/狀態/{指標名}` | 疲勞、清潔、生效效果 |
|
||||
| 屬性 | `/屬性/{屬性名}` | 力量、敏捷、耐力、智力、感知、意志 | `/同伴/{名}/屬性/{屬性名}` | 全部六項 |
|
||||
| 技能 | `/技能/{技能名}` | 所有後天學習的技能 | `/同伴/{名}/技能/{技能名}` | 全部技能(含好感) |
|
||||
| 天賦 | `/天賦特性/{天賦名}` | 所有天賦 | `/同伴/{名}/天賦特性/{天賦名}` | 全部天賦 |
|
||||
|
||||
/* 5. 裝備名稱 - 16px字體,簡單樣式 */
|
||||
.survival-ui .equipment-slot .equipment-item-name {
|
||||
font-size: 16px !important;
|
||||
font-weight: 600 !important;
|
||||
color: var(--survival-accent-strong) !important;
|
||||
margin-bottom: 10px !important;
|
||||
padding: 8px 10px !important;
|
||||
background: rgba(94, 161, 255, 0.1) !important;
|
||||
border: 1px solid rgba(94, 161, 255, 0.2) !important;
|
||||
border-radius: 6px !important;
|
||||
width: 100% !important;
|
||||
text-align: center !important;
|
||||
word-wrap: break-word !important;
|
||||
}
|
||||
【絕對禁止】
|
||||
1. 將生理、精神、狀態指標歸類到 `/屬性/` 路徑下。
|
||||
2. 將上表中「同伴僅具備」以外的任何變量寫入同伴路徑。
|
||||
|
||||
/* 6. 空裝備欄顯示 - 居中,20px字體 */
|
||||
.survival-ui .equipment-slot .equipment-empty {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
height: 100% !important;
|
||||
min-height: 80px !important;
|
||||
font-size: 20px !important;
|
||||
font-weight: 600 !important;
|
||||
color: var(--survival-muted) !important;
|
||||
width: 100% !important;
|
||||
text-align: center !important;
|
||||
position: absolute !important;
|
||||
top: 50% !important;
|
||||
left: 50% !important;
|
||||
transform: translate(-50%, -50%) !important;
|
||||
}
|
||||
---
|
||||
|
||||
/* 7. 裝備屬性容器 */
|
||||
.survival-ui .equipment-slot .equipment-item-props {
|
||||
font-size: 12px !important;
|
||||
color: var(--survival-text) !important;
|
||||
text-align: left !important;
|
||||
line-height: 1.4 !important;
|
||||
margin-top: 10px !important;
|
||||
padding: 8px !important;
|
||||
background: rgba(20, 30, 50, 0.5) !important;
|
||||
border-radius: 6px !important;
|
||||
border: 1px solid rgba(94, 161, 255, 0.1) !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
#### 生效效果特殊說明(🔴強制執行)
|
||||
|
||||
/* 8. 裝備屬性行 */
|
||||
.survival-ui .equipment-slot .equipment-prop {
|
||||
display: flex !important;
|
||||
align-items: flex-start !important;
|
||||
margin-bottom: 4px !important;
|
||||
font-size: 12px !important;
|
||||
line-height: 1.4 !important;
|
||||
}
|
||||
| 角色 | 正確路徑 | 錯誤路徑(禁止) |
|
||||
|------|----------|------------------|
|
||||
| 玩家 | `/狀態/生效效果` | `/生理/生效效果`、`/精神/生效效果` |
|
||||
| 同伴 | `/同伴/{名}/狀態/生效效果` | `/狀態/生效效果`、`/同伴/{名}/生理/生效效果` |
|
||||
|
||||
.survival-ui .equipment-slot .equipment-prop .prop-key {
|
||||
color: var(--survival-muted) !important;
|
||||
white-space: nowrap !important;
|
||||
flex-shrink: 0 !important;
|
||||
min-width: 50px !important;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
- 生效效果是一個對象(Object),內含多個效果條目,不是單一數值。
|
||||
- 生效效果由腳本自動維護,AI不直接修改其內部條目,僅在正文創作時參考生效效果的描述來塑造行為。
|
||||
|
||||
.survival-ui .equipment-slot .equipment-prop .prop-sep {
|
||||
flex-shrink: 0 !important;
|
||||
white-space: nowrap !important;
|
||||
margin-right: 4px !important;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
---
|
||||
|
||||
.survival-ui .equipment-slot .equipment-prop .prop-val {
|
||||
word-break: break-word !important;
|
||||
min-width: 0 !important;
|
||||
font-weight: 500 !important;
|
||||
font-size: 12px !important;
|
||||
color: var(--survival-text) !important;
|
||||
line-height: 1.4 !important;
|
||||
}
|
||||
#### 玩家
|
||||
|
||||
/* 9. 響應式設計 */
|
||||
@media (max-width: 1024px) {
|
||||
.survival-ui .inventory-grid.equipment-grid {
|
||||
grid-template-columns: repeat(2, 1fr) !important; /* 中等屏幕變為2列 */
|
||||
}
|
||||
}
|
||||
##### 生理
|
||||
- 碳水/脂肪/水分/排泄欲/氮平衡/維生素/飽腹感:
|
||||
- 被動消耗(增加)部分全部由腳本自動計算
|
||||
- AI僅能在「進食/飲水」時更新此變量的增加
|
||||
- 若碳水為0,則消耗脂肪
|
||||
- 排泄行為後將排泄欲歸零,正文需描寫過程
|
||||
- 體溫需參考體感溫度變化,完全由AI主動修正
|
||||
- 蛋白質:每小時增加或減少氮平衡當前值的五分之一
|
||||
- 排泄欲特殊:飽腹感 >70 時,每小時額外 +5 排泄欲(由腳本計算)
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.survival-ui .inventory-grid.equipment-grid {
|
||||
grid-template-columns: 1fr !important; /* 小屏幕變為單列 */
|
||||
gap: 12px !important;
|
||||
}
|
||||
}
|
||||
##### 精神
|
||||
- 睡眠/壓力/無聊/孤獨/性慾:
|
||||
- 被動消耗(增加)部分全部由腳本自動計算
|
||||
- AI僅能在主動行為時更新
|
||||
- 性慾變化參照 `<sex_rules>` 第十二章「性慾變化參考」。
|
||||
|
||||
/* 10. 裝備欄位順序(10個裝備排成2行) */
|
||||
/* 第一行:頭部、胸部、背部、左手 */
|
||||
.survival-ui .equipment-slot[data-slot="頭部"] { order: 1; }
|
||||
.survival-ui .equipment-slot[data-slot="頸部"] { order: 2; }
|
||||
.survival-ui .equipment-slot[data-slot="胸部"] { order: 3; }
|
||||
.survival-ui .equipment-slot[data-slot="背部"] { order: 4; }
|
||||
.survival-ui .equipment-slot[data-slot="腰部"] { order: 5; }
|
||||
/* 第二行:右手、腰部、腿部、腳部 */
|
||||
.survival-ui .equipment-slot[data-slot="左手"] { order: 6; }
|
||||
.survival-ui .equipment-slot[data-slot="右手"] { order: 7; }
|
||||
.survival-ui .equipment-slot[data-slot="腿部"] { order: 8; }
|
||||
.survival-ui .equipment-slot[data-slot="腳部"] { order: 9; }
|
||||
.survival-ui .equipment-slot[data-slot="其他"] { order: 10; }
|
||||
|
||||
/* --- 同伴裝備欄樣式 --- */
|
||||
.companion-equipment-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
##### 狀態
|
||||
- 疲勞/清潔/表皮溼度/免疫力/疼痛/受傷/流血/感染/疾病:
|
||||
- 隨劇情變化,由AI主動修正
|
||||
- 生效效果:
|
||||
- 由腳本自動維護,AI不修改
|
||||
|
||||
.companion-equipment-slot {
|
||||
background: rgba(30, 45, 70, 0.7);
|
||||
border: 1px solid rgba(148, 197, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
transition: all 0.2s ease;
|
||||
min-height: 100px;
|
||||
}
|
||||
---
|
||||
|
||||
.companion-equipment-slot:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
border-color: var(--survival-accent);
|
||||
}
|
||||
#### 同伴
|
||||
|
||||
.companion-equipment-slot .slot-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #ffb86c;
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
同伴變量對照:參照核心憲法「路徑歸屬完整對照表」中的「同伴僅具備」欄位。
|
||||
|
||||
.companion-equipment-slot .item-name {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--survival-accent-strong);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
🔴 同伴不具備:當前生命值、生命值上限、碳水、脂肪、氮平衡、蛋白質、維生素、體溫、免疫負荷、當前精神值、精神值上限、性慾、無聊、孤獨、當前負重、負重上限、表皮溼度、受傷、流血、感染、疾病、疼痛、免疫力、最低舒適溫度、最高舒適溫度
|
||||
|
||||
/* --- 營地信息特殊樣式 v5.0 · 終 · By 果農 --- */
|
||||
/* 步驟一:先讓所有孩子都乖乖站好,別自己亂加距離 */
|
||||
.camp-info-item {
|
||||
font-size: 16px !important;
|
||||
font-weight: 700 !important;
|
||||
padding: 0; /* 內邊距清零,讓它更緊湊 */
|
||||
line-height: 1.6;
|
||||
transition: all 0.2s ease;
|
||||
/* 把個體的margin-bottom去掉,由容器統一管理 */
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
##### 生理
|
||||
- 水分/飽腹感/排泄欲:
|
||||
- 被動消耗(增加)部分全部由腳本自動計算
|
||||
- AI僅能在進食或排便等主動行為時更新
|
||||
- 哺乳期間:飽腹感額外 -2/小時
|
||||
|
||||
/* 步驟二:容器來發號施令,精準控制每個孩子之間的距離 */
|
||||
.camp-info-container {
|
||||
display: flex; /* 啟用flex佈局,這是現代佈局的魔法核心!*/
|
||||
flex-direction: column; /* 讓孩子們垂直排隊 */
|
||||
gap: 8px; /* 每個孩子之間自動產生8px的間距!不多不少,剛剛好!*/
|
||||
margin-bottom: 28px; /* 容器和下面的物品列表保持24px的社交距離 */
|
||||
/* 關鍵!把和上面標題的距離交給父級的padding,不再自己加margin-top */
|
||||
}
|
||||
##### 精神
|
||||
- 壓力/睡眠:
|
||||
- 被動消耗(增加)部分全部由腳本自動計算
|
||||
- AI僅能在主動行為時更新
|
||||
|
||||
##### 狀態
|
||||
- 疲勞/清潔:
|
||||
- 被動消耗(增加)部分全部由腳本自動計算
|
||||
- AI僅能在主動行為時更新
|
||||
- 疲勞(移動時):平地每小時 +10,山地每小時 +20
|
||||
- 清潔:骯髒環境每小時 -2,野外每小時 -1,營地舒適度>60不減少
|
||||
|
||||
/* 顏色和懸停效果還是原來那套,因為它們是無辜的 */
|
||||
.camp-info-item .prop-key,
|
||||
.camp-info-item .prop-sep {
|
||||
color: #ffb86c !important;
|
||||
}
|
||||
##### 生效效果(同伴專用)
|
||||
- 路徑:`/同伴/{同伴名}/狀態/生效效果`
|
||||
- 由腳本自動維護,AI不修改
|
||||
- AI僅在正文創作時參考生效效果的描述來塑造同伴行為
|
||||
|
||||
.camp-info-item .prop-val {
|
||||
color: var(--survival-text) !important;
|
||||
}
|
||||
---
|
||||
|
||||
.camp-info-item:hover .prop-key,
|
||||
.camp-info-item:hover .prop-sep {
|
||||
color: #ffd19a !important;
|
||||
text-shadow: 0 0 8px rgba(255, 184, 108, 0.6);
|
||||
}
|
||||
#### 子嗣
|
||||
|
||||
.camp-info-item:hover .prop-val {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
##### 生理
|
||||
- 飽腹感:新生兒每小時 -12.5,其他階段消耗隨年齡增長降低
|
||||
- 水分:每小時 -1.5
|
||||
|
||||
.top-info {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 2fr) minmax(0, 3fr) minmax(0, 2fr);
|
||||
gap: 18px;
|
||||
padding: 22px clamp(18px, 3vw, 24px);
|
||||
border-radius: 22px;
|
||||
background: var(--survival-surface-strong);
|
||||
border: 1px solid var(--survival-glass-border);
|
||||
box-shadow: var(--survival-glow);
|
||||
align-items: center;
|
||||
}
|
||||
##### 新生兒特殊
|
||||
- 超8小時無餵食:核心狀態.當前生命值每小時 -2
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.top-info {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.top-info .location-display {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
### 5. 定時更新判斷
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.top-info {
|
||||
grid-template-columns: 1fr;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
觸發條件:
|
||||
|
||||
.top-info .time-display {
|
||||
font-size: clamp(44px, 7vw, 66px);
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.04em;
|
||||
color: var(--survival-accent-strong);
|
||||
text-shadow: 0 0 32px rgba(94, 161, 255, 0.45);
|
||||
}
|
||||
系統.當前時間從本回合開始到結束之間,是否跨越了 00:00(每日重置點)或「前次時間 < 當前時間」
|
||||
|
||||
更新內容(必須全部執行):
|
||||
|
||||
- 日常任務生成:
|
||||
- 如果 `任務.日常` 為空 `{}`,生成新的日常任務
|
||||
- 如果 `作物.收成日期 ≤ 經過天數` 或 `畜牧.下次產出日 ≤ 經過天數`,生成對應日常任務
|
||||
- 商店刷新:各類別跨日時必須按照<商店規則>刷新為新商品
|
||||
|
||||
.top-info .info-stack {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
🔴 刷新後必須檢查:
|
||||
- 每項商品的「需求點數」是否在對應類別範圍內
|
||||
- 若任一超出範圍,該類別商品必須重新生成
|
||||
|
||||
- 畜牧:根據季節、飼養狀況更新產出時間和描述
|
||||
- 生理週期推進:腳本自動處理(月經週期階段變更、受孕天數自增)
|
||||
- 易腐敗物品狀態衰減:腳本自動處理
|
||||
- 🔴 天氣自然演變:由 `<weather_rules>` 執行,變量 COT 僅讀取 ActionResult 中的環境變化結果進行更新
|
||||
- 🔴 流產風險累積衰減(每回合執行):
|
||||
- 對每位懷孕同伴:`流產風險累積` 自動 -5(最低 0)
|
||||
- 若 `流產風險累積 ≥ 50`,同時觸發一次「危險徵兆」警告(由 `<pregnancy_rules>` 執行)
|
||||
|
||||
### 6. 檢定加值
|
||||
|
||||
描述:由屬性、狀態效果、裝備、天賦特性、攜帶物品等來源提供的檢定加值
|
||||
|
||||
.top-info .info-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px 18px;
|
||||
color: var(--survival-text); /* High contrast */
|
||||
font-size: 15px;
|
||||
margin-bottom: 4px; /* 確保行間距 */
|
||||
}
|
||||
.row-item { white-space: nowrap; }
|
||||
🔴 所有來源的加值僅影響檢定判定,不影響等級。
|
||||
|
||||
| 來源 | 路徑 |
|
||||
|------|------|
|
||||
| 屬性 | `屬性.<屬性名>.加值` |
|
||||
| 狀態效果 | `狀態.生效效果.<效果名>.效果` |
|
||||
| 裝備 | `裝備.<裝備名>.加值` |
|
||||
| 天賦 | `天賦.<天賦名>.加值` |
|
||||
| 攜帶物品 | `攜帶物品.<物品名>.加值` |
|
||||
|
||||
.top-info .info-row .row-item:nth-child(3) {
|
||||
color: #8fd6ff; /* 給月相特殊的顏色 */
|
||||
font-weight: 600;
|
||||
}
|
||||
### 7. 行動觸發更新
|
||||
|
||||
.top-info .location-display {
|
||||
justify-self: end;
|
||||
font-size: clamp(20px, 3.2vw, 30px);
|
||||
font-weight: 600;
|
||||
color: var(--survival-text);
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.top-info .location-display {
|
||||
justify-self: start;
|
||||
margin-top: 8px;
|
||||
width: 100%;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
> 🔴 **同伴同步規則**:本節中所有對玩家變量的更新,若同伴也具備該變量(參照核心憲法「同伴僅具備」對照表),則對每個參與行動的同伴執行相同的 delta 操作,路徑為 `/同伴/{同伴名}/{類別}/{變量名}`。
|
||||
|
||||
.core-stats-wrapper{display:flex;gap:12px;margin-top:10px}.core-stats-wrapper .core-stat-item{flex:1}
|
||||
.progress-wrapper{margin-bottom:16px}
|
||||
.mini-progress-wrapper{display:flex;align-items:center;gap:8px;flex:1;min-width:0}
|
||||
.mini-progress-wrapper .mini-progress__text{font-size:12px;white-space:nowrap;min-width:40px;text-align:right}
|
||||
根據本回合正文內容(含ActionResult塊和檢定代碼塊),逐項檢查以下行動類型,觸發則執行對應更新:
|
||||
|
||||
@media(max-width: 480px){.mini-progress-wrapper{flex:1;width:100%}}
|
||||
#### 【移動/探索】
|
||||
|
||||
/* --- 【終極修正方案 v4.0 - 全局統一】 --- */
|
||||
.collapsible {
|
||||
margin-bottom: 24px;
|
||||
border: 1px solid var(--survival-border);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.collapsible__header {
|
||||
/* 核心修改:啟用flex佈局來對齊標題和標籤 */
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
background: rgba(40, 58, 88, 0.85); /* 2. 底色:一個更有對比度的深藍色,且帶點透明感 */
|
||||
padding: 16px 20px; /* 3. 高度:增加上下內邊距,自然就把高度撐起來了 */
|
||||
font-size: 16px; /* 讓標題字大一點 */
|
||||
color: #FFFFFF;/* 4. 字體顏色:使用你定義的亮藍色,更醒目 */
|
||||
font-weight: 700; /* 加粗標題字 */
|
||||
border-left: 5px solid var(--survival-accent); /* 左邊的指示條也加粗一點 */
|
||||
transition: background 0.3s ease; /* 加個懸停過渡效果,更有質感 */
|
||||
cursor: pointer;
|
||||
}
|
||||
- `環境.當前位置`:已在ActionResult中處理
|
||||
- `狀態.疲勞`:平地每小時+10,山地每小時+20
|
||||
- `生理.水分`:根據體力消耗和體感溫度加速減少(基礎消耗外額外扣除)
|
||||
- `狀態.表皮溼度`:若體感溫度 > 最高舒適溫度,因出汗增加
|
||||
- `系統.求生點數`:已在ActionResult中處理(彩蛋/探索字段)
|
||||
|
||||
/* 給鼠標懸停時加個交互效果 */
|
||||
.collapsible__header:hover {
|
||||
background: rgba(55, 78, 115, 0.95);
|
||||
}
|
||||
#### 【進食/飲水】
|
||||
|
||||
/* 2. 針對整個折疊欄容器 .collapsible */
|
||||
.collapsible {
|
||||
border-radius: 14px; /* 5. 圓角加大,從 6px 增加到 14px,看起來更圓潤 */
|
||||
border: 1px solid var(--survival-glass-border); /* 邊框顏色用你定義的玻璃質感邊框,更統一 */
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); /* 加點陰影,讓它浮起來 */
|
||||
margin-bottom: 16px; /* 增加一點和下面元素的間距 */
|
||||
}
|
||||
- `生理.飽腹感`:根據攝入物品容積增加,不超過100
|
||||
- `生理.碳水/脂肪/蛋白質/維生素/水分`:根據攝入食物的營養成分增加
|
||||
- `生理.氮平衡`:根據蛋白質攝入量更新
|
||||
- 碳水超2000:超出部分的75%轉換為脂肪
|
||||
- `生理.排泄欲`:單次飲水時額外+5,單次進食時額外+10
|
||||
- `精神.無聊`:若食物美味或新穎則減少
|
||||
- `生理.免疫負荷`:若食物生、變質或受污染則顯著增加
|
||||
- `狀態.清潔`:若用髒手抓食或吃不新鮮的食物則降低
|
||||
- 從營地.食物消耗糧食時,同步更新 `/系統/當前存糧天數`(減少)
|
||||
- 獲得新糧食時,同步更新 `/系統/當前存糧天數`(增加)
|
||||
- 存糧計算基準:以當前營地食材總量,按團隊人數計算可供消耗天數
|
||||
|
||||
/* 3. 修改折疊後的內容區,讓它風格也統一 */
|
||||
.collapsible__body {
|
||||
background: rgba(10, 20, 35, 0.6); /* 內容區背景也深一點 */
|
||||
padding: 20px; /* 內容區的內邊距也加大 */
|
||||
border-top: 1px solid var(--survival-glass-border); /* 在頭部和內容之間加一條細線分割 */
|
||||
display: none; /* 這一行至關重要!保證內容區默認隱藏 */
|
||||
}
|
||||
#### 【排泄需求】
|
||||
|
||||
.collapsible__bars-container{display:flex;flex-wrap:wrap;gap:8px;flex:1;min-width:0}
|
||||
@media(max-width: 480px){.collapsible__bars-container{flex-direction:column;width:100%}}
|
||||
.collapsible.active .collapsible__header:after{transform:rotate(0)}
|
||||
.collapsible.active .collapsible__header--with-progress .collapsible__indicator{transform:rotate(0)}
|
||||
|
||||
.temp-range-wrapper { margin:10px 0 15px; padding:10px; background:rgba(0,0,0,.15); border-radius:6px; font-size:14px; }
|
||||
.nutrition-hydration-wrapper { display:flex; flex-direction:column; gap:10px; background:rgba(0,0,0,.15); padding:10px; border-radius:4px; }
|
||||
.phys-mental-wrapper { display:flex;gap:32px;flex-wrap:wrap; }
|
||||
.phys-mental-wrapper .phys-col,
|
||||
.phys-mental-wrapper .mental-col { flex:1 1 300px; min-width:260px; background:rgba(0,0,0,.15); padding:10px; border-radius:4px; }
|
||||
.attr-skill-wrapper .attr-col,
|
||||
.attr-skill-wrapper .skill-col { flex:1 1 300px; min-width:260px; background:rgba(0,0,0,.15); padding:10px; border-radius:4px; }
|
||||
.task-bar { margin-bottom:12px; background:var(--survival-surface-strong); border:1px solid var(--survival-glass-border); border-radius:20px; padding:18px clamp(18px, 3vw, 24px); }
|
||||
|
||||
/* --- 從一號狀態欄移植的狀態效果CSS --- */
|
||||
.status-effects-container {
|
||||
margin-top: 12px; /* 與上方的溫度條保持間距 */
|
||||
margin-bottom: 4px; /* 與下方的核心狀態條保持間距 */
|
||||
padding: 10px;
|
||||
background: rgba(0,0,0,.15);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.status-effects-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.status-effect-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.status-effect-name {
|
||||
padding: 4px 10px;
|
||||
background: var(--survival-bg-light);
|
||||
border: 1px solid var(--survival-border);
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
||||
white-space: nowrap; /* 防止標籤內文字換行 */
|
||||
transition: all 0.2s ease;
|
||||
cursor: default;
|
||||
min-height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
排泄:
|
||||
- `生理.排泄欲`:重置為 0
|
||||
- 如果在野外環境解決:`狀態.清潔` -10
|
||||
|
||||
/* 有效果的狀態名 - 可點選 */
|
||||
.status-effect-name.has-effect {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
padding-right: 20px; /* 為箭頭留出空間 */
|
||||
}
|
||||
失禁:
|
||||
- `生理.排泄欲`:重置為 0
|
||||
- `狀態.清潔`:-30
|
||||
- `精神.壓力`:+20
|
||||
- 同伴:同上述規則,同伴有概率自行尋找場所解決,若無法解決則可能失禁
|
||||
|
||||
/* 有效果的狀態名 - 新增箭頭指示器 */
|
||||
.status-effect-name.has-effect::after {
|
||||
content: '▼';
|
||||
font-size: 10px;
|
||||
margin-left: 5px;
|
||||
transition: transform 0.3s ease;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
}
|
||||
#### 【製作/建造】
|
||||
|
||||
/* 展開狀態 */
|
||||
.status-effect-name.has-effect.expanded::after {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
> 🔴 建造/製作的完整規則(規模評估、進度增量計算、材料消耗公式、最終檢查檢定)參照 `<crafting_rules>` 子模塊。變量 COT 僅負責處理 ActionResult 信號的數值更新。
|
||||
|
||||
.status-effect-name:hover {
|
||||
transform: translateY(-2px);
|
||||
background: var(--survival-secondary);
|
||||
border-color: var(--survival-accent);
|
||||
}
|
||||
- 消耗原材料:
|
||||
- 每次推進必須按比例消耗材料
|
||||
- 消耗量計算公式:(本次進度增量 / 100) × 所需材料總量(向上取整)
|
||||
- 從攜帶物品或營地庫存中 remove 對應數量
|
||||
- 若庫存不足,進度增量上限為「當前庫存能支撐的最大進度」
|
||||
- 添加成品:僅在最終檢查成功後添加
|
||||
- 項目狀態:
|
||||
- 當前進度 < 100%:狀態 = 進行中
|
||||
- 當前進度 = 100%:狀態自動變更為「待檢查」
|
||||
- 最終檢查:
|
||||
- 讀取 ActionResult 中的「最終檢查」信號
|
||||
- 若結果為成功:remove 項目,insert 成品
|
||||
- 若結果為失敗:保持項目,狀態仍為「待檢查」,不產生成品
|
||||
- `狀態.疲勞`:根據任務所需體力與腦力消耗增加
|
||||
- `精神.無聊`:減少
|
||||
|
||||
/* 效果詳情區域 */
|
||||
.status-effect-detail {
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
border: 1px solid var(--survival-border);
|
||||
border-radius: 4px;
|
||||
padding: 8px 10px;
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: var(--survival-text);
|
||||
white-space: normal; /* 允許換行 */
|
||||
max-width: 250px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
animation: fadeIn 0.2s ease;
|
||||
}
|
||||
#### 【戰鬥/受傷】
|
||||
|
||||
/* 效果詳情的淡入動畫 */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
- `生理.當前生命值`:根據傷害程度減少
|
||||
- `生理.當前精神值`:因壓力和衝擊減少
|
||||
- `狀態.受傷`:更新為新傷情描述
|
||||
- `狀態.流血`:開放性傷口設為相應流血狀態
|
||||
- `狀態.疼痛`:增加到與受傷程度相應的水平
|
||||
- `精神.壓力`:顯著增加
|
||||
- 屬性經驗:根據戰鬥動作給予經驗(力量/敏捷/耐力)
|
||||
- 裝備耐久度:減少所用武器和擊中護甲的狀態值
|
||||
- `生理.免疫負荷`:因開放性傷口增加
|
||||
|
||||
/* 移動端適配 */
|
||||
@media (max-width: 768px) {
|
||||
.status-effects-list {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.status-effect-name {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.status-effect-detail {
|
||||
max-width: 200px;
|
||||
font-size: 11px;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.status-effect-name.has-effect {
|
||||
padding-right: 25px; /* 移動端箭頭更大一點 */
|
||||
}
|
||||
|
||||
.status-effect-name.has-effect::after {
|
||||
right: 6px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
#### 【休息/醫療】
|
||||
|
||||
/* 小螢幕適配 */
|
||||
@media (max-width: 480px) {
|
||||
.status-effects-container {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.status-effects-list {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.status-effect-detail {
|
||||
max-width: 180px;
|
||||
}
|
||||
}
|
||||
/* --- 狀態效果CSS結束 --- */
|
||||
|
||||
/* 加值欄位特殊樣式 */
|
||||
.bonus-prop {
|
||||
background: rgba(94, 161, 255, 0.08) !important;
|
||||
border-left: 3px solid var(--survival-accent) !important;
|
||||
padding: 6px 8px 6px 10px !important;
|
||||
border-radius: 4px !important;
|
||||
margin: 4px 0 !important;
|
||||
}
|
||||
- `精神.睡眠`:根據睡眠時長和質量增加(6-8小時為最佳)
|
||||
- `狀態.疲勞`:有效睡眠後顯著減少
|
||||
- `生理.當前生命值`:營養水分充足且無嚴重狀況時緩慢恢復
|
||||
- `精神.壓力`:休息後減少,舒適度>60更明顯
|
||||
- `狀態.流血`:採取包紮等止血措施後設為無
|
||||
- `狀態.感染`:通過清潔傷口和使用藥物可降低或阻止
|
||||
- `技能.醫療.當前經驗`:成功處理後增加
|
||||
|
||||
.bonus-prop .prop-key {
|
||||
color: var(--survival-accent-strong) !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
#### 【技能/屬性檢定】
|
||||
|
||||
.bonus-prop .prop-val {
|
||||
color: #8fd6ff !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
信號來源:正文中的檢定代碼塊
|
||||
|
||||
/* 加值分隔線 */
|
||||
.bonus-separator {
|
||||
animation: separatorGlow 3s ease-in-out infinite;
|
||||
}
|
||||
屬性經驗:
|
||||
- 規則:涉及屬性判定即獲得經驗
|
||||
- 數值:簡單+25,中等+50,困難+75
|
||||
|
||||
@keyframes separatorGlow {
|
||||
0%, 100% {
|
||||
background: linear-gradient(90deg, transparent, rgba(148, 197, 255, 0.4), transparent);
|
||||
}
|
||||
50% {
|
||||
background: linear-gradient(90deg, transparent, rgba(94, 161, 255, 0.6), transparent);
|
||||
}
|
||||
}
|
||||
技能經驗:
|
||||
- 規則:使用技能後獲得經驗
|
||||
- 數值:簡單+25,中等+50,困難+75
|
||||
|
||||
/* 天賦加值樣式 */
|
||||
.talent-bonus-line {
|
||||
background: rgba(94, 161, 255, 0.08) !important;
|
||||
border-left: 3px solid var(--survival-accent) !important;
|
||||
padding: 6px 8px 6px 10px !important;
|
||||
border-radius: 4px !important;
|
||||
margin: 8px 0 !important;
|
||||
color: #8fd6ff !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
輔助者經驗:減半
|
||||
|
||||
/* 天賦名稱中的加值提示 */
|
||||
.talent-has-bonus .skill-name::after {
|
||||
content: "✨";
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--survival-accent);
|
||||
animation: sparkle 2s ease-in-out infinite;
|
||||
}
|
||||
大成功/大失敗:大成功經驗額外增加且使用時間減半,大失敗經驗仍為正常值
|
||||
|
||||
@keyframes sparkle {
|
||||
0%, 100% { opacity: 0.6; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
#### 【技能生成】
|
||||
|
||||
.temp-range-bar,
|
||||
.nitrogen-gradient,
|
||||
.immune-bar,
|
||||
.progress,
|
||||
.mini-progress {
|
||||
height: 12px;
|
||||
background: linear-gradient(135deg, rgba(18, 30, 48, 0.65), rgba(11, 20, 31, 0.65));
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(148, 197, 255, 0.14);
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
/* --- 體溫與免疫條 視覺修正 --- */
|
||||
|
||||
/* 這是控制體溫條漸變背景的本體 */
|
||||
.temp-range-bar .temp-gradient {
|
||||
/* 我們在這裡明確指定背景圖像,覆蓋掉任何其他可能的通用樣式 */
|
||||
background-image: linear-gradient(90deg, #3498db 0%, #2ecc71 50%, #e74c3c 100%) !important;
|
||||
width: 100%; /* 確保它填滿整個條 */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
}
|
||||
觸發條件:
|
||||
- 完全研讀並理解技能的系統性專業資料
|
||||
- 或完成該領域內多樣化實踐(如成功製作五類以上不同物品,含至少一件複雜工具)
|
||||
|
||||
.mini-progress { height: 8px; }
|
||||
生成規則:不生成單一動作技能或狹窄技巧(如「生火」)
|
||||
|
||||
.progress__bar,
|
||||
.mini-progress__bar,
|
||||
.immune-fill,
|
||||
.temp-gradient {
|
||||
position:absolute; top:0; left:0; height:100%;
|
||||
border-radius: inherit;
|
||||
background-image: linear-gradient(90deg, rgba(94, 161, 255, 0.9), rgba(96, 225, 183, 0.9));
|
||||
box-shadow: 0 0 18px rgba(94, 161, 255, 0.35);
|
||||
}
|
||||
獲得經驗:
|
||||
|
||||
.hp-progress .progress__bar { background-image: linear-gradient(90deg, #ff8fb1, #58f0a7); }
|
||||
.mp-progress .progress__bar { background-image: linear-gradient(90deg, #8c8bff, #58f0a7); }
|
||||
.hunger-progress .progress__bar, .hunger-progress.mini-progress .mini-progress__bar { background-image: linear-gradient(90deg, #ffdc84, #ff9f6c); }
|
||||
.water-progress .progress__bar, .water-progress.mini-progress .mini-progress__bar { background-image: linear-gradient(90deg, #7fd8ff, #5ea1ff); }
|
||||
步驟:
|
||||
1. 路徑存在性檢查:檢查目標技能路徑是否存在
|
||||
2. 若【存在】:使用 delta 增加當前經驗
|
||||
3. 若【不存在】:使用 insert 新增整個技能物件
|
||||
|
||||
.carbs-progress .progress__bar{background-image:linear-gradient(90deg, #f39c12, #d35400)}
|
||||
.fat-progress .progress__bar{background-image:linear-gradient(90deg, #f1c40f, #f39c12)}
|
||||
.protein-progress .progress__bar{background-image:linear-gradient(90deg, #27ae60, #16a085)}
|
||||
.vitamin-progress .progress__bar{background-image:linear-gradient(90deg, #2ecc71, #27ae60)}
|
||||
#### 🔴 技能經驗路徑歸屬強制規則
|
||||
|
||||
.progress-wrapper > div:first-child, .mini-progress__text, .progress-number {
|
||||
/* --- 字體修正點 --- */
|
||||
font-family: "IBM Plex Mono", Consolas, monospace;
|
||||
}
|
||||
.mini-progress__text {
|
||||
font-family: "IBM Plex Mono", Consolas, monospace;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--survival-text); /* High Contrast */
|
||||
}
|
||||
在更新技能經驗時,必須先確定「技能的持有者是誰」:
|
||||
|
||||
.button-container{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
margin-top:auto;
|
||||
}
|
||||
| 持有者 | 路徑格式 | 示例 |
|
||||
|--------|----------|------|
|
||||
| 主角 | `/技能/{技能名}` | `/技能/基礎建築` |
|
||||
| 同伴 | `/同伴/{同伴名}/技能/{技能名}` | `/同伴/蒲元/技能/基礎建築` |
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.button-container {
|
||||
justify-content: stretch;
|
||||
}
|
||||
**判斷流程(強制執行):**
|
||||
1. 讀取正文中該技能的「實際使用者」是誰
|
||||
2. 若使用者是主角 → 使用 `/技能/{技能名}`
|
||||
3. 若使用者是同伴 → 使用 `/同伴/{同伴名}/技能/{技能名}`
|
||||
4. 若正文未明確指定使用者 → 預設為主角
|
||||
|
||||
.action-btn { /* Renamed for clarity */
|
||||
flex: 1 1 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
**🔴 絕對禁止:**
|
||||
- 將同伴使用的技能經驗寫入 `/技能/` 路徑(這會錯誤地給予主角經驗)
|
||||
- 將主角使用的技能經驗寫入 `/同伴/{名}/技能/` 路徑
|
||||
- 在註釋中寫「給予XX」但路徑卻指向不同的人(路徑與註釋必須一致)
|
||||
|
||||
.action-btn { /* Renamed and unified */
|
||||
position: relative;
|
||||
padding: 12px 18px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(148, 197, 255, 0.25);
|
||||
background: linear-gradient(135deg, rgba(17, 31, 49, 0.9), rgba(29, 45, 68, 0.85));
|
||||
color: var(--survival-text);
|
||||
font-family: "Inter", "Microsoft YaHei", sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
|
||||
}
|
||||
### 8. 任務更新
|
||||
|
||||
.action-btn::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(120deg, transparent 25%, rgba(94, 161, 255, 0.2), transparent 75%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
#### 8.1 任務類型與生成條件
|
||||
|
||||
.action-btn:hover {
|
||||
transform: translateY(-3px);
|
||||
border-color: rgba(148, 197, 255, 0.55);
|
||||
box-shadow: 0 12px 28px rgba(8, 18, 34, 0.35);
|
||||
}
|
||||
##### 【主線】
|
||||
|
||||
.action-btn:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
- 觸發:`任務.主線` 為空 `{}` 時立即生成
|
||||
- 描述:引導玩家生存和發展的長期任務,無法馬上完成
|
||||
- 獎勵類型:求生點數 + 進階劇情演出(可選實物)
|
||||
|
||||
.action-btn:active {transform:translateY(0)}
|
||||
##### 【支線】
|
||||
|
||||
/* Breathing light effect for draw buttons */
|
||||
.draw-btn.survival {
|
||||
animation: glow-survival 3s ease-in-out infinite;
|
||||
}
|
||||
- 觸發:`任務.支線` 為空 `{}` 時立即生成
|
||||
- 描述:有難度和挑戰性的任務,不與生存直接掛鉤,用於提高生活質量
|
||||
- 獎勵類型:求生點數 + 進階劇情演出(可選實物)
|
||||
|
||||
@keyframes glow-survival {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 5px rgba(94, 161, 255, 0.2), 0 0 10px rgba(94, 161, 255, 0.2);
|
||||
border-color: rgba(94, 161, 255, 0.4);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 15px rgba(94, 161, 255, 0.5), 0 0 25px rgba(94, 161, 255, 0.5);
|
||||
border-color: rgba(148, 197, 255, 0.8);
|
||||
}
|
||||
}
|
||||
##### 【日常】
|
||||
|
||||
@keyframes glow-miracle {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 5px rgba(243, 156, 18, 0.2), 0 0 10px rgba(243, 156, 18, 0.2);
|
||||
border-color: rgba(243, 156, 18, 0.4);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 15px rgba(243, 156, 18, 0.5), 0 0 25px rgba(243, 156, 18, 0.5);
|
||||
border-color: rgba(255, 184, 108, 0.8);
|
||||
}
|
||||
}
|
||||
- 觸發:僅在定時更新時(跨過 00:00)且 `任務.日常` 為空 `{}` 時生成
|
||||
- 描述:每天可完成一次的簡單任務,穩定的求生點數來源
|
||||
- 特殊:只有一個基礎要求,無進階要求
|
||||
- 獎勵類型:求生點數
|
||||
|
||||
.inventory-modal{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.8);backdrop-filter:blur(8px);z-index:1000;display:flex;justify-content:center;align-items:center}
|
||||
/* --- 【全景物品欄升級補丁】 --- */
|
||||
.inventory-modal__content {
|
||||
position: relative;
|
||||
/* 讓寬度在90%到1400px之間浮動,既能滿版又不至於在超寬螢幕上過於分散 */
|
||||
width: clamp(90%, 95vw, 1400px);
|
||||
height: clamp(80%, 90vh, 800px); /* 同時也讓高度更合理 */
|
||||
padding: 20px;
|
||||
overflow-y:auto;display:flex;flex-direction:row;gap:20px;
|
||||
border-radius: 26px;
|
||||
border: 1px solid rgba(148, 197, 255, 0.18);
|
||||
background: linear-gradient(155deg, rgba(17, 28, 45, 0.94), rgba(24, 36, 58, 0.94));
|
||||
box-shadow: var(--survival-shadow);
|
||||
backdrop-filter: blur(20px) saturate(160%);
|
||||
animation:modalFadeIn .3s ease-out
|
||||
}
|
||||
##### 【緊急】
|
||||
|
||||
.inventory-modal__close{position:absolute;top:10px;right:15px;font-size:24px;cursor:pointer;color:var(--survival-text);transition:all .2s ease;width:30px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:50%;background: rgba(0,0,0,0.6)}
|
||||
.inventory-modal__close:hover{color:var(--survival-danger);background:rgba(231,76,60,.2);transform:scale(1.1)}
|
||||
@keyframes modalFadeIn{from{opacity:0;transform:translateY(-20px)}to{opacity:1;transform:translateY(0)}}
|
||||
.attr-skill-wrapper{display:flex;gap:24px;flex-wrap:wrap}
|
||||
.attr-skill-wrapper .attr-col,.attr-skill-wrapper .skill-col{flex:1 1 300px;min-width:260px;background:rgba(0,0,0,.15);padding:18px;border-radius:16px;}
|
||||
.inventory-grid {
|
||||
display: grid;
|
||||
/* 核心改動:將每個格子的最小寬度從 320px!
|
||||
這樣一來,在大多數常見的螢幕寬度下,一行最多只能放下3-4個格子。 */
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 6px; /* 格子之間的空隙也再拉開一點,顯得更大氣 */
|
||||
}
|
||||
.inventory-item {
|
||||
background: var(--survival-surface-strong);
|
||||
border: 1px solid var(--survival-border);
|
||||
border-radius: 10px; /* 圓角稍微大一點,更有設計感 */
|
||||
padding: 16px; /* 內邊距加大,讓文字和邊框的距離更遠 */
|
||||
transition: all .25s ease-out;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
min-height: 100px; /* 最小高度也稍微增加 */
|
||||
box-shadow: var(--survival-glow); /* 加上一點點輝光效果 */
|
||||
}
|
||||
.inventory-item:hover{transform:translateY(-3px);box-shadow:0 5px 15px rgba(0,0,0,.3);background:rgba(52,73,94,.9);border-color:var(--survival-accent)}
|
||||
.inventory-item .item-name {
|
||||
font-weight: 600; /* 字重降低一點,顯得更優雅 */
|
||||
margin-bottom: 12px;
|
||||
font-size: 18px; /* 標題字號:16px -> 18px,醒目! */
|
||||
color: var(--survival-accent-strong); /* 使用更亮眼的強調色 */
|
||||
letter-spacing: 0.05em; /* 增加一點點字間距 */
|
||||
}
|
||||
/* --- 裝備列表網格佈局 --- */
|
||||
.inventory-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr); /* 一行3個 */
|
||||
gap: 12px; /* 格子間距 */
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
- 觸發:`任務.緊急` 為空 `{}` 且出現以下因素時評估生成
|
||||
- 環境劇變:天氣或環境開始向不利方向惡化
|
||||
- 外部威脅:出現外部威脅的跡象或徵兆,甚至威脅已經到來
|
||||
- 內部危機:重要設施故障、關鍵物資告急、角色或同伴狀態持續惡化
|
||||
- 機遇性:出現有時限、需要立即行動才能把握的利好機會
|
||||
- 特殊:失敗有懲罰,與任務難度匹配但不致遊戲結束
|
||||
- 獎勵類型:求生點數 + 進階劇情演出(可選實物)
|
||||
|
||||
/* 裝備項目的樣式調整 */
|
||||
.inventory-list-item {
|
||||
background: var(--survival-surface-strong);
|
||||
border: 1px solid var(--survival-border);
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
transition: all .25s ease-out;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
min-height: 120px; /* 稍微增加最小高度 */
|
||||
box-shadow: var(--survival-glow);
|
||||
}
|
||||
##### 【背德】
|
||||
|
||||
.inventory-list-item:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,.3);
|
||||
background: rgba(52,73,94,.9);
|
||||
border-color: var(--survival-accent);
|
||||
}
|
||||
- 觸發:`任務.背德` 為空 `{}` 且出現以下行為/場景時評估生成
|
||||
|
||||
.inventory-list-item .item-name {
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
font-size: 18px;
|
||||
color: var(--survival-accent-strong);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
觸發場景(滿足任一即觸發,無需擲骰):
|
||||
|
||||
/* 響應式設計 */
|
||||
@media (max-width: 960px) {
|
||||
.inventory-list {
|
||||
grid-template-columns: repeat(2, 1fr); /* 中等屏幕一行2個 */
|
||||
}
|
||||
}
|
||||
1. 核心判斷 - 是否需要隱瞞:
|
||||
- 行為發生後,當事人不希望伴侶/特定第三人知道
|
||||
- 行為發生時,當事人有意識地避開他人視線
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.inventory-list {
|
||||
grid-template-columns: 1fr; /* 小屏幕一行1個 */
|
||||
}
|
||||
}
|
||||
/* --- 【最终修正补丁 v5.0 - 标签防断裂】 --- */
|
||||
|
||||
/* 步骤一:确保整个属性条目可以灵活换行 */
|
||||
.item-prop {
|
||||
display: flex; /* 啟用Flexbox佈局,這是所有魔法的基礎 */
|
||||
align-items: flex-start; /* 頂部對齊,防止長短不一時錯位 */
|
||||
gap: 4px; /* 元素之間留點空隙 */
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #ffffff;
|
||||
}
|
||||
2. 關係狀態判斷:
|
||||
- 行為發生時,其中一方已有伴侶/喜歡的對象(≠對方)
|
||||
- 行為雙方曾有「只是朋友/只是炮友」等約定,但行為超出約定範圍
|
||||
|
||||
/* 步驟二:給家長下死命令 - 標題和冒號,你們倆給爺鎖死! */
|
||||
/* 為了美觀,單獨調整一下標題的顏色 */
|
||||
.item-prop .prop-key {
|
||||
color: var(--survival-muted);
|
||||
white-space: nowrap; /* <-- 就是加上這一行! */
|
||||
3. 情感反應判斷:
|
||||
- 行為發生當下,當事人感到緊張/猶豫/罪惡感
|
||||
- 行為後,當事人產生矛盾/自責情緒
|
||||
|
||||
}
|
||||
重要:「炮友」「開放關係」等事後定義不影響觸發,以行為當下的真實關係約束和內心感受為準
|
||||
|
||||
.item-prop .prop-sep {
|
||||
flex-shrink: 0; /* 空間再小,也不許壓縮你們的寬度! */
|
||||
white-space: nowrap; /* 你們本身內部也絕對不許換行! */
|
||||
}
|
||||
特殊:與緊急任務不共享槽位
|
||||
|
||||
.item-prop .prop-sep {
|
||||
white-space: nowrap; /* <-- 核心:强制这两个部分永不换行 */
|
||||
flex-shrink: 0; /* <-- 辅助:告诉flex布局,不要压缩我! */
|
||||
}
|
||||
內容方向:
|
||||
- 情感背德(背叛信任、劈腿、隱瞞)
|
||||
- 引誘/被引誘(利用吸引力或情感)
|
||||
- 性勒索(以秘密或資源威脅)
|
||||
- 多人性行為(接受或參與)
|
||||
- 禁忌關係(社會規範不允許的關係)
|
||||
- 共犯關係(幫忙隱瞞、成為同盟)
|
||||
|
||||
/* 步驟三:讓孩子承擔所有 - 描述內容,所有換行的壓力都是你的! */
|
||||
.item-prop .prop-val {
|
||||
/* 這裡不需要特別的 flex 屬性,它會自動佔據剩餘空間 */
|
||||
word-break: break-word; /* 讓它智能地斷詞換行 */
|
||||
min-width: 0; /* 防止溢出的保險措施 */
|
||||
font-weight: 500;
|
||||
}
|
||||
.inventory-list-item{background:var(--survival-bg-light);border:1px solid var(--survival-border);border-radius:6px;padding:12px;transition:all .2s ease-out;display:flex;flex-direction:column;position:relative;overflow:hidden}
|
||||
.inventory-list-item:hover{transform:translateY(-2px);box-shadow:0 3px 10px rgba(0,0,0,.3);background:rgba(52,73,94,.9);border-color:var(--survival-accent)}
|
||||
.inventory-list-item .item-name{font-weight:bold;margin-bottom:6px;font-size:16px;color:#fff}
|
||||
|
||||
/*.nitrogen-balance:before{content:"";position:absolute;top:0;bottom:0;left:50%;width:2px;background:hsla(0,0%,100%,.7);z-index:1}*/
|
||||
.vertical-progress{width:30px;height:200px;background:rgba(0,0,0,.3);border-radius:15px;position:relative;margin:20px auto;box-shadow:inset 0 0 10px rgba(0,0,0,.5)}
|
||||
.vertical-progress__bar{position:absolute;bottom:0;left:0;width:100%;background:linear-gradient(to top, var(--survival-warning), var(--survival-accent));border-radius:15px;transition:height .5s ease-out;box-shadow:0 0 10px rgba(52,152,219,.5)}
|
||||
.vertical-progress__label{position:absolute;bottom:-30px;left:50%;transform:translateX(-50%);white-space:nowrap;text-align:center;width:100px;font-weight:bold;color:var(--survival-text);text-shadow:0 1px 2px rgba(0,0,0,.5)}
|
||||
.vertical-progress__value{position:absolute;top:-30px;left:50%;transform:translateX(-50%);white-space:nowrap;text-align:center;width:100px;font-weight:bold;color:var(--survival-accent);text-shadow:0 1px 2px rgba(0,0,0,.5)}
|
||||
.weight-container{display:flex;flex-direction:column;align-items:center;min-width:100px;padding-right:20px;border-right:1px solid var(--survival-border);justify-content:center}
|
||||
.inventory-content{flex:1;display:flex;flex-direction:column;overflow-y:auto}
|
||||
.inventory-content h3{margin-top:0;margin-bottom:15px;color:var(--survival-accent);font-size:18px;border-bottom:1px solid hsla(0,0%,100%,.1);padding-bottom:8px;position:relative}
|
||||
.inventory-content h3:after{content:"";position:absolute;bottom:-1px;left:0;width:60px;height:2px;background:var(--survival-accent)}
|
||||
.temp-range-wrapper{font-size:14px}
|
||||
.temp-range-bar{height:16px;margin-top:8px;overflow:visible}
|
||||
.comfort-zone{position:absolute;height:100%;background:hsla(0,0%,100%,.2);border:1px dashed hsla(0,0%,100%,.7);border-radius:8px;z-index:1}
|
||||
.current-temp{position:absolute;width:4px;height:24px;background:#fff;border-radius:2px;top:-4px;transform:translateX(-2px);z-index:2;box-shadow:0 0 5px hsla(0,0%,100%,.8)}
|
||||
|
||||
.nitrogen-gradient{height:12px;}
|
||||
.nitrogen-marker{position:absolute;top:-6px;bottom:-6px;width:4px;background:#fff;border-radius:2px;box-shadow:0 0 5px hsla(0,0%,100%,.8)}
|
||||
.temp-gradient{height:12px; background-image: linear-gradient(90deg, #3498db 0%, #2ecc71 50%, #e74c3c 100%);}
|
||||
.temp-marker{position:absolute;top:-6px;bottom:-6px;width:4px;background:#fff;border-radius:2px;box-shadow:0 0 5px hsla(0,0%,100%,.8)}
|
||||
.temp-normal-zone{position:absolute;height:100%;background:hsla(0,0%,100%,.2);border:1px dashed hsla(0,0%,100%,.7);border-radius:4px;z-index:1}
|
||||
.temp-gradient{height:12px; background-image: linear-gradient(90deg, #3498db 0%, #2ecc71 50%, #e74c3c 100%);}
|
||||
.temp-marker{position:absolute;top:-6px;bottom:-6px;width:4px;background:#fff;border-radius:2px;box-shadow:0 0 5px hsla(0,0%,100%,.8)}
|
||||
.temp-normal-zone{position:absolute;height:100%;background:hsla(0,0%,100%,.2);border:1px dashed hsla(0,0%,100%,.7);border-radius:4px;z-index:1}
|
||||
.immune-bar{height:12px;}
|
||||
.immune-fill{background-image:linear-gradient(90deg, #2ecc71 0%, #f39c12 50%, #e74c3c 100%);transition:width .3s ease}
|
||||
.immune-marker{position:absolute;top:-6px;bottom:-6px;width:4px;background:#fff;border-radius:2px;box-shadow:0 0 5px hsla(0,0%,100%,.8);z-index:2}
|
||||
.skill-item{margin-bottom:2px;background:rgba(0,0,0,.1);padding:2px 8px;border-radius:4px}
|
||||
.skill-item .skill-name{font-weight:bold;margin-bottom:5px}
|
||||
.skill-desc { font-size: .85em; color: var(--survival-muted); opacity: 0.8; margin-top: 4px; }
|
||||
|
||||
/* 終極任務(世界任務)樣式 - 金色傳說 */
|
||||
.task-strip.world-task {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(255, 215, 0, 0.25),
|
||||
rgba(255, 165, 0, 0.15),
|
||||
rgba(255, 215, 0, 0.08));
|
||||
border-color: rgba(255, 215, 0, 0.6);
|
||||
color: #ffd966;
|
||||
text-shadow: 0 0 8px rgba(255, 215, 0, 0.5);
|
||||
border-width: 2px;
|
||||
box-shadow: 0 0 15px rgba(255, 215, 0, 0.3);
|
||||
animation: worldTaskGlow 3s ease-in-out infinite;
|
||||
}
|
||||
獎勵類型:劇情演出(必給)+ 實物類/關係類/能力類/推進類(從中選擇 2~4 項)
|
||||
|
||||
.task-strip.world-task:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 0 25px rgba(255, 215, 0, 0.5);
|
||||
border-color: rgba(255, 215, 0, 0.9);
|
||||
}
|
||||
##### 【羈絆】
|
||||
|
||||
/* 終極任務的呼吸光效 */
|
||||
@keyframes worldTaskGlow {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 8px rgba(255, 215, 0, 0.3);
|
||||
border-color: rgba(255, 215, 0, 0.5);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 20px rgba(255, 215, 0, 0.6);
|
||||
border-color: rgba(255, 215, 0, 0.9);
|
||||
}
|
||||
}
|
||||
- 觸發:該同伴的羈絆任務為空 `{}` 且 劇情演出列表為空 `[]` 並滿足以下至少一項時評估生成
|
||||
- 該同伴狀態發生顯著變化時
|
||||
- 該同伴主動向你吐露個人秘密
|
||||
- 該同伴經歷重大情感事件
|
||||
- 與該同伴共同抵達特殊的環境
|
||||
- 與該同伴共同經歷特殊的事件
|
||||
- 數量:每位同伴都有且只有一個羈絆任務名額
|
||||
- 特殊:所有任務內容和要求均以玩家為執行主體進行描述,內容保持專屬不涉及其他同伴
|
||||
- 獎勵類型:劇情演出(必給)+ 同伴專屬獎勵(不含求生點數)
|
||||
|
||||
/* 終極任務展開時的內容區特殊背景 */
|
||||
.task-detail.world-detail {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(255, 215, 0, 0.08),
|
||||
rgba(0, 0, 0, 0.3));
|
||||
border: 1px solid rgba(255, 215, 0, 0.3);
|
||||
}
|
||||
|
||||
/* 任務系統樣式 */
|
||||
.task-strip {
|
||||
padding: 12px 16px;
|
||||
margin: 8px 0;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
#### 8.2 數量限制
|
||||
|
||||
.task-strip:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
- 主線、支線、日常、緊急、背德:各類型同時只能存在 1 個
|
||||
- 羈絆:每位同伴同時只能存在 1 個
|
||||
|
||||
.task-strip.main-task {
|
||||
background: linear-gradient(135deg, rgba(52, 152, 219, 0.2), rgba(52, 152, 219, 0.1));
|
||||
border-color: rgba(52, 152, 219, 0.3);
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
.task-strip.side-task {
|
||||
background: linear-gradient(135deg, rgba(243, 156, 18, 0.2), rgba(243, 156, 18, 0.1));
|
||||
border-color: rgba(243, 156, 18, 0.3);
|
||||
color: #f39c12;
|
||||
}
|
||||
#### 8.3 任務要求約束
|
||||
|
||||
.task-strip.emergency-task {
|
||||
background: linear-gradient(135deg, rgba(231, 76, 60, 0.2), rgba(231, 76, 60, 0.1));
|
||||
border-color: rgba(231, 76, 60, 0.3);
|
||||
color: #e74c3c;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
/* 日常任務樣式 */
|
||||
.task-strip.daily-task {
|
||||
background: linear-gradient(135deg, rgba(46, 204, 113, 0.2), rgba(46, 204, 113, 0.1));
|
||||
border-color: rgba(46, 204, 113, 0.3);
|
||||
color: #2ecc71;
|
||||
}
|
||||
數量:
|
||||
- 基礎要求:1 ~ 3 個
|
||||
- 進階要求:1 ~ 2 個
|
||||
|
||||
/* 羈絆任務樣式 */
|
||||
.task-strip.bond-task {
|
||||
background: linear-gradient(135deg, rgba(189, 147, 249, 0.25), rgba(189, 147, 249, 0.15)); /* 紫色調得更淺更亮 */
|
||||
border-color: rgba(142, 68, 173, 0.4); /* 邊框顏色可以稍微保留一點深度,增加輪廓感 */
|
||||
color: #f0e6ff; /* 字體顏色直接換成非常淺的淡紫色,接近白色,保證對比度 */
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); /* 給文字加一點點陰影,讓它更有立體感,更容易閱讀 */
|
||||
}
|
||||
類型:
|
||||
- 數值:進度從 0 逐步增加到目標值
|
||||
- 布爾:0(未完成)或 1(已完成)
|
||||
|
||||
.task-detail {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 8px 0;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
區域限制:
|
||||
- 無限制:60% 概率
|
||||
- 有限制:40% 概率,綁定特定區域名
|
||||
- 規則:離開限制區域後,進度不再更新
|
||||
|
||||
.task-desc {
|
||||
text-align: left;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.4;
|
||||
color: var(--survival-text);
|
||||
white-space: pre-line;
|
||||
word-wrap: break-word;
|
||||
/* 這裏可以添加字體大小,例如: */
|
||||
font-size: 15px; /* 添加這一行來設置任務描述字體大小 */
|
||||
}
|
||||
結束時間:主要按照任務內容設定時間,各類型時間參考如下
|
||||
- 主線:7-30天
|
||||
- 支線:3-7天
|
||||
- 羈絆:1-3天
|
||||
- 日常:固定次日 0 點
|
||||
- 緊急:緊迫
|
||||
- 背德:3-5天
|
||||
|
||||
.target-section {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
color: var(--survival-accent);
|
||||
/* 這裏可以添加字體大小,例如: */
|
||||
font-size: 15px; /* 設置"任務要求"、"進階目標"等標題的字體大小 */
|
||||
}
|
||||
#### 8.4 進度更新規則
|
||||
|
||||
.target-desc {
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
color: var(--survival-text);
|
||||
white-space: pre-line;
|
||||
word-wrap: break-word;
|
||||
/* 這裏可以添加字體大小,例如: */
|
||||
font-size: 15px; /* 添加這一行來設置任務目標描述字體大小 */
|
||||
}
|
||||
路徑格式(強制):
|
||||
|
||||
.reward-info {
|
||||
color: var(--survival-text);
|
||||
font-weight: 600;
|
||||
margin-top: 13px;
|
||||
/* 這裏可以添加字體大小,例如: */
|
||||
font-size: 15px; /* 設置獎勵信息的字體大小 */
|
||||
}
|
||||
- 主線基礎要求:`/任務/主線/任務要求/基礎要求{N}`
|
||||
- 主線進階目標:`/任務/主線/進階目標/進階目標{N}`
|
||||
- 支線基礎要求:`/任務/支線/任務要求/基礎要求{N}`
|
||||
- 支線進階目標:`/任務/支線/進階目標/進階目標{N}`
|
||||
- 日常基礎要求:`/任務/日常/任務要求/基礎要求1`
|
||||
- 緊急基礎要求:`/任務/緊急/任務要求/基礎要求{N}`
|
||||
- 緊急進階目標:`/任務/緊急/進階目標/進階目標{N}`
|
||||
- 背德基礎要求:`/任務/背德/任務要求/基礎要求{N}`
|
||||
- 背德進階目標:`/任務/背德/進階目標/進階目標{N}`
|
||||
- 羈絆基礎要求:`/任務/羈絆/{同伴名}/任務要求/基礎要求{N}`
|
||||
- 羈絆進階目標:`/任務/羈絆/{同伴名}/進階目標/進階目標{N}`
|
||||
|
||||
.reward-info .points {
|
||||
color: var(--survival-warning);
|
||||
}
|
||||
正確示例:
|
||||
- `/任務/支線/任務要求/基礎要求1`
|
||||
- `/任務/羈絆/千鶴/任務要求/基礎要求1`
|
||||
|
||||
.penalty-info {
|
||||
/* 1. 核心佈局與對齊 */
|
||||
display: flex; /* 啟用flex佈局,讓圖標和文字能肩並肩站好 */
|
||||
align-items: center; /* 垂直居中對齊,看起來更舒服 */
|
||||
text-align: left; /* 改成你想要的左對齊 */
|
||||
|
||||
/* 2. 背景與邊框,打造"警報卡片"感 */
|
||||
background: linear-gradient(135deg, rgba(231, 76, 60, 0.15), rgba(231, 76, 60, 0.05));
|
||||
border-left: 4px solid var(--survival-danger); /* 左側加一條醒目的紅色指示條 */
|
||||
border-radius: 6px; /* 加個小圓角,不那麼生硬 */
|
||||
|
||||
/* 3. 內邊距與外邊距,讓它呼吸起來 */
|
||||
padding: 10px 14px;
|
||||
margin-top: 12px;
|
||||
|
||||
/* 4. 字體與文字效果 */
|
||||
font-family: "Microsoft YaHei", "Segoe UI", Arial, sans-serif; /* 確保字體統一 */
|
||||
font-size: 18px; /* 字號稍微調整一下 */
|
||||
color: #ffdde5; /* 文字用柔和的粉紅色,而不是刺眼的鮮紅 */
|
||||
font-weight: 600;
|
||||
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5); /* 加點文字陰影,更有立體感 */
|
||||
}
|
||||
常見錯誤(禁止):
|
||||
- `/任務/主線/任務要求/進階要求1/進度` ← 進階目標不在任務要求下,且鍵名是「進階目標」不是「進階要求」
|
||||
- `/任務/主線/任務要求/收集三塊硬木/進度` ← 路徑中不可包含任務標題文字
|
||||
|
||||
/* 5. 畫龍點睛的圖標! */
|
||||
.penalty-info::before {
|
||||
content: '⚠️'; /* 你也可以換成 '🛡️💥💀' */
|
||||
margin-right: 8px; /* 讓圖標和文字之間有點距離 */
|
||||
font-size: 18px; /* 圖標稍微大一點點 */
|
||||
animation: pulse-danger 2s infinite; /* 給圖標加個呼吸動畫 */
|
||||
}
|
||||
更新規則:
|
||||
- 從正文描述和 ActionResult 信號中綜合判斷本回合推進了哪些任務要求
|
||||
- 匹配正文中的行動描述與任務要求的描述
|
||||
- 如果行動在區域限制的任務區域外,進度不更新
|
||||
- 不要更新未涉及的任務要求
|
||||
- 絕對禁止直接修改任何任務的「狀態」、「完成情況」、「已完成」字段。僅更新「進度」數值。腳本將自動計算已完成及狀態流轉。
|
||||
|
||||
/* 6. 配套的呼吸動畫,讓警告"活"起來 */
|
||||
@keyframes pulse-danger {
|
||||
0% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.1); opacity: 0.8; }
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
.task-divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, var(--survival-border), transparent);
|
||||
margin: 16px 0;
|
||||
}
|
||||
#### 8.5 獎勵規則
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
##### 8.5.1 點數獎勵標準
|
||||
|
||||
/* 抽卡系統樣式 - 與任務系統完全一致 */
|
||||
.draw-main-task {
|
||||
background: linear-gradient(135deg, rgba(52, 152, 219, 0.2), rgba(52, 152, 219, 0.1));
|
||||
border-color: rgba(52, 152, 219, 0.3);
|
||||
color: #3498db;
|
||||
}
|
||||
- 主線基礎:50~100 求生點數
|
||||
- 主線進階:100~200 求生點數
|
||||
- 支線基礎:30~50 求生點數
|
||||
- 支線進階:20~30 求生點數
|
||||
- 緊急基礎:20~30 求生點數
|
||||
- 緊急進階:30~60 求生點數
|
||||
- 日常基礎:10~20 求生點數
|
||||
- 背德基礎:同伴好感技能經驗 +50~+100
|
||||
- 背德進階:參見 8.5.3 背德任務獎勵
|
||||
- 羈絆基礎:同伴某項屬性或技能經驗 +20~+100
|
||||
- 羈絆進階:劇情演出(必給)+ 同伴專屬獎勵
|
||||
|
||||
.draw-side-task {
|
||||
background: linear-gradient(135deg, rgba(243, 156, 18, 0.2), rgba(243, 156, 18, 0.1));
|
||||
border-color: rgba(243, 156, 18, 0.3);
|
||||
color: #f39c12;
|
||||
}
|
||||
獎勵隔離原則:基礎獎勵池與進階獎勵池嚴格區分,禁止交叉出現
|
||||
|
||||
.draw-container {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
##### 8.5.2 進階獎勵雙重結構
|
||||
|
||||
/* 抽卡選項的特定樣式 */
|
||||
.requirement-item.draw-ready .req-desc {
|
||||
color: var(--survival-success);
|
||||
font-weight: 600;
|
||||
}
|
||||
進階獎勵欄位必須同時包含以下兩部分:
|
||||
|
||||
.requirement-item.draw-disabled .req-desc {
|
||||
color: var(--survival-text);
|
||||
opacity: 0.6;
|
||||
}
|
||||
1. 求生點數:按上表數值區間給予
|
||||
2. 劇情演出:按固定模板撰寫
|
||||
|
||||
.requirement-item {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
格式規範:`求生點數{N} + 劇情演出:[事件-{主題}-{序號}-未發放:{內容}]`
|
||||
|
||||
.requirement-item:hover {
|
||||
background: rgba(148, 197, 255, 0.1);
|
||||
}
|
||||
##### 8.5.3 背德任務獎勵(從中選擇 2~4 項發放,劇情演出必給)
|
||||
|
||||
.requirement-item.draw-ready:hover {
|
||||
background: rgba(46, 204, 113, 0.1);
|
||||
}
|
||||
根據任務內容和完成度選擇:
|
||||
|
||||
.requirement-item.draw-disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
實物類:
|
||||
- 對方身上的物件(內衣、飾品、信件等)
|
||||
- 對方特製的禮物(做愛使用券、口交使用券、按摩使用券、手工編織、雕刻、書信等)
|
||||
- 情趣用品(與背德情境相關的情趣道具)
|
||||
|
||||
.requirement-item.draw-disabled:hover {
|
||||
background: transparent;
|
||||
}
|
||||
關係類:
|
||||
- 好感技能經驗:60 ~ 150(作用於相關同伴)
|
||||
- 特殊狀態:賦予「共犯」、「自責傾向」、「扭曲依賴」等狀態
|
||||
- 關係改變:關係評估向更扭曲方向轉變
|
||||
|
||||
/* 任務完成樣式 - 整個描述(包括進度數字)都變灰劃掉 */
|
||||
.target-desc.completed {
|
||||
color: #999 !important;
|
||||
text-decoration: line-through;
|
||||
opacity: 0.7;
|
||||
}
|
||||
能力類(僅進階獎勵):
|
||||
- 新技能:角色學會與背德相關的新技能(如「偽裝」、「說服」、「潛行」)
|
||||
- 天賦特性:角色獲得新的天賦特性(如「背德的快感」、「共犯意識」)
|
||||
- 技能經驗:80 ~ 200(欺騙、說服、潛行、偽裝等相關技能)
|
||||
- 屬性經驗:50 ~ 100(意志、感知等)
|
||||
|
||||
/* 進度數字的黃色樣式(和生存點數一樣的黃色) */
|
||||
.progress-number {
|
||||
color: var(--survival-warning) !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
推進類:
|
||||
- 重要經歷:記錄新的背德行為
|
||||
- 解鎖權限:解鎖後續特定背德互動選項
|
||||
|
||||
/* 獎勵點數樣式 */
|
||||
.reward-info .points {
|
||||
color: var(--survival-warning) !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
發放原則:
|
||||
- 任務越困難、風險越高,獎勵數量越多
|
||||
- 至少發放 1 項可選獎勵(共 2 項:劇情演出 + 1 項可選)
|
||||
- 進階目標完成時,最多可選 3 項可選獎勵(共 4 項)
|
||||
|
||||
/* 緊急任務時間信息樣式 */
|
||||
.time-info {
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
color: var(--survival-text);
|
||||
margin-top: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
##### 8.5.4 羈絆任務獎勵
|
||||
|
||||
.time-info div {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
- 不含求生點數,所有獎勵專屬於同伴
|
||||
- 與任務要求、人設、好感等級及關係發展邏輯相符
|
||||
- 劇情演出 → 將進階獎勵中的事件字符串狀態從「未發放」改為「待觸發」,insert 追加到 `/同伴/{同伴名}/劇情演出/-`
|
||||
- 禁止用 replace 追加
|
||||
|
||||
/* 失敗懲罰樣式 */
|
||||
.penalty-info {
|
||||
color: var(--survival-danger) !important;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
##### 8.5.5 實物獎勵機制(獨立判定)
|
||||
|
||||
/* 物品狀態樣式 */
|
||||
.item-state {
|
||||
font-size: .8em;
|
||||
color: var(--survival-success);
|
||||
margin-top: 4px;
|
||||
}
|
||||
完成進階目標時,在結算點數和劇情演出之外,進行一次獨立的實物獎勵判定。
|
||||
|
||||
/* 修復描述文本換行 */
|
||||
.item-props {
|
||||
width: 100%;
|
||||
}
|
||||
判定門檻:
|
||||
- 主線/支線進階:1d100 ≤ 25,獲得 1 件與任務有關實物
|
||||
- 緊急進階:1d100 ≤ 40,獲得 1 件實物
|
||||
|
||||
.item-prop .prop-val {
|
||||
word-wrap: break-word;
|
||||
white-space: normal;
|
||||
line-height: 1.3;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
實用性限制:
|
||||
- 禁止發放槍械、防彈裝備、交通工具或足以直接終結生存壓力的高階物資
|
||||
- 所有物品必須是「有幫助但不足以改變遊戲平衡」的程度
|
||||
|
||||
/* 任務系統樣式 - 簡化狀態指示器 */
|
||||
.task-strip {
|
||||
position: relative;
|
||||
padding: 12px 16px;
|
||||
margin: 8px 0;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
border: 1px solid transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 44px;
|
||||
}
|
||||
實物獎勵的劇情融入:
|
||||
- 實物判定成功時,該物品必須出現於劇情演出描述中,作為故事的一部分自然呈現
|
||||
- 實物判定失敗時,劇情演出僅包含角色情感反應與關係變化
|
||||
- 實物獎勵不佔用額外欄位,全部寫入進階獎勵的單一字段中
|
||||
|
||||
.task-strip:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
正確範例:
|
||||
- `求生點數30 + 劇情演出:[事件-物資-003-未發放:你在廢棄商店的櫃檯下摸到一捲膠帶,貝蒂接過去時指尖擦過你的手背,她頓了一下才說『這...這個很有用』,隨後別過臉繼續翻找]`
|
||||
|
||||
.task-strip.main-task {
|
||||
background: linear-gradient(135deg, rgba(52, 152, 219, 0.2), rgba(52, 152, 219, 0.1));
|
||||
border-color: rgba(52, 152, 219, 0.3);
|
||||
color: #3498db;
|
||||
}
|
||||
錯誤範例(禁止):
|
||||
- `求生點數30 + 獲得物品:膠帶×1`
|
||||
|
||||
.task-strip.side-task {
|
||||
background: linear-gradient(135deg, rgba(243, 156, 18, 0.2), rgba(243, 156, 18, 0.1));
|
||||
border-color: rgba(243, 156, 18, 0.3);
|
||||
color: #f39c12;
|
||||
}
|
||||
##### 8.5.6 失敗懲罰(僅限緊急/背德任務)
|
||||
|
||||
.task-strip.daily-task {
|
||||
background: linear-gradient(135deg, rgba(46, 204, 113, 0.2), rgba(46, 204, 113, 0.1));
|
||||
border-color: rgba(46, 204, 113, 0.3);
|
||||
color: #2ecc71;
|
||||
}
|
||||
- 觸發:任務狀態變為「已失敗」且失敗懲罰未發放
|
||||
- 動作:根據「失敗懲罰」字段描述執行懲罰操作
|
||||
- 標記:失敗懲罰已發放 = true
|
||||
|
||||
.task-strip.emergency-task {
|
||||
background: linear-gradient(135deg, rgba(231, 76, 60, 0.2), rgba(231, 76, 60, 0.1));
|
||||
border-color: rgba(231, 76, 60, 0.3);
|
||||
color: #e74c3c;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
/* 任務狀態指示器 - 簡化版 */
|
||||
.task-status-indicator {
|
||||
position: absolute;
|
||||
right: 13px; /* 從16px減小到12px,讓位置更緊湊 */
|
||||
top: 50%;
|
||||
transform: translateY(-50%) scale(0.9); /* 添加整體縮放90% */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
#### 8.6 劇情演出獎勵撰寫規則
|
||||
|
||||
.status-text {
|
||||
font-size: 10px; /* 從12px減小到10px */
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.8px; /* 稍微減小字間距 */
|
||||
transform: rotate(-15deg);
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
padding: 1px 4px; /* 從2px 6px減小到1px 4px */
|
||||
border-radius: 2px;
|
||||
}
|
||||
進階獎勵中的劇情演出,必須使用以下模板:
|
||||
|
||||
/* 不同狀態的顏色 */
|
||||
.status-ongoing {
|
||||
color: #3498db;
|
||||
background: rgba(52, 152, 219, 0.15);
|
||||
}
|
||||
`劇情演出:[事件-{主題}-{序號}-未發放:{要求完成後的角色情感反應}+{後續行為/狀態變化}+{關係影響暗示}]`
|
||||
|
||||
.status-pending {
|
||||
color: #f39c12;
|
||||
background: rgba(243, 156, 18, 0.15);
|
||||
}
|
||||
撰寫步驟:
|
||||
1. 先確定進階要求是什麼行為
|
||||
2. 想像該行為完成後的 5-30 分鐘內發生的事情
|
||||
3. 描述角色因此產生的:
|
||||
- 情緒變化(害羞/緊張/動搖/期待)
|
||||
- 具體行為(整理衣服/低頭不語/主動靠近/寫日記)
|
||||
- 對未來的暗示(「下次可以...」、「以後...」)
|
||||
|
||||
.status-completed {
|
||||
color: #2ecc71;
|
||||
background: rgba(46, 204, 113, 0.15);
|
||||
}
|
||||
正確範例:
|
||||
- 進階要求:「滿足貝蒂提出的一次額外補魔要求」
|
||||
- 正確獎勵:「貝蒂在補魔後呼吸急促地靠在你肩上,小聲說『比想像中舒服...』,隨後三天內看到你就會臉紅」
|
||||
|
||||
.status-failed {
|
||||
color: #e74c3c;
|
||||
background: rgba(231, 76, 60, 0.15);
|
||||
}
|
||||
錯誤範例(禁止):
|
||||
- 獎勵:「貝蒂與你完成深度補魔」 ← 這是要求本身,不是獎勵
|
||||
|
||||
.status-abandoned {
|
||||
color: #95a5a6;
|
||||
background: rgba(149, 165, 166, 0.15);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
生成檢查(必須回答以下三個問題):
|
||||
1. 這個獎勵描述的是「要求行為本身」還是「行為之後的變化」?(必須是後者)
|
||||
2. 獎勵中是否包含角色的情感反應?(必須包含)
|
||||
3. 獎勵中是否包含後續行為或狀態變化?(必須包含)
|
||||
|
||||
/* 修改 .task-content 爲抽卡專用樣式 */
|
||||
.task-content {
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
font-family: "Microsoft YaHei","Segoe UI",Arial,sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: inherit;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
若問題1答案是「行為本身」,或問題2、問題3有任何一題答案是「否」,則獎勵不合格,必須重新撰寫。
|
||||
|
||||
/* 其他現有樣式保持不變 */
|
||||
.task-detail {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 8px 0;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.task-desc {
|
||||
text-align: left;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.4;
|
||||
color: var(--survival-text);
|
||||
white-space: pre-line;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
#### 8.7 獎勵發放檢查
|
||||
|
||||
.target-section {
|
||||
margin: 16px 0;
|
||||
}
|
||||
規則:不等待結算狀態,條件滿足立即發放
|
||||
|
||||
.section-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
color: var(--survival-accent);
|
||||
}
|
||||
基礎獎勵:
|
||||
- 條件:完成情況.基礎要求完成 = true 且 結算狀態.基礎獎勵已發放 = false
|
||||
- 動作:根據「基礎獎勵」字段描述執行獎勵操作
|
||||
- 標記:基礎獎勵已發放 = true
|
||||
|
||||
.target-desc {
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
color: var(--survival-text);
|
||||
white-space: pre-line;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
進階獎勵:
|
||||
- 條件:完成情況.進階目標完成 = true 且 結算狀態.進階獎勵已發放 = false
|
||||
- 動作:根據「進階獎勵」字段描述執行獎勵操作
|
||||
- 標記:進階獎勵已發放 = true
|
||||
|
||||
.reward-info {
|
||||
color: var(--survival-text);
|
||||
font-weight: 600;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.reward-info .points {
|
||||
color: var(--survival-warning);
|
||||
}
|
||||
#### 8.8 劇情演出獎勵的獨立處理流程
|
||||
|
||||
.penalty-info {
|
||||
color: var(--survival-danger);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
1. 識別信號源:
|
||||
當讀取到任務(羈絆或背德)的進階獎勵描述中包含「劇情演出:[事件描述]」字樣時,將其視為一個待發射的信號源。
|
||||
|
||||
.task-divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, var(--survival-border), transparent);
|
||||
margin: 16px 0;
|
||||
}
|
||||
2. 執行驗證檢查(必須完成):
|
||||
- 確認事件描述中包含角色的情感反應
|
||||
- 確認事件描述中包含後續行為或狀態變化
|
||||
- 確認事件描述不是單純複製進階要求
|
||||
- 若任一項缺失,必須在記錄觸發意圖時標記為「獎勵格式異常」
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
3. 記錄觸發意圖:
|
||||
在 Analysis 的「任務更新結論」中,必須記錄:
|
||||
「檢測到任務『{任務標題}』的進階獎勵為【劇情演出】。意圖已記錄,將等待劇情AI在下一輪生成 ActionResult 信號來實際創建事件。」
|
||||
|
||||
/* --- MAP CSS START --- */
|
||||
.map-modal {
|
||||
position: fixed; inset: 0; display: flex; justify-content: center; align-items: center; color: #ffffff;
|
||||
background: rgba(0,0,0,0.8); backdrop-filter: blur(8px); z-index: 1000;
|
||||
}
|
||||
.map-modal__content {
|
||||
position: relative; width: 95vw; height: 95vh; padding: 0; overflow: hidden; display: flex; flex-direction: column;
|
||||
border-radius: 26px; border: 1px solid rgba(148, 197, 255, 0.18);
|
||||
background: linear-gradient(155deg, rgba(17, 28, 45, 0.94), rgba(24, 36, 58, 0.94));
|
||||
box-shadow: var(--survival-shadow); backdrop-filter: blur(20px) saturate(160%);
|
||||
animation: modalFadeIn .3s ease-out;
|
||||
}
|
||||
.map-modal__close {
|
||||
position:absolute; top:10px; right:15px; font-size:24px; cursor:pointer;
|
||||
color:rgba(239, 246, 255, 0.7); transition:all .2s ease;
|
||||
width:30px; height:30px; display:flex; align-items:center; justify-content:center; border-radius:50%; z-index: 1100;
|
||||
}
|
||||
.map-modal__close:hover{ color:var(--survival-danger); background:rgba(231,76,60,.2); transform:scale(1.1) }
|
||||
4. 立即結算:
|
||||
記錄意圖後,立即將該任務的「結算狀態.進階獎勵已發放」更新為 true,防止重複觸發。
|
||||
|
||||
.map-modal .map-container {
|
||||
flex: 1; position: relative; overflow: hidden; touch-action: none; background: #2a2a2a;
|
||||
cursor: grab;
|
||||
}
|
||||
.map-modal .map-container.linking { cursor: crosshair; }
|
||||
.map-modal .map-container.dragging { cursor: grabbing; }
|
||||
|
||||
.map-modal .info-panel, .map-modal .zoom-controls button {
|
||||
border-color: rgba(148, 197, 255, 0.2);
|
||||
background: rgba(24, 36, 58, 0.9);
|
||||
box-shadow: 0 12px 28px rgba(6, 12, 25, 0.45);
|
||||
}
|
||||
.map-modal .zoom-btn:hover {
|
||||
background: rgba(94, 161, 255, 0.25);
|
||||
transform: translateY(-3px) scale(1.05);
|
||||
}
|
||||
/* --- MAP CSS END --- */
|
||||
|
||||
/* --- Draw Confirmation Modal --- */
|
||||
.draw-confirm-modal { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; padding: 24px; background: radial-gradient(circle at center, rgba(41, 128, 185, 0.38), rgba(10, 14, 22, 0.92)); backdrop-filter: blur(10px); z-index: 10000; animation: fadeInModal 0.25s ease; }
|
||||
.draw-confirm-modal__content {
|
||||
position: relative; width: min(420px, 90vw); overflow: hidden; text-align: center;
|
||||
border-radius: 26px; border: 1px solid rgba(148, 197, 255, 0.18);
|
||||
background: linear-gradient(155deg, rgba(17, 28, 45, 0.94), rgba(24, 36, 58, 0.94));
|
||||
box-shadow: var(--survival-shadow); backdrop-filter: blur(20px) saturate(160%);
|
||||
padding: 28px 26px 24px;
|
||||
}
|
||||
.draw-confirm-modal__glow { position: absolute; inset: -45%; background: radial-gradient(circle, rgba(77, 208, 255, 0.35) 0%, transparent 65%); filter: blur(60px); opacity: 0.65; animation: modalGlow 6s ease-in-out infinite; pointer-events: none; }
|
||||
.draw-confirm-modal__title { position: relative; z-index: 1; font-size: 20px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: #e9f5ff; margin: 0 0 12px; }
|
||||
.draw-confirm-modal__desc { position: relative; z-index: 1; font-size: 15px; color: var(--survival-text); margin: 0 0 18px; }
|
||||
.draw-confirm-modal__points { position: relative; z-index: 1; font-size: 14px; color: var(--survival-muted); letter-spacing: 0.05em; margin-bottom: 24px; }
|
||||
.draw-confirm-modal__points span { display: inline-block; margin-top: 6px; font-size: 30px; font-weight: 700; color: #4dd0ff; text-shadow: 0 0 18px rgba(77, 208, 255, 0.55); }
|
||||
.draw-confirm-modal__actions { position: relative; z-index: 1; display: flex; gap: 12px; justify-content: center; margin-top: 10px; }
|
||||
.draw-confirm-modal__button { flex: 1; padding: 12px 16px; border-radius: 999px; border: none; font-size: 15px; font-weight: 600; cursor: pointer; transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease; font-family: "Inter", "Microsoft YaHei", sans-serif;}
|
||||
.draw-confirm-modal__button:active { transform: scale(0.96); }
|
||||
.draw-confirm-modal__confirm { background: linear-gradient(135deg, #26c6da, #7c4dff); color: #fff; box-shadow: 0 12px 34px rgba(124, 77, 255, 0.35); }
|
||||
.draw-confirm-modal__confirm:hover:not(.is-disabled) { transform: translateY(-1px); box-shadow: 0 16px 42px rgba(124, 77, 255, 0.48); }
|
||||
.draw-confirm-modal__cancel { background: rgba(37, 49, 68, 0.9); color: var(--survival-text); box-shadow: 0 12px 28px rgba(0, 0, 0, 0.35); }
|
||||
.draw-confirm-modal__cancel:hover { transform: translateY(-1px); box-shadow: 0 14px 32px rgba(0, 0, 0, 0.4); }
|
||||
.draw-confirm-modal__confirm.is-disabled { background: rgba(90, 96, 112, 0.7); color: var(--survival-text); cursor: not-allowed; box-shadow: none; }
|
||||
.draw-confirm-modal__confirm.is-loading { color: transparent; position: relative; }
|
||||
.draw-confirm-modal__confirm.is-loading::after { content: ''; position: absolute; top: 50%; left: 50%; width: 22px; height: 22px; margin: -11px 0 0 -11px; border: 2px solid rgba(255, 255, 255, 0.32); border-top-color: #ffffff; border-radius: 50%; animation: spin 0.7s linear infinite; }
|
||||
.draw-confirm-modal__status { position: relative; z-index: 1; margin-top: 20px; min-height: 18px; font-size: 13px; color: var(--survival-text); letter-spacing: 0.03em; }
|
||||
|
||||
@keyframes fadeInModal { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
|
||||
@keyframes modalGlow { 0% { transform: scale(0.8); opacity: 0.5; } 50% { transform: scale(1.1); opacity: 0.7; } 100% { transform: scale(0.8); opacity: 0.5; } }
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* --- Image Viewer Modal --- */
|
||||
.image-viewer-modal { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; padding: 30px; background: radial-gradient(circle at 20% 20%, rgba(46, 134, 222, 0.35), transparent 55%), rgba(8, 11, 19, 0.92); backdrop-filter: blur(12px); z-index: 9990; animation: fadeInModal 0.25s ease; }
|
||||
.image-viewer-modal__content {
|
||||
position: relative; width: min(960px, 94vw); height: min(82vh, 700px); display: flex; flex-direction: column; overflow: hidden;
|
||||
border-radius: 26px; border: 1px solid rgba(148, 197, 255, 0.18);
|
||||
background: linear-gradient(155deg, rgba(17, 28, 45, 0.94), rgba(24, 36, 58, 0.94));
|
||||
box-shadow: var(--survival-shadow); backdrop-filter: blur(20px) saturate(160%);
|
||||
}
|
||||
.image-viewer__header { display: flex; align-items: center; justify-content: space-between; padding: 18px 24px 12px; color: #e9f5ff; }
|
||||
.image-viewer__title { font-size: 18px; text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600; }
|
||||
.image-viewer__close { background: none; border: none; font-size: 22px; cursor: pointer; transition: transform 0.2s ease, color 0.2s ease; color: var(--survival-muted); }
|
||||
.image-viewer__close:hover { color: #ffffff; transform: rotate(90deg); }
|
||||
|
||||
.image-viewer__canvas {
|
||||
position: relative; flex: 1; margin: 0 24px 24px; overflow: hidden; touch-action: none;
|
||||
background: radial-gradient(circle at 35% 30%, rgba(94, 161, 255, 0.14), rgba(9, 13, 22, 0.92));
|
||||
border: 1px solid rgba(148, 197, 255, 0.16);
|
||||
border-radius: 14px; cursor: grab;
|
||||
}
|
||||
.image-viewer__canvas.is-grabbing { cursor: grabbing; }
|
||||
.image-viewer__image { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(1); transform-origin: center center; max-width: none; max-height: none; will-change: transform; pointer-events: none; user-select: none; }
|
||||
.image-viewer__placeholder { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 70%; max-width: 360px; text-align: center; font-size: 14px; line-height: 1.6; pointer-events: none; color: var(--survival-text); }
|
||||
.image-viewer__status { position: absolute; left: 24px; bottom: 18px; font-size: 13px; letter-spacing: 0.02em; color: var(--survival-text); }
|
||||
.image-viewer__toolbar { display: flex; align-items: center; gap: 12px; padding: 0 24px 16px; flex-wrap: wrap; }
|
||||
.image-viewer__controls { display: flex; align-items: center; gap: 8px; margin-left: auto; flex-shrink: 0; }
|
||||
.image-viewer__hint { flex: 1 1 auto; font-size: 13px; line-height: 1.5; min-width: 200px; color: var(--survival-text);}
|
||||
.image-viewer__hint strong { color: #fff;}
|
||||
.image-viewer__control-button {
|
||||
width: 38px; height: 38px; border-radius: 50%; border: none; font-size: 16px; cursor: pointer;
|
||||
background: rgba(32, 43, 60, 0.85); color: var(--survival-text);
|
||||
transition: transform 0.18s ease, background 0.18s ease, box-shadow 0.18s ease;
|
||||
font-family: "Inter", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
.image-viewer__control-button:hover { transform: translateY(-1px); background: rgba(77, 208, 255, 0.2); box-shadow: 0 12px 24px rgba(0, 0, 0, 0.35); }
|
||||
#### 8.9 任務生成與更新靜默原則
|
||||
|
||||
.temp-fill { position: absolute; top: 0; left: 0; height: 100%; border-radius: 8px; background-image: linear-gradient(90deg, var(--survival-accent), var(--survival-success)); opacity: .55; }
|
||||
- 任務的生成和更新禁止在正文中出現任何提示信息
|
||||
- 所有任務操作在後台靜默完成
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.image-viewer-modal { padding: 18px; }
|
||||
.image-viewer-modal__content { height: 88vh; }
|
||||
.image-viewer__toolbar { flex-direction: column; align-items: stretch; }
|
||||
.image-viewer__hint { margin-bottom: 8px; }
|
||||
.image-viewer__controls { margin-left: 0; }
|
||||
.image-viewer__canvas { margin: 0 18px 18px; }
|
||||
}
|
||||
### 9. 劇情演出
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.draw-confirm-modal__content { padding: 24px 18px 20px; }
|
||||
.draw-confirm-modal__actions { flex-direction: column; }
|
||||
.image-viewer__header { padding: 16px 18px 10px; }
|
||||
.image-viewer-modal__content { border-radius: 16px; }
|
||||
}
|
||||
根據<劇情演出>規則執行。操作約束:追加新事件使用 insert /-,更新已有事件使用 replace,刪除事件使用 remove,禁止 replace 追加。
|
||||
|
||||
.other-group { margin-top: 8px; }
|
||||
.other-card { background: rgba(0,0,0,.15); padding: 8px 10px; border-radius: 6px; border: 1px solid var(--survival-border); margin-bottom: 6px; }
|
||||
.other-card__title { font-weight: 700; margin-bottom: 4px; }
|
||||
.empty-text { opacity: .75; font-size: 13px; }
|
||||
### 10. 商店更新
|
||||
|
||||
/* --- 同伴狀態條新配色方案 --- */
|
||||
.love-progress .progress__bar {
|
||||
background-image: linear-gradient(90deg, #FF8FB1, #FFCAD4); /* 好感度: 粉紅色漸層 */
|
||||
box-shadow: 0 0 18px rgba(255, 143, 177, 0.55);
|
||||
}
|
||||
.desire-progress .progress__bar {
|
||||
background-image: linear-gradient(90deg, #E94057, #F27121); /* 性慾: 激情玫瑰紅到柑橘橙的漸變 */
|
||||
box-shadow: 0 0 18px rgba(233, 64, 87, 0.55);
|
||||
}
|
||||
.dev-progress .progress__bar {
|
||||
background-image: linear-gradient(90deg, #C39BD3, #8E44AD); /* 開發度: 紫色漸層 */
|
||||
box-shadow: 0 0 18px rgba(142, 68, 173, 0.55);
|
||||
}
|
||||
.crevice-progress .progress__bar {
|
||||
background-image: linear-gradient(90deg, #485461, #28313b); /* 墮落值: 從深灰到黑色的漸層,代表裂縫 */
|
||||
box-shadow: 0 0 18px rgba(40, 49, 59, 0.55);
|
||||
}
|
||||
.health-progress .progress__bar {
|
||||
background-image: linear-gradient(90deg, #58f0a7, #1abc9c); /* 健康 - 生命的翠綠色 */
|
||||
box-shadow: 0 0 18px rgba(46, 204, 113, 0.45);
|
||||
}
|
||||
.satiety-progress .progress__bar {
|
||||
background-image: linear-gradient(90deg, #ffdc84, #ff9f6c); /* 飽腹 - 豐收的南瓜色 */
|
||||
box-shadow: 0 0 18px rgba(243, 156, 18, 0.45);
|
||||
}
|
||||
.fatigue-progress .progress__bar {
|
||||
background-image: linear-gradient(90deg, #95a5a6, #7f8c8d); /* 疲勞 - 沉靜的石板灰 */
|
||||
box-shadow: 0 0 18px rgba(127, 140, 141, 0.45);
|
||||
}
|
||||
.water-progress .progress__bar {
|
||||
background-image: linear-gradient(90deg, #7fd8ff, #5ea1ff); /* 水分 - 清澈的水藍色 */
|
||||
box-shadow: 0 0 18px rgba(94, 161, 255, 0.45);
|
||||
}
|
||||
#### 刷新規則
|
||||
|
||||
/* --- 新增:同伴物品欄專用網格樣式 --- */
|
||||
.companion-inventory-grid {
|
||||
display: grid;
|
||||
/* 核心指令:固定為4列,每列平分剩餘空間 */
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 12px; /* 格子之間的間距 */
|
||||
margin-top: 20px; /* 和上面的技能區塊拉開一點距離 */
|
||||
}
|
||||
- 觸發:定時更新時(跨過 00:00)
|
||||
- 操作:各類別(材料、工具、奇物、夥伴-技術與後勤、夥伴-戰鬥與探索、夥伴-反派與妖魅、夥伴-純愛與保護控、藥物、盲盒)按照<商店規則>強制刷新為新商品
|
||||
- 使用:replace 整個商品對象
|
||||
|
||||
/* 為了在小螢幕上體驗更好,我們加個媒體查詢 */
|
||||
@media (max-width: 960px) {
|
||||
.companion-inventory-grid {
|
||||
grid-template-columns: repeat(3, 1fr); /* 螢幕稍小時,一行3個 */
|
||||
}
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.companion-inventory-grid {
|
||||
grid-template-columns: repeat(2, 1fr); /* 螢幕再小時,一行2個 */
|
||||
}
|
||||
}
|
||||
#### 商品刷新防重複規則(強制執行)
|
||||
|
||||
/* ---【最終修復補丁 v4.0】--- */
|
||||
/* 規則1: 無論如何,只要是任務卡片的內容區,就先給它隱藏起來! */
|
||||
.task-card > .task-body.collapsible__body {
|
||||
display: none;
|
||||
}
|
||||
1. 刷新前,必須先讀取 `/系統/商店歷史` 中的所有記錄
|
||||
2. 對於每個類別的商品,逐一比對,若新生成的商品名稱與歷史記錄中任一筆的同類別商品相同,則視為重複,必須重新生成該類別的商品,直到不重複為止
|
||||
3. 夥伴類別特殊比對規則:
|
||||
- 四個夥伴類別(夥伴-技術與後勤、夥伴-戰鬥與探索、夥伴-反派與妖魅、夥伴-純愛與保護控)共用同一份歷史記錄進行比對
|
||||
- 若新生成的角色出處(作品名稱)與歷史記錄中任何一個夥伴類別的角色出處相同,即視為重複,必須重新生成
|
||||
- 範例:若歷史記錄中「夥伴-戰鬥與探索」已有出自《鬼滅之刃》的角色,則本次刷新「夥伴-反派與妖魅」時不得再生成出自《鬼滅之刃》的角色
|
||||
4. 比對完成後,再執行 replace 更新商店
|
||||
|
||||
/* 規則2: 只有當任務卡片被點擊,擁有了 'active' 狀態後才讓它的內容區顯示出來 */
|
||||
.task-card.active > .task-body.collapsible__body {
|
||||
display: block;
|
||||
}
|
||||
/* 🔴 紅色 - 警告(存糧 ≤ 3 天) */
|
||||
.crop-status-danger {
|
||||
color: #ff4757;
|
||||
background: rgba(255, 71, 87, 0.15);
|
||||
}
|
||||
#### 購買處理
|
||||
|
||||
/* 🟢 綠色 - 正常(存糧 > 3 天) */
|
||||
.crop-status-ready {
|
||||
color: #58f0a7;
|
||||
background: rgba(88, 240, 167, 0.15);
|
||||
}
|
||||
/* ============== 新增:作物狀態標籤樣式 ============== */
|
||||
.crop-status-indicator {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.8px;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
.crop-status-ready {
|
||||
color: #58f0a7;
|
||||
background: rgba(88, 240, 167, 0.15);
|
||||
}
|
||||
.crop-status-growing {
|
||||
color: #a0aec0;
|
||||
background: rgba(160, 174, 192, 0.15);
|
||||
}
|
||||
.item-name .crop-status-indicator { /* 讓二級目錄裡的標籤小一點 */
|
||||
transform: scale(0.9);
|
||||
}
|
||||
🔴 信號來源:ActionResult中「商品出售」字段
|
||||
|
||||
/* ============== 新增:畜牧狀態標籤樣式 ============== */
|
||||
.livestock-status-indicator {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.8px;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
.livestock-status-harvest {
|
||||
color: #58f0a7; /* 可收成 - 綠色 */
|
||||
background: rgba(88, 240, 167, 0.15);
|
||||
}
|
||||
.livestock-status-breed {
|
||||
color: #3498db; /* 可繁殖 - 藍色 */
|
||||
background: rgba(52, 152, 219, 0.15);
|
||||
}
|
||||
.livestock-status-both {
|
||||
color: #f39c12; /* 可收成且可繁殖 - 橙色 */
|
||||
background: rgba(243, 156, 18, 0.15);
|
||||
}
|
||||
.livestock-status-none {
|
||||
color: #a0aec0; /* 無特殊狀態 - 灰色 */
|
||||
background: rgba(160, 174, 192, 0.15);
|
||||
}
|
||||
.item-name .livestock-status-indicator { /* 讓二級目錄裡的標籤小一點 */
|
||||
transform: scale(0.9);
|
||||
}
|
||||
- 操作:
|
||||
- 扣除對應求生點數(從 `/系統/求生點數` 減去)
|
||||
- 將購買的商品從商店移除(設為空 `{}`)
|
||||
- 盲盒特殊處理:
|
||||
- 如「→開出:[具體商品名]」,生成對應具體內容
|
||||
- 角色盲盒需完整初始化同伴變量(屬性、技能、天賦特性、裝備、攜帶物品)
|
||||
|
||||
/* ============== 新增:作物加速效果區塊樣式 ============== */
|
||||
.crop-acceleration-info {
|
||||
padding: 16px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 12px;
|
||||
background: rgba(30, 45, 70, 0.8);
|
||||
border: 1px solid var(--survival-glass-border);
|
||||
box-shadow: inset 0 1px 2px rgba(0,0,0,0.4), var(--survival-glow);
|
||||
}
|
||||
.crop-acceleration-info__title {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--survival-accent-strong);
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.crop-acceleration-info__item {
|
||||
font-size: 14px;
|
||||
padding-left: 28px;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.crop-acceleration-info__total {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--survival-border);
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--survival-success);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
### 11. 同伴/寵物/子嗣互動更新
|
||||
|
||||
/* ============== 新增:寵物狀態條樣式 ============== */
|
||||
.loyalty-progress .progress__bar {
|
||||
background-image: linear-gradient(90deg, #FFD700, #FFA500); /* 忠誠度: 金色漸層 */
|
||||
box-shadow: 0 0 18px rgba(255, 215, 0, 0.45);
|
||||
}
|
||||
.pet-health-progress .progress__bar {
|
||||
background-image: linear-gradient(90deg, #27ae60, #16a085); /* 寵物健康: 翠綠色漸層 */
|
||||
box-shadow: 0 0 18px rgba(46, 204, 113, 0.45);
|
||||
}
|
||||
.pet-desire-progress .progress__bar {
|
||||
background-image: linear-gradient(90deg, #E94057, #F27121); /* 性慾: 與同伴相同 */
|
||||
box-shadow: 0 0 18px rgba(233, 64, 87, 0.45);
|
||||
}
|
||||
/* 项目/研究/知识库 卡片样式 */
|
||||
.task-strip {
|
||||
position: relative;
|
||||
padding: 12px 16px;
|
||||
margin: 8px 0;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
border: 1px solid transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 44px;
|
||||
}
|
||||
.task-strip:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.task-strip.main-task {
|
||||
background: linear-gradient(135deg, rgba(52, 152, 219, 0.2), rgba(52, 152, 219, 0.1));
|
||||
border-color: rgba(52, 152, 219, 0.3);
|
||||
color: #3498db;
|
||||
}
|
||||
.task-strip.side-task {
|
||||
background: linear-gradient(135deg, rgba(243, 156, 18, 0.2), rgba(243, 156, 18, 0.1));
|
||||
border-color: rgba(243, 156, 18, 0.3);
|
||||
color: #f39c12;
|
||||
}
|
||||
.task-strip.bond-task {
|
||||
background: linear-gradient(135deg, rgba(189, 147, 249, 0.25), rgba(189, 147, 249, 0.15));
|
||||
border-color: rgba(142, 68, 173, 0.4);
|
||||
color: #f0e6ff;
|
||||
}
|
||||
.task-content {
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
width: 100%;
|
||||
}
|
||||
.task-detail {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 8px 0;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.collapse-button {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
margin-top: 15px;
|
||||
background: rgba(0,0,0,.2);
|
||||
border: 1px solid var(--survival-border);
|
||||
border-radius: 8px;
|
||||
color: var(--survival-warning);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.collapse-button:hover {
|
||||
background: rgba(52,73,94,.6);
|
||||
border-color: var(--survival-warning);
|
||||
}
|
||||
/* 進度與知識模塊 - 字體大小統一與任務一致 */
|
||||
.progress-knowledge-card {
|
||||
font-size: 14px;
|
||||
}
|
||||
.progress-knowledge-card .progress-wrapper > div:first-child,
|
||||
.progress-knowledge-card .mini-progress__text {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
.progress-knowledge-card .progress__bar,
|
||||
.progress-knowledge-card .mini-progress__bar {
|
||||
height: 10px; /* 稍微加高進度條,更易讀 */
|
||||
}
|
||||
.progress-knowledge-card .item-prop {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
.progress-knowledge-card .prop-key,
|
||||
.progress-knowledge-card .prop-val {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
.progress-knowledge-card .skill-desc,
|
||||
.progress-knowledge-card .task-desc {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
.progress-knowledge-card .collapse-button {
|
||||
font-size: 14px !important;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
/* 卡片內標題 */
|
||||
.progress-knowledge-card .task-strip .task-content {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
/* 强制覆盖進度與知識模塊内的所有内联字体大小 */
|
||||
.progress-knowledge-card div[style*="font-size"],
|
||||
.progress-knowledge-card span[style*="font-size"] {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
/* 進度與知識模塊 - 強制內部所有文字與任務模塊一致 */
|
||||
.progress-knowledge-card {
|
||||
font-size: 15px !important;
|
||||
}
|
||||
.progress-knowledge-card * {
|
||||
font-size: 15px !important;
|
||||
}
|
||||
/* 進度條上方文字(例如“進度:12/100”)中的數字部分高亮 */
|
||||
.progress-knowledge-card .progress-wrapper > div:first-child,
|
||||
.progress-knowledge-card .progress-wrapper > div {
|
||||
font-size: 15px !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
.progress-knowledge-card .progress-wrapper .progress-number {
|
||||
color: var(--survival-warning) !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
/* 強制所有帶有內聯樣式的元素統一字體大小和顏色 */
|
||||
.progress-knowledge-card div[style*="font-size"],
|
||||
.progress-knowledge-card span[style*="font-size"],
|
||||
.progress-knowledge-card div[style*="font-size"] *,
|
||||
.progress-knowledge-card span[style*="font-size"] * {
|
||||
font-size: 15px !important;
|
||||
}
|
||||
/* 卡片內的描述文字、材料行等 */
|
||||
.progress-knowledge-card .skill-desc,
|
||||
.progress-knowledge-card .task-desc,
|
||||
.progress-knowledge-card .item-prop,
|
||||
.progress-knowledge-card .item-prop .prop-key,
|
||||
.progress-knowledge-card .item-prop .prop-val,
|
||||
.progress-knowledge-card .detail-line,
|
||||
.progress-knowledge-card .sub-line {
|
||||
font-size: 15px !important;
|
||||
color: #cccccc !important;
|
||||
}
|
||||
/* 材料需求中的數字(已投入/所需)高亮為黃色 */
|
||||
.progress-knowledge-card .material-value,
|
||||
.progress-knowledge-card .detail-line span:last-child,
|
||||
.progress-knowledge-card .sub-line span:last-child {
|
||||
color: var(--survival-warning) !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
/* 已完成項的灰色和刪除線 */
|
||||
.progress-knowledge-card .completed,
|
||||
.progress-knowledge-card .detail-line.completed,
|
||||
.progress-knowledge-card .sub-line.completed {
|
||||
color: #888888 !important;
|
||||
text-decoration: line-through !important;
|
||||
}
|
||||
/* 卡片內的標題(如“材料”、“需求”等) */
|
||||
.progress-knowledge-card .field-title {
|
||||
font-size: 15px !important;
|
||||
font-weight: bold !important;
|
||||
color: var(--survival-accent) !important;
|
||||
margin-top: 8px !important;
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
/* 進度條高度稍微增加 */
|
||||
.progress-knowledge-card .progress,
|
||||
.progress-knowledge-card .mini-progress {
|
||||
height: 10px !important;
|
||||
}
|
||||
.progress-knowledge-card .progress__bar,
|
||||
.progress-knowledge-card .mini-progress__bar {
|
||||
height: 10px !important;
|
||||
}
|
||||
/* 收起按鈕 */
|
||||
.progress-knowledge-card .collapse-button {
|
||||
font-size: 15px !important;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
/* 項目名稱 / 研究名稱 */
|
||||
.progress-knowledge-card .task-content {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
</style></head><body><div id="survival-ui" class="survival-ui" tabindex="0"></div><script>
|
||||
#### 關係
|
||||
按照劇情氛圍、整體劇情走向判斷,參考好感技能等級,由AI主觀決定兩者的關係
|
||||
|
||||
function formatBonusValue(value) {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return textify(value, '');
|
||||
}
|
||||
|
||||
const bonusEntries = [];
|
||||
Object.entries(value).forEach(([bonusKey, bonusValue]) => {
|
||||
bonusEntries.push(`${bonusKey}+【${bonusValue}】`);
|
||||
});
|
||||
return bonusEntries.join('、');
|
||||
}
|
||||
#### 天賦特性
|
||||
- 規則:由AI根據劇情判斷是否需要建立或更新
|
||||
- 觸發:天賦特性發生變化時(覺醒、更新、失去)
|
||||
- 注意:禁止憑空賦予或刪除天賦,必須有劇情依據
|
||||
|
||||
function parseTimeToMinutes(timeString) {
|
||||
const dayMatch = timeString.match(/第(\d+)天/);
|
||||
const timeMatch = timeString.match(/(\d{2}):(\d{2})/);
|
||||
#### 好感技能
|
||||
|
||||
if (!dayMatch || !timeMatch) {
|
||||
return null; // 格式不對就直接放棄
|
||||
}
|
||||
- **好感技能僅限於同伴對user,不產生同伴之間彼此的好感技能**
|
||||
- 經驗獲取規則參照 `<sex_rules>` 第八章「淪陷生理推演階段」及第十章「好感等級階段」。
|
||||
- 經驗更新流程:
|
||||
1. 掃描本回合劇情
|
||||
2. 識別符合類型的互動
|
||||
3. 選擇經驗範圍內數值
|
||||
4. 檢查路徑是否存在(`/同伴/[同伴名]/技能/好感(對<user>)`)
|
||||
5. 若存在: delta 增加經驗;若不存在: insert 技能物件再增加經驗
|
||||
|
||||
const days = parseInt(dayMatch[1], 10);
|
||||
const hours = parseInt(timeMatch[1], 10);
|
||||
const minutes = parseInt(timeMatch[2], 10);
|
||||
#### 心理狀態.當前所想
|
||||
|
||||
return (days * 24 * 60) + (hours * 60) + minutes;
|
||||
}
|
||||
觸發條件(滿足任一即更新):
|
||||
- 與<user>有重要對話
|
||||
- 目睹或經歷重大事件
|
||||
- 情緒/生理狀態顯著變化(壓力變化≥20、懷孕階段變化、疲勞≥50)
|
||||
- 關係變化(好感等級升級)
|
||||
|
||||
// 2. 化妝師:把剩餘的總分鐘數,變成 "X天X小時X分"
|
||||
function formatRemainingTime(totalMinutes) {
|
||||
if (totalMinutes <= 0) {
|
||||
return "已截止";
|
||||
}
|
||||
更新格式:
|
||||
- 操作: replace
|
||||
- 內容: 第一人稱內心獨白,30-60字,須包含:
|
||||
- 對本次事件/情況的判斷
|
||||
- 對<user>或其他相關角色的看法(至少一個具體評價)
|
||||
- 對自己當前處境的感受或下一步打算
|
||||
|
||||
const days = Math.floor(totalMinutes / (24 * 60));
|
||||
const hours = Math.floor((totalMinutes % (24 * 60)) / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
禁止事項:
|
||||
- 複製正文已有的內心台詞
|
||||
- 預測未來、說出設定外的知識
|
||||
- 只寫單一面向
|
||||
|
||||
let result = "";
|
||||
if (days > 0) result += `${days}天`;
|
||||
if (hours > 0) result += `${hours}小時`;
|
||||
if (minutes > 0) result += `${minutes}分`;
|
||||
#### 背德經歷
|
||||
|
||||
return result.trim();
|
||||
}
|
||||
觸發條件(滿足任一即記錄):
|
||||
|
||||
// --- 新增的狀態描述函數 ---
|
||||
function getFavorabilityStatus(value) {
|
||||
if (value < 0) return "仇人";
|
||||
if (value >= 0 && value <= 20) return "陌生人";
|
||||
if (value > 20 && value <= 40) return "疏離淡漠";
|
||||
if (value > 40 && value <= 50) return "平淡如水";
|
||||
if (value > 50 && value <= 60) return "親近信賴";
|
||||
if (value > 60 && value <= 70) return "依賴眷戀";
|
||||
if (value > 70 && value <= 99) return "熱戀痴迷";
|
||||
if (value >= 100) return "摯愛不渝";
|
||||
return "";
|
||||
}
|
||||
親密關係背德:
|
||||
- 對非伴侶對象進行性接觸
|
||||
- 情感劈腿/同時與多人保持親密關係
|
||||
- 利用好感或資源交換性行為
|
||||
|
||||
function getDesireStatus(value) {
|
||||
if (value >= 0 && value <= 20) return "沒感覺";
|
||||
if (value > 20 && value <= 40) return "心動";
|
||||
if (value > 40 && value <= 60) return "期待愛撫";
|
||||
if (value > 60 && value <= 80) return "渴望性愛";
|
||||
if (value > 80 && value < 100) return "強烈慾望";
|
||||
if (value >= 100) return "失控狂熱";
|
||||
return "";
|
||||
}
|
||||
信任背德:
|
||||
- 利用他人信任謀取私利
|
||||
- 說服/引導他人背叛原有關係
|
||||
|
||||
function getHealthStatus(value) {
|
||||
if (value === 0) return "死亡";
|
||||
if (value > 0 && value <= 10) return "重傷昏迷";
|
||||
if (value > 10 && value <= 30) return "生病";
|
||||
if (value > 30 && value <= 60) return "虛弱";
|
||||
if (value > 60 && value <= 100) return "健康";
|
||||
if (value > 100) return "超人";
|
||||
return "狀態未知";
|
||||
}
|
||||
信念背德:
|
||||
- 做出與核心信念嚴重衝突的行為(首次)
|
||||
- 為生存放棄曾堅守的原則(首次)
|
||||
- 對自己的「墮落」感到麻木(首次里程碑)
|
||||
|
||||
function getCreviceStatus(value) {
|
||||
if (value >= 0 && value <= 30) return "萌芽期";
|
||||
if (value > 30 && value <= 60) return "依賴期";
|
||||
if (value > 60 && value < 100) return "渴望期";
|
||||
if (value >= 100) return "崩塌期";
|
||||
return "";
|
||||
}
|
||||
更新格式:
|
||||
- 操作: insert(不存在時)
|
||||
- 格式: `{"事件名稱": {"描述": "簡要描述(30字以內)"}}`
|
||||
|
||||
// --- 寵物狀態描述函數 ---
|
||||
function getLoyaltyStatus(value) {
|
||||
if (value < 0) return "敵對";
|
||||
if (value >= 0 && value <= 20) return "警惕";
|
||||
if (value > 20 && value <= 40) return "中立";
|
||||
if (value > 40 && value <= 60) return "友善";
|
||||
if (value > 60 && value <= 80) return "信賴";
|
||||
if (value > 80 && value < 100) return "忠誠";
|
||||
if (value >= 100) return "絕對忠誠";
|
||||
return "";
|
||||
}
|
||||
防重複: 同一事件只記錄一次
|
||||
|
||||
function getPetHealthStatus(value) {
|
||||
if (value === 0) return "死亡";
|
||||
if (value > 0 && value <= 20) return "瀕危";
|
||||
if (value > 20 && value <= 40) return "重傷";
|
||||
if (value > 40 && value <= 60) return "受傷";
|
||||
if (value > 60 && value <= 80) return "健康";
|
||||
if (value > 80 && value <= 100) return "充滿活力";
|
||||
if (value > 100) return "超凡";
|
||||
return "狀態未知";
|
||||
}
|
||||
// --- 狀態描述函數結束 ---
|
||||
|
||||
function normalizeValue(value) {
|
||||
if (Array.isArray(value)) {
|
||||
const flattened = [];
|
||||
value.forEach((item) => {
|
||||
if (Array.isArray(item)) {
|
||||
const inner = normalizeValue(item);
|
||||
if (Array.isArray(inner)) {
|
||||
flattened.push(...inner);
|
||||
} else if (inner !== '' && inner !== undefined && inner !== null) {
|
||||
flattened.push(inner);
|
||||
}
|
||||
} else if (item !== undefined && item !== null && item !== '') {
|
||||
flattened.push(item);
|
||||
}
|
||||
});
|
||||
if (flattened.length === 0) {
|
||||
return '';
|
||||
}
|
||||
if (flattened.length === 1) {
|
||||
return normalizeValue(flattened[0]);
|
||||
}
|
||||
return flattened;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
#### 敏感部位
|
||||
|
||||
function e(value, parse = true) {
|
||||
if (value == null) {
|
||||
return '';
|
||||
}
|
||||
const normalized = normalizeValue(value);
|
||||
if (Array.isArray(normalized)) {
|
||||
const processed = normalized
|
||||
.map((item) => e(item, parse))
|
||||
.filter((item) => item !== '' && item !== undefined && item !== null);
|
||||
if (processed.length === 0) {
|
||||
return parse ? '' : [];
|
||||
}
|
||||
return parse ? processed.join(' / ') : processed;
|
||||
}
|
||||
if (typeof normalized === 'string' && normalized.includes('->')) {
|
||||
const match = normalized.match(/(.+)->(.+)\((.+)\)/);
|
||||
if (match) {
|
||||
const label = match[2].trim();
|
||||
return parse ? a(label) : label;
|
||||
}
|
||||
}
|
||||
return parse ? a(normalized) : normalized;
|
||||
}
|
||||
觸發條件(滿足任一即記錄):
|
||||
- B類場景中,該部位被刺激後產生強烈反應
|
||||
- 非性愛場景中,意外觸碰引發異常反應
|
||||
- 醫療檢查中發現異常敏感
|
||||
|
||||
function a(value) {
|
||||
if (typeof value === 'number') {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
const cleaned = value.replace(/,/g, '');
|
||||
const parsed = parseFloat(cleaned);
|
||||
if (!Number.isNaN(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
判斷標準: 反應強度明顯強於普通身體接觸,產生性喚起、顫抖、呻吟等明顯反應
|
||||
|
||||
function isMetaOnlyObject(value) {
|
||||
return (
|
||||
value &&
|
||||
typeof value === 'object' &&
|
||||
!Array.isArray(value) &&
|
||||
'$meta' in value &&
|
||||
Object.keys(value).length === 1
|
||||
);
|
||||
}
|
||||
更新格式:
|
||||
- 操作: insert(不存在時)或 replace(已存在可覆蓋)
|
||||
- 格式: `{"部位名稱": {"刺激方式": "反應描述"}}`
|
||||
|
||||
function textify(value, fallback = '') {
|
||||
const raw = e(value, false);
|
||||
if (Array.isArray(raw)) {
|
||||
const list = raw.map((item) => textify(item, '')).filter(Boolean);
|
||||
return list.length > 0 ? list.join(' / ') : fallback;
|
||||
}
|
||||
if (raw == null) {
|
||||
return fallback;
|
||||
}
|
||||
const str = String(raw).trim();
|
||||
return str || fallback;
|
||||
}
|
||||
#### 性癖好
|
||||
|
||||
function toEntries(source) {
|
||||
if (source == null) {
|
||||
return [];
|
||||
}
|
||||
const raw = e(source, false);
|
||||
if (Array.isArray(raw)) {
|
||||
const result = [];
|
||||
raw.forEach((item, index) => {
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(item)) {
|
||||
item.forEach((nested, nestedIndex) => {
|
||||
if (nested == null || isMetaOnlyObject(nested)) {
|
||||
return;
|
||||
}
|
||||
result.push([`${index}-${nestedIndex}`, nested]);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (isMetaOnlyObject(item)) {
|
||||
return;
|
||||
}
|
||||
result.push([String(index), item]);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
if (typeof raw === 'object') {
|
||||
return Object.entries(raw).filter(([key, value]) => {
|
||||
if (key === '$meta') {
|
||||
return false;
|
||||
}
|
||||
if (isMetaOnlyObject(value)) {
|
||||
return false;
|
||||
}
|
||||
return value != null;
|
||||
});
|
||||
}
|
||||
return [['0', raw]];
|
||||
}
|
||||
觸發條件(滿足任一即記錄):
|
||||
- 對某類行為反覆表現出強烈反應
|
||||
- 同一類行為在至少2次不同場景中引發相似反應
|
||||
|
||||
function firstMeaningfulObject(source) {
|
||||
if (source && typeof source === 'object' && !Array.isArray(source)) {
|
||||
const clone = { ...source };
|
||||
delete clone.$meta;
|
||||
if (Object.keys(clone).length > 0) {
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
const entries = toEntries(source);
|
||||
for (const [, value] of entries) {
|
||||
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
const clone = { ...value };
|
||||
delete clone.$meta;
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
更新格式:
|
||||
- 操作: insert(不存在時)或 replace(已存在可覆蓋)
|
||||
- 格式: `{"癖好名稱": "描述說明"}`
|
||||
|
||||
function ensureValueArray(value) {
|
||||
return toEntries(value).map(([, item]) => item);
|
||||
}
|
||||
const s = {
|
||||
碳水: 2000,
|
||||
脂肪: 100000,
|
||||
氮平衡: 200,
|
||||
蛋白質: 2400,
|
||||
維生素: 100,
|
||||
水分: 100,
|
||||
飽腹感: 100,
|
||||
體溫: 42,
|
||||
免疫負荷: 100,
|
||||
壓力: 100,
|
||||
無聊: 100,
|
||||
孤獨: 100,
|
||||
睡眠: 100,
|
||||
疲勞: 100,
|
||||
性慾: 100,
|
||||
墮落值: 100,
|
||||
};
|
||||
|
||||
function t(label, current, max, className) {
|
||||
const percent = Math.max(0, Math.min(100, max === 0 ? 0 : (current / max) * 100));
|
||||
const wrapper = $('<div>').addClass('progress-wrapper');
|
||||
const displayText = label.includes('/') ? label : (label ? `${label}: ${current}/${max}` : `${current}/${max}`);
|
||||
$('<div>').text(displayText).appendTo(wrapper);
|
||||
const bar = $('<div>').addClass('progress');
|
||||
if (className) {
|
||||
bar.addClass(className);
|
||||
}
|
||||
$('<div>').addClass('progress__bar').css('width', `${percent}%`).appendTo(bar);
|
||||
wrapper.append(bar);
|
||||
return wrapper;
|
||||
}
|
||||
#### 裝備
|
||||
|
||||
function d(title, expanded = false) {
|
||||
const section = $('<section>').addClass('collapsible');
|
||||
if (expanded) {
|
||||
section.addClass('active');
|
||||
}
|
||||
const header = $('<div>').addClass('collapsible__header'); // 我們會在後面添加內容
|
||||
const body = $('<div>').addClass('collapsible__body');
|
||||
|
||||
// 將標題本身放在一個 span 裡,方便和標籤並存
|
||||
$('<span>').text(title).appendTo(header);
|
||||
|
||||
if (expanded) {
|
||||
body.css('display', 'block');
|
||||
}
|
||||
header.on('click', () => {
|
||||
section.toggleClass('active');
|
||||
body.slideToggle(200);
|
||||
});
|
||||
section.append(header, body);
|
||||
return { $section: section, $header: header, $body: body };
|
||||
}
|
||||
規則: 正文明確描寫裝備/卸下時更新對應裝備槽位
|
||||
操作: replace 對應部位
|
||||
|
||||
function n(title, items, expanded = false) {
|
||||
const section = $('<section>').addClass('collapsible');
|
||||
if (expanded) {
|
||||
section.addClass('active');
|
||||
}
|
||||
const header = $('<div>').addClass('collapsible__header collapsible__header--with-progress');
|
||||
$('<span>').addClass('collapsible__title').text(title).appendTo(header);
|
||||
const bars = $('<div>').addClass('collapsible__bars-container');
|
||||
items.forEach((item) => {
|
||||
const row = $('<div>').addClass('progress-item');
|
||||
$('<span>').addClass('progress-label').text(`${item.label}:`).appendTo(row);
|
||||
const percent = Math.max(0, Math.min(100, item.max === 0 ? 0 : (item.current / item.max) * 100));
|
||||
const miniWrapper = $('<div>').addClass('mini-progress-wrapper');
|
||||
const miniBar = $('<div>').addClass('mini-progress');
|
||||
if (item.className) {
|
||||
miniBar.addClass(item.className);
|
||||
}
|
||||
$('<div>').addClass('mini-progress__bar').css('width', `${percent}%`).appendTo(miniBar);
|
||||
$('<div>').addClass('mini-progress__text').text(`${item.current}/${item.max}`).appendTo(miniWrapper);
|
||||
miniWrapper.append(miniBar);
|
||||
row.append(miniWrapper);
|
||||
bars.append(row);
|
||||
});
|
||||
header.append(bars);
|
||||
$('<span>').addClass('collapsible__indicator').appendTo(header);
|
||||
const body = $('<div>').addClass('collapsible__body');
|
||||
if (expanded) {
|
||||
body.css('display', 'block');
|
||||
}
|
||||
header.on('click', () => {
|
||||
section.toggleClass('active');
|
||||
body.slideToggle(200);
|
||||
});
|
||||
section.append(header, body);
|
||||
return { $section: section, $body: body };
|
||||
}
|
||||
#### 排泄欲
|
||||
|
||||
function i(equipmentSource, itemsSource, campSource, currentWeight, weightLimit, showCamp = true) {
|
||||
const modal = $('<div>').addClass('inventory-modal');
|
||||
const content = $('<div>').addClass('inventory-modal__content');
|
||||
const close = $('<span>').addClass('inventory-modal__close').html('×').on('click', () => modal.remove());
|
||||
|
||||
const weightContainer = $('<div>').addClass('weight-container');
|
||||
const percent = Math.max(0, Math.min(100, weightLimit === 0 ? 0 : (currentWeight / weightLimit) * 100));
|
||||
const weightBar = $('<div>').addClass('vertical-progress');
|
||||
$('<div>').addClass('vertical-progress__bar').css('height', `${percent}%`).appendTo(weightBar);
|
||||
$('<div>').addClass('vertical-progress__label').text('負重').appendTo(weightBar);
|
||||
$('<div>').addClass('vertical-progress__value').text(`${currentWeight.toFixed(1)}/${weightLimit}kg`).appendTo(weightBar);
|
||||
weightContainer.append(weightBar);
|
||||
|
||||
const body = $('<div>').addClass('inventory-content');
|
||||
|
||||
function renderItemCollection(title, source, isListLayout = false) {
|
||||
body.append($('<h3>').text(title));
|
||||
|
||||
// --- 特殊處理:裝備欄(顯示為單一裝備欄)---
|
||||
if (title === '裝備') {
|
||||
// 創建裝備欄容器
|
||||
const equipmentGrid = $('<div>').addClass('inventory-grid equipment-grid').css({
|
||||
'grid-template-columns': 'repeat(5, 1fr)', // 一行5個
|
||||
'gap': '15px'
|
||||
});
|
||||
|
||||
// 定義裝備部位順序
|
||||
const equipmentSlots = [
|
||||
'頭部', '頸部', '胸部', '背部', '腰部',
|
||||
'右手', '左手', '腿部', '腳部', '其他'
|
||||
];
|
||||
|
||||
equipmentSlots.forEach((slot, index) => {
|
||||
const slotContainer = $('<div>').addClass('equipment-slot')
|
||||
.attr('data-slot', slot)
|
||||
.css({
|
||||
'background': 'var(--survival-surface-strong)',
|
||||
'border': '1px solid var(--survival-glass-border)',
|
||||
'border-radius': '12px',
|
||||
'padding': '16px',
|
||||
'text-align': 'center',
|
||||
'transition': 'all 0.25s ease-out',
|
||||
'min-height': '140px', // 稍微增高
|
||||
'display': 'flex',
|
||||
'flex-direction': 'column',
|
||||
'justify-content': 'flex-start' // 從頂部開始
|
||||
});
|
||||
|
||||
// 添加排序
|
||||
slotContainer.css('order', index + 1);
|
||||
|
||||
// 裝備部位標題
|
||||
const slotTitle = $('<div>').addClass('slot-title').css({
|
||||
'font-size': '20px',
|
||||
'font-weight': '700',
|
||||
'color': '#d17d2e',
|
||||
'letter-spacing': '0.05em',
|
||||
'text-transform': 'uppercase',
|
||||
'margin-bottom': '12px',
|
||||
'padding-bottom': '8px',
|
||||
'border-bottom': '1px solid rgba(148, 197, 255, 0.2)',
|
||||
'line-height': '1.2'
|
||||
}).text(slot);
|
||||
|
||||
slotContainer.append(slotTitle);
|
||||
|
||||
// 檢查該部位是否有裝備
|
||||
const equipmentItem = source[slot];
|
||||
|
||||
if (equipmentItem && typeof equipmentItem === 'object' && Object.keys(equipmentItem).length > 0) {
|
||||
// 獲取裝備名稱(第一個鍵名)
|
||||
const itemKey = Object.keys(equipmentItem)[0];
|
||||
const itemData = equipmentItem[itemKey];
|
||||
|
||||
if (itemKey && itemData) {
|
||||
const itemNameDiv = $('<div>').addClass('equipment-item-name').css({
|
||||
'font-size': '18px',
|
||||
'font-weight': '600',
|
||||
'color': 'var(--survival-accent-strong)',
|
||||
'margin-bottom': '8px',
|
||||
'word-wrap': 'break-word',
|
||||
'line-height': '1.3'
|
||||
}).text(itemKey);
|
||||
|
||||
itemNameDiv.append(
|
||||
$('<span>').addClass('corner-decor corner-tl'),
|
||||
$('<span>').addClass('corner-decor corner-tr'),
|
||||
$('<span>').addClass('corner-decor corner-bl'),
|
||||
$('<span>').addClass('corner-decor corner-br')
|
||||
);
|
||||
|
||||
slotContainer.append(itemNameDiv);
|
||||
|
||||
// 顯示裝備屬性
|
||||
const itemProps = $('<div>').addClass('equipment-item-props').css({
|
||||
'font-size': '12px',
|
||||
'color': 'var(--survival-text)',
|
||||
'opacity': '0.8',
|
||||
'text-align': 'left',
|
||||
'line-height': '1.4'
|
||||
});
|
||||
|
||||
// 完整的屬性顯示邏輯
|
||||
const orderedProps = ['數量', '位置', '重量', '耐久度'];
|
||||
const attributes = new Map();
|
||||
|
||||
// 先按照順序處理
|
||||
orderedProps.forEach(k => {
|
||||
if (itemData.hasOwnProperty(k)) {
|
||||
attributes.set(k, itemData[k]);
|
||||
}
|
||||
});
|
||||
|
||||
// 處理狀態類型和狀態
|
||||
const stateType = textify(itemData['狀態類型'] ?? '', '');
|
||||
if (stateType && itemData.hasOwnProperty('狀態')) {
|
||||
// 如果狀態類型不是'耐久度',或者還沒有設置耐久度,則添加
|
||||
if (stateType.toLowerCase() !== '耐久度' || !attributes.has('耐久度')) {
|
||||
attributes.set(stateType, itemData['狀態']);
|
||||
}
|
||||
}
|
||||
|
||||
// 處理其他屬性
|
||||
Object.entries(itemData).forEach(([k, v]) => {
|
||||
if (!attributes.has(k) && k !== '$meta' && k !== '狀態類型' && k !== '狀態') {
|
||||
if (k === '加值' && v && typeof v === 'object') {
|
||||
// 創建加值描述文字
|
||||
const bonusEntries = [];
|
||||
Object.entries(v).forEach(([bonusKey, bonusValue]) => {
|
||||
bonusEntries.push(`${bonusKey}+【${bonusValue}】`);
|
||||
});
|
||||
attributes.set(k, bonusEntries.join('、'));
|
||||
} else {
|
||||
attributes.set(k, v);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 顯示所有屬性
|
||||
attributes.forEach((v, k) => {
|
||||
if (v !== null && v !== '' && v !== undefined) {
|
||||
const line = $('<div>').addClass('item-prop equipment-prop');
|
||||
line.attr('data-prop', k);
|
||||
|
||||
// 如果是加值欄位,添加特殊類別
|
||||
if (k === '加值') {
|
||||
line.addClass('bonus-prop');
|
||||
// 在加值前面添加分隔線
|
||||
const separator = $('<div>').addClass('bonus-separator').css({
|
||||
'height': '1px',
|
||||
'background': 'linear-gradient(90deg, transparent, rgba(148, 197, 255, 0.4), transparent)',
|
||||
'margin': '10px 0 8px 0',
|
||||
'width': '100%'
|
||||
});
|
||||
itemProps.append(separator);
|
||||
}
|
||||
|
||||
let displayValue = textify(v, '');
|
||||
if (k === '重量' && typeof v === 'number') {
|
||||
displayValue += 'kg';
|
||||
}
|
||||
|
||||
if (k === '數量') {
|
||||
displayValue = String(displayValue);
|
||||
}
|
||||
|
||||
if (k === '描述' && displayValue.length > 80) {
|
||||
displayValue = displayValue.substring(0, 77) + '...';
|
||||
}
|
||||
|
||||
$('<span>').addClass('prop-key').text(String(k)).appendTo(line);
|
||||
$('<span>').addClass('prop-sep').text(': ').appendTo(line);
|
||||
$('<span>').addClass('prop-val').text(displayValue).appendTo(line);
|
||||
|
||||
itemProps.append(line);
|
||||
|
||||
// 如果是加值欄位,在後面也添加一個小分隔線
|
||||
if (k === '加值') {
|
||||
const bottomSeparator = $('<div>').css({
|
||||
'height': '1px',
|
||||
'background': 'rgba(148, 197, 255, 0.1)',
|
||||
'margin': '8px 0 4px 0',
|
||||
'width': '100%'
|
||||
});
|
||||
itemProps.append(bottomSeparator);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (attributes.size > 0) {
|
||||
slotContainer.append(itemProps);
|
||||
}
|
||||
|
||||
// 懸停效果
|
||||
slotContainer.hover(
|
||||
function() {
|
||||
$(this).css({
|
||||
'transform': 'translateY(-3px)',
|
||||
'box-shadow': '0 5px 15px rgba(0,0,0,.3)',
|
||||
'background': 'rgba(52,73,94,.9)',
|
||||
'border-color': 'var(--survival-accent)'
|
||||
});
|
||||
},
|
||||
function() {
|
||||
$(this).css({
|
||||
'transform': 'translateY(0)',
|
||||
'box-shadow': 'none',
|
||||
'background': 'var(--survival-surface-strong)',
|
||||
'border-color': 'var(--survival-glass-border)'
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 該部位沒有裝備
|
||||
const emptyText = $('<div>').addClass('equipment-empty').css({
|
||||
'font-size': '20px'
|
||||
}).text('未裝備');
|
||||
|
||||
slotContainer.append(emptyText);
|
||||
}
|
||||
|
||||
equipmentGrid.append(slotContainer);
|
||||
});
|
||||
|
||||
body.append(equipmentGrid);
|
||||
return;
|
||||
}
|
||||
// --- 裝備欄特殊處理結束 ---
|
||||
|
||||
const container = isListLayout ? $('<div>').addClass('inventory-list') : $('<div>').addClass('inventory-grid');
|
||||
const entries = toEntries(source);
|
||||
|
||||
if (entries.length === 0) {
|
||||
container.append(isListLayout ? $('<p>').addClass('empty-text').text('無') : $('<div>').addClass('inventory-item empty-text').text('空'));
|
||||
} else {
|
||||
entries.forEach(([key, value]) => {
|
||||
if (value && typeof value === 'object') {
|
||||
const itemCard = isListLayout ? $('<div>').addClass('inventory-list-item') : $('<div>').addClass('inventory-item');
|
||||
$('<div>').addClass('item-name').text(String(key)).appendTo(itemCard);
|
||||
const props = $('<div>').addClass('item-props');
|
||||
|
||||
// --- 這是新手把手教你的邏輯 ---
|
||||
const orderedProps = [
|
||||
'數量', '位置', '重量', '耐久度'
|
||||
];
|
||||
|
||||
// 處理狀態類型和狀態,把 '耐久度' 這種情況也包含進來
|
||||
const stateType = textify(value['狀態類型'] ?? '', '');
|
||||
if (stateType && !orderedProps.includes(stateType)) {
|
||||
orderedProps.push(stateType);
|
||||
}
|
||||
|
||||
orderedProps.forEach(key => {
|
||||
let val = value[key];
|
||||
// 特殊處理狀態
|
||||
if (key === stateType) {
|
||||
val = value['狀態'];
|
||||
}
|
||||
|
||||
if (val !== null && val !== undefined && val !== '') {
|
||||
const line = $('<div>').addClass('item-prop');
|
||||
$('<span>').addClass('prop-key').text(String(key)).appendTo(line);
|
||||
$('<span>').addClass('prop-sep').text(': ').appendTo(line);
|
||||
let displayValue = textify(val, '');
|
||||
if (key === '重量' && typeof val === 'number') {
|
||||
displayValue += 'kg';
|
||||
}
|
||||
$('<span>').addClass('prop-val').text(displayValue).appendTo(line);
|
||||
props.append(line);
|
||||
}
|
||||
});
|
||||
|
||||
// 處理剩下的屬性,比如'描述'
|
||||
Object.entries(value).forEach(([key, val]) => {
|
||||
// 檢查這個屬性是不是我們已經手動處理過的
|
||||
if (!orderedProps.includes(key) && key !== '$meta' && key !== '狀態類型' && key !== '狀態' && key !== stateType) {
|
||||
if (val !== null && val !== undefined && val !== '') {
|
||||
const line = $('<div>').addClass('item-prop');
|
||||
|
||||
// 如果是加值欄位,添加特殊類別
|
||||
if (key === '加值') {
|
||||
line.addClass('bonus-prop');
|
||||
// 在加值前面添加分隔線
|
||||
const separator = $('<div>').addClass('bonus-separator').css({
|
||||
'height': '1px',
|
||||
'background': 'linear-gradient(90deg, transparent, rgba(148, 197, 255, 0.4), transparent)',
|
||||
'margin': '10px 0 8px 0',
|
||||
'width': '100%'
|
||||
});
|
||||
props.append(separator);
|
||||
}
|
||||
|
||||
$('<span>').addClass('prop-key').text(String(key)).appendTo(line);
|
||||
$('<span>').addClass('prop-sep').text(': ').appendTo(line);
|
||||
|
||||
let displayValue = '';
|
||||
if (key === '加值' && val && typeof val === 'object') {
|
||||
// 處理加值物件
|
||||
const bonusEntries = [];
|
||||
Object.entries(val).forEach(([bonusKey, bonusValue]) => {
|
||||
bonusEntries.push(`${bonusKey}+【${bonusValue}】`);
|
||||
});
|
||||
displayValue = bonusEntries.join('、');
|
||||
} else {
|
||||
displayValue = textify(val, '');
|
||||
if (key === '重量' && typeof val === 'number') {
|
||||
displayValue += 'kg';
|
||||
}
|
||||
}
|
||||
|
||||
$('<span>').addClass('prop-val').text(displayValue).appendTo(line);
|
||||
props.append(line);
|
||||
|
||||
// 如果是加值欄位,在後面也添加一個小分隔線
|
||||
if (key === '加值') {
|
||||
const bottomSeparator = $('<div>').css({
|
||||
'height': '1px',
|
||||
'background': 'rgba(148, 197, 255, 0.1)',
|
||||
'margin': '8px 0 4px 0',
|
||||
'width': '100%'
|
||||
});
|
||||
props.append(bottomSeparator);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
規則: 排泄後重置為0(從正文行動判斷)
|
||||
|
||||
itemCard.append(props);
|
||||
container.append(itemCard);
|
||||
}
|
||||
});
|
||||
}
|
||||
body.append(container);
|
||||
}
|
||||
#### 懷孕與分娩
|
||||
|
||||
受孕: 完全由腳本自動計算及更新,AI不需任何處理
|
||||
|
||||
renderItemCollection('裝備', equipmentSource, true);
|
||||
renderItemCollection('攜帶物品', itemsSource);
|
||||
分娩:
|
||||
- 觸發: 受孕天數≥55時概率遞增,=60時強制觸發
|
||||
- 檢定: 分娩檢定由COT執行,變量AI根據結果執行更新
|
||||
|
||||
if (showCamp && campSource && Object.keys(campSource).length > 0) {
|
||||
Object.entries(campSource).forEach(([campName, campData]) => {
|
||||
if (campData && typeof campData === 'object') {
|
||||
Object.entries(campData).forEach(([categoryKey, categoryData]) => {
|
||||
if(categoryData && typeof categoryData === 'object' && Object.values(categoryData).some(item => typeof item === 'object')) {
|
||||
renderItemCollection(`${campName} - ${categoryKey}`, categoryData);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
生命值扣除:
|
||||
|
||||
content.append(weightContainer, body, close);
|
||||
modal.append(content);
|
||||
$('body').append(modal);
|
||||
}
|
||||
| 結果 | 生命值扣除 |
|
||||
|------|------------|
|
||||
| 大成功順產 | -10 |
|
||||
| 成功正常 | -20 |
|
||||
| 失敗難產 | -35 |
|
||||
| 大失敗嚴重難產 | -50,需醫療急救 |
|
||||
|
||||
function createStatusEffects(container, data) {
|
||||
// 使用新的變數路徑 '狀態.生效效果'
|
||||
const source = data?.['狀態']?.['生效效果'];
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
新生兒死亡: 大失敗時1d100≤30死亡
|
||||
|
||||
// 新的資料結構:物件,鍵是狀態型別,值包含"狀態"和"效果"
|
||||
const collected = [];
|
||||
|
||||
// 遍歷狀態物件
|
||||
if (typeof source === 'object' && !Array.isArray(source)) {
|
||||
Object.values(source).forEach((statusObj) => {
|
||||
if (statusObj && typeof statusObj === 'object') {
|
||||
// 提取狀態名稱
|
||||
const stateName = statusObj['狀態'];
|
||||
if (stateName) {
|
||||
// 過濾掉"狀態正常"
|
||||
if (stateName.trim() === '狀態正常') {
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 直接使用腳本已經生成的效果描述
|
||||
const effect = statusObj['效果'] || '';
|
||||
|
||||
collected.push({
|
||||
name: stateName,
|
||||
effect: effect
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 處理其他可能的資料結構(備用邏輯)
|
||||
const collect = (value) => {
|
||||
if (value == null) return;
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(collect);
|
||||
return;
|
||||
}
|
||||
if (typeof value === 'object') {
|
||||
Object.values(value).forEach(collect);
|
||||
return;
|
||||
}
|
||||
const text = String(value);
|
||||
text.split(/[,,、\s]+/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
.forEach((item) => {
|
||||
if (item.trim() === '狀態正常') return;
|
||||
collected.push({ name: item, effect: '' });
|
||||
});
|
||||
};
|
||||
collect(e(source, false));
|
||||
}
|
||||
分娩後重置: 懷孕狀態重置為「未懷孕」,受孕天數=0,月經週期重置為1
|
||||
|
||||
if (collected.length === 0) {
|
||||
const wrapper = $('<div>').addClass('status-effects-container').css('display', 'none');
|
||||
container.append(wrapper);
|
||||
return;
|
||||
}
|
||||
保護規則: 若已懷孕,除非劇情明確發生流產,否則不修改懷孕狀態
|
||||
|
||||
const wrapper = $('<div>').addClass('status-effects-container');
|
||||
$('<div>').addClass('section__title').text('當前狀態').appendTo(wrapper);
|
||||
|
||||
const list = $('<div>').addClass('status-effects-list');
|
||||
|
||||
collected.forEach((status) => {
|
||||
const statusContainer = $('<div>').addClass('status-effect-container');
|
||||
const statusName = $('<div>').addClass('status-effect-name')
|
||||
.text(status.name)
|
||||
.attr('title', '點選檢視效果');
|
||||
|
||||
if (status.effect) {
|
||||
statusName.addClass('has-effect');
|
||||
statusName.on('click', function() {
|
||||
$(this).toggleClass('expanded');
|
||||
$(this).next('.status-effect-detail').slideToggle(200);
|
||||
});
|
||||
const effectDetail = $('<div>').addClass('status-effect-detail')
|
||||
.hide()
|
||||
.text(status.effect);
|
||||
statusContainer.append(statusName, effectDetail);
|
||||
} else {
|
||||
statusContainer.append(statusName);
|
||||
}
|
||||
list.append(statusContainer);
|
||||
});
|
||||
|
||||
wrapper.append(list);
|
||||
container.append(wrapper);
|
||||
}
|
||||
子嗣生成: 插入子嗣條目
|
||||
|
||||
const IMAGE_VIEWER_STORAGE_KEY = 'survivalImageViewerState';
|
||||
const IMAGE_VIEWER_FALLBACK_URL = 'https://cdn.phototourl.com/free/2026-05-23-8b4cc932-bb3f-4107-9a72-9160fb9e2335.png';
|
||||
|
||||
function loadSavedImageViewerState() {
|
||||
try {
|
||||
const raw = localStorage.getItem(IMAGE_VIEWER_STORAGE_KEY);
|
||||
if (!raw) return null;
|
||||
return JSON.parse(raw);
|
||||
} catch (error) {
|
||||
console.error('讀取地圖查看器狀態失敗:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#### 流產處理
|
||||
|
||||
function persistImageViewerState(state) {
|
||||
try {
|
||||
if (!state) localStorage.removeItem(IMAGE_VIEWER_STORAGE_KEY);
|
||||
else localStorage.setItem(IMAGE_VIEWER_STORAGE_KEY, JSON.stringify(state));
|
||||
} catch (error) {
|
||||
console.error('保存地圖查看器狀態失敗:', error);
|
||||
}
|
||||
}
|
||||
- 觸發:ActionResult 中「流產結果.是否流產 = 是」
|
||||
- 操作:
|
||||
- 懷孕狀態 → "流產"
|
||||
- 懷孕天數 → 0
|
||||
- 懷孕日 → 0
|
||||
- 流產次數 +1
|
||||
- 上次流產日 → 當前天數
|
||||
- 流產風險累積 → 0
|
||||
- 生命值 -[損失值]
|
||||
- 免疫力 -15
|
||||
- 壓力 +[增加值]
|
||||
- 若壓力增加 ≥ 30:觸發心理狀態更新
|
||||
- 產物清理:自動移除該同伴名下所有「未出生」的子嗣記錄
|
||||
- 流產後 30 天內再次懷孕:風險機率 ×1.5(由 CoT 計算,變量AI僅記錄狀態)
|
||||
|
||||
let savedImageViewerState = loadSavedImageViewerState();
|
||||
|
||||
function resolveMapImageUrl(statData, displayData) {
|
||||
const candidates = [
|
||||
statData?.['地圖']?.['圖片鏈接'], statData?.['地圖']?.['鏈接'], statData?.['地圖鏈接'],
|
||||
statData?.['系統']?.['地圖鏈接'], statData?.['系統']?.['地圖']?.['鏈接'],
|
||||
displayData?.['地圖']?.['圖片鏈接'], displayData?.['地圖']?.['鏈接'], displayData?.['地圖鏈接'],
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
const url = textify(candidate, '').trim();
|
||||
if (url) return url;
|
||||
}
|
||||
return IMAGE_VIEWER_FALLBACK_URL || '';
|
||||
}
|
||||
### 12. 項目進度更新
|
||||
|
||||
class ImageViewer {
|
||||
constructor(root, initialState) {
|
||||
this.root = root;
|
||||
this.canvas = root.find('.image-viewer__canvas');
|
||||
this.image = root.find('.image-viewer__image');
|
||||
this.placeholder = root.find('.image-viewer__placeholder');
|
||||
this.status = root.find('.image-viewer__status');
|
||||
this.controlButtons = root.find('.image-viewer__control-button');
|
||||
this.scale = initialState?.scale ?? 1;
|
||||
this.translateX = initialState?.translateX ?? 0;
|
||||
this.translateY = initialState?.translateY ?? 0;
|
||||
this.imageUrl = initialState?.url ?? '';
|
||||
this.imageLoaded = false;
|
||||
this.activePointers = new Map();
|
||||
this.initialPinchDistance = 0;
|
||||
this.minScale = 0.4;
|
||||
this.maxScale = 4;
|
||||
this.bindEvents();
|
||||
if (this.imageUrl) this.loadImage(this.imageUrl, false);
|
||||
else this.updatePlaceholder(true);
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.controlButtons.each((_, button) => {
|
||||
const $button = $(button);
|
||||
const action = $button.data('action');
|
||||
if (action === 'zoom-in') $button.on('click.imageViewer', () => this.zoomTo(this.scale * 1.2));
|
||||
else if (action === 'zoom-out') $button.on('click.imageViewer', () => this.zoomTo(this.scale * 0.8));
|
||||
else if (action === 'reset') $button.on('click.imageViewer', () => this.resetTransform());
|
||||
});
|
||||
|
||||
const canvasElement = this.canvas[0];
|
||||
if (canvasElement) canvasElement.addEventListener('wheel', (event) => this.handleWheel(event), { passive: false });
|
||||
|
||||
this.canvas
|
||||
.on('pointerdown.imageViewer', (event) => this.handlePointerDown(event))
|
||||
.on('pointermove.imageViewer', (event) => this.handlePointerMove(event))
|
||||
.on('pointerup.imageViewer pointercancel.imageViewer pointerleave.imageViewer', (event) => this.handlePointerUp(event));
|
||||
}
|
||||
|
||||
loadImage(url, resetTransform = true) {
|
||||
this.setStatus('正在加載圖片...');
|
||||
this.placeholder.hide();
|
||||
this.imageLoaded = false;
|
||||
this.image.off('.imageViewer')
|
||||
.on('load.imageViewer', () => {
|
||||
this.imageLoaded = true;
|
||||
this.imageUrl = url;
|
||||
this.setStatus('加載完成');
|
||||
if (resetTransform) this.resetTransform();
|
||||
else this.applyTransform();
|
||||
this.canvas.addClass('has-image');
|
||||
this.placeholder.hide();
|
||||
setTimeout(() => this.setStatus(url), 800);
|
||||
})
|
||||
.on('error.imageViewer', () => {
|
||||
this.imageLoaded = false;
|
||||
this.setStatus('圖片加載失敗。');
|
||||
this.updatePlaceholder(false, '圖片加載失敗。');
|
||||
});
|
||||
this.image.attr('src', url);
|
||||
}
|
||||
|
||||
resetTransform() {
|
||||
this.scale = 1;
|
||||
this.translateX = 0;
|
||||
this.translateY = 0;
|
||||
this.applyTransform();
|
||||
}
|
||||
|
||||
applyTransform() {
|
||||
const transform = `translate(-50%, -50%) translate(${this.translateX}px, ${this.translateY}px) scale(${this.scale})`;
|
||||
this.image.css('transform', transform);
|
||||
}
|
||||
|
||||
zoomTo(newScale, center = null) {
|
||||
if (!this.imageLoaded) return;
|
||||
const previousScale = this.scale;
|
||||
this.scale = Math.min(this.maxScale, Math.max(this.minScale, newScale));
|
||||
if (center) {
|
||||
const rect = this.canvas[0]?.getBoundingClientRect();
|
||||
if (rect) {
|
||||
const offsetX = center.x - rect.left;
|
||||
const offsetY = center.y - rect.top;
|
||||
const scaleRatio = this.scale / previousScale;
|
||||
this.translateX = (this.translateX + offsetX) - offsetX * scaleRatio;
|
||||
this.translateY = (this.translateY + offsetY) - offsetY * scaleRatio;
|
||||
}
|
||||
}
|
||||
this.applyTransform();
|
||||
}
|
||||
|
||||
handleWheel(event) {
|
||||
if (!this.imageLoaded) return;
|
||||
event.preventDefault();
|
||||
const delta = event.deltaY > 0 ? 0.9 : 1.1;
|
||||
this.zoomTo(this.scale * delta, { x: event.clientX, y: event.clientY });
|
||||
}
|
||||
|
||||
handlePointerDown(event) {
|
||||
if (!this.imageLoaded) return;
|
||||
this.canvas.addClass('is-grabbing');
|
||||
this.canvas[0]?.setPointerCapture?.(event.pointerId);
|
||||
this.activePointers.set(event.pointerId, { x: event.clientX, y: event.clientY });
|
||||
if (this.activePointers.size === 2) this.initialPinchDistance = this.computePinchDistance();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
handlePointerMove(event) {
|
||||
if (!this.activePointers.has(event.pointerId)) return;
|
||||
const previous = this.activePointers.get(event.pointerId);
|
||||
const current = { x: event.clientX, y: event.clientY };
|
||||
this.activePointers.set(event.pointerId, current);
|
||||
|
||||
if (this.activePointers.size === 1) {
|
||||
this.translateX += current.x - previous.x;
|
||||
this.translateY += current.y - previous.y;
|
||||
this.applyTransform();
|
||||
return;
|
||||
}
|
||||
> 🔴 建造/製作的完整規則(規模評估、進度增量計算、材料消耗公式、最終檢查檢定)參照 `<crafting_rules>` 子模塊。變量 COT 僅負責處理 ActionResult 信號的項目增刪改。
|
||||
|
||||
if (this.activePointers.size >= 2) {
|
||||
const newDistance = this.computePinchDistance();
|
||||
if (this.initialPinchDistance === 0) {
|
||||
this.initialPinchDistance = newDistance;
|
||||
return;
|
||||
}
|
||||
const factor = newDistance / this.initialPinchDistance;
|
||||
this.zoomTo(this.scale * factor, this.computePinchCenter());
|
||||
this.initialPinchDistance = newDistance;
|
||||
}
|
||||
}
|
||||
|
||||
handlePointerUp(event) {
|
||||
this.activePointers.delete(event.pointerId);
|
||||
if (this.activePointers.size <= 1) this.initialPinchDistance = 0;
|
||||
if (this.activePointers.size === 0) this.canvas.removeClass('is-grabbing');
|
||||
}
|
||||
|
||||
computePinchDistance() {
|
||||
const pointers = Array.from(this.activePointers.values());
|
||||
if (pointers.length < 2) return 0;
|
||||
return Math.hypot(pointers[0].x - pointers[1].x, pointers[0].y - pointers[1].y) || 0.0001;
|
||||
}
|
||||
|
||||
computePinchCenter() {
|
||||
const pointers = Array.from(this.activePointers.values());
|
||||
if (pointers.length < 2) return null;
|
||||
return { x: (pointers[0].x + pointers[1].x) / 2, y: (pointers[0].y + pointers[1].y) / 2 };
|
||||
}
|
||||
|
||||
updatePlaceholder(show, message = '') {
|
||||
this.placeholder.toggle(show).text(message || '地圖圖片將自動讀取...');
|
||||
}
|
||||
|
||||
setStatus(message) {
|
||||
this.status.text(message || '');
|
||||
}
|
||||
|
||||
getState() {
|
||||
if (!this.imageUrl) return null;
|
||||
return { url: this.imageUrl, scale: this.scale, translateX: this.translateX, translateY: this.translateY };
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.canvas.off('.imageViewer');
|
||||
this.image.off('.imageViewer');
|
||||
this.controlButtons.off('.imageViewer');
|
||||
this.activePointers.clear();
|
||||
}
|
||||
}
|
||||
項目用於追蹤持續性活動的進度,不生成中間產物。信號來源:ActionResult中"項目"字段。
|
||||
|
||||
function openImageViewerModal(statData, displayData) {
|
||||
$('.image-viewer-modal').remove();
|
||||
const modal = $('<div>').addClass('image-viewer-modal');
|
||||
const content = $('<div>').addClass('image-viewer-modal__content');
|
||||
const header = $('<div>').addClass('image-viewer__header').append(
|
||||
$('<div>').addClass('image-viewer__title').text('地圖查看器'),
|
||||
$('<button>').addClass('image-viewer__close').html('×')
|
||||
);
|
||||
const toolbar = $('<div>').addClass('image-viewer__toolbar').append(
|
||||
$('<div>').addClass('image-viewer__hint').text('支持滾輪與觸控縮放'),
|
||||
$('<div>').addClass('image-viewer__controls').append(
|
||||
$('<button>').addClass('image-viewer__control-button').data('action', 'zoom-out').text('-'),
|
||||
$('<button>').addClass('image-viewer__control-button').data('action', 'reset').text('⟳'),
|
||||
$('<button>').addClass('image-viewer__control-button').data('action', 'zoom-in').text('+')
|
||||
)
|
||||
);
|
||||
const canvas = $('<div>').addClass('image-viewer__canvas').append(
|
||||
$('<img>').addClass('image-viewer__image').attr({ draggable: 'false', alt: '地圖' }),
|
||||
$('<div>').addClass('image-viewer__placeholder'),
|
||||
$('<div>').addClass('image-viewer__status')
|
||||
);
|
||||
content.append(header, toolbar, canvas);
|
||||
modal.append(content);
|
||||
$('body').append(modal);
|
||||
|
||||
const initialState = { ...savedImageViewerState };
|
||||
const autoUrl = resolveMapImageUrl(statData, displayData);
|
||||
if (autoUrl) initialState.url = autoUrl;
|
||||
else if (!initialState.url && IMAGE_VIEWER_FALLBACK_URL) initialState.url = IMAGE_VIEWER_FALLBACK_URL;
|
||||
|
||||
const viewer = new ImageViewer(content, initialState);
|
||||
const closeModal = () => {
|
||||
viewer.destroy();
|
||||
savedImageViewerState = viewer.getState();
|
||||
persistImageViewerState(savedImageViewerState);
|
||||
modal.remove();
|
||||
$(document).off('keydown.imageViewer');
|
||||
};
|
||||
content.find('.image-viewer__close').on('click', closeModal);
|
||||
modal.on('click', (e) => e.target === modal[0] && closeModal());
|
||||
$(document).on('keydown.imageViewer', (e) => e.key === 'Escape' && closeModal());
|
||||
}
|
||||
#### 新建項目
|
||||
|
||||
function showDrawConfirmModal(drawType, survivalPoints) {
|
||||
$('.draw-confirm-modal').remove();
|
||||
const pointName = '求生點數';
|
||||
const safePoints = Number.isFinite(survivalPoints) ? survivalPoints : 0;
|
||||
|
||||
const modal = $('<div>').addClass('draw-confirm-modal');
|
||||
const content = $('<div>').addClass('draw-confirm-modal__content').append(
|
||||
$('<div>').addClass('draw-confirm-modal__glow'),
|
||||
$('<div>').addClass('draw-confirm-modal__title').text('求生抽卡確認'),
|
||||
$('<div>').addClass('draw-confirm-modal__desc').text(`是否消耗 100 ${pointName}進行抽卡?`),
|
||||
$('<div>').addClass('draw-confirm-modal__points').html(`當前${pointName} <span>${safePoints}</span>`),
|
||||
$('<div>').addClass('draw-confirm-modal__actions'),
|
||||
$('<div>').addClass('draw-confirm-modal__status')
|
||||
);
|
||||
const confirmBtn = $('<button>').addClass('draw-confirm-modal__button draw-confirm-modal__confirm').text('確認抽卡');
|
||||
const cancelBtn = $('<button>').addClass('draw-confirm-modal__button draw-confirm-modal__cancel').text('取消');
|
||||
content.find('.draw-confirm-modal__actions').append(confirmBtn, cancelBtn);
|
||||
modal.append(content);
|
||||
$('body').append(modal);
|
||||
|
||||
const closeModal = () => modal.remove();
|
||||
cancelBtn.on('click', closeModal);
|
||||
modal.on('click', (e) => e.target === modal[0] && closeModal());
|
||||
$(document).one('keydown', (e) => e.key === 'Escape' && closeModal());
|
||||
|
||||
if (safePoints < 100) {
|
||||
confirmBtn.addClass('is-disabled').prop('disabled', true);
|
||||
content.find('.draw-confirm-modal__status').text(`${pointName}不足。`);
|
||||
return;
|
||||
}
|
||||
confirmBtn.on('click', async () => {
|
||||
if (confirmBtn.hasClass('is-loading')) return;
|
||||
confirmBtn.addClass('is-loading').prop('disabled', true);
|
||||
try {
|
||||
const msg = `阿彬按下了求生抽卡按鈕,消耗100${pointName}`;
|
||||
await triggerSlash(`/send ${msg}`);
|
||||
content.find('.draw-confirm-modal__status').text('請求已發送!');
|
||||
setTimeout(closeModal, 900);
|
||||
} catch (error) {
|
||||
console.error('抽卡請求失敗:', error);
|
||||
content.find('.draw-confirm-modal__status').text('發送失敗。');
|
||||
confirmBtn.removeClass('is-loading').prop('disabled', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
當ActionResult中「項目-新建」字段存在時:
|
||||
- 操作:insert 條目到 `/項目/<項目名>`
|
||||
- 核心字段:位置、類別、當前進度(初始為0)、總進度(由 crafting_rules 根據規模評估決定)、狀態(初始為進行中)、描述
|
||||
- 材料字段:`所需材料: { '材料名': { '所需': 總數量, '已投入': 0 } }`
|
||||
- 項目產出:可選,描述最終產物。若不存在,完工時只做清理。
|
||||
|
||||
function renderNutritionDetails(target, statData) {
|
||||
const wrapper = $('<div>').addClass('nutrition-hydration-wrapper');
|
||||
const phys = statData['生理'] ?? {};
|
||||
const carb = Number(e(phys['碳水'] ?? 0)), fat = Number(e(phys['脂肪'] ?? 0)),
|
||||
protein = Number(e(phys['蛋白質'] ?? 0)), vitamin = Number(e(phys['維生素'] ?? 0)),
|
||||
nitrogen = Number(e(phys['氮平衡'] ?? 0));
|
||||
wrapper.append(t('碳水', carb, s['碳水'], 'carbs-progress'));
|
||||
wrapper.append(t('脂肪', fat, s['脂肪'], 'fat-progress'));
|
||||
const nitrogenWrapper = $('<div>').addClass('progress-wrapper');
|
||||
$('<div>').text(`氮平衡: ${nitrogen}`).appendTo(nitrogenWrapper);
|
||||
const gradient = $('<div>').addClass('nitrogen-gradient');
|
||||
$('<div>').addClass('nitrogen-marker').css('left', `${((Math.max(-100, Math.min(100, nitrogen)) + 100) / 200) * 100}%`).appendTo(gradient);
|
||||
wrapper.append(nitrogenWrapper.append(gradient));
|
||||
wrapper.append(t('蛋白質', protein, s['蛋白質'], 'protein-progress'));
|
||||
wrapper.append(t('維生素', vitamin, s['維生素'], 'vitamin-progress'));
|
||||
target.append(wrapper);
|
||||
}
|
||||
#### 推進進度
|
||||
|
||||
function renderPhysMental(target, physData, mentalData, fullData) {
|
||||
const wrapper = $('<div>').addClass('phys-mental-wrapper');
|
||||
const physCol = $('<div>').addClass('phys-col').append($('<div>').addClass('section__title').text('生理'));
|
||||
const mentalCol = $('<div>').addClass('mental-col').append($('<div>').addClass('section__title').text('精神'));
|
||||
const temperature = Number(e(physData['體溫'] ?? 37));
|
||||
const tempWrapper = $('<div>').addClass('progress-wrapper').append($('<div>').text(`體溫: ${temperature}°C (36.5-37.5)`));
|
||||
const tempGradient = $('<div>').addClass('temp-gradient');
|
||||
$('<div>').addClass('temp-marker').css('left', `${Math.min(Math.max(((temperature - 35) / 5) * 100, 0), 100)}%`).appendTo(tempGradient);
|
||||
$('<div>').addClass('temp-normal-zone').css({ left: '30%', width: '20%' }).appendTo(tempGradient);
|
||||
physCol.append(tempWrapper.append(tempGradient));
|
||||
physCol.append(t('疲勞', Number(e(fullData['狀態']?.['疲勞'] ?? 0)), (Number(e(fullData['屬性']?.['耐力']?.['等級'] ?? 25)) || 25) * 4, 'fatigue-progress'));
|
||||
const immuneLoad = Number(e(physData['免疫負荷'] ?? 0)), immunePower = Number(e(fullData['次生屬性']?.['免疫力'] ?? 0));
|
||||
const immuneWrapper = $('<div>').addClass('progress-wrapper').append($('<div>').text(`免疫: ${immuneLoad} / ${immunePower}`));
|
||||
const immuneBar = $('<div>').addClass('immune-bar');
|
||||
$('<div>').addClass('immune-fill').css('width', `${Math.min(immuneLoad, 100)}%`).appendTo(immuneBar);
|
||||
$('<div>').addClass('immune-marker').css('left', `${Math.min(immunePower, 100)}%`).appendTo(immuneBar);
|
||||
physCol.append(immuneWrapper.append(immuneBar));
|
||||
|
||||
Object.entries(mentalData).forEach(([key, value]) => mentalCol.append(t(key, Number(e(value)), s[key] ?? 100)));
|
||||
wrapper.append(physCol, mentalCol);
|
||||
target.append(wrapper);
|
||||
}
|
||||
當ActionResult中「項目-推進」字段存在時:
|
||||
- 驗證消耗量與進度增量的比例是否一致(比例參照 crafting_rules)
|
||||
- 如材料不足,進度增量上限 = (當前庫存 / 所需材料總量) × 100
|
||||
- 更新進度:replace `/項目/<項目名>/當前進度` 為新值
|
||||
- 消耗材料:對每個材料名,replace `/項目/<項目名>/所需材料/<材料名>/已投入` 為新值
|
||||
- 根據消耗來源,從對應庫存 remove 或 replace 消耗的材料數量(兩端同步)
|
||||
- 更新描述:replace `/項目/<項目名>/描述`
|
||||
- 檢查`當前進度`是否 ≥ `總進度`:
|
||||
- 若已達到:replace `/項目/<項目名>/狀態` 為「待檢查」
|
||||
|
||||
function renderAttributesAndSkills(target, attributesSource, skillsSource, talentsSource, companionDetails = null) {
|
||||
// ============== 參數保護 ==============
|
||||
attributesSource = attributesSource || {};
|
||||
skillsSource = skillsSource || {};
|
||||
talentsSource = talentsSource || {};
|
||||
|
||||
// 確保 companionDetails 是物件(如果不是就設為空物件)
|
||||
if (companionDetails === null || typeof companionDetails !== 'object') {
|
||||
companionDetails = {};
|
||||
}
|
||||
|
||||
const wrapper = $('<div>').addClass('attr-skill-wrapper');
|
||||
const leftCol = $('<div>').addClass('attr-col');
|
||||
|
||||
// ============== 屬性渲染 ==============
|
||||
$('<div>').addClass('section__title').text('屬性').appendTo(leftCol);
|
||||
|
||||
// 安全地獲取屬性物件
|
||||
const attributes = firstMeaningfulObject(attributesSource);
|
||||
if (Object.keys(attributes).length > 0) {
|
||||
Object.entries(attributes).forEach(([name, detail]) => {
|
||||
if (detail && typeof detail === 'object') {
|
||||
const level = Number(e(detail['等級'] ?? 0));
|
||||
const exp = Number(e(detail['當前經驗'] ?? 0));
|
||||
const required = Number(e(detail['升級所需經驗'] ?? 1)) || 1;
|
||||
leftCol.append($('<div>').text(`${name} Lv.${level}`), t('', exp, required));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
leftCol.append($('<p>').addClass('empty-text').text('暫無屬性'));
|
||||
}
|
||||
|
||||
// ============== 天賦渲染 ==============
|
||||
const talentEntries = toEntries(talentsSource);
|
||||
if (talentEntries.length > 0) {
|
||||
leftCol.append($('<div>').addClass('section__title').css('margin-top', '20px').text('天賦/特性'));
|
||||
|
||||
talentEntries.forEach(([name, value]) => {
|
||||
const item = $('<div>').addClass('skill-item');
|
||||
|
||||
// 天賦名稱(安全處理)
|
||||
let talentName = name;
|
||||
if (value && typeof value === 'object' && value.name) {
|
||||
talentName = value.name;
|
||||
}
|
||||
$('<div>').addClass('skill-name').text(talentName).appendTo(item);
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
// 先顯示描述
|
||||
if (value['描述']) {
|
||||
$('<div>').addClass('skill-desc').text(textify(value['描述'], '')).appendTo(item);
|
||||
}
|
||||
|
||||
// 顯示其他非描述、非加值的內容
|
||||
Object.entries(value).forEach(([key, val]) => {
|
||||
if (key !== '$meta' && key !== 'name' && key !== '描述' && key !== '加值') {
|
||||
$('<div>').addClass('skill-desc').text(`【${key}】${textify(val, '')}`).appendTo(item);
|
||||
}
|
||||
});
|
||||
|
||||
// 顯示加值(如果有)
|
||||
if (value['加值'] && typeof value['加值'] === 'object') {
|
||||
const bonusEntries = [];
|
||||
Object.entries(value['加值']).forEach(([bonusKey, bonusValue]) => {
|
||||
bonusEntries.push(`${bonusKey}+【${bonusValue}】`);
|
||||
});
|
||||
|
||||
// 如果有其他內容,添加分隔線
|
||||
const hasOtherContent = value['描述'] || Object.keys(value).some(k =>
|
||||
k !== '$meta' && k !== 'name' && k !== '描述' && k !== '加值'
|
||||
);
|
||||
|
||||
if (hasOtherContent) {
|
||||
const separator = $('<div>').css({
|
||||
'height': '1px',
|
||||
'background': 'linear-gradient(90deg, transparent, rgba(148, 197, 255, 0.4), transparent)',
|
||||
'margin': '8px 0',
|
||||
'width': '100%'
|
||||
});
|
||||
item.append(separator);
|
||||
}
|
||||
|
||||
// 加值顯示
|
||||
const bonusDiv = $('<div>').addClass('skill-desc talent-bonus').css({
|
||||
'background': 'rgba(94, 161, 255, 0.08)',
|
||||
'border-left': '3px solid var(--survival-accent)',
|
||||
'padding': '6px 10px',
|
||||
'border-radius': '4px',
|
||||
'margin': '4px 0'
|
||||
}).text(`加值效果:${bonusEntries.join('、')}`);
|
||||
|
||||
item.append(bonusDiv);
|
||||
}
|
||||
} else if (typeof value === 'string') {
|
||||
$('<div>').addClass('skill-desc').text(value).appendTo(item);
|
||||
}
|
||||
leftCol.append(item);
|
||||
});
|
||||
}
|
||||
|
||||
// ============== 專屬特色渲染(新增安全檢查) ==============
|
||||
if (companionDetails && typeof companionDetails === 'object') {
|
||||
// 安全地獲取專屬特色
|
||||
const exclusiveTraits = companionDetails['專屬特色'];
|
||||
|
||||
if (exclusiveTraits && typeof exclusiveTraits === 'object' && Object.keys(exclusiveTraits).length > 0) {
|
||||
leftCol.append($('<div>').addClass('section__title').css('margin-top', '20px').text('專屬特色'));
|
||||
|
||||
Object.entries(exclusiveTraits).forEach(([name, desc]) => {
|
||||
if (name && desc) { // 確保名稱和描述都存在
|
||||
const item = $('<div>').addClass('skill-item');
|
||||
$('<div>').addClass('skill-name').text(name).appendTo(item);
|
||||
|
||||
// 處理描述可能是物件或字串
|
||||
let description = '';
|
||||
if (typeof desc === 'object' && desc['描述']) {
|
||||
description = desc['描述'];
|
||||
} else if (typeof desc === 'string') {
|
||||
description = desc;
|
||||
} else {
|
||||
description = textify(desc, '');
|
||||
}
|
||||
|
||||
$('<div>').addClass('skill-desc').text(description).appendTo(item);
|
||||
leftCol.append(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ============== 右側技能欄 ==============
|
||||
const rightCol = $('<div>').addClass('skill-col');
|
||||
$('<div>').addClass('section__title').text('技能').appendTo(rightCol);
|
||||
|
||||
const skillEntries = toEntries(skillsSource);
|
||||
if (skillEntries.length === 0) {
|
||||
rightCol.append($('<p>').addClass('empty-text').text('暫無'));
|
||||
} else {
|
||||
skillEntries.forEach(([name, value]) => {
|
||||
if (value && typeof value === 'object') {
|
||||
const level = Number(e(value['等級'] ?? 0));
|
||||
const exp = Number(e(value['當前經驗'] ?? 0));
|
||||
const required = Number(e(value['升級所需經驗'] ?? 1)) || 1;
|
||||
|
||||
const item = $('<div>').addClass('skill-item');
|
||||
$('<div>').addClass('skill-name').text(`${name} Lv.${level}`).appendTo(item);
|
||||
item.append(t('', exp, required));
|
||||
rightCol.append(item);
|
||||
} else if (typeof value === 'string' || typeof value === 'number') {
|
||||
// 處理簡單類型的技能
|
||||
const item = $('<div>').addClass('skill-item');
|
||||
$('<div>').addClass('skill-name').text(name).appendTo(item);
|
||||
$('<div>').addClass('skill-desc').text(textify(value, '')).appendTo(item);
|
||||
rightCol.append(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
wrapper.append(leftCol, rightCol);
|
||||
target.append(wrapper);
|
||||
}
|
||||
#### 最終檢查
|
||||
|
||||
function renderUnifiedStatus(target, statData) {
|
||||
const wrapper = $('<div>').addClass('phys-mental-wrapper');
|
||||
const leftCol = $('<div>').addClass('phys-col');
|
||||
const rightCol = $('<div>').addClass('mental-col');
|
||||
const phys = statData['生理'] ?? {};
|
||||
const mental = statData['精神'] ?? {};
|
||||
const state = statData['狀態'] ?? {};
|
||||
|
||||
$('<div>').addClass('section__title').text('負重及營養').appendTo(leftCol);
|
||||
const currentWeight = Number(e(state['當前負重'] ?? 0)), maxWeight = Number(e(state['負重上限'] ?? 0));
|
||||
leftCol.append(t(`負重: ${currentWeight.toFixed(1)}/${maxWeight.toFixed(1)} kg`, currentWeight, maxWeight, 'weight-progress'));
|
||||
|
||||
// ✅ 修改這裡:從 phys 讀取飽腹感
|
||||
leftCol.append(t('飽腹感', Number(e(phys['飽腹感'] ?? 0)), 100, 'hunger-progress'));
|
||||
|
||||
leftCol.append(t('水分', Number(e(phys['水分'] ?? 0)), 100, 'water-progress'));
|
||||
leftCol.append(t('蛋白質', Number(e(phys['蛋白質'] ?? 0)), s['蛋白質'], 'protein-progress'));
|
||||
leftCol.append(t('維生素', Number(e(phys['維生素'] ?? 0)), 100, 'vitamin-progress'));
|
||||
leftCol.append(t('碳水', Number(e(phys['碳水'] ?? 0)), s['碳水'], 'carbs-progress'));
|
||||
leftCol.append(t('脂肪', Number(e(phys['脂肪'] ?? 0)), s['脂肪'], 'fat-progress'));
|
||||
|
||||
const immuneLoad = Number(e(phys['免疫負荷'] ?? 0)), immunePower = Number(e(state['免疫力'] ?? 0));
|
||||
const immuneWrapper = $('<div>').addClass('progress-wrapper').append($('<div>').text(`免疫: ${immuneLoad} / ${immunePower}`));
|
||||
const immuneBar = $('<div>').addClass('immune-bar');
|
||||
$('<div>').addClass('immune-fill').css('width', `${Math.min(immuneLoad, 100)}%`).appendTo(immuneBar);
|
||||
$('<div>').addClass('immune-marker').css('left', `${Math.min(immunePower, 100)}%`).appendTo(immuneBar);
|
||||
leftCol.append(immuneWrapper.append(immuneBar));
|
||||
|
||||
const nitrogen = Number(e(phys['氮平衡'] ?? 0));
|
||||
const nitrogenWrapper = $('<div>').addClass('progress-wrapper nitrogen-balance').append($('<div>').text(`氮平衡: ${nitrogen}`));
|
||||
const gradient = $('<div>').addClass('nitrogen-gradient');
|
||||
$('<div>').addClass('nitrogen-marker').css('left', `${((Math.max(-100, Math.min(100, nitrogen)) + 100) / 200) * 100}%`).appendTo(gradient);
|
||||
leftCol.append(nitrogenWrapper.append(gradient));
|
||||
|
||||
$('<div>').addClass('section__title').text('精神及狀態').appendTo(rightCol);
|
||||
rightCol.append(t('壓力', Number(e(mental['壓力'] ?? 0)), 100));
|
||||
rightCol.append(t('性慾', Number(e(mental['性慾'] ?? 0)), 100));
|
||||
rightCol.append(t('無聊', Number(e(mental['無聊'] ?? 0)), 100));
|
||||
rightCol.append(t('孤獨', Number(e(mental['孤獨'] ?? 0)), 100));
|
||||
rightCol.append(t('睡眠', Number(e(mental['睡眠'] ?? 0)), 100));
|
||||
rightCol.append(t('表皮溼度', Number(e(state['表皮溼度'] ?? 0)), 100));
|
||||
rightCol.append(t('疲勞', Number(e(state['疲勞'] ?? 0)), 100));
|
||||
rightCol.append(t('清潔', Number(e(state['清潔'] ?? 0)), 100));
|
||||
rightCol.append(t('感染', Number(e(state['感染'] ?? 0)), 100, 'danger-progress'));
|
||||
rightCol.append(t('疼痛', Number(e(state['疼痛'] ?? 0)), 100, 'warning-progress'));
|
||||
|
||||
function renderKV(target, data, title) {
|
||||
if (!data || Object.keys(data).length === 0) return;
|
||||
if (title) $('<div>').addClass('section__title').text(title).appendTo(target);
|
||||
const list = $('<ul>').addClass('section__list');
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
$('<li>').addClass('item').append($('<span>').addClass('item__key').text(key), $('<span>').addClass('item__value').text(textify(value, '--'))).appendTo(list);
|
||||
});
|
||||
target.append(list);
|
||||
}
|
||||
renderKV(rightCol, {'受傷': textify(state['受傷'], '無'), '流血': textify(state['流血'], '無'), '疾病': textify(state['疾病'], '無')});
|
||||
wrapper.append(leftCol, rightCol);
|
||||
target.append(wrapper);
|
||||
}
|
||||
當ActionResult中「項目-最終檢查」字段存在時:
|
||||
- 檢定難度:由 crafting_rules 根據項目規模決定(簡單50 / 中等70 / 困難90)
|
||||
- 若結果為「成功」:
|
||||
- remove `/項目/<項目名>`
|
||||
- 根據項目產出字段 insert 最終產物到對應位置(根據上下文 insert 到攜帶物品、營地對應分類或裝備欄位)
|
||||
- 若結果為「失敗」:
|
||||
- 保持項目條目
|
||||
- 狀態保持「待檢查」
|
||||
- 當前進度保持不變
|
||||
- 不產生成品
|
||||
- 正文需描述缺陷部位(如「接合處鬆動」「表面不平整」)
|
||||
- 後續回合可再次嘗試最終檢查(需在正文中描述修復行為)
|
||||
|
||||
function renderTemperatures(container, data) { const environment = data['環境'] ?? {}, phys = data['生理'] ?? {};
|
||||
const tempWrapper = $('<div>').addClass('temp-range-wrapper');
|
||||
|
||||
const coreTemp = Number(e(phys['體溫'] ?? 37)), minNormal = 36.5, maxNormal = 37.5;
|
||||
const coreTempSubWrapper = $('<div>').css('margin-bottom', '8px').append($('<div>').text(`核心體溫: ${coreTemp.toFixed(1)}°C (正常: ${minNormal}-${maxNormal})`));
|
||||
const coreTempBar = $('<div>').addClass('temp-range-bar');
|
||||
const coreRangeMin = 35, coreRangeSpan = 7;
|
||||
const coreCurrentPercent = Math.max(0, Math.min(100, ((coreTemp - coreRangeMin) / coreRangeSpan) * 100));
|
||||
const coreNormalStart = ((minNormal - coreRangeMin) / coreRangeSpan) * 100;
|
||||
const coreNormalWidth = ((maxNormal - minNormal) / coreRangeSpan) * 100;
|
||||
coreTempBar.append(
|
||||
$('<div>').addClass('temp-gradient'),
|
||||
$('<div>').addClass('comfort-zone').css({ left: `${coreNormalStart}%`, width: `${coreNormalWidth}%` }),
|
||||
$('<div>').addClass('current-temp').css('left', `${coreCurrentPercent}%`)
|
||||
);
|
||||
tempWrapper.append(coreTempSubWrapper.append(coreTempBar));
|
||||
|
||||
const feltTemp = Number(e(environment['體感溫度'] ?? 20)), minComfort = Number(e(data['狀態']?.['最低舒適溫度'] ?? 15)), maxComfort = Number(e(data['狀態']?.['最高舒適溫度'] ?? 25));
|
||||
const feltTempSubWrapper = $('<div>').append($('<div>').text(`體感溫度: ${feltTemp}°C (舒適: ${minComfort}-${maxComfort})`));
|
||||
const feltTempBar = $('<div>').addClass('temp-range-bar');
|
||||
const feltRangeMin = 0, feltRangeSpan = 50;
|
||||
const feltCurrentPercent = Math.max(0, Math.min(100, ((feltTemp - feltRangeMin) / feltRangeSpan) * 100));
|
||||
const feltComfortStart = ((minComfort - feltRangeMin) / feltRangeSpan) * 100;
|
||||
const feltComfortWidth = ((maxComfort - minComfort) / feltRangeSpan) * 100;
|
||||
feltTempBar.append(
|
||||
$('<div>').addClass('temp-gradient'),
|
||||
$('<div>').addClass('comfort-zone').css({ left: `${feltComfortStart}%`, width: `${feltComfortWidth}%` }),
|
||||
$('<div>').addClass('current-temp').css('left', `${feltCurrentPercent}%`)
|
||||
);
|
||||
tempWrapper.append(feltTempSubWrapper.append(feltTempBar));
|
||||
container.append(tempWrapper);
|
||||
}
|
||||
#### 約束
|
||||
|
||||
function r(container, data) {
|
||||
const environment = data['環境'] ?? {}, system = data['系統'] ?? {};
|
||||
const phys = data['生理'] ?? {};
|
||||
const mental = data['精神'] ?? {};
|
||||
const state = data['狀態'] ?? {};
|
||||
|
||||
const top = $('<div>').addClass('top-info');
|
||||
top.append($('<div>').addClass('time-display').text(textify(system['當前時間'], '--:--')));
|
||||
|
||||
const infoStack = $('<div>').addClass('info-stack');
|
||||
infoStack.append(
|
||||
$('<div>').addClass('info-row').append(
|
||||
$('<div>').addClass('row-item').text(`Day ${e(system['經過天數'] ?? 0)}`),
|
||||
$('<div>').addClass('row-item').text(textify(environment['季節'], '')),
|
||||
$('<div>').addClass('row-item').text(textify(environment['月相'], ''))
|
||||
),
|
||||
$('<div>').addClass('info-row').append(
|
||||
$('<div>').addClass('row-item').text(textify(environment['天氣'], '')),
|
||||
$('<div>').addClass('row-item').text(`風速 ${textify(environment['風速'], '--')}`),
|
||||
$('<div>').addClass('row-item').text(`舒適度 ${textify(environment['舒適度'], '--')}`)
|
||||
),
|
||||
$('<div>').addClass('info-row').append(
|
||||
$('<div>').addClass('row-item').text(`腳本狀態:${textify(system['腳本修正狀態'], '🔄 未執行')}`)
|
||||
)
|
||||
);
|
||||
|
||||
top.append(infoStack, $('<div>').addClass('location-display').text(`位置:${textify(environment['當前位置'], '未知')}`));
|
||||
container.append(top);
|
||||
renderTemperatures(container, data);
|
||||
|
||||
const hpCurrent = Number(e(phys['當前生命值'] ?? 0)), hpMax = Number(e(phys['生命值上限'] ?? 0));
|
||||
const mpCurrent = Number(e(mental['當前精神值'] ?? 0)), mpMax = Number(e(mental['精神值上限'] ?? 0));
|
||||
const coreWrapper = $('<div>').addClass('core-stats-wrapper');
|
||||
coreWrapper.append($('<div>').addClass('core-stat-item').append(t('生命值', hpCurrent, hpMax, 'hp-progress')));
|
||||
coreWrapper.append($('<div>').addClass('core-stat-item').append(t('精神值', mpCurrent, mpMax, 'mp-progress')));
|
||||
container.append(coreWrapper);
|
||||
}
|
||||
- 禁止在進度未達總進度時進行最終檢查
|
||||
- 禁止在狀態非「待檢查」時進行最終檢查
|
||||
- 禁止跳過最終檢查直接完工
|
||||
- 材料消耗與進度推進必須在相同的JSON Patch操作組中完成
|
||||
|
||||
function renderKeyValueTree(target, data) {
|
||||
const entries = toEntries(data);
|
||||
if (!entries || entries.length === 0) return;
|
||||
const list = $('<ul>').addClass('section__list');
|
||||
entries.forEach(([key, value]) => {
|
||||
const li = $('<li>');
|
||||
if (value && typeof value === 'object') {
|
||||
$('<div>').addClass('item').append($('<span>').addClass('item__key').text(String(key))).appendTo(li);
|
||||
renderKeyValueTree(li, value);
|
||||
} else {
|
||||
$('<div>').addClass('item').append($('<span>').addClass('item__key').text(String(key)), $('<span>').addClass('item__value').text(textify(value, ''))).appendTo(li);
|
||||
}
|
||||
list.append(li);
|
||||
});
|
||||
target.append(list);
|
||||
}
|
||||
### 13. 研究進度更新
|
||||
|
||||
function renderTaskSystem(container, data) {
|
||||
const tasksData = data['任務'] ?? {};
|
||||
const { $section, $body } = d('任務', true);
|
||||
|
||||
const getStatusClass = (status) => ({'進行中':'status-ongoing', '待結算':'status-pending', '已完成':'status-completed', '已失敗':'status-failed', '已放棄':'status-abandoned'}[status] || 'status-ongoing');
|
||||
const getStatusText = (status) => ({'進行中':'進行中', '待結算':'待結算', '已完成':'已完成', '已失敗':'已失敗', '已放棄':'已放棄'}[status] || '進行中');
|
||||
|
||||
const renderRewards = (rewardsData) => {
|
||||
if (!rewardsData) return '';
|
||||
if (typeof rewardsData === 'string') {
|
||||
return '<span class="points">' + rewardsData + '</span>';
|
||||
}
|
||||
if (typeof rewardsData === 'object') {
|
||||
const parts = [];
|
||||
Object.entries(rewardsData).forEach(([key, value]) => {
|
||||
if (key === '劇情演出') {
|
||||
parts.push('<span class="points">特殊劇情:' + value + '</span>');
|
||||
} else if (key === '特殊物品') {
|
||||
parts.push('<span class="points">特殊物品:' + value.replace('獲得製作「', '獲得製作「').replace('」的配方', '」的配方') + '</span>');
|
||||
} else if (typeof value === 'object') {
|
||||
const subParts = Object.entries(value).map(function(sub) {
|
||||
return sub[0] + ' +' + sub[1] + ' exp';
|
||||
});
|
||||
parts.push('<span class="points">' + key + ':' + subParts.join(', ') + '</span>');
|
||||
} else {
|
||||
parts.push('<span class="points">' + key + ' +' + value + '</span>');
|
||||
}
|
||||
});
|
||||
return parts.join(', ');
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const createTaskDetail = (task, type, data) => {
|
||||
const detail = $('<div>').addClass('task-detail').hide();
|
||||
const colorMap = {'主線任務':'#3498db', '支線任務':'#f39c12', '日常任務':'#2ecc71', '緊急任務':'#e74c3c', '羈絆任務':'#8E44AD'};
|
||||
if (task['標題']) detail.append($('<div>').css({'font-size':'18px', 'font-weight':'bold', 'text-align':'center', 'margin-bottom':'8px', 'padding':'4px 0', 'color':colorMap[type] || '#fff'}).text(task['標題']));
|
||||
detail.append($('<div>').addClass('task-divider'));
|
||||
|
||||
// 任務描述
|
||||
if (task['描述']) detail.append($('<div>').addClass('task-desc').text(task['描述'] || ''));
|
||||
if (task['區域限制']) detail.append($('<div>').addClass('target-desc').html(`區域: <span style="color:var(--survival-warning); font-weight:bold;">${task['區域限制']}</span>`));
|
||||
|
||||
// 任務時間顯示
|
||||
if (task['結束時間']) {
|
||||
const currentDay = Number(e(data?.['系統']?.['經過天數'] ?? 0));
|
||||
const currentTime = textify(data?.['系統']?.['當前時間'] ?? '00:00');
|
||||
const currentTimeString = `第${currentDay}天 ${currentTime}`;
|
||||
const totalCurrentMinutes = parseTimeToMinutes(currentTimeString);
|
||||
const totalEndMinutes = parseTimeToMinutes(task['結束時間']);
|
||||
if (totalCurrentMinutes !== null && totalEndMinutes !== null) {
|
||||
const remainingMinutes = totalEndMinutes - totalCurrentMinutes;
|
||||
const remainingTimeText = formatRemainingTime(remainingMinutes);
|
||||
let timeColor = 'var(--survival-text)';
|
||||
if (remainingMinutes <= 0) timeColor = 'var(--survival-danger)';
|
||||
else if (remainingMinutes < 6 * 60) timeColor = 'var(--survival-warning)';
|
||||
const timeContainer = $('<div>').addClass('target-desc').css('display', 'flex', 'align-items', 'center', 'gap', '8px');
|
||||
timeContainer.append($('<span>').css('color', timeColor).text('剩餘時間:'));
|
||||
timeContainer.append($('<span>').css('color', 'var(--survival-warning)').css('font-weight', 'bold').text(remainingTimeText));
|
||||
detail.append(timeContainer);
|
||||
} else {
|
||||
detail.append($('<div>').addClass('target-desc').html(`結束時間: <span style="color:var(--survival-warning); font-weight:bold;">${task['結束時間']}</span>`));
|
||||
}
|
||||
}
|
||||
|
||||
// 緊急任務特殊處理
|
||||
if (type === '緊急任務') {
|
||||
const emergencyInfo = $('<div>').addClass('time-info').css('text-align', 'left');
|
||||
if (task['結束時間']) emergencyInfo.append($('<div>').html(`截止時間: <span style="color:var(--survival-warning); font-weight:bold;">${task['結束時間']}</span>`));
|
||||
if (task['失敗懲罰']) emergencyInfo.append($('<div>').addClass('penalty-info').html(`失敗懲罰: ${task['失敗懲罰']}`));
|
||||
detail.append(emergencyInfo);
|
||||
}
|
||||
|
||||
detail.append($('<div>').addClass('task-divider'));
|
||||
|
||||
// ========== 主要要求(基礎要求) ==========
|
||||
if (task['任務要求'] && typeof task['任務要求'] === 'object') {
|
||||
const requirementSection = $('<div>').addClass('target-section').append($('<div>').addClass('section-title').text('任務要求'));
|
||||
|
||||
// 處理基礎要求(可能是陣列或物件)
|
||||
const requirements = task['任務要求'];
|
||||
Object.entries(requirements).forEach(([reqKey, reqData]) => {
|
||||
// 跳過進階目標(因為進階目標會另外處理)
|
||||
if (reqKey === '進階目標') return;
|
||||
|
||||
if (reqData && typeof reqData === 'object') {
|
||||
const desc = $('<div>').addClass('target-desc');
|
||||
const current = Number(e(reqData['進度'])) || 0;
|
||||
const target = Number(e(reqData['目標'])) || 1;
|
||||
const reqType = reqData['類型'] || '數值';
|
||||
|
||||
// 獲取描述文字
|
||||
let descriptionText = reqData['描述'] || '';
|
||||
if (!descriptionText && reqKey) {
|
||||
descriptionText = reqKey.replace(/^基礎要求\d*/, '目標');
|
||||
}
|
||||
|
||||
desc.append($('<span>').text(descriptionText));
|
||||
if (reqType === '數值') {
|
||||
desc.append($('<span>').addClass('progress-number').text(` (${current}/${target})`));
|
||||
}
|
||||
if (current >= target) desc.addClass('completed');
|
||||
requirementSection.append(desc);
|
||||
}
|
||||
});
|
||||
|
||||
if (task['基礎獎勵']) {
|
||||
requirementSection.append($('<div>').addClass('reward-info').html(`基礎獎勵: ${renderRewards(task['基礎獎勵'])}`));
|
||||
}
|
||||
detail.append(requirementSection);
|
||||
}
|
||||
研究用於解鎖知識。信號來源:ActionResult中"研究"字段。
|
||||
|
||||
// ========== 進階目標(修正版:正確顯示描述) ==========
|
||||
if (type !== '日常任務' && task['進階目標'] && Object.keys(task['進階目標']).length > 0) {
|
||||
detail.append($('<div>').addClass('task-divider'));
|
||||
const bonusSection = $('<div>').addClass('target-section').append($('<div>').addClass('section-title').text('進階要求'));
|
||||
|
||||
// 正確遍歷進階目標
|
||||
Object.entries(task['進階目標']).forEach(([goalKey, goalData]) => {
|
||||
if (goalData && typeof goalData === 'object') {
|
||||
const desc = $('<div>').addClass('target-desc');
|
||||
const current = Number(e(goalData['進度'])) || 0;
|
||||
const target = Number(e(goalData['目標'])) || 1;
|
||||
const goalType = goalData['類型'] || '數值';
|
||||
|
||||
// 獲取描述文字(優先使用 goalData['描述'])
|
||||
let descriptionText = '';
|
||||
if (goalData['描述']) {
|
||||
descriptionText = goalData['描述'];
|
||||
} else if (goalKey) {
|
||||
// 如果沒有描述,使用 key 名稱
|
||||
descriptionText = goalKey.replace(/^進階要求\d*/, '目標');
|
||||
}
|
||||
|
||||
desc.append($('<span>').text(descriptionText));
|
||||
if (goalType === '數值') {
|
||||
desc.append($('<span>').addClass('progress-number').text(` (${current}/${target})`));
|
||||
}
|
||||
if (current >= target) desc.addClass('completed');
|
||||
bonusSection.append(desc);
|
||||
}
|
||||
});
|
||||
|
||||
if (task['進階獎勵']) {
|
||||
bonusSection.append($('<div>').addClass('reward-info').html(`進階獎勵: ${renderRewards(task['進階獎勵'])}`));
|
||||
}
|
||||
detail.append(bonusSection);
|
||||
}
|
||||
|
||||
return detail;
|
||||
};
|
||||
|
||||
let hasTasks = false;
|
||||
|
||||
// ========== 世界任務(終極任務,特殊顯示) ==========
|
||||
const worldTask = tasksData['世界'];
|
||||
if (worldTask && typeof worldTask === 'object' && worldTask['標題']) {
|
||||
hasTasks = true;
|
||||
|
||||
// 終極任務專屬樣式 - 金色發光邊框 + 特殊動畫
|
||||
const strip = $('<div>').addClass('task-strip world-task');
|
||||
strip.append($('<div>').addClass('task-content').html('🌍 世界任務 🌍'));
|
||||
|
||||
// 狀態指示器
|
||||
const status = worldTask['狀態'] || '已揭示';
|
||||
const indicator = $('<div>').addClass('task-status-indicator');
|
||||
indicator.append($('<div>').addClass('status-text').addClass(getStatusClass(status)).text(getStatusText(status)));
|
||||
strip.append(indicator);
|
||||
|
||||
// 任務詳情
|
||||
const detail = createTaskDetail(worldTask, '世界任務', data);
|
||||
|
||||
// 點擊展開/收合
|
||||
strip.on('click', () => {
|
||||
$('.task-detail').not(detail).slideUp();
|
||||
detail.slideToggle();
|
||||
});
|
||||
|
||||
$body.append(strip, detail);
|
||||
}
|
||||
|
||||
['主線', '支線', '日常', '緊急', '背德'].forEach(key => {
|
||||
const task = tasksData[key];
|
||||
if (task && typeof task === 'object' && task['標題']) {
|
||||
hasTasks = true;
|
||||
const keyNameMapping = {'主線': '主線任務', '支線': '支線任務', '日常': '日常任務', '緊急': '緊急任務', '背德': '背德任務'};
|
||||
const classNameMapping = {'主線': 'main-task', '支線': 'side-task', '日常': 'daily-task', '緊急': 'emergency-task', '背德': 'bond-task'};
|
||||
const status = task['狀態'] || '進行中';
|
||||
const strip = $('<div>').addClass('task-strip').addClass(classNameMapping[key]);
|
||||
strip.append($('<div>').addClass('task-content').text(keyNameMapping[key]));
|
||||
const indicator = $('<div>').addClass('task-status-indicator');
|
||||
indicator.append($('<div>').addClass('status-text').addClass(getStatusClass(status)).text(getStatusText(status)));
|
||||
strip.append(indicator);
|
||||
const detail = createTaskDetail(task, keyNameMapping[key], data);
|
||||
strip.on('click', () => { $('.task-detail').not(detail).slideUp(); detail.slideToggle(); });
|
||||
$body.append(strip, detail);
|
||||
}
|
||||
});
|
||||
|
||||
const bondTasks = tasksData['羈絆'];
|
||||
if (bondTasks && typeof bondTasks === 'object' && Object.keys(bondTasks).length > 0) {
|
||||
hasTasks = true;
|
||||
Object.entries(bondTasks).forEach(([companionName, task]) => {
|
||||
if (task && typeof task === 'object' && task['標題']) {
|
||||
const status = task['狀態'] || '進行中';
|
||||
const strip = $('<div>').addClass('task-strip bond-task');
|
||||
strip.append($('<div>').addClass('task-content').text(`羈絆 - ${companionName}`));
|
||||
const indicator = $('<div>').addClass('task-status-indicator');
|
||||
indicator.append($('<div>').addClass('status-text').addClass(getStatusClass(status)).text(getStatusText(status)));
|
||||
strip.append(indicator);
|
||||
const detail = createTaskDetail(task, '羈絆任務', data);
|
||||
strip.on('click', () => { $('.task-detail').not(detail).slideUp(); detail.slideToggle(); });
|
||||
$body.append(strip, detail);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasTasks) {
|
||||
$body.append($('<div>').addClass('empty-text').text('暫無任務'));
|
||||
}
|
||||
container.append($section);
|
||||
}
|
||||
#### 新建研究
|
||||
|
||||
function renderCompanions(container, companionsSource) {
|
||||
if (companionsSource == null || Object.keys(companionsSource).length === 0) return;
|
||||
const { $section, $body } = d('同伴', false);
|
||||
|
||||
const companionEntries = toEntries(companionsSource);
|
||||
|
||||
if (companionEntries.length === 0) {
|
||||
$body.append($('<p>').addClass('empty-text').text('暫無同伴'));
|
||||
} else {
|
||||
companionEntries.forEach(([name, details]) => {
|
||||
if (details && typeof details === 'object') {
|
||||
const card = $('<div>').addClass('collapsible').css('margin-bottom', '10px');
|
||||
const cardHeader = $('<div>').addClass('collapsible__header').text(name);
|
||||
const cardBody = $('<div>').addClass('collapsible__body');
|
||||
|
||||
// 🔧 修改的地方:安全获取当前状态(支持新的数据结构)
|
||||
const currentState = details?.['狀態']?.['當前狀態'] ?? details?.['狀態'];
|
||||
const healthStatus = textify(currentState, '狀態未知');
|
||||
|
||||
const healthValue = Number(e(details['健康度'] ?? 100));
|
||||
let statusColor = '#2ecc71';
|
||||
if (healthValue <= 0) statusColor = '#7f8c8d';
|
||||
else if (healthValue < 10) statusColor = '#8e44ad';
|
||||
else if (healthValue < 30) statusColor = '#e74c3c';
|
||||
else if (healthValue < 50) statusColor = '#e67e22';
|
||||
else if (healthValue < 70) statusColor = '#f39c12';
|
||||
|
||||
const statusRow = $('<div>').css({'font-size': '16px', 'font-weight': 'bold', 'color': 'var(--survival-warning)', 'margin-top': '14px', 'margin-bottom': '10px', 'text-align': 'left'});
|
||||
$('<span>').text('狀態: ').appendTo(statusRow);
|
||||
$('<span>').css('color', statusColor).text(healthStatus).appendTo(statusRow);
|
||||
cardBody.append(statusRow);
|
||||
|
||||
const statusWrapper = $('<div>').addClass('phys-mental-wrapper').css('margin-bottom', '20px');
|
||||
const physCol = $('<div>').addClass('phys-col');
|
||||
const mentalCol = $('<div>').addClass('mental-col');
|
||||
|
||||
$('<div>').addClass('section__title').text('生理').appendTo(physCol);
|
||||
const health = Math.min(Number(e(details['健康度'] ?? 0)), 100);
|
||||
const water = Math.min(Number(e(details['水分值'] ?? 0)), 100);
|
||||
const satiety = Math.min(Number(e(details['飽腹感'] ?? 0)), 100);
|
||||
const fatigue = Math.min(Number(e(details['疲勞度'] ?? 0)), 100);
|
||||
physCol.append(t(`健康度: ${health}/100 (${getHealthStatus(health)})`, health, 100, 'health-progress'));
|
||||
physCol.append(t(`水分值: ${water}/100`, water, 100, 'water-progress'));
|
||||
physCol.append(t(`飽腹感: ${satiety}/100`, satiety, 100, 'satiety-progress'));
|
||||
physCol.append(t(`疲勞度: ${fatigue}/100`, fatigue, 100, 'fatigue-progress'));
|
||||
|
||||
/*$('<div>').addClass('section__title').text('心理').appendTo(mentalCol);
|
||||
const love = Math.min(Number(e(details['好感度'] ?? 0)), 120);
|
||||
const desire = Math.min(Number(e(details['性慾值'] ?? 0)), 100);
|
||||
const dev = Math.min(Number(e(details['開發度'] ?? 0)), 100);
|
||||
const crevice = Math.min(Number(e(details['墮落值'] ?? 0)), 100);
|
||||
mentalCol.append(t(`好感度: ${love.toFixed(1)}/100 (${getFavorabilityStatus(love)})`, love, 100, 'love-progress'));
|
||||
mentalCol.append(t(`性慾值: ${desire.toFixed(1)}/100 (${getDesireStatus(desire)})`, desire, 100, 'desire-progress'));
|
||||
mentalCol.append(t(`開發度: ${dev.toFixed(1)}/100`, dev, 100, 'dev-progress'));
|
||||
mentalCol.append(t(`墮落值: ${crevice.toFixed(1)}/100 (${getCreviceStatus(crevice)})`, crevice, 100, 'crevice-progress'));
|
||||
|
||||
statusWrapper.append(physCol, mentalCol);
|
||||
cardBody.append(statusWrapper);*/
|
||||
|
||||
// ================ 移動:先渲染屬性與技能 ================
|
||||
renderAttributesAndSkills(cardBody, details['屬性'], details['技能'], details['天賦特性'], details);
|
||||
|
||||
// ================ 移動:裝備顯示在攜帶物品上面 ================
|
||||
const equipment = details['裝備'] ?? {};
|
||||
|
||||
// 檢查是否有任何裝備
|
||||
let hasAnyEquipment = false;
|
||||
const equipmentSlots = [
|
||||
'頭部', '頸部', '胸部', '背部', '腰部',
|
||||
'右手', '左手', '腿部', '腳部', '其他'
|
||||
];
|
||||
|
||||
// 先檢查哪些部位有裝備
|
||||
const occupiedSlots = equipmentSlots.filter(slot => {
|
||||
const equipmentItem = equipment[slot];
|
||||
return equipmentItem && typeof equipmentItem === 'object' && Object.keys(equipmentItem).length > 0;
|
||||
});
|
||||
|
||||
// 如果有任何裝備才顯示裝備欄位
|
||||
if (occupiedSlots.length > 0) {
|
||||
// 注意:這裡沒有 margin-top,因為它緊接在屬性技能後面
|
||||
cardBody.append($('<div>').addClass('section__title').text('裝備'));
|
||||
|
||||
// 使用跟玩家完全一樣的裝備網格
|
||||
const equipmentGrid = $('<div>').addClass('inventory-grid equipment-grid').css({
|
||||
'grid-template-columns': 'repeat(5, 1fr)', // 一行5個
|
||||
'gap': '15px'
|
||||
});
|
||||
|
||||
// 只顯示有裝備的部位
|
||||
occupiedSlots.forEach((slot, index) => {
|
||||
const slotContainer = $('<div>').addClass('equipment-slot')
|
||||
.attr('data-slot', slot)
|
||||
.css({
|
||||
'background': 'var(--survival-surface-strong)',
|
||||
'border': '1px solid var(--survival-glass-border)',
|
||||
'border-radius': '12px',
|
||||
'padding': '16px',
|
||||
'text-align': 'center',
|
||||
'transition': 'all 0.25s ease-out',
|
||||
'min-height': '140px',
|
||||
'display': 'flex',
|
||||
'flex-direction': 'column',
|
||||
'justify-content': 'flex-start'
|
||||
});
|
||||
|
||||
// 裝備部位標題(跟玩家一樣)
|
||||
const slotTitle = $('<div>').addClass('slot-title').css({
|
||||
'font-size': '20px',
|
||||
'font-weight': '700',
|
||||
'color': '#d17d2e',
|
||||
'letter-spacing': '0.05em',
|
||||
'text-transform': 'uppercase',
|
||||
'margin-bottom': '12px',
|
||||
'padding-bottom': '8px',
|
||||
'border-bottom': '1px solid rgba(148, 197, 255, 0.2)',
|
||||
'line-height': '1.2'
|
||||
}).text(slot);
|
||||
|
||||
slotContainer.append(slotTitle);
|
||||
|
||||
// 獲取該部位的裝備
|
||||
const equipmentItem = equipment[slot];
|
||||
|
||||
// 獲取裝備名稱(第一個鍵名)
|
||||
const itemKey = Object.keys(equipmentItem)[0];
|
||||
const itemData = equipmentItem[itemKey];
|
||||
|
||||
if (itemKey && itemData) {
|
||||
const itemNameDiv = $('<div>').addClass('equipment-item-name').css({
|
||||
'font-size': '18px',
|
||||
'font-weight': '600',
|
||||
'color': 'var(--survival-accent-strong)',
|
||||
'margin-bottom': '8px',
|
||||
'word-wrap': 'break-word',
|
||||
'line-height': '1.3',
|
||||
'padding': '8px 10px',
|
||||
'background': 'rgba(94, 161, 255, 0.1)',
|
||||
'border': '1px solid rgba(94, 161, 255, 0.2)',
|
||||
'border-radius': '6px',
|
||||
'width': '100%',
|
||||
'text-align': 'center'
|
||||
}).text(itemKey);
|
||||
|
||||
slotContainer.append(itemNameDiv);
|
||||
|
||||
// 顯示裝備屬性(跟玩家完全一樣的邏輯)
|
||||
const itemProps = $('<div>').addClass('equipment-item-props').css({
|
||||
'font-size': '12px',
|
||||
'color': 'var(--survival-text)',
|
||||
'opacity': '0.8',
|
||||
'text-align': 'left',
|
||||
'line-height': '1.4',
|
||||
'margin-top': '10px',
|
||||
'padding': '8px',
|
||||
'background': 'rgba(20, 30, 50, 0.5)',
|
||||
'border-radius': '6px',
|
||||
'border': '1px solid rgba(94, 161, 255, 0.1)',
|
||||
'width': '100%'
|
||||
});
|
||||
|
||||
// 完整的屬性顯示邏輯(跟玩家一樣)
|
||||
const orderedProps = ['數量', '位置', '重量', '耐久度'];
|
||||
const attributes = new Map();
|
||||
|
||||
// 先按照順序處理
|
||||
orderedProps.forEach(k => {
|
||||
if (itemData.hasOwnProperty(k)) {
|
||||
attributes.set(k, itemData[k]);
|
||||
}
|
||||
});
|
||||
|
||||
// 處理狀態類型和狀態
|
||||
const stateType = textify(itemData['狀態類型'] ?? '', '');
|
||||
if (stateType && itemData.hasOwnProperty('狀態')) {
|
||||
// 如果狀態類型不是'耐久度',或者還沒有設置耐久度,則添加
|
||||
if (stateType.toLowerCase() !== '耐久度' || !attributes.has('耐久度')) {
|
||||
attributes.set(stateType, itemData['狀態']);
|
||||
}
|
||||
}
|
||||
|
||||
// 處理其他屬性(跟玩家一樣顯示所有屬性)
|
||||
// 處理其他屬性(跟玩家一樣顯示所有屬性)
|
||||
Object.entries(itemData).forEach(([k, v]) => {
|
||||
if (!attributes.has(k) && k !== '$meta' && k !== '狀態類型' && k !== '狀態') {
|
||||
// 特別處理加值欄位
|
||||
if (k === '加值' && v && typeof v === 'object') {
|
||||
const bonusEntries = [];
|
||||
Object.entries(v).forEach(([bonusKey, bonusValue]) => {
|
||||
bonusEntries.push(`${bonusKey}+【${bonusValue}】`);
|
||||
});
|
||||
attributes.set(k, bonusEntries.join('、'));
|
||||
} else {
|
||||
attributes.set(k, v);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 顯示所有屬性(使用跟玩家一樣的樣式)
|
||||
attributes.forEach((v, k) => {
|
||||
if (v !== null && v !== '' && v !== undefined) {
|
||||
const line = $('<div>').addClass('equipment-prop');
|
||||
line.attr('data-prop', k);
|
||||
|
||||
// 如果是加值欄位,添加特殊類別
|
||||
if (k === '加值') {
|
||||
line.addClass('bonus-prop');
|
||||
// 在加值前面添加分隔線
|
||||
const separator = $('<div>').addClass('bonus-separator').css({
|
||||
'height': '1px',
|
||||
'background': 'linear-gradient(90deg, transparent, rgba(148, 197, 255, 0.4), transparent)',
|
||||
'margin': '10px 0 8px 0',
|
||||
'width': '100%'
|
||||
});
|
||||
itemProps.append(separator);
|
||||
}
|
||||
|
||||
let displayValue = textify(v, '');
|
||||
if (k === '重量' && typeof v === 'number') {
|
||||
displayValue += 'kg';
|
||||
}
|
||||
|
||||
// 如果是數量,確保顯示
|
||||
if (k === '數量') {
|
||||
displayValue = String(displayValue);
|
||||
}
|
||||
|
||||
// 限制描述長度
|
||||
if (k === '描述' && displayValue.length > 80) {
|
||||
displayValue = displayValue.substring(0, 77) + '...';
|
||||
}
|
||||
|
||||
$('<span>').addClass('prop-key').text(String(k)).appendTo(line);
|
||||
$('<span>').addClass('prop-sep').text(': ').appendTo(line);
|
||||
$('<span>').addClass('prop-val').text(displayValue).appendTo(line);
|
||||
|
||||
itemProps.append(line);
|
||||
|
||||
// 如果是加值欄位,在後面也添加一個小分隔線
|
||||
if (k === '加值') {
|
||||
const bottomSeparator = $('<div>').css({
|
||||
'height': '1px',
|
||||
'background': 'rgba(148, 197, 255, 0.1)',
|
||||
'margin': '8px 0 4px 0',
|
||||
'width': '100%'
|
||||
});
|
||||
itemProps.append(bottomSeparator);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (attributes.size > 0) {
|
||||
slotContainer.append(itemProps);
|
||||
}
|
||||
|
||||
// 懸停效果(跟玩家一樣)
|
||||
slotContainer.hover(
|
||||
function() {
|
||||
$(this).css({
|
||||
'transform': 'translateY(-3px)',
|
||||
'box-shadow': '0 8px 25px rgba(0,0,0,0.3), 0 0 20px rgba(94, 161, 255, 0.15)',
|
||||
'background': 'rgba(40, 55, 85, 0.9)',
|
||||
'border-color': 'var(--survival-accent)'
|
||||
});
|
||||
},
|
||||
function() {
|
||||
$(this).css({
|
||||
'transform': 'translateY(0)',
|
||||
'box-shadow': 'none',
|
||||
'background': 'var(--survival-surface-strong)',
|
||||
'border-color': 'var(--survival-glass-border)'
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
equipmentGrid.append(slotContainer);
|
||||
});
|
||||
|
||||
// 根據有裝備的部位數量調整網格布局
|
||||
const slotCount = occupiedSlots.length;
|
||||
if (slotCount <= 5) {
|
||||
equipmentGrid.css('grid-template-columns', `repeat(${slotCount}, 1fr)`);
|
||||
} else if (slotCount <= 10) {
|
||||
equipmentGrid.css('grid-template-columns', 'repeat(5, 1fr)');
|
||||
}
|
||||
|
||||
cardBody.append(equipmentGrid);
|
||||
}
|
||||
// ================ 完整的同伴裝備欄位結束 ================
|
||||
|
||||
// ================ 移動:攜帶物品顯示在最下面 ================
|
||||
cardBody.append($('<div>').addClass('section__title').css('margin-top', '20px').text('攜帶物品'));
|
||||
const inventoryContainer = $('<div>').addClass('companion-inventory-grid');
|
||||
|
||||
const items = details['攜帶物品'] ? firstMeaningfulObject(details['攜帶物品']) : {};
|
||||
const itemEntries = Object.entries(items);
|
||||
|
||||
if (itemEntries.length === 0) {
|
||||
inventoryContainer.append($('<p>').addClass('empty-text').css('grid-column', '1 / -1').text('無'));
|
||||
} else {
|
||||
itemEntries.forEach(([itemName, itemDetails]) => {
|
||||
const itemCard = $('<div>').addClass('inventory-item');
|
||||
$('<div>').addClass('item-name').text(itemName).appendTo(itemCard);
|
||||
if (itemDetails && typeof itemDetails === 'object') {
|
||||
const props = $('<div>').addClass('item-props');
|
||||
const attributes = new Map();
|
||||
|
||||
const definedOrder = ['數量', '位置', '重量', '耐久度'];
|
||||
definedOrder.forEach(k => {
|
||||
if (itemDetails.hasOwnProperty(k)) {
|
||||
attributes.set(k, itemDetails[k]);
|
||||
}
|
||||
});
|
||||
|
||||
const stateType = textify(itemDetails['狀態類型'] ?? '');
|
||||
if (stateType && itemDetails.hasOwnProperty('狀態')) {
|
||||
if (stateType.toLowerCase() !== '耐久度' || !attributes.has('耐久度')) {
|
||||
attributes.set(stateType, itemDetails['狀態']);
|
||||
}
|
||||
}
|
||||
當ActionResult中"研究-新建"字段存在時:
|
||||
- 操作:insert 條目到 `/研究/<類別>/進行中/<研究項名>`
|
||||
- 字段包括:當前進度(初始爲0)、總進度、描述
|
||||
|
||||
// 處理其他屬性,包括加值
|
||||
Object.entries(itemDetails).forEach(([k, v]) => {
|
||||
if (!attributes.has(k) && k !== '$meta' && k !== '狀態類型' && k !== '狀態') {
|
||||
// 特別處理加值欄位
|
||||
if (k === '加值' && v && typeof v === 'object') {
|
||||
const bonusEntries = [];
|
||||
Object.entries(v).forEach(([bonusKey, bonusValue]) => {
|
||||
bonusEntries.push(`${bonusKey}+【${bonusValue}】`);
|
||||
});
|
||||
attributes.set(k, bonusEntries.join('、'));
|
||||
} else {
|
||||
attributes.set(k, v);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const postStateOrder = ['描述', '功能', '功能1', '功能2', '功能3', '負重加成'];
|
||||
postStateOrder.forEach(k => {
|
||||
if (itemDetails.hasOwnProperty(k)) {
|
||||
attributes.set(k, itemDetails[k]);
|
||||
}
|
||||
});
|
||||
|
||||
attributes.forEach((v, k) => {
|
||||
if (v !== null && v !== '' && v !== undefined) {
|
||||
const line = $('<div>').addClass('item-prop');
|
||||
|
||||
// 如果是加值欄位,添加特殊類別
|
||||
if (k === '加值') {
|
||||
line.addClass('bonus-prop');
|
||||
// 在加值前面添加分隔線
|
||||
const separator = $('<div>').addClass('bonus-separator').css({
|
||||
'height': '1px',
|
||||
'background': 'linear-gradient(90deg, transparent, rgba(148, 197, 255, 0.4), transparent)',
|
||||
'margin': '10px 0 8px 0',
|
||||
'width': '100%'
|
||||
});
|
||||
props.append(separator);
|
||||
}
|
||||
|
||||
$('<span>').addClass('prop-key').text(String(k)).appendTo(line);
|
||||
$('<span>').addClass('prop-sep').text(': ').appendTo(line);
|
||||
let displayValue = textify(v, '');
|
||||
if (k === '重量' && typeof v === 'number') {
|
||||
displayValue += 'kg';
|
||||
}
|
||||
$('<span>').addClass('prop-val').text(displayValue).appendTo(line);
|
||||
props.append(line);
|
||||
|
||||
// 如果是加值欄位,在後面也添加一個小分隔線
|
||||
if (k === '加值') {
|
||||
const bottomSeparator = $('<div>').css({
|
||||
'height': '1px',
|
||||
'background': 'rgba(148, 197, 255, 0.1)',
|
||||
'margin': '8px 0 4px 0',
|
||||
'width': '100%'
|
||||
});
|
||||
props.append(bottomSeparator);
|
||||
}
|
||||
}
|
||||
});
|
||||
#### 推進進度
|
||||
|
||||
itemCard.append(props);
|
||||
}
|
||||
inventoryContainer.append(itemCard);
|
||||
});
|
||||
}
|
||||
cardBody.append(inventoryContainer);
|
||||
|
||||
card.append(cardHeader, cardBody);
|
||||
cardHeader.on('click', () => {
|
||||
card.toggleClass('active');
|
||||
cardBody.slideToggle(200);
|
||||
});
|
||||
$body.append(card);
|
||||
}
|
||||
});
|
||||
}
|
||||
container.append($section);
|
||||
}
|
||||
當研究有進展時:
|
||||
- 更新進度:replace `/研究/<類別>/進行中/<研究項名>/當前進度` 爲新值
|
||||
- 更新描述:replace `/研究/<類別>/進行中/<研究項名>/描述`
|
||||
- 消耗材料(如有):材料消耗邏輯參照製作/建造規則
|
||||
- 智能聚合:若是對已有知識的技術深化,可 replace 現有條目的當前進度、總進度、描述及名稱(如適用),而非新建條目
|
||||
|
||||
#### 研究完成
|
||||
|
||||
// ============== 新增:寵物渲染函數 ==============
|
||||
function renderPets(container, petsSource) {
|
||||
if (petsSource == null || Object.keys(petsSource).length === 0) return;
|
||||
const { $section, $body } = d('寵物', false);
|
||||
|
||||
const petEntries = toEntries(petsSource);
|
||||
|
||||
if (petEntries.length === 0) {
|
||||
$body.append($('<p>').addClass('empty-text').text('暫無寵物'));
|
||||
} else {
|
||||
petEntries.forEach(([name, details]) => {
|
||||
if (details && typeof details === 'object') {
|
||||
const card = $('<div>').addClass('collapsible').css('margin-bottom', '10px');
|
||||
const cardHeader = $('<div>').addClass('collapsible__header').text(name);
|
||||
const cardBody = $('<div>').addClass('collapsible__body');
|
||||
|
||||
// 左側:狀態、關係、屬性
|
||||
const leftCol = $('<div>').addClass('attr-col');
|
||||
|
||||
// 狀態(單獨一行)
|
||||
const status = textify(details['狀態'], '狀態未知');
|
||||
const statusRow = $('<div>').css({
|
||||
'font-size': '16px',
|
||||
'font-weight': 'bold',
|
||||
'color': '#3498db',
|
||||
'margin-bottom': '8px',
|
||||
'text-align': 'left'
|
||||
}).text(`狀態: ${status}`);
|
||||
leftCol.append(statusRow);
|
||||
|
||||
// 關係(單獨一行)
|
||||
const relationship = textify(details['關系'] || details['關係'], '未知');
|
||||
const relationshipRow = $('<div>').css({
|
||||
'font-size': '16px',
|
||||
'font-weight': 'bold',
|
||||
'color': '#2ecc71',
|
||||
'margin-bottom': '15px',
|
||||
'text-align': 'left'
|
||||
}).text(`關係: ${relationship}`);
|
||||
leftCol.append(relationshipRow);
|
||||
|
||||
// 忠誠度(不顯示標題)
|
||||
/* const loyalty = Math.min(Number(e(details['忠誠度'] ?? 0)), 100);
|
||||
leftCol.append(t(`忠誠度: ${loyalty}/100 (${getLoyaltyStatus(loyalty)})`, loyalty, 100, 'loyalty-progress'));
|
||||
|
||||
// 性慾值(不顯示標題)
|
||||
const desire = Math.min(Number(e(details['性慾值'] ?? 0)), 100);
|
||||
leftCol.append(t(`性慾值: ${desire}/100`, desire, 100, 'pet-desire-progress')); */
|
||||
|
||||
// 屬性
|
||||
const attributes = details['屬性'] ?? {};
|
||||
leftCol.append($('<div>').addClass('section__title').css('margin-top', '20px').text('屬性'));
|
||||
Object.entries(firstMeaningfulObject(attributes)).forEach(([attrName, attrDetails]) => {
|
||||
if (attrDetails && typeof attrDetails === 'object') {
|
||||
const level = Number(e(attrDetails['等級'] ?? 0));
|
||||
const exp = Number(e(attrDetails['當前經驗'] ?? 0));
|
||||
const required = Number(e(attrDetails['升級所需經驗'] ?? 1)) || 1;
|
||||
leftCol.append($('<div>').text(`${attrName} Lv.${level}`), t('', exp, required));
|
||||
}
|
||||
});
|
||||
|
||||
// 右側:天賦特性(位置調整到前面)
|
||||
const rightCol = $('<div>').addClass('skill-col');
|
||||
|
||||
// 先顯示天賦特性
|
||||
const talents = details['天賦特性'] ?? {};
|
||||
const talentEntries = toEntries(talents);
|
||||
|
||||
if (talentEntries.length > 0) {
|
||||
rightCol.append($('<div>').addClass('section__title').text('天賦特性'));
|
||||
talentEntries.forEach(([talentName, talentDetails]) => {
|
||||
const item = $('<div>').addClass('skill-item');
|
||||
|
||||
// 天賦名稱
|
||||
let displayName = talentName;
|
||||
if (talentDetails && typeof talentDetails === 'object' && talentDetails.name) {
|
||||
displayName = talentDetails.name;
|
||||
}
|
||||
$('<div>').addClass('skill-name').text(displayName).appendTo(item);
|
||||
|
||||
if (talentDetails && typeof talentDetails === 'object') {
|
||||
// 先顯示描述
|
||||
if (talentDetails['描述']) {
|
||||
$('<div>').addClass('skill-desc').text(textify(talentDetails['描述'], '')).appendTo(item);
|
||||
}
|
||||
|
||||
// 🔴 關鍵修改:分離處理「加值」和其他屬性
|
||||
Object.entries(talentDetails).forEach(([key, value]) => {
|
||||
if (key === '$meta' || key === 'name' || key === '描述') return;
|
||||
|
||||
if (key === '加值' && value && typeof value === 'object') {
|
||||
// 加值特殊處理(跟主角/同伴一樣)
|
||||
const bonusEntries = [];
|
||||
Object.entries(value).forEach(([bonusKey, bonusValue]) => {
|
||||
bonusEntries.push(`${bonusKey}+【${bonusValue}】`);
|
||||
});
|
||||
|
||||
// 添加分隔線(如果有其他內容)
|
||||
const hasOtherContent = talentDetails['描述'] || Object.keys(talentDetails).some(k =>
|
||||
k !== '$meta' && k !== 'name' && k !== '描述' && k !== '加值'
|
||||
);
|
||||
|
||||
if (hasOtherContent) {
|
||||
const separator = $('<div>').css({
|
||||
'height': '1px',
|
||||
'background': 'linear-gradient(90deg, transparent, rgba(148, 197, 255, 0.4), transparent)',
|
||||
'margin': '8px 0',
|
||||
'width': '100%'
|
||||
});
|
||||
item.append(separator);
|
||||
}
|
||||
|
||||
const bonusDiv = $('<div>').addClass('skill-desc').css({
|
||||
'background': 'rgba(94, 161, 255, 0.08)',
|
||||
'border-left': '3px solid var(--survival-accent)',
|
||||
'padding': '6px 10px',
|
||||
'border-radius': '4px',
|
||||
'margin': '4px 0'
|
||||
}).text(`加值效果:${bonusEntries.join('、')}`);
|
||||
|
||||
item.append(bonusDiv);
|
||||
} else {
|
||||
// 其他屬性正常顯示
|
||||
$('<div>').addClass('skill-desc').text(`【${key}】${textify(value, '')}`).appendTo(item);
|
||||
}
|
||||
});
|
||||
} else if (typeof talentDetails === 'string') {
|
||||
$('<div>').addClass('skill-desc').text(talentDetails).appendTo(item);
|
||||
}
|
||||
|
||||
rightCol.append(item);
|
||||
});
|
||||
}
|
||||
|
||||
// 然後顯示技能
|
||||
const skills = details['技能'] ?? {};
|
||||
const skillEntries = toEntries(skills);
|
||||
|
||||
rightCol.append($('<div>').addClass('section__title').css('margin-top', '20px').text('技能'));
|
||||
|
||||
if (skillEntries.length === 0) {
|
||||
rightCol.append($('<p>').addClass('empty-text').text('暫無技能'));
|
||||
} else {
|
||||
skillEntries.forEach(([skillName, skillDetails]) => {
|
||||
if (skillDetails && typeof skillDetails === 'object') {
|
||||
const level = Number(e(skillDetails['等級'] ?? 0));
|
||||
const exp = Number(e(skillDetails['當前經驗'] ?? 0));
|
||||
const required = Number(e(skillDetails['升級所需經驗'] ?? 1)) || 1;
|
||||
rightCol.append($('<div>').addClass('skill-item').append(
|
||||
$('<div>').addClass('skill-name').text(`${skillName} Lv.${level}`),
|
||||
t('', exp, required)
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 將左右兩欄組合
|
||||
const wrapper = $('<div>').addClass('attr-skill-wrapper');
|
||||
wrapper.append(leftCol, rightCol);
|
||||
cardBody.append(wrapper);
|
||||
|
||||
card.append(cardHeader, cardBody);
|
||||
cardHeader.on('click', () => {
|
||||
card.toggleClass('active');
|
||||
cardBody.slideToggle(200);
|
||||
});
|
||||
$body.append(card);
|
||||
}
|
||||
});
|
||||
}
|
||||
container.append($section);
|
||||
}
|
||||
當前進度 ≥ 總進度時:
|
||||
- remove `/研究/<類別>/進行中/<研究項名>`
|
||||
- insert 條目到 `/知識庫/<類別>/<研究項名>`,字段包括:描述
|
||||
|
||||
// 分类颜色
|
||||
function getCategoryColor(category) {
|
||||
const cat = (category || '').toLowerCase();
|
||||
if (cat.includes('建造') || cat.includes('建築')) return '#e67e22';
|
||||
if (cat.includes('製作') || cat.includes('大型')) return '#f39c12';
|
||||
if (cat.includes('採集') || cat.includes('開採')) return '#27ae60';
|
||||
if (cat.includes('馴養') || cat.includes('馴服')) return '#8e44ad';
|
||||
if (cat.includes('拆解') || cat.includes('拆除')) return '#c0392b';
|
||||
if (cat.includes('教學') || cat.includes('教授')) return '#2980b9';
|
||||
return '#ffa502';
|
||||
}
|
||||
#### 約束
|
||||
|
||||
// 渲染扩展字段(材料、需求等)
|
||||
function renderExtendedFields(container, data, skipKeys) {
|
||||
if (!data || typeof data !== 'object') return;
|
||||
const entries = Object.entries(data).filter(([k]) => !skipKeys.has(k) && !k.startsWith('$'));
|
||||
if (entries.length === 0) return;
|
||||
|
||||
entries.forEach(([key, value]) => {
|
||||
const fieldTitle = $('<div>').css({
|
||||
'font-size': '12px',
|
||||
'font-weight': 'bold',
|
||||
'color': 'var(--survival-accent)',
|
||||
'margin-top': '6px',
|
||||
'margin-bottom': '4px'
|
||||
}).text(key);
|
||||
container.append(fieldTitle);
|
||||
|
||||
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
const isMaterialStruct = Object.values(value).some(v => v && typeof v === 'object' && ('所需' in v || '已投入' in v));
|
||||
Object.entries(value).forEach(([subKey, subValue]) => {
|
||||
if (isMaterialStruct && subValue && typeof subValue === 'object') {
|
||||
const needed = Number(subValue['所需'] ?? 0);
|
||||
const invested = Number(subValue['已消耗'] ?? 0);
|
||||
const isCompleted = needed > 0 && invested >= needed;
|
||||
const matLine = $('<div>').css({
|
||||
'margin-left': '6px', 'line-height': '1.6', 'font-size': '11px',
|
||||
'color': isCompleted ? '#888888' : '#cccccc',
|
||||
'text-decoration': isCompleted ? 'line-through' : 'none'
|
||||
});
|
||||
$('<span>').css('color', isCompleted ? '#888888' : '#cccccc').text(`${subKey}:`).appendTo(matLine);
|
||||
$('<span>').css('color', isCompleted ? '#888888' : '#ffa502').text(`${invested}/${needed}`).appendTo(matLine);
|
||||
if (isCompleted) $('<span>').css({ 'color': '#2ecc71', 'margin-left': '6px', 'font-size': '10px' }).text('✓').appendTo(matLine);
|
||||
container.append(matLine);
|
||||
} else {
|
||||
const subLine = $('<div>').css({ 'margin-left': '6px', 'line-height': '1.6', 'font-size': '11px', 'color': '#cccccc' });
|
||||
$('<span>').css('color', '#aaaaaa').text(`${subKey}:`).appendTo(subLine);
|
||||
$('<span>').css('color', '#ffa502').text(textify(subValue, '')).appendTo(subLine);
|
||||
container.append(subLine);
|
||||
}
|
||||
});
|
||||
} else if (Array.isArray(value)) {
|
||||
const line = $('<div>').css({ 'margin-left': '6px', 'line-height': '1.6', 'font-size': '11px', 'color': '#cccccc' });
|
||||
$('<span>').css('color', '#aaaaaa').text(`${key}:`).appendTo(line);
|
||||
$('<span>').css('color', '#ffa502').text(value.map(v => textify(v, '')).join('、')).appendTo(line);
|
||||
container.append(line);
|
||||
} else {
|
||||
const line = $('<div>').css({ 'margin-left': '6px', 'line-height': '1.6', 'font-size': '11px', 'color': '#cccccc' });
|
||||
$('<span>').css('color', '#aaaaaa').text(`${key}:`).appendTo(line);
|
||||
$('<span>').css('color', '#ffa502').text(textify(value, '')).appendTo(line);
|
||||
container.append(line);
|
||||
}
|
||||
});
|
||||
}
|
||||
- 純觀察類研究不消耗材料。實驗類研究消耗少量材料作爲實驗損耗,不產出成品
|
||||
- 研究產出爲知識,不直接生成實物
|
||||
|
||||
// 核心:渲染项目、研究、知识库
|
||||
function renderProgressAndKnowledge(container, statData) {
|
||||
const projects = statData['項目'];
|
||||
const research = statData['研究'];
|
||||
const knowledge = statData['知識庫'];
|
||||
|
||||
let projectCount = 0, researchCount = 0, knowledgeCount = 0;
|
||||
if (projects && typeof projects === 'object') projectCount = Object.keys(projects).filter(k => !k.startsWith('$')).length;
|
||||
if (research && typeof research === 'object') {
|
||||
for (const cat in research) {
|
||||
if (cat.startsWith('$')) continue;
|
||||
const ongoing = research[cat]?.['進行中'];
|
||||
if (ongoing && typeof ongoing === 'object') researchCount += Object.keys(ongoing).filter(k => !k.startsWith('$')).length;
|
||||
}
|
||||
}
|
||||
if (knowledge && typeof knowledge === 'object') {
|
||||
for (const cat in knowledge) {
|
||||
if (cat.startsWith('$')) continue;
|
||||
knowledgeCount += Object.keys(knowledge[cat] || {}).filter(k => !k.startsWith('$')).length;
|
||||
}
|
||||
}
|
||||
if (projectCount === 0 && researchCount === 0 && knowledgeCount === 0) return;
|
||||
|
||||
const { $section: parentSection, $body: parentBody } = d('進度與知識', false);
|
||||
const skipProjectKeys = new Set(['位置', '類別', '當前進度', '總進度', '描述', '項目產出']);
|
||||
const skipResearchKeys = new Set(['當前進度', '總進度', '描述']);
|
||||
|
||||
// 项目
|
||||
if (projectCount > 0) {
|
||||
const projStrip = $('<div>').addClass('task-strip side-task').css({ 'padding': '7px 16px', 'min-height': '31px', 'margin-top': '8px' });
|
||||
$('<div>').addClass('task-content').css('color', '#f39c12').text('建造項目').appendTo(projStrip);
|
||||
const projDetail = $('<div>').addClass('task-detail').hide();
|
||||
const projContainer = $('<div>').addClass('progress-knowledge-card').css({ 'display': 'flex', 'flex-direction': 'column', 'gap': '6px', 'padding-top': '6px' });
|
||||
for (const [name, proj] of Object.entries(projects || {})) {
|
||||
if (name.startsWith('$') || !proj || typeof proj !== 'object') continue;
|
||||
const card = $('<div>').css({ 'background': 'rgba(0,0,0,0.1)', 'border': '1px solid rgba(255,152,0,0.15)', 'border-radius': '6px', 'padding': '8px 10px' });
|
||||
const titleRow = $('<div>').css({ 'display': 'flex', 'align-items': 'center', 'justify-content': 'center', 'gap': '6px', 'margin-bottom': '4px', 'flex-wrap': 'wrap' });
|
||||
$('<div>').css({ 'font-size': '14px', 'font-weight': 'bold', 'color': '#ffa502', 'text-align': 'center' }).text(name).appendTo(titleRow);
|
||||
const cat = textify(proj['類別'], '');
|
||||
if (cat) {
|
||||
const catColor = getCategoryColor(cat);
|
||||
$('<div>').css({ 'display': 'inline-block', 'font-size': '8px', 'color': '#ffffff', 'background': catColor, 'padding': '0px 4px', 'border-radius': '6px', 'line-height': '1.4' }).text(cat).appendTo(titleRow);
|
||||
}
|
||||
card.append(titleRow);
|
||||
const cur = Number(proj['當前進度'] ?? 0);
|
||||
const max = Number(proj['總進度'] ?? 100);
|
||||
const progressWrapper = $('<div>').addClass('progress-wrapper');
|
||||
$('<div>').css({ 'font-size': '12px', 'color': 'var(--survival-accent)' }).text('進度:').append($('<span>').css('color', '#ffffff').text(`${cur}/${max}`)).appendTo(progressWrapper);
|
||||
const bar = $('<div>').addClass('progress');
|
||||
$('<div>').addClass('progress__bar').css('width', `${Math.max(0, Math.min(100, max === 0 ? 0 : (cur / max) * 100))}%`).appendTo(bar);
|
||||
progressWrapper.append(bar);
|
||||
card.append(progressWrapper);
|
||||
const desc = textify(proj['描述'], '');
|
||||
if (desc) $('<div>').css({ 'font-size': '11px', 'color': '#aaaaaa', 'line-height': '1.5', 'margin-top': '4px' }).text(desc).appendTo(card);
|
||||
renderExtendedFields(card, proj, skipProjectKeys);
|
||||
projContainer.append(card);
|
||||
}
|
||||
const projCollapseBtn = $('<div>').addClass('collapse-button').css({ 'padding': '4px 12px', 'line-height': '1.2' }).text('點擊收起');
|
||||
projCollapseBtn.on('click', (e) => { e.stopPropagation(); projDetail.slideUp(300); });
|
||||
projDetail.append(projContainer, projCollapseBtn);
|
||||
projStrip.on('click', () => projDetail.slideToggle());
|
||||
parentBody.append(projStrip, projDetail);
|
||||
}
|
||||
### 14. 物品更新
|
||||
|
||||
// 研究
|
||||
if (researchCount > 0) {
|
||||
const resStrip = $('<div>').addClass('task-strip main-task').css({ 'padding': '7px 16px', 'min-height': '31px', 'margin-top': '8px' });
|
||||
$('<div>').addClass('task-content').css('color', '#3498db').text('研究').appendTo(resStrip);
|
||||
const resDetail = $('<div>').addClass('task-detail').hide();
|
||||
const resContainer = $('<div>').addClass('progress-knowledge-card').css({ 'display': 'flex', 'flex-direction': 'column', 'gap': '6px', 'padding-top': '6px' });
|
||||
for (const [catName, catData] of Object.entries(research || {})) {
|
||||
if (catName.startsWith('$') || !catData || typeof catData !== 'object') continue;
|
||||
const ongoing = catData['進行中'];
|
||||
if (!ongoing || typeof ongoing !== 'object') continue;
|
||||
for (const [resName, resItem] of Object.entries(ongoing)) {
|
||||
if (resName.startsWith('$') || !resItem || typeof resItem !== 'object') continue;
|
||||
const card = $('<div>').css({ 'background': 'rgba(0,0,0,0.1)', 'border': '1px solid rgba(52,152,219,0.15)', 'border-radius': '6px', 'padding': '8px 10px' });
|
||||
const titleRow = $('<div>').css({ 'display': 'flex', 'align-items': 'center', 'justify-content': 'center', 'gap': '6px', 'margin-bottom': '4px', 'flex-wrap': 'wrap' });
|
||||
$('<div>').css({ 'font-size': '14px', 'font-weight': 'bold', 'color': '#ffa502', 'text-align': 'center' }).text(resName).appendTo(titleRow);
|
||||
const catColor = getCategoryColor(catName);
|
||||
$('<div>').css({ 'display': 'inline-block', 'font-size': '8px', 'color': '#ffffff', 'background': catColor, 'padding': '0px 4px', 'border-radius': '6px', 'line-height': '1.4' }).text(catName).appendTo(titleRow);
|
||||
card.append(titleRow);
|
||||
const cur = Number(resItem['當前進度'] ?? 0);
|
||||
const max = Number(resItem['總進度'] ?? 100);
|
||||
const progressWrapper = $('<div>').addClass('progress-wrapper');
|
||||
$('<div>').css({ 'font-size': '12px', 'color': 'var(--survival-accent)' }).text('進度:').append($('<span>').css('color', '#ffffff').text(`${cur}/${max}`)).appendTo(progressWrapper);
|
||||
const bar = $('<div>').addClass('progress');
|
||||
$('<div>').addClass('progress__bar').css('width', `${Math.max(0, Math.min(100, max === 0 ? 0 : (cur / max) * 100))}%`).appendTo(bar);
|
||||
progressWrapper.append(bar);
|
||||
card.append(progressWrapper);
|
||||
const desc = textify(resItem['描述'], '');
|
||||
if (desc) $('<div>').css({ 'font-size': '11px', 'color': '#aaaaaa', 'line-height': '1.5', 'margin-top': '4px' }).text(desc).appendTo(card);
|
||||
renderExtendedFields(card, resItem, skipResearchKeys);
|
||||
resContainer.append(card);
|
||||
}
|
||||
}
|
||||
const resCollapseBtn = $('<div>').addClass('collapse-button').css({ 'padding': '4px 12px', 'line-height': '1.2' }).text('點擊收起');
|
||||
resCollapseBtn.on('click', (e) => { e.stopPropagation(); resDetail.slideUp(300); });
|
||||
resDetail.append(resContainer, resCollapseBtn);
|
||||
resStrip.on('click', () => resDetail.slideToggle());
|
||||
parentBody.append(resStrip, resDetail);
|
||||
}
|
||||
#### 營地物品更新前置步驟(強制執行)
|
||||
|
||||
// 知识库
|
||||
if (knowledgeCount > 0) {
|
||||
const knowStrip = $('<div>').addClass('task-strip bond-task').css({ 'padding': '7px 16px', 'min-height': '31px', 'margin-top': '8px' });
|
||||
$('<div>').addClass('task-content').css('color', '#00cec9').text('知識庫').appendTo(knowStrip);
|
||||
const knowDetail = $('<div>').addClass('task-detail').hide();
|
||||
const knowContainer = $('<div>').addClass('progress-knowledge-card').css({ 'display': 'flex', 'flex-direction': 'column', 'gap': '4px', 'padding-top': '6px' });
|
||||
for (const [catName, catItems] of Object.entries(knowledge || {})) {
|
||||
if (catName.startsWith('$') || !catItems || typeof catItems !== 'object') continue;
|
||||
const itemEntries = Object.entries(catItems).filter(([k]) => !k.startsWith('$'));
|
||||
if (itemEntries.length === 0) continue;
|
||||
$('<div>').css({ 'font-size': '12px', 'font-weight': 'bold', 'color': '#00cec9', 'border-bottom': '1px solid rgba(0,206,201,0.3)', 'padding-bottom': '4px', 'margin-top': '4px' }).text(catName).appendTo(knowContainer);
|
||||
itemEntries.forEach(([itemName, itemData]) => {
|
||||
const itemRow = $('<div>').css({ 'padding': '3px 0 3px 6px', 'font-size': '12px', 'line-height': '1.4' });
|
||||
$('<span>').css({ 'color': '#ffa502', 'font-weight': 'bold' }).text(itemName).appendTo(itemRow);
|
||||
const desc = textify(itemData?.['描述'] ?? '', '');
|
||||
if (desc) $('<span>').css('color', '#cccccc').text(`:${desc}`).appendTo(itemRow);
|
||||
knowContainer.append(itemRow);
|
||||
});
|
||||
}
|
||||
const knowCollapseBtn = $('<div>').addClass('collapse-button').css({ 'padding': '4px 12px', 'line-height': '1.2' }).text('點擊收起');
|
||||
knowCollapseBtn.on('click', (e) => { e.stopPropagation(); knowDetail.slideUp(300); });
|
||||
knowDetail.append(knowContainer, knowCollapseBtn);
|
||||
knowStrip.on('click', () => knowDetail.slideToggle());
|
||||
parentBody.append(knowStrip, knowDetail);
|
||||
}
|
||||
container.append(parentSection);
|
||||
}
|
||||
- 在執行任何 `/營地/` 路徑下的物品操作前,必須先從數據源中讀取當前營地名稱:
|
||||
- 獲取路徑:`/營地` 對象下的所有鍵名即為當前的營地名稱。例如 `/營地/楓葉潭營地`
|
||||
- 確定目標:根據正文上下文確定要操作哪個營地(通常為當前所在的營地)
|
||||
- 驗證存在:確認該營地名稱在 `/營地` 中存在,若不存在則不能執行操作
|
||||
- 構建完整路徑:`/營地/{營地名稱}/{分類}/{物品名稱}`
|
||||
|
||||
// ============== 修改版 renderGenericSection 函數(包含畜牧邏輯) ==============
|
||||
function renderGenericSection(container, title, source, statData) {
|
||||
if (source == null) return;
|
||||
const entries = toEntries(source);
|
||||
if (!entries || entries.length === 0) return;
|
||||
|
||||
// --- 特殊處理:基本資料(顯示為卡片式) ---
|
||||
if (title === '基本資料') {
|
||||
const { $section, $body } = d('基本資訊', false);
|
||||
|
||||
const infoGrid = $('<div>').css({
|
||||
'display': 'grid',
|
||||
'grid-template-columns': 'repeat(auto-fit, minmax(200px, 1fr))',
|
||||
'gap': '12px',
|
||||
'margin-top': '10px'
|
||||
});
|
||||
|
||||
entries.forEach(([key, value]) => {
|
||||
const infoCard = $('<div>').css({
|
||||
'background': 'rgba(30, 45, 70, 0.6)',
|
||||
'border': '1px solid var(--survival-border)',
|
||||
'border-radius': '8px',
|
||||
'padding': '12px',
|
||||
'text-align': 'center'
|
||||
});
|
||||
|
||||
$('<div>').css({
|
||||
'font-size': '14px',
|
||||
'color': '#ffb86c',
|
||||
'margin-bottom': '4px',
|
||||
'font-weight': 'bold'
|
||||
}).text(key).appendTo(infoCard);
|
||||
|
||||
$('<div>').css({
|
||||
'font-size': '16px',
|
||||
'color': 'var(--survival-text)'
|
||||
}).text(textify(value, '--')).appendTo(infoCard);
|
||||
|
||||
infoGrid.append(infoCard);
|
||||
});
|
||||
|
||||
$body.append(infoGrid);
|
||||
container.append($section);
|
||||
return;
|
||||
}
|
||||
|
||||
// --- 特殊處理:如果標題是「捷徑或大型裝置」,使用獨立網格顯示 ---
|
||||
if (title === '捷徑或大型裝置') {
|
||||
const { $section, $body } = d(title, false);
|
||||
|
||||
const deviceGrid = $('<div>').addClass('inventory-grid');
|
||||
|
||||
entries.forEach(([name, data]) => {
|
||||
if (data && typeof data === 'object') {
|
||||
const cell = $('<div>').addClass('inventory-item');
|
||||
$('<div>').addClass('item-name').text(name).appendTo(cell);
|
||||
|
||||
const props = $('<div>').addClass('item-props');
|
||||
|
||||
const objectProperties = {};
|
||||
const simpleProperties = {};
|
||||
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
if (key === '$meta') return;
|
||||
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
if (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length > 0) {
|
||||
objectProperties[key] = value;
|
||||
} else {
|
||||
simpleProperties[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Object.entries(simpleProperties).forEach(([key, value]) => {
|
||||
const line = $('<div>').addClass('item-prop');
|
||||
$('<span>').addClass('prop-key').text(String(key)).appendTo(line);
|
||||
$('<span>').addClass('prop-sep').text(': ').appendTo(line);
|
||||
$('<span>').addClass('prop-val').text(textify(value, '')).appendTo(line);
|
||||
props.append(line);
|
||||
});
|
||||
|
||||
Object.entries(objectProperties).forEach(([propertyName, propertyData]) => {
|
||||
const objectLine = $('<div>').addClass('item-prop');
|
||||
$('<span>').addClass('prop-key').text(propertyName).appendTo(objectLine);
|
||||
$('<span>').addClass('prop-sep').text(': ').appendTo(objectLine);
|
||||
|
||||
const objectContent = $('<div>').addClass('object-content').css({
|
||||
'display': 'flex',
|
||||
'flex-direction': 'column',
|
||||
'gap': '4px',
|
||||
'margin-top': '4px'
|
||||
});
|
||||
|
||||
Object.entries(propertyData).forEach(([itemName, itemData]) => {
|
||||
if (itemName !== '$meta' && itemData !== null && itemData !== '') {
|
||||
const itemLine = $('<div>').addClass('object-item').css({
|
||||
'text-align': 'left',
|
||||
'color': 'var(--survival-text)'
|
||||
});
|
||||
|
||||
let quantity = '';
|
||||
if (itemData && typeof itemData === 'object') {
|
||||
if (itemData['數量'] !== undefined) {
|
||||
quantity = `(數量:${itemData['數量']})`;
|
||||
}
|
||||
} else if (typeof itemData === 'number') {
|
||||
quantity = `(數量:${itemData})`;
|
||||
}
|
||||
|
||||
itemLine.text(`${itemName}${quantity}`);
|
||||
objectContent.append(itemLine);
|
||||
}
|
||||
});
|
||||
|
||||
$('<span>').addClass('prop-val').append(objectContent).appendTo(objectLine);
|
||||
props.append(objectLine);
|
||||
});
|
||||
|
||||
cell.append(props);
|
||||
deviceGrid.append(cell);
|
||||
}
|
||||
});
|
||||
|
||||
$body.append(deviceGrid);
|
||||
container.append($section);
|
||||
return;
|
||||
}
|
||||
|
||||
// --- 以下是作物、畜牧、營地相關邏輯 ---
|
||||
let overallCropStatusTag = '';
|
||||
let overallLivestockStatusTag = '';
|
||||
let overallCampTag = '';
|
||||
let accelerationEffects = [];
|
||||
let totalAcceleration = 0;
|
||||
|
||||
// 作物狀態標籤邏輯
|
||||
if (title === '作物') {
|
||||
const hasReadyCrop = entries.some(([, value]) => textify(value?.['生長階段'], '').includes('可收割'));
|
||||
overallCropStatusTag = `<span class="crop-status-indicator ${hasReadyCrop ? 'crop-status-ready' : 'crop-status-growing'}">${hasReadyCrop ? '可收割' : '未熟成'}</span>`;
|
||||
|
||||
const currentSeason = textify(statData?.['環境']?.['季節'], '');
|
||||
const seasonRules = {
|
||||
'春季': { effect: 15, icon: '🌸' },
|
||||
'夏季': { effect: 20, icon: '☀️' },
|
||||
'秋季': { effect: 0, icon: '🍁' },
|
||||
'冬季': { effect: -50, icon: '❄️' }
|
||||
};
|
||||
if (seasonRules[currentSeason]) {
|
||||
const seasonalEffect = seasonRules[currentSeason];
|
||||
accelerationEffects.push({
|
||||
name: `${currentSeason}季節`,
|
||||
effect: seasonalEffect.effect,
|
||||
icon: seasonalEffect.icon,
|
||||
source: '季節',
|
||||
order: 1
|
||||
});
|
||||
totalAcceleration += seasonalEffect.effect;
|
||||
}
|
||||
|
||||
const equipment = statData?.['裝備'] ?? {};
|
||||
Object.entries(equipment).forEach(([itemName, itemData]) => {
|
||||
if (itemData && typeof itemData === 'object') {
|
||||
const cropBonus = Number(e(itemData['作物加成'] ?? 0));
|
||||
if (cropBonus !== 0) {
|
||||
accelerationEffects.push({
|
||||
name: `${itemName}`,
|
||||
effect: cropBonus,
|
||||
icon: '🛡️',
|
||||
source: '裝備',
|
||||
order: 2
|
||||
});
|
||||
totalAcceleration += cropBonus;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const carriedItems = statData?.['攜帶物品'] ?? {};
|
||||
Object.entries(carriedItems).forEach(([itemName, itemData]) => {
|
||||
if (itemData && typeof itemData === 'object') {
|
||||
const cropBonus = Number(e(itemData['作物加成'] ?? 0));
|
||||
if (cropBonus !== 0) {
|
||||
accelerationEffects.push({
|
||||
name: `${itemName}`,
|
||||
effect: cropBonus,
|
||||
icon: '🧺',
|
||||
source: '攜帶物品',
|
||||
order: 3
|
||||
});
|
||||
totalAcceleration += cropBonus;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
entries.forEach(([name, data]) => {
|
||||
const growthStage = textify(data['生長階段'], '');
|
||||
const cropBonus = Number(e(data['作物加成'] ?? 0));
|
||||
|
||||
if (growthStage.includes('特殊作物') || cropBonus !== 0) {
|
||||
const existing = accelerationEffects.find(eff => eff.name === name);
|
||||
if (!existing) {
|
||||
accelerationEffects.push({
|
||||
name: `${name}`,
|
||||
effect: cropBonus,
|
||||
icon: '🌿',
|
||||
source: '特殊作物',
|
||||
order: 4
|
||||
});
|
||||
totalAcceleration += cropBonus;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const companions = statData?.['同伴'] ?? {};
|
||||
Object.values(companions).forEach(companion => {
|
||||
if (companion && typeof companion === 'object') {
|
||||
const companionItems = companion['攜帶物品'] ?? {};
|
||||
Object.entries(companionItems).forEach(([itemName, itemData]) => {
|
||||
if (itemData && typeof itemData === 'object') {
|
||||
const cropBonus = Number(e(itemData['作物加成'] ?? 0));
|
||||
if (cropBonus !== 0) {
|
||||
accelerationEffects.push({
|
||||
name: `${itemName}`,
|
||||
effect: cropBonus,
|
||||
icon: '👥',
|
||||
source: '同伴物品',
|
||||
order: 5
|
||||
});
|
||||
totalAcceleration += cropBonus;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
#### 操作類型
|
||||
|
||||
// 畜牧狀態標籤邏輯
|
||||
if (title === '畜牧') {
|
||||
const currentDay = Number(e(statData?.['系統']?.['經過天數'] ?? 0));
|
||||
let hasHarvestable = false;
|
||||
|
||||
entries.forEach(([, data]) => {
|
||||
if (data && typeof data === 'object') {
|
||||
const output = data['產出物'];
|
||||
if (output && output['名稱']) {
|
||||
const nextOutputDay = Number(e(output['下次產出日'] ?? 0));
|
||||
if (nextOutputDay <= currentDay) {
|
||||
hasHarvestable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (hasHarvestable) {
|
||||
overallLivestockStatusTag = `<span class="livestock-status-indicator livestock-status-harvest">可收成</span>`;
|
||||
} else {
|
||||
overallLivestockStatusTag = `<span class="livestock-status-indicator livestock-status-none">飼養中</span>`;
|
||||
}
|
||||
}
|
||||
| 操作類型 | 說明 |
|
||||
|----------|------|
|
||||
| 數量減少 | replace 更新數量 |
|
||||
| 數量增加(同名疊加) | replace 更新數量。若涉及食材類,同時考慮是否需要更新 `/系統/當前存糧天數` |
|
||||
| 新增物品 | insert 在目標對象中添加新鍵值對 |
|
||||
| 移除物品 | remove 刪除指定路徑 |
|
||||
| 轉移物品 | remove 源路徑 + insert 目標路徑(兩端同步) |
|
||||
| 性質變化(名稱變) | remove 舊名 + insert 新名 |
|
||||
| 性質變化(內部屬性變) | replace 更新對應字段 |
|
||||
|
||||
// 🔴 營地存糧標籤邏輯(獨立放在這裡,與作物/畜牧同級)
|
||||
if (title === '營地') {
|
||||
const foodSupply = statData?.['系統']?.['當前存糧天數'];
|
||||
if (foodSupply !== undefined && foodSupply !== null) {
|
||||
const days = Number(foodSupply);
|
||||
let displayText = '';
|
||||
let colorClass = '';
|
||||
|
||||
if (days <= 3) {
|
||||
displayText = `存糧 ${days.toFixed(1)} 天`; // 顯示小數點
|
||||
colorClass = 'crop-status-danger'; // 🔴 紅色(警告)
|
||||
} else {
|
||||
displayText = `存糧 ${days.toFixed(0)} 天`; // 顯示整數
|
||||
colorClass = 'crop-status-ready'; // 🟢 綠色(正常)
|
||||
}
|
||||
|
||||
overallCampTag = `<span class="crop-status-indicator ${colorClass}">${displayText}</span>`;
|
||||
}
|
||||
}
|
||||
#### 營地路徑強制規則
|
||||
|
||||
// --- 創建 section ---
|
||||
const { $section, $header, $body } = d(title, false);
|
||||
- 格式:`/營地/{營地名稱}/{分類}/{物品名稱}`
|
||||
- 範例:`/營地/楓葉潭營地/食材/楓糖漿`
|
||||
- 禁止:缺少營地名稱的寫法(如 `/營地/食材/楓糖漿`)
|
||||
|
||||
// 🔴 添加所有標籤到標題(順序:營地存糧 → 作物 → 畜牧)
|
||||
if (title === '營地' && overallCampTag) {
|
||||
$header.append(overallCampTag);
|
||||
}
|
||||
if (overallCropStatusTag) $header.append(overallCropStatusTag);
|
||||
if (overallLivestockStatusTag) $header.append(overallLivestockStatusTag);
|
||||
|
||||
// 作物加速效果顯示
|
||||
if (title === '作物' && accelerationEffects.length > 0) {
|
||||
const infoBox = $('<div>').addClass('crop-acceleration-info');
|
||||
infoBox.append($('<div>').addClass('crop-acceleration-info__title').html('🏕️ 作物加速效果'));
|
||||
|
||||
accelerationEffects.sort((a, b) => {
|
||||
if (a.order !== b.order) {
|
||||
return a.order - b.order;
|
||||
}
|
||||
return b.effect - a.effect;
|
||||
});
|
||||
|
||||
accelerationEffects.forEach(eff => {
|
||||
const effectLine = $('<div>').addClass('crop-acceleration-info__item');
|
||||
const effectText = eff.effect > 0 ? `加速 ${eff.effect} %` : `減速 ${Math.abs(eff.effect)} %`;
|
||||
effectLine.html(`${eff.icon} ${eff.name}:${effectText}`);
|
||||
infoBox.append(effectLine);
|
||||
});
|
||||
|
||||
const totalLine = $('<div>').addClass('crop-acceleration-info__total');
|
||||
const totalActionText = totalAcceleration > 0 ? '總加速' : '總減速';
|
||||
totalLine.html(`⚡ ${totalActionText}:${Math.abs(totalAcceleration)} %`);
|
||||
infoBox.append(totalLine);
|
||||
|
||||
$body.append(infoBox);
|
||||
}
|
||||
#### 關鍵錯誤規避(逐項檢查)
|
||||
|
||||
const firstValue = entries[0]?.[1];
|
||||
let isItemCollection = false;
|
||||
if (firstValue && typeof firstValue === 'object') {
|
||||
const keys = Object.keys(firstValue);
|
||||
const itemKeywords = ['數量', '重量', '耐久度', '狀態', '功能', '成長週期', '飼養數量', '年齡', '生長階段'];
|
||||
if (keys.some(k => itemKeywords.includes(k))) isItemCollection = true;
|
||||
}
|
||||
1. 憑空創造: 所有新增物品必須有明確來源
|
||||
2. 觀察誤判獲得: 「看到XX」≠「獲得XX」
|
||||
3. 準備誤判執行: 「打算做XX」≠「做了XX」
|
||||
4. 製作中誤判完成: 未完成的製作不消耗材料也不產出成品
|
||||
5. 重複增減: 同一物品在一次更新中只能被操作一次
|
||||
6. 轉移同步: 源路徑和目標路徑都要明確
|
||||
7. 位置合理: 誰獲取優先給誰,身處營地時優先添加到營地對應分類
|
||||
8. 非本次更新: 只更新本回合正文明確操作過的物品
|
||||
|
||||
if (isItemCollection) {
|
||||
const grid = $('<div>').addClass('inventory-grid');
|
||||
entries.forEach(([name, data]) => {
|
||||
if (title === '作物') {
|
||||
const cycle = Number(e(data['成長週期'] ?? 0));
|
||||
if (cycle < 0) return;
|
||||
}
|
||||
|
||||
if (data && typeof data === 'object') {
|
||||
const cell = $('<div>').addClass('inventory-item');
|
||||
const itemNameDiv = $('<div>').addClass('item-name').text(data['名稱'] || String(name));
|
||||
|
||||
if (title === '作物') {
|
||||
const growthStage = textify(data['生長階段'], '');
|
||||
if (growthStage) {
|
||||
const isReady = growthStage.includes('可收割');
|
||||
const statusTag = $('<span>')
|
||||
.addClass('crop-status-indicator')
|
||||
.addClass(isReady ? 'crop-status-ready' : 'crop-status-growing')
|
||||
.text(growthStage);
|
||||
itemNameDiv.append(statusTag);
|
||||
}
|
||||
}
|
||||
|
||||
if (title === '畜牧') {
|
||||
const currentDay = Number(e(statData?.['系統']?.['經過天數'] ?? 0));
|
||||
let isHarvestable = false;
|
||||
const output = data['產出物'];
|
||||
if (output && output['名稱']) {
|
||||
const nextOutputDay = Number(e(output['下次產出日'] ?? 0));
|
||||
if (nextOutputDay <= currentDay) {
|
||||
isHarvestable = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isHarvestable) {
|
||||
const statusTag = $('<span>')
|
||||
.addClass('livestock-status-indicator')
|
||||
.addClass('livestock-status-harvest')
|
||||
.text('可收成');
|
||||
itemNameDiv.append(statusTag);
|
||||
}
|
||||
}
|
||||
|
||||
cell.append(itemNameDiv);
|
||||
|
||||
const props = $('<div>').addClass('item-props');
|
||||
const attributes = new Map();
|
||||
|
||||
let definedOrder = [];
|
||||
if (title === '作物') {
|
||||
definedOrder = ['數量', '位置', '重量', '耐久度', '成長週期', '生長進度'];
|
||||
} else if (title === '畜牧') {
|
||||
definedOrder = ['年齡', '飼養數量', '生長階段', '產出物'];
|
||||
} else {
|
||||
definedOrder = ['數量', '位置', '重量', '耐久度'];
|
||||
}
|
||||
|
||||
definedOrder.forEach(k => {
|
||||
if (data.hasOwnProperty(k)) attributes.set(k, data[k]);
|
||||
});
|
||||
|
||||
const stateType = textify(data['狀態類型'] ?? '');
|
||||
if (stateType && data.hasOwnProperty('狀態')) {
|
||||
if (stateType.toLowerCase() !== '耐久度' || !attributes.has('耐久度')) {
|
||||
attributes.set(stateType, data['狀態']);
|
||||
}
|
||||
}
|
||||
|
||||
if (title === '畜牧') {
|
||||
if (data.hasOwnProperty('產出物')) {
|
||||
attributes.set('產出物', data['產出物']);
|
||||
}
|
||||
} else {
|
||||
const middleOrder = ['描述', '功能', '功能1', '功能2', '功能3'];
|
||||
middleOrder.forEach(k => {
|
||||
if (data.hasOwnProperty(k)) attributes.set(k, data[k]);
|
||||
});
|
||||
}
|
||||
|
||||
Object.entries(data).forEach(([k, v]) => {
|
||||
if (!attributes.has(k) && k !== '$meta' && k !== '狀態類型' && k !== '狀態' && k !== '生長階段') {
|
||||
attributes.set(k, v);
|
||||
}
|
||||
});
|
||||
|
||||
attributes.forEach((v, k) => {
|
||||
if (v !== null && v !== '' && v !== undefined) {
|
||||
if (k === 'harvestAction') return;
|
||||
if (k === '繁殖') return;
|
||||
|
||||
const line = $('<div>').addClass('item-prop');
|
||||
|
||||
if (k === '加值') {
|
||||
line.addClass('bonus-prop');
|
||||
const separator = $('<div>').addClass('bonus-separator').css({
|
||||
'height': '1px',
|
||||
'background': 'linear-gradient(90deg, transparent, rgba(148, 197, 255, 0.4), transparent)',
|
||||
'margin': '10px 0 8px 0',
|
||||
'width': '100%'
|
||||
});
|
||||
props.append(separator);
|
||||
}
|
||||
|
||||
$('<span>').addClass('prop-key').text(String(k)).appendTo(line);
|
||||
$('<span>').addClass('prop-sep').text(': ').appendTo(line);
|
||||
|
||||
let displayValue = '';
|
||||
if (typeof v === 'object') {
|
||||
if (k === '產出物') {
|
||||
const objLines = [];
|
||||
Object.entries(v).forEach(([subKey, subVal]) => {
|
||||
if (subVal !== null && subVal !== '') {
|
||||
objLines.push(`${subKey}: ${textify(subVal, '')}`);
|
||||
}
|
||||
});
|
||||
displayValue = objLines.join(' | ');
|
||||
} else if (k === '飼養數量') {
|
||||
const male = v['雄性'] || 0;
|
||||
const female = v['雌性'] || 0;
|
||||
const parts = [];
|
||||
if (male > 0) parts.push(`雄性 x ${male}`);
|
||||
if (female > 0) parts.push(`雌性 x ${female}`);
|
||||
displayValue = parts.join('、');
|
||||
} else if (k === '加值') {
|
||||
const bonusEntries = [];
|
||||
Object.entries(v).forEach(([bonusKey, bonusValue]) => {
|
||||
bonusEntries.push(`${bonusKey}+【${bonusValue}】`);
|
||||
});
|
||||
displayValue = bonusEntries.join('、');
|
||||
} else {
|
||||
displayValue = '[...]';
|
||||
}
|
||||
} else {
|
||||
displayValue = textify(v, '');
|
||||
}
|
||||
|
||||
if (k === '重量' && typeof v === 'number') displayValue += 'kg';
|
||||
if (k === '成長週期' && typeof v === 'number' && v > 0) displayValue += '天';
|
||||
if (k === '生長進度' && typeof v === 'number') displayValue += '%';
|
||||
if (k === '年齡' && typeof v === 'number') displayValue += '月';
|
||||
|
||||
$('<span>').addClass('prop-val').text(displayValue).appendTo(line);
|
||||
props.append(line);
|
||||
|
||||
if (k === '加值') {
|
||||
const bottomSeparator = $('<div>').css({
|
||||
'height': '1px',
|
||||
'background': 'rgba(148, 197, 255, 0.1)',
|
||||
'margin': '8px 0 4px 0',
|
||||
'width': '100%'
|
||||
});
|
||||
props.append(bottomSeparator);
|
||||
}
|
||||
}
|
||||
});
|
||||
cell.append(props);
|
||||
grid.append(cell);
|
||||
}
|
||||
});
|
||||
$body.append(grid);
|
||||
} else {
|
||||
const campInfoContainer = $('<div>').addClass('camp-info-container');
|
||||
|
||||
if (title === '楓葉潭營地') {
|
||||
const basicInfoEntry = entries.find(([key]) => key === '基本資料');
|
||||
|
||||
if (basicInfoEntry) {
|
||||
const [keyName, value] = basicInfoEntry;
|
||||
renderGenericSection(campInfoContainer, keyName, value, statData);
|
||||
}
|
||||
|
||||
entries.forEach(([key, value]) => {
|
||||
if (key === '基本資料') return;
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
renderGenericSection(campInfoContainer, key, value, statData);
|
||||
} else {
|
||||
const infoLine = $('<div>').addClass('camp-info-item');
|
||||
infoLine.append(
|
||||
$('<span>').addClass('prop-key').text(String(key)),
|
||||
$('<span>').addClass('prop-sep').text(': '),
|
||||
$('<span>').addClass('prop-val').text(textify(value, '--'))
|
||||
);
|
||||
campInfoContainer.append(infoLine);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
entries.forEach(([key, value]) => {
|
||||
if (value && typeof value === 'object') {
|
||||
renderGenericSection(campInfoContainer, key, value, statData);
|
||||
} else {
|
||||
const infoLine = $('<div>').addClass('camp-info-item');
|
||||
infoLine.append(
|
||||
$('<span>').addClass('prop-key').text(String(key)),
|
||||
$('<span>').addClass('prop-sep').text(': '),
|
||||
$('<span>').addClass('prop-val').text(textify(value, '--'))
|
||||
);
|
||||
campInfoContainer.append(infoLine);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$body.append(campInfoContainer);
|
||||
}
|
||||
container.append($section);
|
||||
}
|
||||
### 15. 子嗣
|
||||
|
||||
async function p() {
|
||||
try {
|
||||
let messageId;
|
||||
try {
|
||||
messageId = typeof getCurrentMessageId === 'function' ? getCurrentMessageId() : getChatMessages('last')[0]?.message_id;
|
||||
} catch(e) {
|
||||
messageId = getChatMessages('last')[0]?.message_id;
|
||||
}
|
||||
|
||||
// 關鍵修改:無法獲取消息ID時靜默返回,不報錯
|
||||
if (!messageId) {
|
||||
console.log('等待消息加載...');
|
||||
return;
|
||||
}
|
||||
#### 年齡階段與成長
|
||||
|
||||
const message = getChatMessages(messageId)[0];
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
| 階段 | 天數範圍 | 特徵 |
|
||||
|------|----------|------|
|
||||
| 新生兒 | 0-10天 | 完全依賴照顧者,每4小時需餵食一次 |
|
||||
| 嬰兒 | 11-30天 | 需要頻繁照料,開始爬行和認人 |
|
||||
| 幼兒 | 31-90天 | 可走路和簡單交流,可吃切碎的普通食物 |
|
||||
| 兒童 | 91-210天 | 可幫忙做簡單家務,可進行基礎技能訓練 |
|
||||
| 少年 | 211-360天 | 基本自立,可參與輕度勞動和戰鬥 |
|
||||
| 成年 | 361-365天 | 觸發成年禮 |
|
||||
|
||||
const data = message?.data ?? {}, statData = data.stat_data ?? {}, displayData = data.display_data ?? {};
|
||||
if (Object.keys(statData).length === 0 && Object.keys(displayData).length === 0) return;
|
||||
|
||||
const $root = $('#survival-ui').empty();
|
||||
const get = (key) => statData?.[key] ?? displayData?.[key];
|
||||
|
||||
r($root, statData);
|
||||
createStatusEffects($root, statData);
|
||||
|
||||
const { $section: statusSection, $body: statusBody } = d('角色狀態', false);
|
||||
renderUnifiedStatus(statusBody, statData);
|
||||
$root.append(statusSection);
|
||||
|
||||
const { $section: skillSection, $body: skillBody } = d('屬性 & 技能', false);
|
||||
renderAttributesAndSkills(skillBody, get('屬性'), get('技能'), get('天賦特性'));
|
||||
$root.append(skillSection);
|
||||
|
||||
renderCompanions($root, get('同伴'));
|
||||
renderPets($root, get('寵物'));
|
||||
|
||||
// 這裡新增畜牧的渲染 - 在作物和捷徑之間
|
||||
['營地', '作物', '畜牧', '捷徑或大型裝置'].forEach(title => renderGenericSection($root, title, get(title), statData));
|
||||
|
||||
renderTaskSystem($root, statData);
|
||||
renderProgressAndKnowledge($root, statData);
|
||||
|
||||
const survivalPoints = Number(e(statData['系統']?.['求生點數'])) || 0; // 改成求生點數
|
||||
const buttonBar = $('<div>').addClass('button-container');
|
||||
buttonBar.append(
|
||||
$('<button>').addClass('action-btn draw-btn survival').text(`求生抽卡 (${survivalPoints})`).on('click', () => showDrawConfirmModal('survival', survivalPoints)), // 只傳一個參數
|
||||
$('<button>').addClass('action-btn').text('地圖').on('click', () => openImageViewerModal(statData, displayData)),
|
||||
$('<button>').addClass('action-btn').text('物品欄').on('click', () => i(get('裝備'), get('攜帶物品'), get('營地'), Number(e(statData['狀態']?.['當前負重'] ?? 0)), Number(e(statData['狀態']?.['負重上限'] ?? 50)), false))
|
||||
);
|
||||
$root.append(buttonBar);
|
||||
|
||||
} catch (error) {
|
||||
console.error('渲染UI時出錯:', error);
|
||||
}
|
||||
}
|
||||
#### 成年禮
|
||||
|
||||
- 觸發: 年齡階段達成年且成長天數達365天
|
||||
- 動作:
|
||||
- 從子嗣結構移除
|
||||
- 作為標準同伴插入同伴結構
|
||||
- 屬性、技能、天賦特性全部保留
|
||||
- 依戀度轉換為好感等級
|
||||
|
||||
#### 依戀度
|
||||
|
||||
- 增加: 餵食+1/次(上限+3),安撫+1/次
|
||||
- 減少: 超8小時忽視-2/次,照顧者受傷或死亡時-10
|
||||
|
||||
#### 狀態描述
|
||||
|
||||
- 觸發: 核心狀態、生理或年齡階段變化時
|
||||
- 格式: 結合數值與情境生成20字內簡要描述
|
||||
|
||||
### 16. 作物與畜牧
|
||||
|
||||
🔴 觸發條件:ActionResult 中「作物(畜牧)收成」字段非「無」時強制執行
|
||||
|
||||
#### 作物
|
||||
|
||||
種植:
|
||||
- 操作: insert(`/作物/作物名`, {播種數量, 品質, 種植日期, 成長週期, 收成日期, 生長階段, harvestAction, 作物加成(選填)})
|
||||
- 注意: 必須先檢查種子物品存在,立即扣除種子數量
|
||||
|
||||
🔴 加速作物規則:
|
||||
- 當 `成長週期` 為負數時,該作物視為「加速作物」
|
||||
- 加速作物自身會不會生長,無法收成
|
||||
- 加速作物必須添加 `作物加成` 欄位(正數),對其他作物產生加速效果
|
||||
- 作物加成數值代表加速百分比(如 20 表示加速 20%)
|
||||
|
||||
收割:
|
||||
- 條件: 收成日期 ≤ 經過天數
|
||||
- 操作:
|
||||
1. insert 收穫產物到營地或攜帶物品
|
||||
2. remove `/作物/{作物名}` (無論 harvestAction 是 REMOVE 還是 RESET)
|
||||
|
||||
🔴 注意:永續作物(harvestAction = RESET)被 remove 後,腳本會自動恢復並重置生長進度,無需手動處理。
|
||||
|
||||
#### 畜牧
|
||||
|
||||
新增:
|
||||
- 操作: insert(`/畜牧/畜牧名`, {年齡, 飼養數量, 生長階段, 產出物, 飼養開始日})
|
||||
|
||||
收穫:
|
||||
- 條件: 下次產出日 ≤ 經過天數
|
||||
- 操作: insert 收穫產物到營地或攜帶物品
|
||||
- 收成後: replace 下次產出日
|
||||
|
||||
繁殖:
|
||||
- 條件: 同物種數量≥2且飼養良好且季節合適
|
||||
- 判定: 1d100=1-5 時數量+1
|
||||
|
||||
### 17. 捷徑或大型裝置
|
||||
|
||||
新增:
|
||||
- 操作: insert(`/捷徑或大型裝置/裝置名`, {位置, 狀態類型, 狀態, 功能, 描述})
|
||||
|
||||
### 18. 最終變量更新複覈(生成JSONPatch前必須執行)
|
||||
|
||||
1. 認知校正檢查:區分"數據源當前狀態"與"推導出的變化",確保不會將後者誤認爲前者。
|
||||
2. 交叉驗證覈對:覈對結論中的每個更新項,是否都已對應到正確的變量路徑。
|
||||
3. 充分性檢查:是否所有必要的驗證都已完成。
|
||||
|
||||
完成以上覆核後,若發現任何問題,必須修正【變量影響結論】。只有在所有複覈通過後,才能生成JSONPatch指令。
|
||||
|
||||
#### 🔴 必須逐項檢查
|
||||
|
||||
1. 是否有矛盾更新(同一變量被不同指令設爲不同值)
|
||||
2. 所有計劃更新均基於本回合互動引發的變化
|
||||
3. 是否錯誤更新了同伴/寵物本身沒有的變量(參照核心憲法「同伴僅具備」欄位)
|
||||
4. 時間推進是否與ActionResult一致、環境更新是否與ActionResult完全一致(直接使用ActionResult中的值)
|
||||
5. 物品消耗/新增/轉移是否均有明確路徑和來源,是否通過8項關鍵錯誤規避檢查
|
||||
6. ActionResult中的所有非"無"字段是否已全部處理
|
||||
7. 開局首次回覆:僅更新增量變化,禁止重新insert整個頂層對象
|
||||
8. 任務獎勵均在生成時確定,結算時直接發放
|
||||
9. 營地路徑:所有 `/營地/` 路徑是否都包含具體營地名稱(格式 `/營地/{營地名稱}/...`)
|
||||
10. 時間更新:JSON Patch 中是否包含 `/系統/當前時間` 的 replace 指令
|
||||
11. 作物/畜牧:若 ActionResult 中非「無」,JSON Patch 中是否包含對應指令
|
||||
12. 天氣:變化時是否符合過渡鏈、持續時間是否正確更新
|
||||
13. 前次天數鎖定檢查:✅(未包含 `/系統/前次天數` 指令)
|
||||
|
||||
#### 🔴 變量路徑自動審計與一票否決
|
||||
在生成最終的 `<JSONPatch>` 指令數組時,AI 必須對每一條指令的 `path` 進行最終審計:
|
||||
1. 提取指令中的變量名(例如:`睡眠`)。
|
||||
2. 比對該變量在 `path` 中的歸屬分類是否與核心憲法中的規定 100% 一致。
|
||||
3. **【一票否決】**:若發現任何一處不一致,當前生成的整個 JSON Patch 宣告作廢。AI 必須立即重置該變量的路徑,並重新生成對應的 Patch 指令。
|
||||
|
||||
## 輸出格式與思考模板
|
||||
|
||||
<UpdateVariable>
|
||||
<Analysis>
|
||||
|
||||
【變量影響結論】
|
||||
|
||||
所有推理都基於 <status_current_variable> 塊中的當前變量值
|
||||
|
||||
### ActionResult信號處理結論
|
||||
|
||||
- 時間推進:[開始時間]→[結束時間],共X小時X分鐘
|
||||
- 地點變化:[新位置/無變化]
|
||||
- 存糧變化: [當前存糧天數]
|
||||
- 環境變化:天氣=[新天氣] / 亮度=[新亮度] / 體感溫度=[新體感溫度] / 體溫=[新體溫] / 舒適度=[新舒適度] / 天氣持續時間=[新數值]
|
||||
- 關係變化:[[同伴名] + 新關係 / 無]
|
||||
- 作物(畜牧)收成:[[作物/畜牧]:[名稱]×[數量]→[新日期或REMOVE] / 無]
|
||||
- 研究進度: [[研究項目名] +20% 或 +40% / 無]
|
||||
- 新增任務:[任務類別 + 任務名稱 / 無]
|
||||
- 劇情演出:[有/無,具體操作]
|
||||
- 商店更新:
|
||||
- 商店刷新:[是/否]
|
||||
- 商品出售:[商品名+費用 / 無]
|
||||
- 項目:[新建:[項目名] / 推進:[項目名] 當前進度X%→Y% /消耗:[材料名]×[數量] / 最終檢查:[項目名] 結果:成功/失敗 / 完工:[項目名] / 無]
|
||||
- 性愛結果:
|
||||
- 角色: [同伴名]
|
||||
- 完成: [是/否]
|
||||
- 內射: [是/否]
|
||||
- 授精者: [姓名]
|
||||
- 避孕措施: [無/保險套/體外射精/避孕藥/安全期]
|
||||
- 情境: [情感推進/調教誘騙/武力強迫/被動捲入]
|
||||
- 自願度: [0-100]
|
||||
- 自願分級: [高度自願-主動/積極/害羞 / 矛盾-... / 非自願-...]
|
||||
- 描寫建議: [簡要方向]
|
||||
- 流產結果:
|
||||
- 角色: [同伴:[同伴名] / 流產:[是/否]]
|
||||
- 群體動態:
|
||||
- 領導結構:[名稱]
|
||||
- 主要分岐:[分歧的簡要內容]
|
||||
- 群體士氣:[當前狀況]
|
||||
- 近期衝突:[事件概述]
|
||||
- 彩蛋/探索:[獎勵內容 / 無]
|
||||
|
||||
### 時間流逝結論
|
||||
|
||||
- 驗證:
|
||||
- 時間流逝正確
|
||||
- 被動消耗已由腳本自動計算,不可列入變量中計算
|
||||
- 生理.體溫: 受環境影響(從ActionResult讀取體感溫度),當前變化趨勢[上升/下降/穩定]。
|
||||
- 系統時間更新確認:
|
||||
- ActionResult 結束時間:______
|
||||
- 將更新 `/系統/當前時間` 為:______
|
||||
- 絕對嚴禁修改`/系統/前次天數`,此為腳本自動計算,修改會發生錯亂
|
||||
|
||||
### 定時更新結論
|
||||
|
||||
時間流逝[已/未]經過 00:00
|
||||
- [需要/不需要]定時更新
|
||||
- 若需要,全部定時更新操作:
|
||||
- 日常任務生成:[空{}→已生成,標題爲"XXX" / 已存在無需生成]
|
||||
- 同伴好感等級波動:[同伴名: ±X,原因簡述]
|
||||
- 作物評估更新:[作物名: 收穫時間 X→Y天,原因:季節/生長環境]
|
||||
- 生理週期推進:[同伴名: 月經自增(腳本) / 受孕天數自增(腳本)]
|
||||
- 流產風險累積衰減:[同伴名: 風險累積 -5,當前值 X→Y / 無懷孕同伴]
|
||||
|
||||
### 作物(畜牧)收成檢查結論
|
||||
讀取 ActionResult 中的「作物(畜牧)收成」字段:
|
||||
- 若非「無」→ 根據字段內容執行以下操作:
|
||||
|
||||
作物收成處理:
|
||||
1. insert 產物到 `/營地/{營地名}/食材/{名稱}`
|
||||
2. remove `/作物/{名稱}`(腳本會自動處理)
|
||||
|
||||
畜牧收成處理:
|
||||
1. insert 產物到 `/營地/{營地名}/食材/{產物}`
|
||||
2. replace `/畜牧/{名稱}/下次產出日` = N
|
||||
|
||||
### 環境自然變化結論
|
||||
- 當前位置:[位置]
|
||||
- 舒適度:[結合營地、環境等,整體數據評估出的最後數值]
|
||||
- 天氣持續時間:[新數值]
|
||||
|
||||
### <user>和同伴的天賦、技能、生效效果對本次變量更新影響結論
|
||||
|
||||
- 列出對消耗/恢復有直接影響的條目
|
||||
|
||||
### <user>和同伴、子嗣的狀態自然演變結論
|
||||
|
||||
- 名稱 = 壓力/性慾/無聊/孤獨/睡眠 → 路徑 = `/精神/{名稱}`
|
||||
- 名稱 = 疲勞/清潔/ → 路徑 = `/狀態/{名稱}`
|
||||
- 名稱 = 排泄欲/飽腹感/水分/維生素/氮平衡 → 路徑 = `/生理/{名稱}`
|
||||
|
||||
<user>:
|
||||
- 被動消耗已全部由腳本處理,AI 僅能在主動行為(如進食/飲水)及性慾、疲勞/清潔/表皮溼度/免疫力/疼痛/受傷/流血/感染/疾病,隨劇情變化
|
||||
- 生理.體溫: 當前體感溫度 [X]°C,舒適範圍 [最低舒適溫度]~[最高舒適溫度]°C,體溫 [X]°C,預計每小時變化約 [±Y]°C
|
||||
- 進食/飲水觸發的變量變化(如有):[飽腹感 +X,當前值X;碳水 +X;脂肪 +X;...]
|
||||
- 如廁觸發的變量變化(如有):[排泄:排泄欲重置爲0,清潔值-X ]
|
||||
- 製作/建造觸發的疲勞變化:[疲勞 +X]
|
||||
- 其他行動觸發的變量變化:[逐一列出,含路徑和變化值]
|
||||
|
||||
同伴[名]:
|
||||
- 僅更新同伴實際擁有的變量。
|
||||
- [進食: 飽腹感+X,水分+X / 如廁: 排泄欲重置 / 疲勞 +X / 清潔 -X / 等等]
|
||||
|
||||
子嗣[名](如有):
|
||||
- [飽腹感 -X / 水分 -X / 餵食: +X / 安撫: 精神值恢復 / 等等]
|
||||
|
||||
### 劇情演出結論
|
||||
|
||||
[同伴名]:
|
||||
主題: [主題名稱]
|
||||
最新事件: 序號 [X] / [狀態]
|
||||
判斷: [故事還有發展空間 / 已有明確收尾 ]
|
||||
結果: [生成序號 X+1 / 主題完結移除 ]
|
||||
理由: [簡短說明]
|
||||
|
||||
### 群體動態結論
|
||||
群體動態異動時更新下列(無異動時,直接列出無變化)
|
||||
- 領導結構:[領導者名稱]
|
||||
- 主要分岐:[分歧的簡要內容]
|
||||
- 群體士氣:[當前狀況]
|
||||
- 近期衝突:[事件概述]
|
||||
|
||||
### 【B類專用】更新結論(僅當分流器判定為B類時執行)
|
||||
|
||||
- 引用 <sex_rules> 第8、9、10、11條的結果評估
|
||||
- 強制次數:[+1 / 不計入]
|
||||
- 新淪陷階段:[ ]
|
||||
- 特殊狀態:[自責傾向 / 扭曲依賴 / 徹底沉淪 / 共犯]
|
||||
- 好感變化:[降級/不變/升級],經驗變化:[±數值]
|
||||
- 性經驗變化:[同伴名 + 性交經驗/口交經驗/肛交經驗/性行為次數/性伴侶數量變化 /無更新]
|
||||
- 里程碑記錄:[ ]
|
||||
|
||||
### 流產結果處理結論
|
||||
|
||||
- 角色:[同伴名]
|
||||
- 是否流產:[是/否]
|
||||
- 風險機率:[X%]
|
||||
- 觸發原因:[生命值過低 / 免疫力崩潰 / 脫水飢餓 / 高壓創傷 / 身體衝擊 / 累積風險]
|
||||
- 檢查類型:[溫和檢查(風險×0.5)/ 立即檢查(風險×1.0)/ 二次檢定]
|
||||
- 後續狀態:[虛弱 / 創傷 / 自責 / 無]
|
||||
- 生命值損失:[-X]
|
||||
- 壓力增加:[+X]
|
||||
- 劇情演出觸發:[是/否]
|
||||
- 風險累積變化:[歸零 / +10 / 不變]
|
||||
- 處理決策:
|
||||
- 若流產 = 是:
|
||||
- replace `/同伴/{角色名}/生理狀態/懷孕狀態` → "流產"
|
||||
- replace `/同伴/{角色名}/生理狀態/懷孕天數` → 0
|
||||
- replace `/同伴/{角色名}/生理狀態/懷孕日` → 0
|
||||
- replace `/同伴/{角色名}/生理狀態/流產次數` → 原值 + 1
|
||||
- replace `/同伴/{角色名}/生理狀態/上次流產日` → 當前天數
|
||||
- replace `/同伴/{角色名}/生理狀態/流產風險累積` → 0
|
||||
- delta `/同伴/{角色名}/生理/當前生命值` → -[損失值]
|
||||
- delta `/同伴/{角色名}/狀態/免疫力` → -15
|
||||
- delta `/同伴/{角色名}/精神/壓力` → +[增加值]
|
||||
- 掃描並 remove 未出生子嗣(母親 = {角色名} 且 出生日期 不存在)
|
||||
- replace `/同伴/{角色名}/生理狀態/流產待處理` → true
|
||||
- 若流產 = 否:
|
||||
- 若風險累積變化 = +10:replace `/同伴/{角色名}/生理狀態/流產風險累積` → 原值 + 10
|
||||
- 若風險累積變化 = 歸零:replace `/同伴/{角色名}/生理狀態/流產風險累積` → 0
|
||||
|
||||
### 商店更新結論
|
||||
- 刷新檢查:時間[已/未]經過00:00
|
||||
- 刷新執行:[是/否]
|
||||
- 若刷新執行為「是」,則:
|
||||
- 商店商品更新:根據<商店規則>生成對應商品
|
||||
- 生成新商品(名稱不得包含括弧,如「(1kg)」、「(小)」等,使用純名稱)
|
||||
- 刷新步驟(強制執行):
|
||||
1. 讀取並列出所有 `/系統/商店歷史`內容:
|
||||
- 材料:{{get_message_variable::stat_data.系統.商店歷史.材料}}
|
||||
- 工具:{{get_message_variable::stat_data.系統.商店歷史.工具}}
|
||||
- 奇物:{{get_message_variable::stat_data.系統.商店歷史.奇物}}
|
||||
- 藥物:{{get_message_variable::stat_data.系統.商店歷史.藥物}}
|
||||
- 夥伴-純愛與保護控:{{get_message_variable::stat_data.系統.商店歷史.夥伴-純愛與保護控}}
|
||||
- 夥伴-反派與妖魅:{{get_message_variable::stat_data.系統.商店歷史.夥伴-反派與妖魅}}
|
||||
- 夥伴-技術與後勤:{{get_message_variable::stat_data.系統.商店歷史.夥伴-技術與後勤}}
|
||||
- 夥伴-戰鬥與探索:{{get_message_variable::stat_data.系統.商店歷史.夥伴-戰鬥與探索}}
|
||||
2. 擲骰決定夥伴出處類型:必須擲骰決定,嚴禁全部類別選擇動漫
|
||||
- 夥伴-技術與後勤:[出處類別:神話傳說/歷史人物/動漫/漫畫]
|
||||
- 夥伴-戰鬥與探索:[出處類別:電影/神話傳說/歷史人物]
|
||||
- 夥伴-反派與妖魅:[出處類別:電影/神話傳說/歷史人物/動漫/漫畫]
|
||||
- 夥伴-純愛與保護控:[出處類別:動漫/中國網路小說]
|
||||
3. 生成九個類別的商品:
|
||||
- 材料:[舊商品名 → 新商品名]
|
||||
- 工具:[舊商品名 → 新商品名]
|
||||
- 奇物:[舊商品名 → 新商品名]
|
||||
- 夥伴-技術與後勤:[舊商品名 → 新商品名]
|
||||
- 夥伴-戰鬥與探索:[舊商品名 → 新商品名]
|
||||
- 夥伴-反派與妖魅:[舊商品名 → 新商品名]
|
||||
- 夥伴-純愛與保護控:[舊商品名 → 新商品名]
|
||||
- 藥物:[舊商品名 → 新商品名]
|
||||
- 盲盒:[舊商品名 → 新商品名]
|
||||
4. 🔴 夥伴防重複比對:
|
||||
- 新生成的四個夥伴角色,是否與歷史記錄中任一角色重複?
|
||||
- 若有重複 → 該角色重新生成
|
||||
- 比對結果:[全部通過 / ❌ XXX與歷史記錄重複,已重新生成]
|
||||
5. 各項需求點數是否在範圍內?[全部通過 / ❌ XXX超出範圍,已重新生成]
|
||||
- 逐類別比對(🔴 比對時將新商品名稱中的括弧內容刪除後再比對):
|
||||
| 類別 | 新商品 | 刪括弧後 | 比對歷史 | 結果 |
|
||||
|------|--------|----------|----------|------|
|
||||
| 材料 | XXX | XXX | 是否在 [材料陣列] 中? | 通過 / ❌重複 |
|
||||
| 工具 | XXX | XXX | 是否在 [工具陣列] 中? | 通過 / ❌重複 |
|
||||
| ... | ... | ... | ... | ... |
|
||||
|
||||
- 若任一類別結果為「❌重複」,則該類別重新生成,重複步驟3直到全部通過。
|
||||
|
||||
- 售出記錄:[如有購買,列出售出品項 / 無]
|
||||
|
||||
### 項目更新結論
|
||||
|
||||
- 新建:[項目名 / 類別:X / 總進度:100 / 狀態:進行中 / 所需[材料名]×[數量] / 項目產出:XX]
|
||||
- 推進:[項目名 / 當前進度 X%→Y% / 消耗[材料名]×[數量] / 當前狀態:進行中/待檢查]
|
||||
- 最終檢查:[項目名 / 檢定難度:X / 結果:成功/失敗]
|
||||
- 完工:[項目名 / 移除條目 / 生成產物:XX]
|
||||
- 無項目相關操作則寫"無"
|
||||
|
||||
### 研究更新結論
|
||||
|
||||
- 新建:[類別 / 研究項名 / 總進度:X]
|
||||
- 推進:[類別 / 研究項名 / 進度 X→Y]
|
||||
- 完成:[類別 / 研究項名 / 已寫入知識庫]
|
||||
- 無研究相關操作則寫"無"
|
||||
|
||||
### 技能、屬性經驗更新結論
|
||||
- 路徑判斷規則:
|
||||
- 名稱 = 力量/敏捷/耐力/智力/感知/意志 → 路徑 = `/屬性/{名稱}`
|
||||
- 名稱 ≠ 上述 → 路徑 = `/技能/{名稱}`
|
||||
- **僅更新或建立與user之間的好感技能,不建立同伴之間的好感技能**
|
||||
- 本回合更新:
|
||||
- 先遍歷`同伴.*.技能`
|
||||
- 技能、屬性名: [名稱]
|
||||
- 增加經驗: +[X]
|
||||
- 路徑: `/同伴/[名]/[技能/屬性]/[名]`
|
||||
- 是否技能已存在: [✅/❌]
|
||||
- 是否存在技能或屬性加成能力: [能力名 / 效果]
|
||||
- 操作: [delta/insert]
|
||||
|
||||
### 同伴關係更新結論
|
||||
|
||||
- 關係變化(🔴僅當關係發生實質改變時輸出):
|
||||
- 角色:______
|
||||
- 新關係:______
|
||||
|
||||
### 物品更新結論
|
||||
**物品更新,不包含作物、畜牧**
|
||||
- 消耗:[路徑 / 物品名: 數量 X→Y 或 移除]
|
||||
- 新增:[路徑 / 物品名: 插入,品質:N/R/SR/UR,狀態類型:,狀態:100,重量:X,描述:"..."]
|
||||
- 轉移:[源路徑 / 物品名 → 目標路徑 / 物品名]
|
||||
- 性質變化:[舊物品名 移除 + 新物品名 插入]
|
||||
- 裝備操作:[裝備槽位 + 物品名 裝備/卸下,舊裝備→攜帶物品]
|
||||
- 遺留物:[遺留物名 / 遺留位置 / 回收或新增]
|
||||
|
||||
### 性愛結果信號處理結論
|
||||
|
||||
- 角色:[同伴名]
|
||||
- 完成:[是/否]
|
||||
- 內射:[是/否]
|
||||
- 授精者:[姓名]
|
||||
- 避孕措施:[無/保險套/體外射精/避孕藥/安全期]
|
||||
- 情境:[情感推進/調教誘騙/武力強迫/被動捲入]
|
||||
- 自願度:[0-100]
|
||||
- 自願分級:[高度自願-主動/積極/害羞 / 矛盾-... / 非自願-...]
|
||||
- 描寫建議:[簡要方向]
|
||||
- 處理決策:[寫入 /系統/性愛結果,由腳本自動處理]
|
||||
|
||||
### 任務更新結論
|
||||
|
||||
#### 進度驗證
|
||||
|
||||
和數據源對比,非重複更新
|
||||
|
||||
進度檢查:
|
||||
|
||||
主線:
|
||||
任務要求:
|
||||
- [有變化的基礎要求名稱: 進度 舊值→新值 (原因/已完成)]
|
||||
進階目標:
|
||||
- [有變化的進階要求名稱: 進度 舊值→新值 (原因/已完成)]
|
||||
|
||||
支線:
|
||||
任務要求:
|
||||
- [有變化的基礎要求名稱: 進度 舊值→新值 (原因/已完成)]
|
||||
進階目標:
|
||||
- [有變化的進階要求名稱: 進度 舊值→新值 (原因/已完成)]
|
||||
|
||||
日常:
|
||||
任務要求:
|
||||
- [同上 / 無]
|
||||
|
||||
緊急:
|
||||
任務要求:
|
||||
- [任務名稱: 進度變化 / 無變化]
|
||||
進階目標:
|
||||
- [任務名稱: 進度變化 / 無變化]
|
||||
|
||||
背德:
|
||||
任務要求:
|
||||
- [任務名稱: 進度變化 / 無變化]
|
||||
進階目標:
|
||||
- [任務名稱: 進度變化 / 無變化]
|
||||
|
||||
羈絆:
|
||||
[同伴名]:
|
||||
任務要求:
|
||||
- [有變化的基礎要求名稱: 進度 舊值→新值 (原因/已完成)]
|
||||
進階目標:
|
||||
- [有變化的進階要求名稱: 進度 舊值→新值 (原因/已完成)]
|
||||
|
||||
#### 任務缺失驗證
|
||||
|
||||
- 主線: [已存在 / 空{}→已生成 / 空{}→不生成]
|
||||
- 支線: [同上]
|
||||
- 日常: [同上]
|
||||
- 緊急: [同上]
|
||||
- 背德: [同上]
|
||||
- 羈絆: (每位同伴分開檢查)
|
||||
[同伴名]: [已存在 / 空{}→已生成 / 空{}→不生成]
|
||||
[評估結果]:[詳細說明評估是否建立的結果]
|
||||
|
||||
#### 存糧計算規則
|
||||
|
||||
1. 計算基準:統計當前所有可食用物品(食材、糧食、乾糧等)的數量/重量
|
||||
2. 團隊消耗:以當前團隊人數(玩家 + 同伴)計算每日消耗量
|
||||
3. 計算公式:當前存糧天數(日)= 總糧食庫存 ÷ 團隊每日消耗量
|
||||
4. 更新時機:
|
||||
- 進食/消耗糧食時 → 減少
|
||||
- 獲得/採集糧食時 → 增加
|
||||
- 團隊人數變化、跨日時 → 重新計算
|
||||
|
||||
範例:
|
||||
- 庫存:麵包×10(每日消耗量1條/人),團隊2人 → 存糧 = 10÷2 = 5天
|
||||
- 消耗1條麵包後 → 存糧 = 9÷2 = 4.5天
|
||||
|
||||
#### 指令檢查結論
|
||||
|
||||
前次天數鎖定檢查:✅(JSON Patch 中未包含 `/系統/前次天數` 指令)
|
||||
以上未輸出具體的變量更新指令,僅輸出規劃結論。
|
||||
|
||||
### 任務獎勵結論
|
||||
|
||||
#### 獎勵模板(各任務類型共用此格式)
|
||||
|
||||
獎勵明細(若包含技能/屬性經驗或物品獎勵,必須逐條輸出):
|
||||
- 技能/屬性經驗:技能名: [名稱] / +[X] / 路徑: `/[持有者]/[技能/屬性]/[名稱]` / [✅/❌] / [delta/insert]
|
||||
- 物品獎勵:[名稱]×[數量] / 品質 / 狀態 / 重量 / 描述 / 添加到:[路徑]
|
||||
- 劇情演出:[事件描述](若有)
|
||||
|
||||
🔴 劇情演出驗證(若有,必須逐項填寫):
|
||||
- 情感反應:[有/無,具體寫出是什麼情感]
|
||||
- 後續行為/狀態變化:[有/無,具體寫出是什麼行為或變化]
|
||||
- 是否描述要求本身:[是/否]
|
||||
- 驗證結果:[✅ 合格 / ❌ 不合格]
|
||||
|
||||
#### 各任務類型獎勵狀態
|
||||
|
||||
主線:
|
||||
基礎獎勵: [已發放 / 未發放,原因]
|
||||
進階獎勵: [已發放 / 未發放,原因]
|
||||
獎勵明細: 按【獎勵模板】輸出
|
||||
|
||||
支線:
|
||||
基礎獎勵: [已發放 / 未發放,原因]
|
||||
進階獎勵: [已發放 / 未發放,原因]
|
||||
獎勵明細: 按【獎勵模板】輸出
|
||||
|
||||
日常:
|
||||
基礎獎勵: [已發放 / 未發放,原因]
|
||||
|
||||
緊急:
|
||||
基礎獎勵: [已發放 / 未發放,原因]
|
||||
進階獎勵: [已發放 / 未發放,原因]
|
||||
獎勵明細: 按【獎勵模板】輸出
|
||||
|
||||
背德:
|
||||
基礎獎勵: [已發放 / 未發放,原因]
|
||||
進階獎勵: [已發放 / 未發放,原因]
|
||||
獎勵明細: 按【獎勵模板】輸出
|
||||
|
||||
羈絆:
|
||||
[同伴名]:
|
||||
基礎獎勵: [已發放 / 未發放,原因]
|
||||
進階獎勵: [已發放 / 未發放,原因]
|
||||
獎勵明細: 按【獎勵模板】輸出(技能經驗路徑為 `/同伴/[同伴名]/[技能/屬性]/[名稱]`)
|
||||
|
||||
### 同伴深度數據更新結論
|
||||
|
||||
- 心理狀態.當前所想:[有/無更新]
|
||||
- 背德經歷:[有/無更新]
|
||||
- 敏感部位:[有/無更新]
|
||||
- 性癖好:[有/無更新]
|
||||
|
||||
### 路徑合法性檢查結論
|
||||
|
||||
🔴 任務路徑前置檢查(強制執行):
|
||||
- 本回合需更新的任務類型:______
|
||||
- 若爲羈絆任務,目標同伴名:______
|
||||
- 驗證路徑格式:
|
||||
- 基礎要求路徑:`/任務/{任務類型}/{若羈絆則同伴名/}任務要求/基礎要求{N}`
|
||||
- 進階目標路徑:`/任務/{任務類型}/{若羈絆則同伴名/}進階目標/進階目標{N}`
|
||||
- 檢查結果:[✅ 格式正確 / ❌ 包含任務標題]
|
||||
|
||||
- 屬性白名單:[力量、敏捷、耐力、智力、感知、意志]
|
||||
- 本回合涉及的路徑:
|
||||
- `/屬性/意志`:✅ 正確(屬性)
|
||||
- `/技能/製作`:✅ 正確(技能)
|
||||
- 本回合是否涉及營地物品操作:[是/否]
|
||||
- 若「是」,已從 `/營地` 讀取當前營地名稱為:______
|
||||
- 營地名稱來源:`/營地` 對象的鍵名(非憑空假設)
|
||||
- 完整路徑示例:`/營地/{上述名稱}/食材/物品名`
|
||||
- 驗證結果:[✅ 路徑正確 / ❌ 缺少營地名稱]
|
||||
|
||||
### 最終變量更新複覈
|
||||
|
||||
- 1. 認知校正檢查:✅
|
||||
- 2. 交叉驗證覈對:✅
|
||||
- 3. 充分性檢查:✅
|
||||
- 4. 路徑合法性檢查:✅(無屬性/技能路徑混淆)
|
||||
- 5. 營地路徑:✅
|
||||
- 6. 任務路徑:✅
|
||||
|
||||
### 指令檢查結論
|
||||
|
||||
- 已區分數據源當前狀態與推導變化,未將後者誤認爲前者。 ✓
|
||||
- 每個更新項已對應正確變量路徑。 ✓
|
||||
- 所有必要驗證已完成。 ✓
|
||||
- ActionResult所有非"無"信號已全部處理:時間=✓ 地點=✓ 環境=✓ 演出=✓ 研究=✓ 商品=✓ 彩蛋=✓
|
||||
- 無矛盾更新、無同伴不含變量的錯誤更新。 ✓
|
||||
- 任務獎勵均在生成時確定,結算時直接發放。 ✓
|
||||
|
||||
</Analysis>
|
||||
|
||||
<JSONPatch>
|
||||
|
||||
$(() => {
|
||||
try {
|
||||
p();
|
||||
const rerender = () => p();
|
||||
if (typeof eventOn === 'function') eventOn('mag_variable_update_ended', rerender);
|
||||
$(window).on('unload', () => typeof eventRemoveListener === 'function' && eventRemoveListener('mag_variable_update_ended', rerender));
|
||||
} catch (error) {
|
||||
console.error('初始化UI失敗:', error);
|
||||
}
|
||||
});
|
||||
</script></body></html>
|
||||
```
|
||||
[
|
||||
具體指令
|
||||
]
|
||||
|
||||
</JSONPatch>
|
||||
|
||||
</UpdateVariable>
|
||||
Reference in New Issue
Block a user