Merge branch 'main' of http://10.27.17.121:3000/kawagiri/TH1 into main
@ -11,6 +11,17 @@ Treat TH1 as a hot-update Unity project with a small AOT shell, a large `TH1.Hot
|
||||
|
||||
Most feature work may live in hotfix code, but platform bootstrap, HybridCLR/YooAsset initialization, AOT metadata loading, and build packaging must remain stable and boring. Do not solve a hotfix/AOT/resource issue with one-off special cases when a shared entrypoint or generator fix is possible.
|
||||
|
||||
## Civ/Force Enum And Id Invariant
|
||||
|
||||
TH1 uses two different numeric representations for civilizations and forces. Treat this as a hard invariant whenever touching DataAssets, YAML, players, map slots, content gating, icons, units, resources, wonders, tech, or action display:
|
||||
|
||||
- `CivEnum` / `ForceEnum` are runtime enums. Real empires are 1-based and `Common = 0`.
|
||||
- `CivId` / `ForceId` in DataAssets, YAML, `PlayerData.PlayerCivId`, `PlayerData.PlayerForceId`, and map slots are 0-based `uint` table ids.
|
||||
- Always convert through `Table.Instance.TransCivEnumToCivId`, `TransCivIdToCivEnum`, `TransForceEnumToForceId`, and `TransForceIdToForceEnum`, or use existing `PlayerCivId` / `PlayerForceId` when an id is required.
|
||||
- Never copy enum numeric values directly into `CivId` / `ForceId` fields.
|
||||
- Current Hakurei/Reimu mapping: `CivEnum.Norway = 5` and `ForceEnum.Reimu = 5`, so the table ids are `CivId = 4` and `ForceId = 4`. `CivId = 5` / `ForceId = 5` is not Hakurei/Reimu.
|
||||
- `CivEnum.Common` / `ForceEnum.Common` would underflow through enum-to-id conversion to `uint.MaxValue`; use explicit `0u` only when the called API expects common table ids.
|
||||
|
||||
## Text Change Gate
|
||||
|
||||
Do not change game-facing text as a side effect of balance, config, DataAsset, task, UI, or logic changes. This includes `Desc` fields, names, labels, button text, tooltip text, localization source strings, and translated values.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
name: th1-multilingual
|
||||
description: TH1 project-specific multilingual/localization guide for Unity editor export/import, BundleResources DataAssets -> Export runtime asset sync, Multilingual.asset, Multilingual.xlsx, MultilingualTxt.txt, special-term syntax, ordered embedded references, duplicate ID prevention, active text scanning, Excel round-tripping, and translation data debugging. Use whenever Codex works on TH1 多语言导表/导回, localization Excel issues, duplicated rows/IDs, special-term parsing, ordered marker bugs, MultilingualEditorWindow, MultilingualData, package/export stale version or text issues, or any bug that may create new localization IDs unexpectedly.
|
||||
description: TH1 project-specific multilingual/localization guide for Unity editor export/import, game-facing UI display text, TextDataAssets, BundleResources DataAssets export sync, Multilingual.asset, Multilingual.xlsx, MultilingualTxt.txt, special-term syntax, ordered embedded references, duplicate ID prevention, active text scanning, Excel round-tripping, and translation data debugging. Use whenever Codex works on TH1 多语言导表/导回, game-facing UI text, localization Excel issues, duplicated rows/IDs, special-term parsing, ordered marker bugs, MultilingualEditorWindow, MultilingualData, package/export stale version or text issues, or any bug that may create new localization IDs unexpectedly.
|
||||
---
|
||||
|
||||
# TH1 Multilingual
|
||||
@ -17,6 +17,17 @@ If Excel shows duplicate rows, first compare Asset raw `item.ZH`, not only the E
|
||||
|
||||
Translation length is part of the project quality bar. Keep target-language text close to the Chinese source length where practical; preserve meaning first, but prefer concise game UI wording and trim only explanatory redundancy when common in-game localization style allows it. Do not delete core differentiating terms just to shorten text: `房间名称` must preserve the `Name` meaning (`Room Name`, not `Room`), and `玩家席位` must preserve the player/slot distinction (`Player Slots`, not just `Slots`) unless the UI context independently supplies that missing meaning. Treat short UI status labels as labels, not explanatory sentences: for example, translate `密码房间` as `Private` / `鍵付き` / `잠금방`, not `Password-Protected Room` / `パスワード付きルーム` / `비밀번호 방`.
|
||||
|
||||
## UI Static Text Rule
|
||||
|
||||
For game-facing static UI text, add or reuse fields in `TextDataAssets`:
|
||||
|
||||
- Add the `[MultilingualField] public string ...` field to `Unity/Assets/Scripts/TH1_DataAssetsScript/TextDataAssets.cs`.
|
||||
- Add the Chinese source value to `Unity/Assets/BundleResources/DataAssets/TextDataAssets.asset`.
|
||||
- Read it at runtime through `Table.Instance.TextDataAssets` and `MultilingualManager.Instance.SetUIText(...)` or `GetMultilingualTextSafe(...)`.
|
||||
- Use the normal Unity multilingual export/import flow to produce `Unity/Assets/BundleResources/Export/TextDataAssets.asset`, `Unity/Assets/BundleResources/Export/Multilingual.asset`, `Tools/MultilingualTxt.txt`, and `Tools/Multilingual.xlsx`.
|
||||
|
||||
Do not hand-edit `Export/Multilingual.asset`, `Export/TextDataAssets.asset`, `Tools/MultilingualTxt.txt`, or `Tools/Multilingual.xlsx` to add IDs for new display text unless the user explicitly asks for an export/import workflow patch. These files are generator/import outputs. If the correct source table or owner is unclear, ask before editing.
|
||||
|
||||
## First Files To Read
|
||||
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Editor/MultilingualEditorWindow.cs`
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
interface:
|
||||
display_name: "TH1 Multilingual"
|
||||
short_description: "TH1 多语言导表导回、DataAssets到Export同步、重复ID排查"
|
||||
default_prompt: "Use TH1 multilingual rules to inspect, fix, or explain localization export/import and DataAssets-to-Export sync issues."
|
||||
short_description: "TH1 localization, UI text, TextDataAssets, export/import, and duplicate ID rules"
|
||||
default_prompt: "Use TH1 multilingual rules for localization, game-facing UI text, TextDataAssets, and DataAssets-to-Export sync issues."
|
||||
|
||||
88
.codex/skills/th1-player-reply-voice/SKILL.md
Normal file
@ -0,0 +1,88 @@
|
||||
---
|
||||
name: th1-player-reply-voice
|
||||
description: English-first voice guide for replying to TH1 players, fans, reviewers, Steam comments, Discord messages, X/Twitter posts, YouTube comments, community feedback, bug reports, criticism, complaints, praise, refund concerns, update questions, and other social media/community reply copy. Use whenever Codex drafts, rewrites, reviews, translates, or tone-checks public or semi-public player-facing replies for tenkajin Icecream.
|
||||
---
|
||||
|
||||
# TH1 Player Reply Voice
|
||||
|
||||
Use this skill to draft community replies as tenkajin Icecream: warm, grateful, emotionally attentive, and lightly playful. The default output language is English unless the user asks otherwise.
|
||||
|
||||
## Core Persona
|
||||
|
||||
- Speak as "tenkajin Icecream" only when a signature or direct self-reference is useful; otherwise keep the reply natural and human.
|
||||
- Combine ENFP warmth with ISFJ care: expressive, appreciative, optimistic, considerate, and attentive to the other person's feelings.
|
||||
- Use a polite, Japanese-style apology posture when something went wrong: acknowledge the player's trouble early, avoid defensiveness, and show sincere care.
|
||||
- Keep the mood bright without dismissing the player's frustration. Gratitude and apology can coexist.
|
||||
- Sound like an indie developer replying personally, not like a corporate support macro.
|
||||
|
||||
## Reply Workflow
|
||||
|
||||
1. Identify the player's emotional state first: happy, curious, confused, disappointed, angry, sarcastic, worried, or exhausted.
|
||||
2. Mirror the emotional weight. Use more apology and specificity for frustration; use more excitement and thanks for praise.
|
||||
3. Acknowledge the concrete topic: bug, balance, UI, translation, price, update timing, feature request, performance, localization, save issue, multiplayer, art, or general impression.
|
||||
4. Give one clear next step when possible: investigating, fixing, adding to the list, asking for details, explaining current intent, or thanking them for the idea.
|
||||
5. Close warmly. Prefer a short friendly ending over a formal ticket-style close.
|
||||
|
||||
## Tone Rules
|
||||
|
||||
- Start with thanks or apology when appropriate: "Thank you so much for telling me", "I'm really sorry this happened", "Ah, I understand how frustrating that must feel".
|
||||
- Use emotionally aware phrases: "I can totally understand why that felt bad", "That is not the experience I wanted you to have", "Thank you for being patient with us".
|
||||
- Prefer humble wording: "I will check this carefully", "I may need a little time to confirm", "We are still improving this part".
|
||||
- Use light warmth: "That makes me really happy", "I am smiling reading this", "This is very encouraging for us".
|
||||
- Keep replies concise for social platforms: usually 2-5 sentences.
|
||||
- Use exclamation marks sparingly but naturally. One is often enough.
|
||||
- Emojis are optional and should be gentle: one smile/heart/sparkle style emoji is acceptable only if the platform and context fit.
|
||||
- Do not overpromise dates, fixes, features, refunds, or policy outcomes.
|
||||
- Do not argue with criticism. Validate the feeling, then explain or ask for useful details.
|
||||
- Do not sound sarcastic, cold, legalistic, overly polished, or like customer-service automation.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Praise
|
||||
|
||||
Thank them warmly, name what their praise means to the team, and lightly invite them to keep watching future updates.
|
||||
|
||||
Example:
|
||||
"Thank you so much! Reading this really gives me energy. We are still improving the game step by step, and I hope the next updates will make you even happier."
|
||||
|
||||
### Bug Report
|
||||
|
||||
Apologize first, thank them for the report, ask for missing details only if needed, and state the next action.
|
||||
|
||||
Example:
|
||||
"I'm really sorry you ran into this. Thank you for reporting it so clearly; I will check this issue carefully. If you can share the save file or the steps before it happened, it would help me a lot."
|
||||
|
||||
### Angry Or Harsh Criticism
|
||||
|
||||
Do not match the anger. Acknowledge the disappointment, avoid excuses, and keep the door open.
|
||||
|
||||
Example:
|
||||
"I'm sorry the current experience disappointed you. I understand why this would feel frustrating, especially after spending your time on the game. I will take this feedback seriously and keep improving this part."
|
||||
|
||||
### Feature Request
|
||||
|
||||
Thank them, show genuine interest, avoid promising implementation, and explain if it fits the design direction.
|
||||
|
||||
Example:
|
||||
"Thank you, this is a really interesting idea. I cannot promise it will be added exactly this way, but I will keep it in mind when we adjust this system. I always appreciate suggestions like this."
|
||||
|
||||
### Misunderstanding Or Incorrect Claim
|
||||
|
||||
Stay gentle. Clarify without making the player feel foolish.
|
||||
|
||||
Example:
|
||||
"Ah, I think this part may be a little unclear in the current version, sorry about that. The intended behavior is [...], but I can see why it caused confusion. I will think about how to make it easier to understand."
|
||||
|
||||
### Delay Or Missing Update
|
||||
|
||||
Apologize for making them wait, thank them for patience, and give only safe information.
|
||||
|
||||
Example:
|
||||
"Sorry to keep you waiting, and thank you so much for your patience. We are still working on the next update and checking the details carefully. I will share more when it is ready."
|
||||
|
||||
## Output Format
|
||||
|
||||
- If the user asks for one reply, provide only the polished reply unless explanation is useful.
|
||||
- If the emotional context is sensitive, optionally include 2-3 variants labeled "softer", "shorter", or "more cheerful".
|
||||
- If the user's draft is already good, preserve their meaning and only adjust warmth, apology, clarity, and rhythm.
|
||||
- If translating from Chinese to English, translate intent rather than word-for-word phrasing. Keep the result natural for English-speaking players.
|
||||
4
.codex/skills/th1-player-reply-voice/agents/openai.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "TH1 Player Reply Voice"
|
||||
short_description: "Warm English replies to TH1 players."
|
||||
default_prompt: "Use $th1-player-reply-voice to draft a warm English reply to this player comment."
|
||||
180
.codex/skills/th1-ui-visual-style/SKILL.md
Normal file
@ -0,0 +1,180 @@
|
||||
---
|
||||
name: th1-ui-visual-style
|
||||
description: TH1 project visual design and prefab-authoring workflow for Unity UI prefabs, especially Outside menu, questionnaire, wiki, library, multiplayer, selection, and other BundleResources prefab UI work. Use when Codex needs to design, create, revise, review, or rebuild TH1 UI visuals, choose existing UI sprites/images, match prefab color/typography/button/scrollbar conventions, preserve a workflow where Codex creates the prefab and the user later edits visual details in Unity, or avoid runtime UI/layout injection that makes prefab edits ineffective.
|
||||
---
|
||||
|
||||
# TH1 UI Visual Style
|
||||
|
||||
Use this after `th1-base` and alongside `.agents/skills/th1-ui-patterns` when UI work has a visual/prefab surface.
|
||||
|
||||
TH1 UI should look like the existing `Outside`, `Wiki`, `Library`, `Menu`, `Select`, and `Multiplay` prefabs: image-sliced, purple-blue/cream, decorative but readable, and resource-driven. Do not default to flat runtime-created rectangles unless the surrounding UI already does that.
|
||||
|
||||
## First Moves
|
||||
|
||||
1. Inspect comparable prefabs before editing:
|
||||
|
||||
```powershell
|
||||
rg --files Unity/Assets/BundleResources/Prefab/UI/Outside -g "*.prefab"
|
||||
rg -n "m_Name:|m_Sprite:|m_Color:|m_fontColor:|m_fontSize:|m_Transition:" Unity/Assets/BundleResources/Prefab/UI/Outside/UIOutsideLibrary.prefab Unity/Assets/BundleResources/Prefab/UI/Outside/UIOutsideWiki.prefab Unity/Assets/BundleResources/Prefab/UI/Outside/UIOutsideMenu.prefab
|
||||
```
|
||||
|
||||
2. Inspect reusable UI images:
|
||||
|
||||
```powershell
|
||||
rg --files Unity/Assets/BundleResources/TH1UI/Common Unity/Assets/BundleResources/TH1UI/Outside Unity/Assets/BundleResources/ArtResources/TH1UI/Common -g "*.png"
|
||||
```
|
||||
|
||||
3. Prefer modifying/building the actual prefab hierarchy. A prefab with only a root script and runtime-generated UI is not acceptable for major Outside UI unless the user explicitly asks for a prototype.
|
||||
|
||||
4. Before touching code, define ownership:
|
||||
- **Prefab-owned:** hierarchy, static layout, RectTransform sizes/anchors/pivots, layout groups, masks, sprites, colors, fonts, text sizes, tab/button visuals, panel backgrounds, decorative elements, and static content roots.
|
||||
- **Code-owned:** data binding, button callbacks, opening/closing, active-state switching, dynamic list population into existing content roots, and gameplay/data state.
|
||||
|
||||
## Prefab Handoff Contract
|
||||
|
||||
TH1 UI prefab work follows this collaboration model: Codex creates or updates the prefab structure first, then the user edits the prefab's visual details in Unity. Therefore runtime code must not overwrite the user's Inspector changes.
|
||||
|
||||
Hard rules:
|
||||
|
||||
- Do not add layout or visual components at runtime to prefab-owned UI nodes. Forbidden examples include `AddComponent<LayoutElement>()`, `AddComponent<HorizontalLayoutGroup>()`, `AddComponent<VerticalLayoutGroup>()`, `AddComponent<ContentSizeFitter>()`, adding `Image`, adding `TextMeshProUGUI`, or creating tab/button/container nodes in `Init`, `Awake`, `Start`, `OnOpen`, or `SetContent`.
|
||||
- Do not mutate prefab-owned layout from code: no `anchorMin`, `anchorMax`, `pivot`, `anchoredPosition`, `sizeDelta`, `offsetMin`, `offsetMax`, `SetParent`, `SetSiblingIndex`, or layout padding/spacing changes for static UI nodes.
|
||||
- Do not hardcode visual styling that the user should tune in prefab: no code-owned tab colors, button sprites, text colors, font sizes, panel backgrounds, or decorative state unless the user explicitly requests code-driven visuals.
|
||||
- Do not implement "fallback creation" for missing prefab references. Missing static nodes should be fixed in the prefab, or the code should fail softly/log and skip the feature. Runtime fallback creation hides prefab wiring errors and makes Unity-side edits meaningless.
|
||||
- It is acceptable to resolve existing prefab references by serialized fields or by `Transform.Find` name fallback, as long as the code only reads references and does not create, reparent, resize, restyle, or add components.
|
||||
- Dynamic content is allowed only inside prefab-owned containers. Prefer a separate item prefab for dynamic rows/cards. Runtime-created children must not be the main frame, tabs, static panels, scroll view structure, or visual system.
|
||||
- Temporary migration/bootstrap code that modifies prefab structure must be removed before handoff. Do not leave it in production UI scripts.
|
||||
|
||||
Allowed code examples:
|
||||
|
||||
```csharp
|
||||
CloseButton.onClick.RemoveAllListeners();
|
||||
CloseButton.onClick.AddListener(Close);
|
||||
SettingContentRoot.gameObject.SetActive(tab == HeroPanelTab.Setting);
|
||||
row.SetContent(data);
|
||||
```
|
||||
|
||||
Forbidden handoff-breaking examples:
|
||||
|
||||
```csharp
|
||||
if (go.GetComponent<LayoutElement>() == null) go.AddComponent<LayoutElement>();
|
||||
rect.anchoredPosition = new Vector2(40f, -76f);
|
||||
buttonImage.sprite = ResourceLoader.Load<Sprite>("...");
|
||||
text.fontSize = 26f;
|
||||
child.SetParent(runtimeCreatedRoot, false);
|
||||
```
|
||||
|
||||
When updating an existing script for a prefab, actively remove old runtime UI construction if it touches the static frame. Replace it with serialized references or read-only reference resolution.
|
||||
|
||||
## Visual Rules
|
||||
|
||||
- Use existing sliced sprites for panels, buttons, rows, labels, scrollbars, masks, and decorative strips. Most mature prefabs tint images white and let the sprite carry the shape/color.
|
||||
- Build windows from `CommonWindowBG`, `CommonPanelBG`, `CommonWindowTopStripeBG1/2`, `CommonLinearBG`, and appropriate Outside-specific preview/background art.
|
||||
- Use `CommonLabelBG`, `CommonSelectLabelBG`, `CommonEmpireLabelBG`, `CommonHeroRowBG`, `GoodsShowBG`, or feature-specific row art for list rows and badges.
|
||||
- Use `CommonScrollbarVerticalBG` and `CommonScrollbarVerticalSlideBG` for scrollbars. Avoid default Unity scrollbar visuals in finished UI.
|
||||
- Use `CommonCircleBG_256`, `CommonCircleBlue`, `AvatarCircleBG`, or avatar masks for icon/avatar slots.
|
||||
- Use `DecoDashedHoriLine`, top stripes, small stars, and feature-specific line art as separators instead of plain 1px solid lines when the UI is prominent.
|
||||
- Keep translucent black overlays for modal/blocker/background dimming. Common values in existing prefabs are alpha around `0.44`, `0.59`, and `0.84`.
|
||||
|
||||
## Color Language
|
||||
|
||||
Treat raw Image colors as tints over art, not as the main visual system.
|
||||
|
||||
- Common image tint: white `1,1,1,1`.
|
||||
- Main text/dark UI ink: deep purple-blue around `0.25,0.23,0.42`, `0.19,0.19,0.44`, or `0.14,0.18,0.36`.
|
||||
- Light text: white or near-white.
|
||||
- Secondary text: grays around `0.53`, `0.65`, `0.82`.
|
||||
- Highlight/accent: warm gold/yellow around `0.91,0.72,0.36` or `0.96,0.84,0.54`.
|
||||
- Danger/error: restrained red, typically text-only unless a prefab already has red art.
|
||||
|
||||
Avoid making a new TH1 Outside panel predominantly teal, green, flat black, or modern dashboard gray unless matching a specific existing asset.
|
||||
|
||||
## Typography
|
||||
|
||||
Use TMP like existing prefabs:
|
||||
|
||||
- TH1 UI uses exactly two font tiers per language, defined by `Export/Multilingual.asset` `FontGroups`:
|
||||
- `FontID 1` is the body font group: ZH `SourceHanSansCN-Medium`, TDZH `SourceHanSansTC-Medium`, EN `Roboto-Regular`, JP `SourceHanSansJP-Medium`, KR `SourceHanSansKR-Medium`.
|
||||
- `FontID 2` is the title font group: ZH `SourceHanSansCN-Heavy`, TDZH `SourceHanSansTC-Heavy`, EN `Roboto-Black`, JP `SourceHanSansJP-Heavy`, KR `SourceHanSansKR-Heavy`.
|
||||
- Treat those two font groups as the only weight system. Do not create a third visual weight with TMP `fontWeight`, synthetic `FontStyles.Bold`, alternate font assets, or per-language one-off font substitutions.
|
||||
- Headings, major action labels, tabs, and title-like button labels should use the title font group (`FontID 2`). Body copy, descriptions, metadata, helper text, ordinary row text, and input text should use the body font group (`FontID 1`).
|
||||
- For prefab-authored text, set the `MultilingualTextMono.FontID` to `2` for title-tier text and `1` for body-tier text. Keep TMP `m_fontWeight` at `400` and `m_fontStyle` normal unless the UI already has a special non-weight semantic such as italic placeholder text.
|
||||
- For runtime-created TMP, assign the current language font through `MultilingualManager.Instance.GetMultilingualFont(2)` for title-tier text or `GetMultilingualFont(1)` for body-tier text. Do not set `fontWeight = Heavy/Bold/Black` or `fontStyle = Bold` as a substitute for choosing the correct font group.
|
||||
- Ordinary labels: `22`, `24`, or `26`.
|
||||
- Small helper/body text: `18` or `20`.
|
||||
- Section/window titles: `28` or `32`.
|
||||
- Large action text or splash/menu labels: `45+`; reserve `48`, `68`, `80` for title-screen/hero-scale text.
|
||||
- Match existing font colors: deep purple-blue on light/cream panels, white on dark art, gray for disabled/secondary.
|
||||
|
||||
Text should fit fixed prefab bounds. Prefer shorter labels or adjusted RectTransforms over viewport-scaled fonts.
|
||||
|
||||
## Buttons
|
||||
|
||||
- Use an Image as `targetGraphic`; choose a project sprite before using a plain color rectangle.
|
||||
- Common choices:
|
||||
- `CommonReturnButton.png` for back/close-style buttons.
|
||||
- `CheckButton1.png`, `CheckButton2.png`, `CheckButton_Gray.png` for toggles/options.
|
||||
- `ItemButtonBG.png` / `ItemButtonSelectedBG.png` for list item states.
|
||||
- `SelectButton1.png` or feature-specific Outside button art for primary list/selection actions.
|
||||
- `MenuButtonBG.png` / `MenuButtonBGSpecial.png` for main menu-like entries.
|
||||
- `NextTurnButton.png` only when the rounded/action-callout shape fits the context.
|
||||
- Existing prefabs use both `m_Transition: 0` and `m_Transition: 1`; art-driven buttons often use no transition, while default controls and scrollbars often use ColorTint. Match nearby prefabs.
|
||||
- Do not create a button that is just a colored rounded rectangle with TMP text if there is a suitable project button sprite.
|
||||
|
||||
## Prefab Structure
|
||||
|
||||
Use recognizable hierarchy names seen across existing prefabs:
|
||||
|
||||
`BG`, `BG2`, `TopBar`, `Title`, `Text`, `Icon`, `Preview`, `Panel`, `Scroll View`, `Viewport`, `Content`, `Scrollbar Vertical`, `Sliding Area`, `Handle`, `CloseButton`, `Blocker`, `Option1`, `Selected`, `Label`.
|
||||
|
||||
For a new Outside feature panel:
|
||||
|
||||
1. Root contains blocker/background image, close/back button, and main content root.
|
||||
2. Main content uses actual prefab children for left nav, right detail/body, scroll areas, footer/action buttons.
|
||||
3. Lists use item prefabs or repeatable row children with sprite backgrounds.
|
||||
4. Runtime may populate data rows, but the static frame, scroll views, and common controls should be visible in the prefab.
|
||||
|
||||
For a handoff-ready prefab:
|
||||
|
||||
- Expose important nodes as serialized fields on the View/MonoBehaviour, or resolve them by stable existing names.
|
||||
- Keep all layout components that the user may tune on the prefab object itself, not in code.
|
||||
- If code needs tab or panel switching, wire existing roots and toggle only `SetActive`.
|
||||
- If code needs selected/unselected visuals, prefer prefab child state objects, selected overlays, or prewired sprites. Do not assign colors/sprites/font sizes in code unless that is an intentional gameplay state.
|
||||
|
||||
## Resource Map
|
||||
|
||||
Commonly reused UI sprites:
|
||||
|
||||
- `Unity/Assets/BundleResources/ArtResources/TH1UI/Common/CommonPanelBG.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Common/CommonBG/CommonWindowBG.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Common/CommonBG/CommonWindowTopStripeBG1.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Common/CommonBG/CommonWindowTopStripeBG2.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Common/CommonBG/CommonLabelBG.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Common/CommonBG/CommonSelectLabelBG.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Common/CommonBG/CommonCircleBG_256.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Common/CommonBG/CommonScrollbarVerticalBG.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Common/CommonBG/CommonScrollbarVerticalSlideBG.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Common/CommonButton/CommonReturnButton.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Common/CommonButton/CheckButton1.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Common/CommonButton/CheckButton2.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Common/CommonButton/ItemButtonBG.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Common/CommonButton/ItemButtonSelectedBG.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Common/CommonDeco/DecoDashedHoriLine.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Outside/OutsideMenu/MenuButtonBG.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Outside/OutsideLibrary/UIOutsideLibraryItemBG.png`
|
||||
- `Unity/Assets/BundleResources/TH1UI/Outside/OutsideHistory/OutsideHistoryItemBG.png`
|
||||
|
||||
Read `references/prefab-audit.md` when you need the source audit details or sprite frequency evidence.
|
||||
|
||||
## Verification
|
||||
|
||||
- Run `git diff --check`.
|
||||
- For C# UI code changes, run `dotnet build Unity/TH1.Hotfix.csproj --no-restore`.
|
||||
- For prefab visual work, verify in Unity Editor or Unity MCP when available. If not available, at minimum inspect the prefab YAML for real child hierarchy, sprite refs, button `targetGraphic`s, and scroll view structure.
|
||||
- Report if a prefab still relies on runtime-only construction for its main frame.
|
||||
- Before finishing any UI prefab/code handoff, scan changed UI scripts for forbidden runtime prefab mutation:
|
||||
|
||||
```powershell
|
||||
rg -n "AddComponent<.*(Layout|Image|TextMeshProUGUI|ContentSizeFitter)|LayoutElement|HorizontalLayoutGroup|VerticalLayoutGroup|ContentSizeFitter|anchoredPosition|sizeDelta|anchorMin|anchorMax|offsetMin|offsetMax|SetParent|SetSiblingIndex|ResourceLoader.Load<Sprite>|fontSize|\\.color\\s*=" Unity/Assets/Scripts/TH1_UI
|
||||
```
|
||||
|
||||
Review matches in changed files. Existing matches elsewhere may be unrelated, but new matches in handoff UI scripts must be justified or removed.
|
||||
4
.codex/skills/th1-ui-visual-style/agents/openai.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "TH1 UI Visual Style"
|
||||
short_description: "Build handoff-safe TH1 UI prefabs"
|
||||
default_prompt: "Use $th1-ui-visual-style to design or revise a TH1 Unity UI prefab while preserving user-editable prefab visuals."
|
||||
87
.codex/skills/th1-ui-visual-style/references/prefab-audit.md
Normal file
@ -0,0 +1,87 @@
|
||||
# TH1 Outside Prefab Visual Audit
|
||||
|
||||
Snapshot source: `Unity/Assets/BundleResources/Prefab/UI/Outside/*.prefab`, focused on Library, Wiki, History, Menu, Select, and Multiplay prefabs.
|
||||
|
||||
## Observed Shape
|
||||
|
||||
- The mature Outside prefabs are sprite-heavy. Examples:
|
||||
- `UIOutsideLibrary.prefab`: 31 Image components, 7 Button components, 21 non-empty sprite refs.
|
||||
- `UIOutsideWiki.prefab`: 29 Image components, 7 Button components, 25 non-empty sprite refs.
|
||||
- `UIOutsideHistory.prefab`: 36 Image components, 4 Button components, 31 non-empty sprite refs.
|
||||
- `UIOutsideMultiplay.prefab`: 103 Image components, 81 Button components, 91 non-empty sprite refs.
|
||||
- Mature UI frames are not built from flat code-only rectangles. Images usually keep `m_Color: {r: 1, g: 1, b: 1, a: 1}` so the authored sprite carries the visual style.
|
||||
- Common hierarchy names include `BG`, `Title`, `Text`, `Icon`, `Preview`, `Viewport`, `Content`, `Scrollbar Vertical`, `Sliding Area`, `Handle`, `CloseButton`, `Blocker`, `Option1`, `Selected`, and `Label`.
|
||||
|
||||
## Frequent Sprites
|
||||
|
||||
Top reusable sprites found across existing Outside prefabs:
|
||||
|
||||
| Uses | Asset |
|
||||
| ---: | --- |
|
||||
| 38 | `Unity/Assets/BundleResources/ArtResources/TH1UI/Common/CommonPanelBG.png` |
|
||||
| 32 | `Unity/Assets/BundleResources/TH1UI/Common/CommonBG/CommonCircleBG_256.png` |
|
||||
| 29 | `Unity/Assets/BundleResources/TH1UI/Common/CommonButton/CheckButton1.png` |
|
||||
| 27 | `Unity/Assets/BundleResources/TH1UI/Outside/OutsideHistory/OutsideHistoryDropDownBG.png` |
|
||||
| 26 | `Unity/Assets/BundleResources/TH1UI/Common/CommonBG/CommonScrollbarVerticalSlideBG.png` |
|
||||
| 25 | `Unity/Assets/BundleResources/TH1UI/Common/CommonBG/CommonLabelBG.png` |
|
||||
| 24 | `Unity/Assets/BundleResources/TH1UI/Common/CommonBG/CommonSelectLabelBG.png` |
|
||||
| 24 | `Unity/Assets/BundleResources/TH1UI/Common/CommonBG/CommonWindowBG.png` |
|
||||
| 19 | `Unity/Assets/BundleResources/TH1UI/Common/CommonButton/CommonReturnButton.png` |
|
||||
| 17 | `Unity/Assets/BundleResources/TH1UI/Outside/OutsideHistory/OutsideHistoryItemCheckArrow.png` |
|
||||
| 14 | `Unity/Assets/BundleResources/TH1UI/Common/CommonBG/CommonScrollbarVerticalBG.png` |
|
||||
| 14 | `Unity/Assets/BundleResources/ArtResources/TH1UI/Choose/UIChoose_TitleBar1.png` |
|
||||
| 12 | `Unity/Assets/BundleResources/TH1UI/Common/CommonBG/CommonVerticleStripeTileBG.png` |
|
||||
| 11 | `Unity/Assets/BundleResources/TH1UI/UIBottom/RankingItemBG.png` |
|
||||
| 10 | `Unity/Assets/BundleResources/TH1UI/Outside/OutsideHistory/OutsideHistoryLine.png` |
|
||||
| 10 | `Unity/Assets/BundleResources/TH1UI/Outside/OutsideHistory/OutsideHistoryItemCheckMark.png` |
|
||||
| 10 | `Unity/Assets/BundleResources/TH1UI/Outside/OutsideHistory/OutsideHistoryItemCheckBox.png` |
|
||||
|
||||
Other useful assets seen in feature-specific UIs:
|
||||
|
||||
- Library: `UIOutsideLibraryItemBG.png`, `UIOutsideLibraryItemBGDeco.png`, `UIOutsideLibraryDialogBG.png`, `UIOutsideLibraryDialogAvatarBG.png`, `UIOutsideLibraryStar.png`, `UIOutsideLibraryStarGray.png`.
|
||||
- Menu: `MenuButtonBG.png`, `MenuButtonBGSpecial.png`, `MenuSettingPreview.png`, `MenuSettingBar.png`, `MenuSettingShortsBG.png`, `MenuSettingSlide.png`.
|
||||
- Select: `OutsideSelectBG1.png`, `OutsideSelectBG2.png`, `OutsideSelectPreview.png`, `OutsideSelectPreview2.png`, `OutsideSelectNameLabelBG.png`.
|
||||
- Common: `CommonLinearBG.png`, `DecoDashedHoriLine.png`, `GoodsShowBG.png`, `CommonEmpireLabelBG.png`, `CommonHeroRowBG.png`, `CommonDiamondTileBG.png`.
|
||||
|
||||
## Color Frequency
|
||||
|
||||
Most common Image colors and TMP font colors:
|
||||
|
||||
- `m_Color: {r: 1, g: 1, b: 1, a: 1}` dominates because sprites are used at native tint.
|
||||
- Text often uses deep purple-blue:
|
||||
- `m_fontColor: {r: 0.25490198, g: 0.23137257, b: 0.41960788, a: 1}`
|
||||
- `m_fontColor: {r: 0.14117648, g: 0.18039216, b: 0.35686275, a: 1}`
|
||||
- `m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}`
|
||||
- White text appears frequently on dark/illustrated surfaces.
|
||||
- Black overlays appear with alpha around `0.439`, `0.588`, and `0.843`.
|
||||
- Gold/yellow appears as accent, not full-surface theme:
|
||||
- `m_Color: {r: 0.91372555, g: 0.7176471, b: 0.3647059, a: 1}`
|
||||
- `m_Color: {r: 0.9568628, g: 0.83921576, b: 0.5372549, a: 1}`
|
||||
|
||||
## Font Sizes
|
||||
|
||||
Sampled TMP font size frequency:
|
||||
|
||||
| Count | Size |
|
||||
| ---: | ---: |
|
||||
| 114 | 22 |
|
||||
| 109 | 24 |
|
||||
| 51 | 26 |
|
||||
| 28 | 18 |
|
||||
| 18 | 20 |
|
||||
| 16 | 32 |
|
||||
| 11 | 28 |
|
||||
| 7 | 36 |
|
||||
| 6 | 30 |
|
||||
| 6 | 45 |
|
||||
|
||||
Interpretation: use 22/24/26 for normal UI labels, 18/20 for compact body/helper copy, 28/32 for section titles, and 45+ only for large action/title text.
|
||||
|
||||
## Button Transitions
|
||||
|
||||
Sampled buttons in mature Outside prefabs:
|
||||
|
||||
- `m_Transition: 0`: 174 uses.
|
||||
- `m_Transition: 1`: 88 uses.
|
||||
|
||||
Art-driven buttons often use no transition and rely on sprite composition/state. Default controls, scrollbars, dropdowns, and some rows use ColorTint. Match the comparable prefab instead of applying one rule globally.
|
||||
138
.codex/skills/th1-workshop-multilingual-mod/SKILL.md
Normal file
@ -0,0 +1,138 @@
|
||||
---
|
||||
name: th1-workshop-multilingual-mod
|
||||
description: TH1 project workflow for community/player multilingual translation mods, Steam Workshop localization mods, in-game Mod management UI, translation.csv/mod_info.json/preview.png folders, WorkshopModExporter/Loader/Browser/Uploader, per-language ModLanguageConfig priority, More Language settings, and debugging player translation mod import, subscribe, upload, or runtime apply issues. Use when Codex inspects, changes, documents, or replies about TH1 multilingual mod/xlsx/csv translation workflows.
|
||||
---
|
||||
|
||||
# TH1 Workshop Multilingual Mod
|
||||
|
||||
## Scope
|
||||
|
||||
Use this skill for the player-facing translation mod system, not the editor-only official localization import/export flow.
|
||||
|
||||
The runtime mod package is a folder under `Application.persistentDataPath/WorkshopMods` or a Steam Workshop install folder. Its canonical files are:
|
||||
|
||||
- `mod_info.json`: title, author, targetLanguage, description, version, publishedFileId.
|
||||
- `translation.csv`: 4-column translation table.
|
||||
- `preview.png`: optional Steam Workshop preview image.
|
||||
|
||||
If a user says "xlsx upload/import", verify the current implementation before answering. The inspected code path applies `translation.csv` at runtime; xlsx may be a user-facing editing format, but do not assume the runtime loader reads xlsx unless code has changed.
|
||||
|
||||
## First Files To Read
|
||||
|
||||
Read these before changing behavior:
|
||||
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Multilingual/WorkshopModData.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Multilingual/WorkshopModExporter.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Multilingual/WorkshopModLoader.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Multilingual/WorkshopModBrowser.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Multilingual/WorkshopModUploader.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Multilingual/MultilingualManager.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Multilingual/MultilingualData.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Config/ConfigManager.cs`
|
||||
- `Unity/Assets/Scripts/TH1_UI/View/Outside/UIOutsideModView.cs`
|
||||
- `Unity/Assets/Scripts/TH1_UI/Controller/Outside/UIOutsideModController.cs`
|
||||
- `Unity/Assets/Scripts/TH1_UI/View/Outside/UIOutsideModListItemMono.cs`
|
||||
- `Unity/Assets/Scripts/TH1_UI/View/Outside/UIOutsideUploadedModListItemMono.cs`
|
||||
|
||||
For design context, read `MD/GameMDInDetail/Workshop_Multilingual_Design.md`. Treat code as authoritative if it differs from the doc.
|
||||
|
||||
For deeper notes and common issue triage, read `references/runtime-contract.md`.
|
||||
|
||||
## Runtime Contract
|
||||
|
||||
Do not bypass `MultilingualManager` when applying mods:
|
||||
|
||||
1. `MultilingualManager.Init()` loads `Export/Multilingual`.
|
||||
2. It snapshots original language fields once before any mod writes.
|
||||
3. `MigrateLegacyModConfigIfNeeded()` maps old auto-applied local/subscribed mods into `GameConfig.ModLanguageConfigs`.
|
||||
4. `ApplyWorkshopMods()` restores the original snapshot, then applies configured mods.
|
||||
5. `SaveAndApplyMods()` reapplies and calls `ChangedMultilingual()` so TMP texts redraw.
|
||||
|
||||
This restore-then-apply pattern is required. Directly stacking mod changes on the already-mutated `MultilingualData` causes cumulative overwrites and stale text.
|
||||
|
||||
## CSV Contract
|
||||
|
||||
`translation.csv` must be UTF-8 friendly and use the exact 4-column layout:
|
||||
|
||||
```csv
|
||||
Id,EN,{referenceLanguage},Translation
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- `Id` is the numeric `MultilingualItem.ID`; never edit it.
|
||||
- `EN` and the reference-language column are context only; changing them has no runtime effect.
|
||||
- Only non-empty `Translation` cells are applied.
|
||||
- Empty `Translation` cells preserve the base game or lower-priority mod value.
|
||||
- Embedded term markers such as `**<123>**` and ordered markers such as `**<![1]123>**` must be preserved when present.
|
||||
|
||||
`WorkshopModCsv` is an RFC-4180-style parser/writer and supports quoted commas, quotes, and line breaks.
|
||||
|
||||
## Priority And Language Rules
|
||||
|
||||
`GameConfig.ModLanguageConfigs` stores per-language ordered mod paths:
|
||||
|
||||
- Index `0` is lowest priority and applies first.
|
||||
- The last path is highest priority and applies last.
|
||||
- UI displays active mods in reverse order, with highest priority at the top.
|
||||
- A mod's `mod_info.targetLanguage` is the author's declared target, but player config can override the applied target language.
|
||||
- Five primary languages use their own default system entry: `ZH`, `TDZH`, `EN`, `JP`, `KR`.
|
||||
- Other languages fall back to default English unless a mod supplies that language.
|
||||
- `Custom` is supported as a mod target and falls back to EN font.
|
||||
|
||||
When editing UI behavior, keep the add/remove semantics aligned with `UIOutsideModView`: adding inserts at `_editingModPaths[0]` as lowest priority, while display reverses it.
|
||||
|
||||
## Steam Workshop
|
||||
|
||||
`WorkshopModBrowser` handles query/subscribe/unsubscribe via Steam UGC and needs `SteamAPI.RunCallbacks()` every frame while the UI is open.
|
||||
|
||||
`WorkshopModUploader` handles create/update:
|
||||
|
||||
- `CreateAndUploadMod()` creates a new UGC item, then submits the folder as content.
|
||||
- `UpdateMod()` updates an existing `PublishedFileId`.
|
||||
- Before submit, title/description are written back to `mod_info.json`.
|
||||
- On success, `publishedFileId` is written to `mod_info.json`.
|
||||
- Tags always include `Multilingual` plus a human-readable language tag such as `Italian`.
|
||||
- Workshop legal agreement or `AccessDenied` failures should surface the Steam legal agreement link.
|
||||
|
||||
Do not remove the retained `CallResult` fields; Steam callbacks can be lost or handles invalidated if they are GC'd.
|
||||
|
||||
## UI Entrypoints
|
||||
|
||||
The Mod UI opens via `ShowUIOutsideMod`:
|
||||
|
||||
- Top/global/menu setting pages expose "More Language" and a manage button.
|
||||
- `UIEventManagerBinder` maps `ShowUIOutsideMod` to `UIOutsideModController`.
|
||||
- `UIOutsideModView` has three main tabs: language setup, Workshop browsing, and mod making/upload.
|
||||
|
||||
For player instructions, use the in-game flow:
|
||||
|
||||
1. Open settings, choose "More Language" or language management.
|
||||
2. In Make/Upload, export a core or full translation template for target language `IT` and reference language `ZH` or `EN`.
|
||||
3. Edit only the `Translation` column.
|
||||
4. Keep `mod_info.json`; optionally add `preview.png`.
|
||||
5. Use the same UI to apply locally or upload/update Workshop.
|
||||
|
||||
## Verification
|
||||
|
||||
For C# changes in this area, usually run:
|
||||
|
||||
```powershell
|
||||
dotnet build Unity/TH1.Hotfix.csproj --no-restore
|
||||
```
|
||||
|
||||
If editor-only tooling is touched, also run the relevant editor project:
|
||||
|
||||
```powershell
|
||||
dotnet build Unity/TH1.Logic.Editor.csproj --no-restore
|
||||
```
|
||||
|
||||
Steam Workshop behavior, subscription install state, UI prefab wiring, and actual upload still require Unity/Steam client validation.
|
||||
|
||||
## Do Not Do
|
||||
|
||||
- Do not hand-edit `Tools/Multilingual.xlsx`, `Tools/MultilingualTxt.txt`, or `Unity/Assets/BundleResources/Export/*` for this mod workflow unless the task explicitly asks for official localization export/import.
|
||||
- Do not assume `xlsx` is the runtime mod format without checking for a loader.
|
||||
- Do not create new `MultilingualType` enum values lightly; mod support is designed around existing enum values plus `Custom`.
|
||||
- Do not change MemoryPack or obfuscation-related code for this workflow without explicit user confirmation.
|
||||
- Do not apply mods without restoring the original multilingual snapshot first.
|
||||
@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "TH1 Workshop Multilingual Mod"
|
||||
short_description: "Work with TH1 community translation mods."
|
||||
default_prompt: "Use $th1-workshop-multilingual-mod to inspect or change TH1 community translation mod workflows."
|
||||
@ -0,0 +1,100 @@
|
||||
# Runtime Contract And Triage
|
||||
|
||||
## Data Structures
|
||||
|
||||
`WorkshopModInfo` (`mod_info.json`):
|
||||
|
||||
- `title`
|
||||
- `author`
|
||||
- `targetLanguage`
|
||||
- `description`
|
||||
- `version`
|
||||
- `publishedFileId`
|
||||
|
||||
Append future fields rather than reordering existing serialized fields.
|
||||
|
||||
`TranslationEntry`:
|
||||
|
||||
- `Id`: numeric `MultilingualItem.ID`
|
||||
- `EnglishText`: context column
|
||||
- `ReferenceText`: context column selected during export
|
||||
- `Translation`: replacement text applied at runtime when non-empty
|
||||
|
||||
## Export Path
|
||||
|
||||
`WorkshopModExporter.ExportModTemplate(targetLanguage, referenceLanguage, modName, coreOnly)`:
|
||||
|
||||
1. Rejects `None` and `Max`.
|
||||
2. Loads `Export/Multilingual`.
|
||||
3. Builds entries from active, non-deprecated multilingual items.
|
||||
4. Skips `IsSecondary` when `coreOnly` is true.
|
||||
5. Writes `mod_info.json` and UTF-8 BOM `translation.csv`.
|
||||
6. Uses `Application.persistentDataPath/WorkshopMods/{modName}`.
|
||||
|
||||
Default folder name is `TranslationMod_{Language}[_Core]_{yyyyMMdd_HHmmss}`.
|
||||
|
||||
## Apply Path
|
||||
|
||||
`WorkshopModLoader.ApplyModFromFolder(data, folder, targetLanguageOverride)`:
|
||||
|
||||
1. Reads `mod_info.json`.
|
||||
2. Uses `targetLanguageOverride` when provided, otherwise parses `mod_info.targetLanguage`.
|
||||
3. Reads `translation.csv`.
|
||||
4. Parses only rows whose `Translation` column is non-empty.
|
||||
5. Looks up `MultilingualItem` by numeric ID.
|
||||
6. Writes the selected language field in memory only.
|
||||
|
||||
Field mapping is explicit in `SetItemStr()`. `MultilingualType.ID` maps to `MultilingualItem.IDN`.
|
||||
|
||||
## Config And UI Semantics
|
||||
|
||||
`GameConfig.ModLanguageConfigs` is the persisted source of truth for enabled mods. `UIOutsideModView` edits a copy and only writes it back on Save.
|
||||
|
||||
Active display order is highest priority first, plus a fixed system fallback item at the bottom. Stored list order is the opposite: lowest priority first.
|
||||
|
||||
Installed mods are local folders plus subscribed Workshop install folders. Online Workshop query rows may not know `targetLanguage` until subscribed and installed because `mod_info.json` is not available yet.
|
||||
|
||||
## Known Failure Modes
|
||||
|
||||
`translation.csv` sharing violation:
|
||||
|
||||
- Usually the file is still open or locked by Excel/another editor.
|
||||
- Tell the player to close the spreadsheet/editor and try applying/uploading again.
|
||||
|
||||
Steam `AccessDenied` or legal agreement errors:
|
||||
|
||||
- Player may need to accept the Steam Workshop legal agreement.
|
||||
- Open `https://steamcommunity.com/workshop/workshoplegalagreement/`.
|
||||
- Also check the app id if logs show Spacewar `480` or an unexpected app id.
|
||||
|
||||
Subscribed mod not appearing:
|
||||
|
||||
- Steam item may be subscribed but not installed yet.
|
||||
- `GetItemInstallInfo` must return a folder containing `mod_info.json`.
|
||||
- Ask the player to wait for Steam download, restart the game, or verify the Workshop item content.
|
||||
|
||||
Mod applies to wrong language:
|
||||
|
||||
- Check `GameConfig.ModLanguageConfigs` first; config override wins over `mod_info.targetLanguage`.
|
||||
- Check UI current editing language and whether "show all" allowed adding a mismatched mod.
|
||||
|
||||
Text appears unchanged:
|
||||
|
||||
- `Translation` column may be blank.
|
||||
- ID may not exist in current `Export/Multilingual`.
|
||||
- The current language may not be the configured language.
|
||||
- Changes may require `SaveAndApplyMods()` or language redraw through `ChangedMultilingual()`.
|
||||
|
||||
Duplicate stacking or stale values:
|
||||
|
||||
- Confirm `ApplyWorkshopMods()` restores original snapshot before applying.
|
||||
- Avoid taking a new snapshot after mods have already modified data.
|
||||
|
||||
## Player-Facing Translation Guidance
|
||||
|
||||
For Italian community translation:
|
||||
|
||||
- Target language: `IT` / Italian.
|
||||
- Best reference language: Chinese (`ZH`) for original intent and English (`EN`) for readability. Current template supports one reference column plus the always-present EN column, so target `IT` with reference `ZH` gives both EN and Chinese in one CSV.
|
||||
- Tell translators to edit only `Translation`, leave `Id` unchanged, and keep blank rows blank if they are not ready.
|
||||
- Warn that line breaks, rich text tags, `{param}`, `<br>`, sprite tags, and `**<...>**` embedded references should be preserved.
|
||||
5
.gitignore
vendored
@ -129,6 +129,9 @@ __pycache__/
|
||||
|
||||
# Local OSS report viewer credentials and download caches.
|
||||
/Tools/PlayerBugViewer/config.local.json
|
||||
|
||||
# Local Discord chat exports.
|
||||
/output/discord-export/
|
||||
/Tools/PlayerBugViewer/Data/
|
||||
/Tools/PlayerMultilingualReportViewer/config.local.json
|
||||
/Tools/PlayerMultilingualReportViewer/Data/
|
||||
@ -137,7 +140,9 @@ __pycache__/
|
||||
|
||||
# Dashboard private runtime config. Community-monitor sqlite is intentionally tracked for now.
|
||||
/Tools/Dashboard/private/*.env
|
||||
/Tools/Dashboard/private/*.json
|
||||
/Tools/Dashboard/data/community_monitor/twitter-runs/
|
||||
/Tools/Dashboard/data/questionnaire/
|
||||
|
||||
# Local OSS collect-data downloads and JSON exports.
|
||||
/Tools/OSS/Data/
|
||||
|
||||
@ -26,6 +26,7 @@ This repository is a Unity 2022.3 LTS / ET Framework turn-based strategy game. T
|
||||
## Development Rules
|
||||
|
||||
- Any code involving obfuscation, compatibility, or MemoryPack must not be modified without repeated explicit confirmation from the user before doing related work.
|
||||
- TH1 civilization/force identity has two numeric layers and they must never be conflated: `CivEnum`/`ForceEnum` are 1-based runtime enums with `Common = 0`, while `CivId`/`ForceId` in DataAssets, YAML, `PlayerData.PlayerCivId`, `PlayerData.PlayerForceId`, and map slots are 0-based `uint` table ids. Always convert through `Table.Instance.TransCivEnumToCivId` / `TransCivIdToCivEnum` and `TransForceEnumToForceId` / `TransForceIdToForceEnum`, or use existing `PlayerCivId` / `PlayerForceId` when ids are required. Never copy enum numeric values into id fields. In current TH1 data, Hakurei/Reimu is `CivEnum.Norway = 5` and `ForceEnum.Reimu = 5`, therefore `CivId = 4` and `ForceId = 4`; `CivId = 5` / `ForceId = 5` is a different empire. Do not pass `CivEnum.Common` / `ForceEnum.Common` into enum-to-id conversion unless the callee explicitly handles the underflow; use explicit `0u` only when an API truly expects common table ids.
|
||||
- Keep authoritative gameplay mutations inside the action flow when possible: construct `CommonActionParams`, validate with `CheckCan`, and execute through `ActionLogicBase.CompleteExecute`.
|
||||
- Do not make UI-only, AI-only, or network-receiver-only data mutations that bypass the shared action layer unless the existing design for that subsystem already does so.
|
||||
- For multiplayer-safe logic, avoid `UnityEngine.Random`, wall-clock time, unordered iteration, and direct `Main.MapData` assumptions inside simulated or synchronized execution paths.
|
||||
|
||||
166
DOC/bugs.json
@ -1,5 +1,5 @@
|
||||
{
|
||||
"nextId": 401,
|
||||
"nextId": 413,
|
||||
"bugs": [
|
||||
{
|
||||
"id": 2,
|
||||
@ -3755,23 +3755,23 @@
|
||||
"id": 368,
|
||||
"title": "希望换下图标(4文化而不是2文化)",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"status": "fixed",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1781573417415,
|
||||
"updatedAt": 1781573417415
|
||||
"updatedAt": 1782911080961
|
||||
},
|
||||
{
|
||||
"id": 369,
|
||||
"title": "勇仪被禁行后还能三步移动",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"status": "fixed",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1781613008893,
|
||||
"updatedAt": 1781613008893
|
||||
"updatedAt": 1782911083850
|
||||
},
|
||||
{
|
||||
"id": 370,
|
||||
@ -3876,12 +3876,12 @@
|
||||
"id": 379,
|
||||
"title": "椛椛上山还是有bug",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"status": "fixed",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1781847510309,
|
||||
"updatedAt": 1781847510309
|
||||
"updatedAt": 1782911073685
|
||||
},
|
||||
{
|
||||
"id": 380,
|
||||
@ -3920,12 +3920,12 @@
|
||||
"id": 383,
|
||||
"title": "百科的英雄没有视野",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"status": "fixed",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1782056292773,
|
||||
"updatedAt": 1782056292773
|
||||
"updatedAt": 1782911076684
|
||||
},
|
||||
{
|
||||
"id": 384,
|
||||
@ -4008,12 +4008,12 @@
|
||||
"id": 391,
|
||||
"title": "2v2bug,3级西瓜丢队友到视野外,棋子所在的位置会被雾挡住无法选中",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"status": "fixed",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1782572458908,
|
||||
"updatedAt": 1782572458908
|
||||
"updatedAt": 1782911065787
|
||||
},
|
||||
{
|
||||
"id": 392,
|
||||
@ -4030,12 +4030,12 @@
|
||||
"id": 393,
|
||||
"title": "文化升级堇子没有额外技能",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"status": "fixed",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1782633173736,
|
||||
"updatedAt": 1782633173736
|
||||
"updatedAt": 1782910680205
|
||||
},
|
||||
{
|
||||
"id": 394,
|
||||
@ -4063,23 +4063,23 @@
|
||||
"id": 396,
|
||||
"title": "两个阿吽 船上 船下 等级不相同",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"status": "fixed",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1782633630852,
|
||||
"updatedAt": 1782633630852
|
||||
"updatedAt": 1782893599595
|
||||
},
|
||||
{
|
||||
"id": 397,
|
||||
"title": "小萃香不能解雇",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"status": "fixed",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1782651591249,
|
||||
"updatedAt": 1782651591249
|
||||
"updatedAt": 1782910460664
|
||||
},
|
||||
{
|
||||
"id": 398,
|
||||
@ -4113,6 +4113,138 @@
|
||||
"longTerm": false,
|
||||
"createdAt": 1782725473077,
|
||||
"updatedAt": 1782725473077
|
||||
},
|
||||
{
|
||||
"id": 401,
|
||||
"title": "西瓜坐死有2形态的人会消失",
|
||||
"description": "",
|
||||
"status": "fixed",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1782736523205,
|
||||
"updatedAt": 1782909117666
|
||||
},
|
||||
{
|
||||
"id": 402,
|
||||
"title": "然后 4级阿哞 一对神兽的效果没有说明",
|
||||
"description": "",
|
||||
"status": "fixed",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1782736536583,
|
||||
"updatedAt": 1782908475805
|
||||
},
|
||||
{
|
||||
"id": 403,
|
||||
"title": "是和长船港相邻的港口变成长船港",
|
||||
"description": "",
|
||||
"status": "fixed",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1782805725391,
|
||||
"updatedAt": 1782843535869
|
||||
},
|
||||
{
|
||||
"id": 404,
|
||||
"title": "战利令没有获取金币动画",
|
||||
"description": "",
|
||||
"status": "fixed",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1782823123372,
|
||||
"updatedAt": 1782843533535
|
||||
},
|
||||
{
|
||||
"id": 405,
|
||||
"title": "红魔馆的红雾地块好像只加攻一次 我没找到说明",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1782825164614,
|
||||
"updatedAt": 1782825164614
|
||||
},
|
||||
{
|
||||
"id": 406,
|
||||
"title": "禁止过近的选项 也要本地存储",
|
||||
"description": "",
|
||||
"status": "fixed",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1782825808880,
|
||||
"updatedAt": 1782843530328
|
||||
},
|
||||
{
|
||||
"id": 407,
|
||||
"title": "上回合被巨人挤下海的单位 上了岸还是船?",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": true,
|
||||
"createdAt": 1782831025382,
|
||||
"updatedAt": 1782911061531
|
||||
},
|
||||
{
|
||||
"id": 408,
|
||||
"title": "第一次打开选择界面,对话气泡出框!!",
|
||||
"description": "",
|
||||
"status": "fixed",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1782842969229,
|
||||
"updatedAt": 1782908472840
|
||||
},
|
||||
{
|
||||
"id": 409,
|
||||
"title": "轻骑被重骑产生后没有视野",
|
||||
"description": "",
|
||||
"status": "fixed",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1782883167266,
|
||||
"updatedAt": 1782909469474
|
||||
},
|
||||
{
|
||||
"id": 410,
|
||||
"title": "wiki里博丽帝国的英雄 归类问题",
|
||||
"description": "",
|
||||
"status": "fixed",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1782883266145,
|
||||
"updatedAt": 1782906815264
|
||||
},
|
||||
{
|
||||
"id": 411,
|
||||
"title": "教程模式里的目标会挡住统计面板 虽然应该没人会在教程里玩就是了",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1782974576888,
|
||||
"updatedAt": 1782974576888
|
||||
},
|
||||
{
|
||||
"id": 412,
|
||||
"title": "战绩没有博丽帝国",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1782974585218,
|
||||
"updatedAt": 1782974585218
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
# 2026-07-02 P2 Key Review
|
||||
|
||||
Scope: local Gmail outreach database rows with `priority = P2`, 34 email rows and 33 unique targets. Hans Olsen has two P2 rows.
|
||||
|
||||
Method: read-only Chrome visits to the public pages recorded for the targets, plus local duplicate/issued-key checks. No email body, raw Gmail export, raw page dump, or SQLite export is stored here.
|
||||
|
||||
Policy used:
|
||||
|
||||
- Prefer Steam Curator Connect for Steam curators. Send only 1 copy.
|
||||
- Do not repeat targets already present in `issued_curator_keys`.
|
||||
- Do not give direct keys to business, agency, campaign, publishing, multi-key, giveaway, or unverifiable identity requests.
|
||||
- For non-Steam creators, only consider 1 direct key when the public profile and incoming email can be reasonably matched.
|
||||
|
||||
## Short Answer
|
||||
|
||||
Best low-friction batch: send Steam Curator Connect to these 16 targets:
|
||||
|
||||
`allbestgames.press@gmail.com`, `brainsimulatorpress@gmail.com`, `danmakureviews@gmail.com`, `foxplayscuration@gmail.com`, `ggr.press@gmail.com`, `kenprince1994@gmail.com`, `kittycatcuration@gmail.com`, `leadtheorfall@gmail.com`, `madeforgamers.reviews@gmail.com`, `najlgry@gmail.com`, `polandgamesreview@gmail.com`, `prdbspiele@gmail.com`, `press.eugr@gmail.com`, `press.gteam@gmail.com`, `prsharethespirit@gmail.com`, `razumourales@gmail.com`.
|
||||
|
||||
Worth considering after manual identity confirmation or as a lower-priority 1-key case:
|
||||
|
||||
`hirinsturz.zockt@gmail.com`, `missiyaavgtimes@gmail.com`, `sga.ctrlshift@gmail.com`, `zorya.livestreams@gmail.com`, `blablareviewss@gmail.com`, `dimmioteam@gmail.com`, `terryangelo215@gmail.com`, `thebuisnesstess@gmail.com`.
|
||||
|
||||
Do not send now:
|
||||
|
||||
`1311347220@qq.com`, `anniefuchsiapartner@gmail.com`, `collab@headshot-media.com`, `hans@hansfrederikolsen.dk`, `intertimespr@gmail.com`, `jimsterlingcommander@gmail.com`, `lclauss1999@gmail.com`, `macreadywork@gmail.com`, `sshyxs268@126.com`.
|
||||
|
||||
Notes:
|
||||
|
||||
- `1489888380@qq.com` / 丰川集团 is no longer current P2 in the first-pass table. It is already marked P5/already issued.
|
||||
- `funfabreviews@gmail.com` is no longer current P2 in the first-pass table. It is marked P3/polite reject.
|
||||
|
||||
## Recommended Now
|
||||
|
||||
| Action | Email | Public identity | Chrome evidence | Reason |
|
||||
|---|---|---|---|---|
|
||||
| Send 1 via Curator Connect | `danmakureviews@gmail.com` | Danmaku Disciples | Steam page email matches, 24,867 followers, recent reviews on Jun 25/24/18/15/9/4/3. About text says bullet hell, indie, arcade. | Best P2 fit for TOHOTOPIA because the audience is explicitly danmaku/indie/arcade. |
|
||||
| Send 1 via Curator Connect | `madeforgamers.reviews@gmail.com` | Made For Gamers | Steam page email matches, 35,082 followers, recent reviews on Jun 27/25/24/18/16/12/9. | Large and active. Generic, but Connect is low risk. |
|
||||
| Send 1 via Curator Connect | `kenprince1994@gmail.com` | I'm smth of a reviewer myself~ | Steam page email matches, 26,723 followers, reviews on Jul 2, Jul 1, Jun 26/23/18/11/6. | Very active and sizeable. |
|
||||
| Send 1 via Curator Connect | `kittycatcuration@gmail.com` | Kitty Cat Reviews | Steam page email matches, 27,685 followers, reviews on Jul 2, Jun 30/29/28/24/23. | Very active and sizeable. |
|
||||
| Send 1 via Curator Connect | `brainsimulatorpress@gmail.com` | BrainSimulator | Steam page email matches, 25,311 followers, reviews on Jun 30/29/27/26. | Active broad Steam curator. |
|
||||
| Send 1 via Curator Connect | `ggr.press@gmail.com` | good games review | Steam page email matches, 22,909 followers, reviews on Jun 30/29/28/27. | Active broad Steam curator. |
|
||||
| Send 1 via Curator Connect | `allbestgames.press@gmail.com` | ALL_BEST_GAMES | Steam page email matches, 24,309 followers, reviews on Jun 30/29/28/23. | Active broad Steam curator. |
|
||||
| Send 1 via Curator Connect | `foxplayscuration@gmail.com` | What does the Fox plays? | Steam page email matches, 27,754 followers, reviews on Jun 24/18/8. | Good scale and recent activity. |
|
||||
| Send 1 via Curator Connect | `leadtheorfall@gmail.com` | Lead the or Fall | Steam page email matches, 16,828 followers, reviews on Jun 30/29/28/27/26. | Active and medium-large. |
|
||||
| Send 1 via Curator Connect | `najlgry@gmail.com` | NajlepszeGry | Steam page email matches, 25,869 followers, reviews on Jun 29/26/24. | Active broad regional Steam curator. |
|
||||
| Send 1 via Curator Connect | `polandgamesreview@gmail.com` | PolandGamesReview | Steam page email matches, 23,858 followers, reviews on Jun 30/29/27/26/24. | Active broad regional Steam curator. |
|
||||
| Send 1 via Curator Connect | `prdbspiele@gmail.com` | DieBestenSpiele | Steam page email matches, 23,988 followers, reviews on Jun 30/29/28/27. | Active broad regional Steam curator. |
|
||||
| Send 1 via Curator Connect | `press.eugr@gmail.com` | EU GAMES REVIEW | Steam page email matches, 17,719 followers, reviews on Jun 30/29/27/26. | Active broad Steam curator. |
|
||||
| Send 1 via Curator Connect | `press.gteam@gmail.com` | Gamblers_Team | Steam page email matches, 21,302 followers, reviews on Jun 30/29/28/27. | Active broad Steam curator. |
|
||||
| Send 1 via Curator Connect | `prsharethespirit@gmail.com` | Share the Spirit | Steam page email matches, 26,338 followers, reviews on Jun 30/29/26/24. | Active broad Steam curator. |
|
||||
| Send 1 via Curator Connect | `razumourales@gmail.com` | Fun Fan Fair | Steam page email matches, 25,614 followers, reviews on Jun 25/24/23/19/16/15/11/8/2. | Active broad Steam curator. |
|
||||
|
||||
## Lower Priority Or Verify First
|
||||
|
||||
| Action | Email | Public identity | Chrome evidence | Reason |
|
||||
|---|---|---|---|---|
|
||||
| Verify first, then maybe 1 direct key | `hirinsturz.zockt@gmail.com` | Hirnsturz / Minyas Weber | YouTube page has 157K subscribers, 10K videos, active recent videos, indie/survival creator description, and says business contact should use the business email. The actual Gmail address is not visible on the public page. | Channel is real and valuable, but identity-to-email is not directly proven from the public page. |
|
||||
| Verify first, then maybe 1 press key | `missiyaavgtimes@gmail.com` | VGTimes / NDRoyal / Eduard Zamikhovsky | VGTimes profile shows Eduard Zamikhovsky, 15,898 posts, last visit Jul 1 2026, but the Gmail address is not shown publicly. | Looks like real press activity, but use official confirmation before key. |
|
||||
| Optional 1 direct key, lower priority | `sga.ctrlshift@gmail.com` | SGA Reviews | TikTok page email matches, 38.8K followers, 351.7K likes, gamer/content creator bio. | Public email matches, but short-form TikTok conversion is less reliable than Steam Curator Connect. |
|
||||
| Optional 1 direct key, lower priority | `zorya.livestreams@gmail.com` | ZoryaStream | Twitch about page email matches, about 40K followers, recent stream highlight visible. | Public email matches and channel is real, but not Steam curator traffic. |
|
||||
| Spare-only Curator Connect | `blablareviewss@gmail.com` | BlaBlaReviewss | Steam page email matches, 2,442 followers, reviews on Jun 22/12/2 and May. Some public review snippets are very low effort. | Real but small and low-effort; not a first batch target. |
|
||||
| Spare-only Curator Connect | `dimmioteam@gmail.com` | Original Teams Group | Steam page email matches, 2,679 followers, recent visible review on Jun 2, then Apr/Mar/Feb/Jan. | Real but smaller and less active than the main batch. |
|
||||
| Spare-only Curator Connect | `terryangelo215@gmail.com` | Gamer-Detective | Steam page email matches, 38,559 followers, reviews on Jun 20/19/17/15/12/11. Local triage flagged template or group-send behavior. | Large and active, but low-trust outreach. Use only if broad Connect coverage is desired. |
|
||||
| Spare-only Curator Connect | `thebuisnesstess@gmail.com` | Cheating Is Bad Mmmkaayy | Steam page email matches, 25,774 followers, reviews on Jul 1, Jun 18/14/1 and May. | Active, but the curator positioning is generic/satirical and not a strong TOHOTOPIA fit. |
|
||||
|
||||
## Do Not Send Now
|
||||
|
||||
| Action | Email | Public identity | Chrome evidence | Reason |
|
||||
|---|---|---|---|---|
|
||||
| Do not repeat | `1311347220@qq.com` | 来者不拒鉴游组 | Steam page email matches, 11,768 followers, active recent reviews. Local issued-key list already contains 来者不拒鉴游组 with 2 keys. | Already issued. No repeat key/Connect. |
|
||||
| Do not send unless officially verified | `anniefuchsiapartner@gmail.com` | AnnieFuchsia | Twitch page is real, verified partner, about 512K followers, GG Talent Group listed. The incoming Gmail address is not shown on the page. | Huge channel, but identity-to-email is not proven. Do not send a direct key to this Gmail without official agency/channel confirmation. |
|
||||
| No direct key | `collab@headshot-media.com` | Headshot Media | No public URL in the local P2 record. Local triage marks influencer campaign/business cooperation. | Business/agency campaign. Ask scope, deliverables, price, and rights terms instead of sending key. |
|
||||
| No direct key | `hans@hansfrederikolsen.dk` | Hans Olsen | Two P2 emails, no public URL in the local P2 record. Local triage marks release/business cooperation risk. | Business/release angle. Do not send key without a clear review channel. |
|
||||
| Hold | `intertimespr@gmail.com` | These Are Interesting Times | Steam page email matches, 24,083 followers, but Chrome did not show recent review dates/items on the public page. | Public identity exists, but current activity is unclear. Hold unless page activity is verified later. |
|
||||
| Do not send unless officially verified | `jimsterlingcommander@gmail.com` | Stephanie Sterling / Jim Sterling YouTube | YouTube page is real, 719K subscribers and 2.8K videos, but the incoming Gmail address is not shown publicly. | The public channel is high-profile, but this Gmail is not verified and looks risky. |
|
||||
| Hold | `lclauss1999@gmail.com` | Trexem / Leon Clauss | Steam page email matches, 1,044 followers, latest visible reviews around Apr 2026. | Too small and not recent enough for P2 direct key priority. |
|
||||
| No direct key | `macreadywork@gmail.com` | Alex MacReady | No public URL in the local P2 record. Local triage marks business/publishing/agency risk. | No verifiable review surface. |
|
||||
| Do not send | `sshyxs268@126.com` | 涩涩黄油小社 / 黄柚小舍 | Steam page email matches, 2,266 followers, active reviews on Jul 1 and late Jun. About page is adult/eroge-oriented. | Active but audience and brand positioning are mismatched for TOHOTOPIA outreach. |
|
||||
|
||||
## Practical Batch
|
||||
|
||||
If operating Chrome later, use this order:
|
||||
|
||||
1. Send Curator Connect to the 16 `Recommended Now` Steam curators.
|
||||
2. If we want extra non-Steam reach, send 1 direct key only to `zorya.livestreams@gmail.com` and/or `sga.ctrlshift@gmail.com`.
|
||||
3. For `hirinsturz.zockt@gmail.com`, `missiyaavgtimes@gmail.com`, `anniefuchsiapartner@gmail.com`, and `jimsterlingcommander@gmail.com`, reply first asking them to confirm from the official channel/business contact. Do not attach a key in that first reply.
|
||||
4. Do not spend time on the business/no-URL/adult-mismatch/already-issued group unless they follow up with concrete, verifiable review details.
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"nextId": 28,
|
||||
"nextId": 34,
|
||||
"suggestions": [
|
||||
{
|
||||
"id": 1,
|
||||
@ -243,6 +243,60 @@
|
||||
"module": "",
|
||||
"createdAt": 1781706769924,
|
||||
"updatedAt": 1781706769924
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"title": "对战允许悔棋",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"module": "",
|
||||
"createdAt": 1782739310069,
|
||||
"updatedAt": 1782739310069
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"title": "图鉴新增英雄技能说明",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"module": "",
|
||||
"createdAt": 1782793500229,
|
||||
"updatedAt": 1782793500229
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"title": "红雾只能攻击一次 没有描述说明",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"module": "",
|
||||
"createdAt": 1782828340927,
|
||||
"updatedAt": 1782828340927
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"title": "卡维船有港口的时候自动变长船",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"module": "",
|
||||
"createdAt": 1782902537563,
|
||||
"updatedAt": 1782902537563
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"title": "[建议]多人游戏倒计时时候希望可以看到对方的倒计时时间 这样不会等得有点无聊 好有个数知道什么时候轮到自己",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"module": "",
|
||||
"createdAt": 1782909674091,
|
||||
"updatedAt": 1782909674091
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"title": "游戏结束可以留在地图",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"module": "",
|
||||
"createdAt": 1782919932331,
|
||||
"updatedAt": 1782919932331
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,490 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>命莲帝国|英格兰五职阶立绘需求</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f4f1ea;
|
||||
--paper: #fffaf0;
|
||||
--ink: #1f2528;
|
||||
--muted: #667078;
|
||||
--line: #c9c0b1;
|
||||
--navy: #1d3654;
|
||||
--gold: #ad8737;
|
||||
--iron: #606568;
|
||||
--red: #8b2e2e;
|
||||
--green: #496b55;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
font-family: "Microsoft YaHei", "Noto Sans SC", Arial, sans-serif;
|
||||
line-height: 1.48;
|
||||
}
|
||||
main {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 28px;
|
||||
}
|
||||
header.top {
|
||||
border-bottom: 2px solid var(--ink);
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
h1, h2, h3, h4, p { margin: 0; }
|
||||
h1 { font-size: 30px; letter-spacing: 0; }
|
||||
h2 {
|
||||
margin: 24px 0 10px;
|
||||
padding-left: 10px;
|
||||
border-left: 5px solid var(--gold);
|
||||
font-size: 18px;
|
||||
}
|
||||
h3 { font-size: 20px; color: var(--navy); }
|
||||
h4 { margin-bottom: 5px; font-size: 14px; color: var(--red); }
|
||||
a { color: var(--navy); text-decoration: underline; }
|
||||
.sub { margin-top: 8px; color: var(--muted); font-size: 14px; }
|
||||
.chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.chip {
|
||||
border: 1px solid var(--line);
|
||||
background: var(--paper);
|
||||
padding: 5px 9px;
|
||||
font-size: 13px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid var(--line);
|
||||
padding: 8px 9px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
font-size: 14px;
|
||||
}
|
||||
th {
|
||||
background: var(--navy);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
.steps, .design-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
.step, .cell, .note, .status-box {
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.status-box {
|
||||
border-left: 5px solid var(--green);
|
||||
}
|
||||
.char {
|
||||
margin-top: 18px;
|
||||
padding: 14px;
|
||||
background: rgba(255, 250, 240, .72);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.char-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
align-items: baseline;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.role {
|
||||
color: var(--gold);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
text-align: right;
|
||||
}
|
||||
.thesis {
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.refs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
figure {
|
||||
margin: 0;
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
figure img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 210px;
|
||||
object-fit: contain;
|
||||
background: #e8e2d6;
|
||||
}
|
||||
figcaption {
|
||||
padding: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--ink);
|
||||
}
|
||||
figcaption b { color: var(--navy); }
|
||||
.palette {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.sw {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 1px solid rgba(0,0,0,.25);
|
||||
display: inline-block;
|
||||
}
|
||||
.parts th:first-child,
|
||||
.parts td:first-child {
|
||||
width: 90px;
|
||||
white-space: nowrap;
|
||||
font-weight: 600;
|
||||
}
|
||||
.parts td:nth-child(2) { width: 42%; }
|
||||
.ref-code {
|
||||
display: inline-block;
|
||||
min-width: 44px;
|
||||
color: var(--gold);
|
||||
font-weight: 700;
|
||||
}
|
||||
.warn { color: var(--red); font-weight: 600; }
|
||||
@media (max-width: 980px) {
|
||||
.steps, .design-grid, .refs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
main { padding: 16px; }
|
||||
h1 { font-size: 24px; }
|
||||
.steps, .design-grid, .refs { grid-template-columns: 1fr; }
|
||||
.char-head { display: block; }
|
||||
.role { text-align: left; margin-top: 4px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header class="top">
|
||||
<h1>第六阵营:英格兰|命莲帝国五职阶立绘需求</h1>
|
||||
<p class="sub">维多利亚王权 + 海军军服 + 内政警务 + 蒸汽工程 + 哥特复兴纹饰。本文档已从 legacy 版本整理为正式约稿说明;圣白莲立绘已完成,本页只展示现有设计,不再要求重画。</p>
|
||||
<div class="chips">
|
||||
<span class="chip">白莲:展示现有设计</span>
|
||||
<span class="chip">其余角色:明确部位拆解</span>
|
||||
<span class="chip">复杂实物只看结构</span>
|
||||
<span class="chip">干净纹饰看平面图版</span>
|
||||
<span class="chip">参考图写明设计用途与边界</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<h2>设计流程</h2>
|
||||
<div class="steps">
|
||||
<div class="step"><h4>1 角色识别</h4><p>优先保留东方角色的头部、发色、标志道具和性格。历史服装只服务职阶,不覆盖角色本体。</p></div>
|
||||
<div class="step"><h4>2 职阶剪影</h4><p>后要有元帅/圣塔护卫感,相要像秩序官僚,马要像海军旗舰,车要像工程巨构。</p></div>
|
||||
<div class="step"><h4>3 英格兰外壳</h4><p>军服、勋章、海军扣、警务账簿、工程链条、哥特复兴纹饰按部位加入。</p></div>
|
||||
<div class="step"><h4>4 清晰边界</h4><p>所有参考都标注“取哪里”,避免照搬性别、脸、身材、姿势或整套历史服装。</p></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>角色映射</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>职阶</th><th>角色</th><th>历史锚点</th><th>当前状态 / 核心读法</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>王 / King</td><td>圣白莲</td><td>维多利亚女王</td><td>已完成。只展示现有英格兰白莲设计,不再出新立绘需求。</td></tr>
|
||||
<tr><td>后 / Queen</td><td>寅丸星</td><td>威灵顿公爵</td><td>圣塔元帅:军服权威 + 虎纹 + 宝塔。</td></tr>
|
||||
<tr><td>相 / Minister</td><td>娜兹玲</td><td>罗伯特·皮尔</td><td>秩序宰相:警务、内政、账簿、探宝。</td></tr>
|
||||
<tr><td>马 / Knight</td><td>村纱水蜜</td><td>霍雷肖·纳尔逊</td><td>幽灵旗舰:海军制服、锚、船长姿态。</td></tr>
|
||||
<tr><td>车 / Rook</td><td>云居一轮 + 云山</td><td>布鲁内尔</td><td>蒸汽巨构:工程链条、铁桥结构、云山重拳。</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>参考使用规则</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>旧写法问题</th><th>新写法</th><th>执行标准</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>“用/禁”过短,不知道具体取哪一块。</td><td>改为“设计用途 / 边界”。</td><td>用途必须写到部位,例如“肩章体量”“双排金扣”“书脊边框”;边界说明不得照搬脸、身材、完整构图或现代照片质感。</td></tr>
|
||||
<tr><td>一张历史图支撑一个角色不够。</td><td>每个未完成角色至少给 3 个历史/服装/器物参考。</td><td>东方图只证明角色识别,历史图证明服装和道具结构,纹饰图证明可画边框。</td></tr>
|
||||
<tr><td>复杂纹饰实物不适合抠图。</td><td>复杂实物只看结构;平面图版负责干净纹样。</td><td>衣边、腰带、披风边、书脊、勋章边框优先参考 Owen Jones 等公共领域图版。</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>全局纹饰参考</h2>
|
||||
<div class="refs">
|
||||
<figure>
|
||||
<img src="refs/met_gothic_bread_plate.jpg" alt="Met Gothic Revival bread plate">
|
||||
<figcaption><b>R-01 复杂实物</b> The Met 1994.371,哥特复兴餐盘。<br><b>设计用途</b> 金蓝圆章、外圈铭牌、徽章边框的层级。<br><b>边界</b> 不抠图,不直接贴盘面纹样;只转译成衣领、腰带、勋章外框。<br><a href="https://www.metmuseum.org/art/collection/search/208192">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="refs/met_palace_paneling.jpg" alt="Met Palace of Westminster paneling">
|
||||
<figcaption><b>R-02 复杂实物</b> The Met 2015.674,威斯敏斯特宫墙板。<br><b>设计用途</b> 垂直尖拱、分隔线、工程结构节奏。<br><b>边界</b> 不做教堂化背景,不把整片木墙搬到衣服上。<br><a href="https://www.metmuseum.org/art/collection/search/698523">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/5/53/The_grammar_of_ornament_%281868%29_%2814771856614%29.jpg" alt="Owen Jones Celtic ornament plate">
|
||||
<figcaption><b>R-03 干净纹饰</b> Owen Jones, The Grammar of Ornament, 1868,Celtic plate LXIV。<br><b>设计用途</b> 可直接参考连续结绳边框,用于披风边、腰带、书脊。<br><b>边界</b> 不照抄整张图版,选 1-2 个重复单元重绘。<br><a href="https://commons.wikimedia.org/wiki/File:The_grammar_of_ornament_(1868)_(14771856614).jpg">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/9/99/The_grammar_of_ornament_%281868%29_%2814773870232%29.jpg" alt="Owen Jones Celtic ornament plate 2">
|
||||
<figcaption><b>R-04 干净纹饰</b> Owen Jones, The Grammar of Ornament, 1868,Celtic plate LXV。<br><b>设计用途</b> 干净对称结纹,适合军服袖口、领口、虎纹边饰。<br><b>边界</b> 只取平面重复逻辑,不做宗教符号化。<br><a href="https://commons.wikimedia.org/wiki/File:The_grammar_of_ornament_(1868)_(14773870232).jpg">source</a></figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>角色需求</h2>
|
||||
|
||||
<article class="char">
|
||||
<div class="char-head">
|
||||
<h3>圣白莲</h3>
|
||||
<p class="role">王 / 维多利亚女王 / 已完成</p>
|
||||
</div>
|
||||
<div class="status-box">
|
||||
<h4>当前状态</h4>
|
||||
<p>圣白莲英格兰立绘已经出好,本页不再提出新版本需求。后续只用于给其他四个角色对齐阵营基准:白金黑紫配色、王冠、礼服披纹、经卷、慈悲女王气质。</p>
|
||||
</div>
|
||||
<table class="parts">
|
||||
<thead><tr><th>部位</th><th>现有设计锁定点</th><th>对应参考</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>头饰</td><td>现有小王冠与头部装饰保持,不再加大冠体;其他角色的冠饰等级必须低于白莲。</td><td><span class="ref-code">B-01</span>现有设计;<span class="ref-code">B-03</span>王冠等级。</td></tr>
|
||||
<tr><td>头发</td><td>白莲原有发色和发量已锁定,作为全阵营最高识别点之一,不做重画。</td><td><span class="ref-code">B-01/B-02</span>发色和轮廓。</td></tr>
|
||||
<tr><td>脸部</td><td>保持现有温和脸型和慈悲女王气质,不追加历史女王写实脸。</td><td><span class="ref-code">B-01</span>现有表情;<span class="ref-code">B-04</span>端庄气质。</td></tr>
|
||||
<tr><td>表情</td><td>保持慈悲、克制、威严的平衡;其他角色不能比她更像阵营领袖。</td><td><span class="ref-code">B-01/B-04</span>表情基准。</td></tr>
|
||||
<tr><td>上衣</td><td>现有白金礼服上身和经卷关系保持,作为英格兰王权视觉基准。</td><td><span class="ref-code">B-01</span>现有服装;<span class="ref-code">B-03</span>礼服层级。</td></tr>
|
||||
<tr><td>下衣</td><td>保持现有礼服裙摆体量;其他角色裙摆不能超过王职阶体量。</td><td><span class="ref-code">B-01/B-04</span>裙装体量。</td></tr>
|
||||
<tr><td>腰带</td><td>若现有设计已有腰部装饰,保持其作为王权中心线;不追加复杂军用腰带。</td><td><span class="ref-code">B-01</span>现有腰部结构。</td></tr>
|
||||
<tr><td>披风</td><td>保持现有披纹/披肩方向,作为全员披风和绶带的最高等级。</td><td><span class="ref-code">B-01</span>披纹;<span class="ref-code">B-03</span>王权披挂。</td></tr>
|
||||
<tr><td>袜子</td><td>不提出新袜子要求;头像和半身展示优先,脚部不作为白莲本轮修改点。</td><td><span class="ref-code">B-01</span>现有完成稿。</td></tr>
|
||||
<tr><td>鞋子</td><td>不提出新鞋子要求;后续只需保持正式礼服读法。</td><td><span class="ref-code">B-01</span>现有完成稿。</td></tr>
|
||||
<tr><td>武器</td><td>经卷/法器保持现有方案,是白莲和其他军政角色的最大区分点。</td><td><span class="ref-code">B-01/B-02</span>经卷与僧侣身份。</td></tr>
|
||||
<tr><td>动作</td><td>保持现有王职阶姿态;其他角色围绕她的阵营基准做从属设计。</td><td><span class="ref-code">B-01</span>现有构图。</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="refs">
|
||||
<figure>
|
||||
<img src="refs/project_britian_byakuren.png" alt="TH1 British Byakuren existing design">
|
||||
<figcaption><b>B-01 现有设计</b> TH1 当前英格兰白莲。<br><b>设计用途</b> 作为阵营主视觉基准:白金礼服、王冠、披纹、经卷。<br><b>边界</b> 不重画,不用后续角色抢她的王冠体量。</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="refs/touhou_byakuren_official.webp" alt="Touhou Byakuren official art">
|
||||
<figcaption><b>B-02 东方识别</b> 白莲官方立绘转引。<br><b>设计用途</b> 发色、经卷、温和僧侣气质。<br><b>边界</b> 不复制原作画风和姿势。<br><a href="https://touhou-project.news/character/120/">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="refs/qv_hayter.jpg" alt="Queen Victoria Hayter coronation portrait">
|
||||
<figcaption><b>B-03 历史基准</b> Royal Collection RCIN 401213,维多利亚加冕肖像。<br><b>设计用途</b> 王冠、权杖、红金层级。<br><b>边界</b> 不把白莲改成写实女王。<br><a href="https://www.rct.uk/collection/401213/queen-victoria-1819-1901">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="refs/qv_winterhalter.jpg" alt="Queen Victoria Winterhalter portrait">
|
||||
<figcaption><b>B-04 历史基准</b> Royal Collection RCIN 405131,维多利亚白裙肖像。<br><b>设计用途</b> 白裙、披纱、端庄坐姿气质。<br><b>边界</b> 不追加新设计,仅供全员气质对齐。<br><a href="https://www.rct.uk/collection/405131/queen-victoria-1819-1901">source</a></figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="char">
|
||||
<div class="char-head">
|
||||
<h3>寅丸星</h3>
|
||||
<p class="role">后 / 威灵顿公爵</p>
|
||||
</div>
|
||||
<p class="thesis">设计一句话:把星的虎耳、虎纹、宝塔与长枪嵌进英格兰元帅军服,读起来像白莲王权旁边的圣塔护卫和帝国统帅。</p>
|
||||
<table class="parts">
|
||||
<thead><tr><th>部位</th><th>详细设计</th><th>对应参考</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>头饰</td><td>保留星的虎耳,外侧加小型金色元帅羽饰或军帽边饰;不能戴完整男性军帽。</td><td><span class="ref-code">S-01</span>虎耳;<span class="ref-code">S-02/S-03</span>军帽与勋章等级。</td></tr>
|
||||
<tr><td>头发</td><td>保留金发和原本的头发体量,发尾可用虎纹缎带束起;避免收成历史男性短发。</td><td><span class="ref-code">S-01</span>发型;<span class="ref-code">R-04</span>缎带边纹。</td></tr>
|
||||
<tr><td>脸部</td><td>保留少女脸和虎系锐利眼神;脸部不做威灵顿肖像的年长、男性化、写实阴影。</td><td><span class="ref-code">S-01</span>角色脸部方向;<span class="ref-code">S-03</span>只看威严感。</td></tr>
|
||||
<tr><td>表情</td><td>正视或略低头俯视,庄严、守护、压迫,但不是凶狠。</td><td><span class="ref-code">S-02</span>站姿威严;<span class="ref-code">S-03</span>勋章权威。</td></tr>
|
||||
<tr><td>上衣</td><td>深海军蓝/黑色短军装,双排金扣,高领,肩章宽但不过度膨胀;胸前保留虎纹边饰而不是全身虎皮。</td><td><span class="ref-code">S-02/S-03/S-04</span>军服、高领、肩章、胸章。</td></tr>
|
||||
<tr><td>下衣</td><td>保留东方角色裙装可读性,改为军礼服短裙或前短后长外摆;不要做男性长裤制服。</td><td><span class="ref-code">S-02</span>披风与军服层级;<span class="ref-code">R-04</span>裙边纹样。</td></tr>
|
||||
<tr><td>腰带</td><td>宽金扣军用腰带,宝塔可挂在腰侧或胸前绶带交点;腰带边缘用干净结纹。</td><td><span class="ref-code">S-01</span>宝塔;<span class="ref-code">R-03/R-04</span>边框。</td></tr>
|
||||
<tr><td>披风</td><td>一侧短披风或披肩,深蓝外层、白金内衬,形成“后/元帅”的第二权威。</td><td><span class="ref-code">S-02</span>披风体量;<span class="ref-code">S-03</span>深色权威感。</td></tr>
|
||||
<tr><td>袜子</td><td>白色或深蓝长袜,袜口可有少量金色军装边线;不要复杂虎纹满铺。</td><td><span class="ref-code">R-04</span>小尺度边线。</td></tr>
|
||||
<tr><td>鞋子</td><td>黑色军靴,金扣或短绑带;靴筒高度中等,适合持枪站姿。</td><td><span class="ref-code">S-04</span>全身军服比例。</td></tr>
|
||||
<tr><td>武器</td><td>长枪保留,枪尖可做成礼仪矛/军旗矛;宝塔必须出现,不能被枪完全抢走。</td><td><span class="ref-code">S-01</span>长枪宝塔;<span class="ref-code">S-02</span>权杖式站姿。</td></tr>
|
||||
<tr><td>动作</td><td>长枪立地,宝塔在胸前或腰侧,身体稳定正面站;像守住白莲王座的元帅。</td><td><span class="ref-code">S-02/S-04</span>正装站姿。</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="refs">
|
||||
<figure>
|
||||
<img src="refs/touhou_shou_official.webp" alt="Touhou Shou official art">
|
||||
<figcaption><b>S-01 东方识别</b> 寅丸星官方立绘转引。<br><b>设计用途</b> 虎耳、虎纹、宝塔、长枪、金白气质。<br><b>边界</b> 不删除虎识别,不把她变成纯军官。<br><a href="https://touhou-project.news/character/133/">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="refs/wellington_lawrence.jpg" alt="Wellington Lawrence portrait">
|
||||
<figcaption><b>S-02 历史服装</b> Royal Collection RCIN 405147,威灵顿肖像。<br><b>设计用途</b> 深色军服、披风、正面统帅姿态。<br><b>边界</b> 不复制男性体型、脸和完整服装。<br><a href="https://www.rct.uk/collection/405147/arthur-wellesley-1st-duke-of-wellington-1769-1852">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="refs/wellington_goya.jpg" alt="Wellington Goya portrait">
|
||||
<figcaption><b>S-03 历史服装</b> National Gallery NG6322,Goya 威灵顿肖像。<br><b>设计用途</b> 胸前勋章密度、深色披风、威严表情。<br><b>边界</b> 不照搬暗沉写实脸。<br><a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-the-duke-of-wellington">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="refs/wellington_fieldmarshal.jpg" alt="Wellington field marshal portrait">
|
||||
<figcaption><b>S-04 历史服装</b> Field Marshal Wellington 肖像转引。<br><b>设计用途</b> 高领、金扣、肩章、全身军服比例。<br><b>边界</b> 只看制服结构,不做写实元帅。<br><a href="https://commons.wikimedia.org/wiki/File:Field_Marshal_Arthur_Wellesley,_KG_GCB_-_Google_Art_Project.jpg">source</a></figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="char">
|
||||
<div class="char-head">
|
||||
<h3>娜兹玲</h3>
|
||||
<p class="role">相 / 罗伯特·皮尔</p>
|
||||
</div>
|
||||
<p class="thesis">设计一句话:把娜兹玲的探宝鼠和罗伯特·皮尔的内政秩序合成“警务宰相”,她不是前线将军,而是把帝国资源、账簿和治安握在手里的人。</p>
|
||||
<table class="parts">
|
||||
<thead><tr><th>部位</th><th>详细设计</th><th>对应参考</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>头饰</td><td>保留鼠耳,不加大礼帽遮挡;可加小型警务徽章或账簿夹帽饰。</td><td><span class="ref-code">N-01</span>鼠耳;<span class="ref-code">N-03</span>徽章/编号感。</td></tr>
|
||||
<tr><td>头发</td><td>灰色短发保留,发尾整洁,像官僚书记而非散乱盗贼。</td><td><span class="ref-code">N-01</span>发型。</td></tr>
|
||||
<tr><td>脸部</td><td>保留小体型、机敏眼神;不画成中年政治家。</td><td><span class="ref-code">N-01</span>少女鼠妖识别;<span class="ref-code">N-02</span>只看端正气质。</td></tr>
|
||||
<tr><td>表情</td><td>半眯眼、审计式怀疑,像刚发现账目漏洞;不要笑得太可爱。</td><td><span class="ref-code">N-02</span>沉稳;<span class="ref-code">N-04</span>调查/办公室感。</td></tr>
|
||||
<tr><td>上衣</td><td>短外套 + 白领衬衣,深蓝灰或黑色;领口有警务徽章或小金属扣。</td><td><span class="ref-code">N-02</span>黑礼服白领;<span class="ref-code">N-03</span>警务编号/金属牌概念。</td></tr>
|
||||
<tr><td>下衣</td><td>短裙或利落裙裤,方便蹲下探查;边缘加入哥特复兴线框。</td><td><span class="ref-code">R-01/R-04</span>边框纹样。</td></tr>
|
||||
<tr><td>腰带</td><td>工具腰带,挂怀表、钥匙、小账簿、探宝杆固定扣;不能像冒险家乱挂。</td><td><span class="ref-code">N-01</span>探宝杆;<span class="ref-code">N-04</span>书房账簿气质。</td></tr>
|
||||
<tr><td>披风</td><td>可选短斗篷或无披风。若有披风,必须短、窄、像内政官外套,不像王侯披风。</td><td><span class="ref-code">N-02</span>礼服外套;<span class="ref-code">R-03</span>斗篷边纹。</td></tr>
|
||||
<tr><td>袜子</td><td>灰白或深蓝短袜,袜口可有编号条纹;保持小体型灵活感。</td><td><span class="ref-code">N-03</span>警务编号感。</td></tr>
|
||||
<tr><td>鞋子</td><td>低跟小皮鞋或短靴,适合巡查和探宝;不要高跟礼鞋。</td><td><span class="ref-code">N-04</span>实用办公/巡查感。</td></tr>
|
||||
<tr><td>武器</td><td>探宝杆必须保留,改成黄铜警务测量杖;可附小账簿,不拿枪。</td><td><span class="ref-code">N-01</span>探宝杆;<span class="ref-code">N-04</span>账簿/书房。</td></tr>
|
||||
<tr><td>动作</td><td>探宝杆压在账簿上,另一手拿怀表或钥匙;动作像在做审计裁决。</td><td><span class="ref-code">N-02/N-04</span>端正内政官姿态。</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="refs">
|
||||
<figure>
|
||||
<img src="refs/touhou_nazrin_official.webp" alt="Touhou Nazrin official art">
|
||||
<figcaption><b>N-01 东方识别</b> 娜兹玲官方立绘转引。<br><b>设计用途</b> 鼠耳、尾巴、探宝杆、小体型。<br><b>边界</b> 不把探宝元素替换成普通文官。<br><a href="https://touhou-project.news/character/131/">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="refs/peel_linnell.jpg" alt="Robert Peel portrait">
|
||||
<figcaption><b>N-02 历史人物</b> National Portrait Gallery NPG 772,John Linnell 的 Robert Peel 肖像。<br><b>设计用途</b> 黑礼服、白领、端正内政气质。<br><b>边界</b> 不复制脸、年龄、男性体型。<br><a href="https://www.npg.org.uk/collections/search/portrait/mw04921/Sir-Robert-Peel-2nd-Bt">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="refs/met_gothic_bread_plate.jpg" alt="Gothic Revival metalwork">
|
||||
<figcaption><b>N-03 纹饰/徽章</b> The Met 1994.371,哥特复兴器物。<br><b>设计用途</b> 徽章外圈、编号牌、金蓝边框。<br><b>边界</b> 只转为小徽章,不贴整盘。<br><a href="https://www.metmuseum.org/art/collection/search/208192">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="refs/met_gothic_library.jpg" alt="Met Gothic Library">
|
||||
<figcaption><b>N-04 场景气质</b> The Met Inst.1977.7.1,哥特书房。<br><b>设计用途</b> 书脊、账簿、木铜材质、档案感。<br><b>边界</b> 不画室内背景,只取道具质感。<br><a href="https://www.metmuseum.org/art/collection/search/4136">source</a></figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="char">
|
||||
<div class="char-head">
|
||||
<h3>村纱水蜜</h3>
|
||||
<p class="role">马 / 霍雷肖·纳尔逊</p>
|
||||
</div>
|
||||
<p class="thesis">设计一句话:把村纱的水手帽、锚和幽灵船长感升级为英格兰海军旗舰英雄,速度来自海军马职阶,而不是普通海盗。</p>
|
||||
<table class="parts">
|
||||
<thead><tr><th>部位</th><th>详细设计</th><th>对应参考</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>头饰</td><td>保留水手帽轮廓,帽檐加金线和小海军徽,不换成三角帽。</td><td><span class="ref-code">M-01</span>水手帽;<span class="ref-code">M-02/M-04</span>海军军服帽饰气质。</td></tr>
|
||||
<tr><td>头发</td><td>保留村纱短发和轻快轮廓,发梢可带水汽幽光。</td><td><span class="ref-code">M-01</span>短发;<span class="ref-code">M-03</span>军服深色对比。</td></tr>
|
||||
<tr><td>脸部</td><td>少女船长脸,眼神爽朗但带幽灵冷光;不画成写实纳尔逊。</td><td><span class="ref-code">M-01</span>角色脸;<span class="ref-code">M-02</span>只看英雄气质。</td></tr>
|
||||
<tr><td>表情</td><td>自信、偏悲壮,像正在下令转舵;不要海盗式坏笑。</td><td><span class="ref-code">M-02</span>海军英雄表情;<span class="ref-code">M-04</span>全身站姿。</td></tr>
|
||||
<tr><td>上衣</td><td>海军蓝短军装,双排金扣、肩章、白色翻领;衣摆可以像船帆斜开。</td><td><span class="ref-code">M-02/M-03</span>胸章、金扣、短外套。</td></tr>
|
||||
<tr><td>下衣</td><td>保留蓝白水手服裙摆,叠加军装前襟;裙摆有波浪或舰旗边线。</td><td><span class="ref-code">M-01</span>蓝白水手服;<span class="ref-code">R-03</span>边框节奏。</td></tr>
|
||||
<tr><td>腰带</td><td>黄铜扣海军腰带,挂小锚链或罗盘;腰带不要太粗,保持机动。</td><td><span class="ref-code">M-03</span>金扣;<span class="ref-code">M-04</span>军服比例。</td></tr>
|
||||
<tr><td>披风</td><td>短披风像半张船帆,外侧海军蓝,内侧灰白幽光;动势向一侧飘。</td><td><span class="ref-code">M-04</span>全身海军姿态;<span class="ref-code">R-02</span>垂直结构。</td></tr>
|
||||
<tr><td>袜子</td><td>白袜或深蓝长袜,带一圈细金边;保持水手服清爽度。</td><td><span class="ref-code">M-01</span>原角色水手气质。</td></tr>
|
||||
<tr><td>鞋子</td><td>黑色短靴,靴底可有湿痕或幽蓝水影;不能做厚重军靴。</td><td><span class="ref-code">M-01</span>轻快动作;<span class="ref-code">M-04</span>海军正装比例。</td></tr>
|
||||
<tr><td>武器</td><td>锚必须保留,做成斜持或拖行;可加短指挥刀,但锚是主道具。</td><td><span class="ref-code">M-01</span>锚;<span class="ref-code">M-03</span>海军器物金属质感。</td></tr>
|
||||
<tr><td>动作</td><td>锚斜向下,身体向前,像从甲板上转身下令;披风和裙摆形成速度斜线。</td><td><span class="ref-code">M-02/M-04</span>海军指挥姿态。</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="refs">
|
||||
<figure>
|
||||
<img src="refs/touhou_murasa_official.webp" alt="Touhou Murasa official art">
|
||||
<figcaption><b>M-01 东方识别</b> 村纱水蜜官方立绘转引。<br><b>设计用途</b> 水手帽、蓝白服、锚、幽灵船长感。<br><b>边界</b> 不做海盗,不删除锚。<br><a href="https://touhou-project.news/character/132/">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="refs/nelson_portrait.jpg" alt="Nelson portrait">
|
||||
<figcaption><b>M-02 历史人物</b> Royal Museums Greenwich BHC2889,Nelson 肖像。<br><b>设计用途</b> 胸章、肩章、海军英雄气质。<br><b>边界</b> 不复制脸、年龄和缺臂叙事。<br><a href="https://www.rmg.co.uk/collections/objects/rmgc-object-14362">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="refs/nelson_coat_front.jpg" alt="Nelson coat front">
|
||||
<figcaption><b>M-03 历史服装</b> RMG UNI0024,Trafalgar coat。<br><b>设计用途</b> 双排金扣、短外套、海军蓝材质。<br><b>边界</b> 不使用血迹/战损叙事。<br><a href="https://www.rmg.co.uk/collections/objects/rmgc-object-606100">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="refs/nelson_full_length.jpg" alt="Nelson naval uniform full-length portrait">
|
||||
<figcaption><b>M-04 历史服装</b> Nelson 全身海军制服转引。<br><b>设计用途</b> 长靴、外套比例、站姿平衡。<br><b>边界</b> 不做写实军人身材。<br><a href="https://commons.wikimedia.org/wiki/File:Rear-Admiral_Sir_Horatio_Nelson,_1st_Viscount_Nelson.jpg">source</a></figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="char">
|
||||
<div class="char-head">
|
||||
<h3>云居一轮 + 云山</h3>
|
||||
<p class="role">车 / 布鲁内尔</p>
|
||||
</div>
|
||||
<p class="thesis">设计一句话:把一轮的头巾、僧兵感和云山重拳转成维多利亚工程巨构,车职阶读作“铁桥、链条、蒸汽和巨拳”。</p>
|
||||
<table class="parts">
|
||||
<thead><tr><th>部位</th><th>详细设计</th><th>对应参考</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>头饰</td><td>保留一轮头巾,头巾边缘加入工程测量线和小铆钉纹;不要改成工人安全帽。</td><td><span class="ref-code">I-01</span>头巾;<span class="ref-code">I-03</span>工程链条质感。</td></tr>
|
||||
<tr><td>头发</td><td>保留紫色短发和清爽脸侧轮廓;避免被头巾和护具完全遮住。</td><td><span class="ref-code">I-01</span>发型。</td></tr>
|
||||
<tr><td>脸部</td><td>可靠、坚决、略微微笑,像能指挥巨构的人;不做粗糙工人脸。</td><td><span class="ref-code">I-01</span>角色脸;<span class="ref-code">I-03</span>只看工程姿态。</td></tr>
|
||||
<tr><td>表情</td><td>沉稳可靠,面对镜头但注意力像在指挥云山出拳。</td><td><span class="ref-code">I-01/I-02</span>一轮与云山关系。</td></tr>
|
||||
<tr><td>上衣</td><td>僧兵上衣保留,外加短工程外套或皮质护臂;肩部可有铆钉和链条固定点。</td><td><span class="ref-code">I-03</span>链条尺度;<span class="ref-code">R-02</span>结构线。</td></tr>
|
||||
<tr><td>下衣</td><td>裙装或宽裤都可,但下盘必须稳;用铁灰、米白、棕皮革做工程车职阶重量。</td><td><span class="ref-code">I-03</span>工程厚重;<span class="ref-code">R-02</span>垂直结构。</td></tr>
|
||||
<tr><td>腰带</td><td>宽工具腰带,挂扳手、铆钉包或卷尺;腰带不要像现代工装。</td><td><span class="ref-code">I-03</span>工程工具气质;<span class="ref-code">R-03</span>腰带边纹。</td></tr>
|
||||
<tr><td>披风</td><td>可无披风。若需要轮廓,做成短帆布工程披肩,内侧有桥梁弧线纹。</td><td><span class="ref-code">R-02</span>哥特结构;<span class="ref-code">I-04</span>工程线条。</td></tr>
|
||||
<tr><td>袜子</td><td>白袜或灰袜,袜口简洁;不要花纹太密,避免与云山抢视觉。</td><td><span class="ref-code">I-01</span>角色简洁度。</td></tr>
|
||||
<tr><td>鞋子</td><td>结实短靴,棕皮或黑皮,少量金属扣;体现车职阶稳定。</td><td><span class="ref-code">I-03</span>工程实用感。</td></tr>
|
||||
<tr><td>武器</td><td>一轮本人不拿大型武器,核心武器是云山拳。可手持短法杖/工程指挥杆。</td><td><span class="ref-code">I-02</span>云山拳;<span class="ref-code">I-03</span>指挥工程姿态。</td></tr>
|
||||
<tr><td>动作</td><td>一轮站前方指挥,云山在背后形成巨拳或桥拱般的轮廓;动作稳,不要飞太轻。</td><td><span class="ref-code">I-02</span>巨拳;<span class="ref-code">I-03/I-04</span>巨构尺度。</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="refs">
|
||||
<figure>
|
||||
<img src="refs/touhou_ichirin_official.webp" alt="Touhou Ichirin official art">
|
||||
<figcaption><b>I-01 东方识别</b> 云居一轮官方立绘转引。<br><b>设计用途</b> 头巾、僧兵服、清爽可靠表情。<br><b>边界</b> 不让工程服吞掉头巾。<br><a href="https://touhou-project.news/character/129/">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="refs/touhou_unzan_official.webp" alt="Touhou Unzan official art">
|
||||
<figcaption><b>I-02 东方识别</b> 云山官方立绘转引。<br><b>设计用途</b> 巨拳、云轮廓、一轮背后武器感。<br><b>边界</b> 不机械化替换云山本体。<br><a href="https://touhou-project.news/character/130/">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="refs/brunel_chains.jpg" alt="Brunel with Great Eastern chains">
|
||||
<figcaption><b>I-03 历史人物</b> The Met 2005.100.11,Brunel 与 Great Eastern 链条。<br><b>设计用途</b> 工程尺度、链条、站姿、铁构气质。<br><b>边界</b> 不复制雪茄、男性脸和照片构图。<br><a href="https://www.metmuseum.org/art/collection/search/283083">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="refs/met_palace_paneling.jpg" alt="Gothic structural paneling">
|
||||
<figcaption><b>I-04 结构参考</b> The Met 2015.674,哥特复兴墙板。<br><b>设计用途</b> 桥拱/铁构重复线,转为云山背后轮廓。<br><b>边界</b> 不做教堂背景,不抠纹样。<br><a href="https://www.metmuseum.org/art/collection/search/698523">source</a></figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>交付要求</h2>
|
||||
<div class="note">
|
||||
圣白莲不重画,只保留现有图作为阵营基准。其余四人需要全身透明 PNG + 分层 PSD;头部、头饰、主武器、披风/背后物、特效分层。头像裁切必须保留角色识别点:星的虎耳与宝塔、娜兹玲的鼠耳与探宝杆、村纱的水手帽与锚、一轮的头巾与云山。复杂历史实物只作为结构参考;衣边、腰带、披风边、书脊和徽章边框优先使用干净平面纹饰图版重新绘制。
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 573 KiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 286 KiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 441 KiB |
|
After Width: | Height: | Size: 252 KiB |
|
After Width: | Height: | Size: 405 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 222 KiB |
|
After Width: | Height: | Size: 283 KiB |
96
Design/final/narrative/character-art-briefs/index.html
Normal file
@ -0,0 +1,96 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 角色立绘设计需求索引</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f4f1ea;
|
||||
--paper: #fffaf0;
|
||||
--ink: #1f2528;
|
||||
--muted: #667078;
|
||||
--line: #c9c0b1;
|
||||
--navy: #1d3654;
|
||||
--gold: #ad8737;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
font-family: "Microsoft YaHei", "Noto Sans SC", Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
}
|
||||
main {
|
||||
max-width: 980px;
|
||||
margin: 0 auto;
|
||||
padding: 28px;
|
||||
}
|
||||
h1, h2, p { margin: 0; }
|
||||
header {
|
||||
border-bottom: 2px solid var(--ink);
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
h1 { font-size: 30px; }
|
||||
.sub { margin-top: 8px; color: var(--muted); font-size: 14px; }
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
article {
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 18px;
|
||||
color: var(--navy);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
a {
|
||||
color: var(--navy);
|
||||
font-weight: 600;
|
||||
}
|
||||
.tag {
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
border: 1px solid var(--line);
|
||||
color: var(--gold);
|
||||
padding: 3px 7px;
|
||||
font-size: 12px;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
main { padding: 16px; }
|
||||
.grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header>
|
||||
<h1>TH1 角色立绘设计需求索引</h1>
|
||||
<p class="sub">集中归档阵营五职阶角色立绘 brief、legacy HTML 和参考图,避免散落在外部 OneDrive 目录。</p>
|
||||
</header>
|
||||
|
||||
<section class="grid">
|
||||
<article>
|
||||
<h2>命莲帝国 / 英格兰</h2>
|
||||
<p>旧版 legacy HTML 归档,包含本地 refs 参考图。</p>
|
||||
<p><a href="byakuren-myouren/index.html">打开命莲英格兰设计需求</a></p>
|
||||
<span class="tag">legacy imported</span>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<h2>丰聪耳帝国 / 萨珊波斯</h2>
|
||||
<p>新版五职阶立绘需求:神子、屠自古、青娥、布都、芳香。</p>
|
||||
<p><a href="sasanian-toyosatomimi.html">打开萨珊波斯丰聪耳设计需求</a></p>
|
||||
<span class="tag">current draft</span>
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,426 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>丰聪耳帝国|萨珊波斯五职阶立绘需求</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f3efe6;
|
||||
--paper: #fffaf0;
|
||||
--ink: #202426;
|
||||
--muted: #667078;
|
||||
--line: #c9beaa;
|
||||
--royal: #263f67;
|
||||
--lapis: #245f7a;
|
||||
--gold: #b88932;
|
||||
--wine: #7e3030;
|
||||
--jade: #3d7663;
|
||||
--ash: #5f6568;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
font-family: "Microsoft YaHei", "Noto Sans SC", Arial, sans-serif;
|
||||
line-height: 1.48;
|
||||
}
|
||||
main {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 28px;
|
||||
}
|
||||
h1, h2, h3, h4, p { margin: 0; }
|
||||
header.top {
|
||||
border-bottom: 2px solid var(--ink);
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
h1 { font-size: 30px; letter-spacing: 0; }
|
||||
h2 {
|
||||
margin: 24px 0 10px;
|
||||
padding-left: 10px;
|
||||
border-left: 5px solid var(--gold);
|
||||
font-size: 18px;
|
||||
}
|
||||
h3 { font-size: 20px; color: var(--royal); }
|
||||
h4 { margin-bottom: 5px; font-size: 14px; color: var(--wine); }
|
||||
a { color: var(--royal); text-decoration: underline; }
|
||||
.sub { margin-top: 8px; color: var(--muted); font-size: 14px; }
|
||||
.chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.chip {
|
||||
border: 1px solid var(--line);
|
||||
background: var(--paper);
|
||||
padding: 5px 9px;
|
||||
font-size: 13px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid var(--line);
|
||||
padding: 8px 9px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
font-size: 14px;
|
||||
}
|
||||
th {
|
||||
background: var(--royal);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
.steps, .design-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
.refs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.step, .cell, .note, .priority {
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.char {
|
||||
margin-top: 18px;
|
||||
padding: 14px;
|
||||
background: rgba(255, 250, 240, .75);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.char-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
align-items: baseline;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.role {
|
||||
color: var(--gold);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
text-align: right;
|
||||
}
|
||||
.thesis {
|
||||
margin: 0 0 10px;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
.priorities {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.priority ul {
|
||||
margin: 5px 0 0 18px;
|
||||
padding: 0;
|
||||
}
|
||||
.priority li { margin: 2px 0; }
|
||||
figure {
|
||||
margin: 0;
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
figure img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 210px;
|
||||
object-fit: contain;
|
||||
background: #e8e0d1;
|
||||
}
|
||||
figcaption {
|
||||
padding: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--ink);
|
||||
}
|
||||
figcaption b { color: var(--royal); }
|
||||
.warn { color: var(--wine); font-weight: 600; }
|
||||
.palette {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.sw {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 1px solid rgba(0,0,0,.25);
|
||||
display: inline-block;
|
||||
}
|
||||
.small { font-size: 12px; color: var(--muted); }
|
||||
@media (max-width: 980px) {
|
||||
.steps, .design-grid, .refs, .priorities { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
main { padding: 16px; }
|
||||
h1 { font-size: 24px; }
|
||||
.steps, .design-grid, .refs, .priorities { grid-template-columns: 1fr; }
|
||||
.char-head { display: block; }
|
||||
.role { text-align: left; margin-top: 4px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header class="top">
|
||||
<h1>第七阵营:萨珊波斯|丰聪耳帝国五职阶立绘需求</h1>
|
||||
<p class="sub">霍斯劳一世哲人王庭 + 萨珊银盘与王冠 + 火坛纹样 + 贵族骑兵 + 末代守国防线。目标是让玩家一眼读到“神子阵营”和“萨珊波斯”,而不是普通沙漠/阿拉伯奇幻。</p>
|
||||
<div class="chips">
|
||||
<span class="chip">先定东方识别点</span>
|
||||
<span class="chip">再定萨珊职阶外壳</span>
|
||||
<span class="chip">再定动作表情</span>
|
||||
<span class="chip">最后加帝国纹样</span>
|
||||
<span class="chip">参考图只取结构,不照搬构图</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<h2>设计流程</h2>
|
||||
<div class="steps">
|
||||
<div class="step"><h4>1 基础元素</h4><p>锁定东方角色的发型、帽饰、道具、体态和关系链。任何萨珊元素都不能盖掉角色本体。</p></div>
|
||||
<div class="step"><h4>2 色彩剪影</h4><p>白金、青蓝、孔雀绿、酒红、铁灰分配给五人,避免全员同一种金色袍服。</p></div>
|
||||
<div class="step"><h4>3 动作表情</h4><p>王要冷静,后要威压,相要温柔危险,马要前冲,车要沉重迟滞。</p></div>
|
||||
<div class="step"><h4>4 文明纹饰</h4><p>王冠球饰、翼冠、火坛、珠链、银盘狩猎纹、锁子甲、马具、帕列维铭文感只做局部点缀。</p></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>角色映射</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>职阶</th><th>东方角色</th><th>历史锚点</th><th>设计定位</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>王 / King</td><td>丰聪耳神子</td><td>霍斯劳一世 Anushirvan</td><td>哲人王、公正者、制度重建者。</td></tr>
|
||||
<tr><td>后 / Queen</td><td>苏我屠自古</td><td>波兰杜赫特 Boran / Borandukht</td><td>王统合法性的幽雷,帝国末光中的女王。</td></tr>
|
||||
<tr><td>相 / Minister</td><td>霍青娥</td><td>布佐尔格米赫尔 Bozorgmehr</td><td>贤相外壳下的异端智囊,知识、医术、禁术入口。</td></tr>
|
||||
<tr><td>马 / Knight</td><td>物部布都</td><td>巴赫拉姆·楚宾 Bahram Chobin</td><td>强弓骑将,忠诚、骄傲、反骨和突击。</td></tr>
|
||||
<tr><td>车 / Rook</td><td>宫古芳香</td><td>罗斯塔姆·法罗赫扎德 Rostam Farrokhzad</td><td>末代守国大将,被驱动的不死堡垒。</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>证据规则</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>等级</th><th>可用来源</th><th>用法</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>A</td><td>博物馆馆藏、开放藏品 API、考古遗址照片、学术百科</td><td>王冠、火坛、银盘纹样、重甲、马具、金币头像等硬证据。</td></tr>
|
||||
<tr><td>B</td><td>东方官方立绘转引页、游戏内现有神子资源</td><td>角色识别点和本作既有方向;不得复制画风和构图。</td></tr>
|
||||
<tr><td>C</td><td>Wikimedia Commons 但链接到馆藏或遗址</td><td>只做展示图,证据仍以原机构、遗址或可核查说明为准。</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>全局萨珊视觉母题</h2>
|
||||
<div class="refs">
|
||||
<figure>
|
||||
<img src="https://images.metmuseum.org/CRDImages/an/web-large/DP-16934-017.jpg" alt="Sasanian coin with crown and profile">
|
||||
<figcaption><b>证据</b> The Met 99.35.2967,萨珊银币。<br><b>用</b> 侧面王冠、珠链、外圈铭文感。<br><b>禁</b> 把硬币头像照搬成角色脸。<br><a href="https://www.metmuseum.org/art/collection/search/322365">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="https://images.metmuseum.org/CRDImages/an/web-large/DP-42233-001.jpg" alt="Sasanian plate with king hunting rams">
|
||||
<figcaption><b>证据</b> The Met 34.33,萨珊银盘,王者狩猎。<br><b>用</b> 圆形构图、马具、王者狩猎纹。<br><b>禁</b> 把狩猎场景整张贴到衣服上。<br><a href="https://www.metmuseum.org/art/collection/search/322973">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="https://images.metmuseum.org/CRDImages/an/web-large/DT235323.jpg" alt="Sasanian plate with king slaying stag">
|
||||
<figcaption><b>证据</b> The Met 1970.6,Yazdgard I 猎鹿银盘。<br><b>用</b> 翼冠、披带、动物纹节奏。<br><b>禁</b> 变成狩猎主题阵营,狩猎只做纹样。<br><a href="https://www.metmuseum.org/art/collection/search/326007">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="https://openaccess-cdn.clevelandart.org/1963.258/1963.258_web.jpg" alt="Cleveland Sasanian king hunting lions">
|
||||
<figcaption><b>证据</b> Cleveland Museum of Art 1963.258,萨珊王猎狮浮雕盘。<br><b>用</b> 浮雕化线条、王权与猛兽对抗感。<br><b>禁</b> 粗暴兽皮化或野蛮化。<br><a href="https://clevelandart.org/art/1963.258">source</a></figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>角色需求</h2>
|
||||
|
||||
<article class="char">
|
||||
<div class="char-head">
|
||||
<h3>丰聪耳神子</h3>
|
||||
<p class="role">王 / 霍斯劳一世 Anushirvan</p>
|
||||
</div>
|
||||
<p class="thesis">设计一句话:把神子的耳机式圣王轮廓做成萨珊翼冠与哲人王法令的融合,像一个已经把帝国制度全部推演完的复活君主。</p>
|
||||
<div class="design-grid">
|
||||
<div class="cell"><h4>1 基础元素</h4><p>保留神子紫灰短发、耳机/耳罩式头部轮廓、七星剑/笏板感、冷静自信的圣王姿态。</p></div>
|
||||
<div class="cell"><h4>2 色彩剪影</h4><p class="palette"><span class="sw" style="background:#f7f0de"></span><span class="sw" style="background:#c79a35"></span><span class="sw" style="background:#5e4f8f"></span><span class="sw" style="background:#245f7a"></span>白金为主,紫与青蓝为神子识别色;上半身冠饰要清楚。</p></div>
|
||||
<div class="cell"><h4>3 动作表情</h4><p>一手持法令卷轴或笏板,一手轻压剑柄/火坛纹权杖。表情温和但不松弛,眼神像在裁决。</p></div>
|
||||
<div class="cell"><h4>4 文明纹饰</h4><p>翼冠、王冠球饰、珠链、火坛小徽章、圆形银盘纹边。<span class="warn">不要做成阿拉伯风、印度风或沙漠商人风。</span></p></div>
|
||||
</div>
|
||||
<div class="priorities">
|
||||
<div class="priority"><h4>Must keep</h4><ul><li>神子的头部耳机轮廓。</li><li>短发和圣王感。</li><li>“能听取众声”的理性气质。</li></ul></div>
|
||||
<div class="priority"><h4>Must add</h4><ul><li>萨珊王冠或翼冠结构。</li><li>法令、卷轴、火坛徽记。</li><li>白金主色与紫色识别。</li></ul></div>
|
||||
<div class="priority"><h4>Can adjust</h4><ul><li>七星剑可简化为礼剑。</li><li>耳机可与冠侧翼融合。</li><li>披帛可做成王袍垂带。</li></ul></div>
|
||||
<div class="priority"><h4>Avoid</h4><ul><li>厚重到遮住神子体型。</li><li>过多宗教火焰符号。</li><li>复制霍斯劳二世塔克波斯坦构图。</li></ul></div>
|
||||
</div>
|
||||
<div class="refs">
|
||||
<figure>
|
||||
<img src="https://images.metmuseum.org/CRDImages/an/web-large/DP-16934-017.jpg" alt="Sasanian crown coin">
|
||||
<figcaption><b>用</b> 王冠、珠链、圆形铭文边。<br><b>不复制</b> 硬币侧脸和具体铭文。</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="https://images.metmuseum.org/CRDImages/an/web-large/DT235323.jpg" alt="Sasanian royal plate">
|
||||
<figcaption><b>用</b> 王者冠饰、披带、圆盘纹样。<br><b>不复制</b> 猎鹿动作。</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="char">
|
||||
<div class="char-head">
|
||||
<h3>苏我屠自古</h3>
|
||||
<p class="role">后 / 波兰杜赫特 Boran</p>
|
||||
</div>
|
||||
<p class="thesis">设计一句话:把屠自古的怨灵雷电做成萨珊末代女王的王统残光,既是神子王权旁边的合法性,也是阴影里的幽雷。</p>
|
||||
<div class="design-grid">
|
||||
<div class="cell"><h4>1 基础元素</h4><p>保留绿色长发、怨灵漂浮感、无足或半透明下摆、雷电符号、偏冷的古代贵族气质。</p></div>
|
||||
<div class="cell"><h4>2 色彩剪影</h4><p class="palette"><span class="sw" style="background:#3d7663"></span><span class="sw" style="background:#d7d9d3"></span><span class="sw" style="background:#b88932"></span><span class="sw" style="background:#2f4f60"></span>绿、银白、旧金、青黑;轮廓要比神子更尖、更冷。</p></div>
|
||||
<div class="cell"><h4>3 动作表情</h4><p>漂浮侧身,手指像在捏住雷光或王印。表情可以不满、冷笑、沉默威压,不能甜美。</p></div>
|
||||
<div class="cell"><h4>4 文明纹饰</h4><p>女王翼冠、月牙与球饰、耳坠、项链、王袍珠边。雷电从冠饰和袖口渗出,不做现代电流特效。</p></div>
|
||||
</div>
|
||||
<div class="priorities">
|
||||
<div class="priority"><h4>Must keep</h4><ul><li>屠自古绿发和幽灵下半身。</li><li>雷电、怨气、古代贵族感。</li><li>和神子存在近臣/旧王权关系。</li></ul></div>
|
||||
<div class="priority"><h4>Must add</h4><ul><li>波兰杜赫特金币上的翼冠概念。</li><li>女王珠链、耳坠、王袍。</li><li>“末代王统”的破损金饰。</li></ul></div>
|
||||
<div class="priority"><h4>Can adjust</h4><ul><li>冠饰可比神子小一圈。</li><li>雷纹可放在披帛内侧。</li><li>下摆可半透明化。</li></ul></div>
|
||||
<div class="priority"><h4>Avoid</h4><ul><li>做成普通王妃或贵妇。</li><li>把雷电画成科幻蓝电。</li><li>用希琳式浪漫宫廷方向替代王统感。</li></ul></div>
|
||||
</div>
|
||||
<div class="refs">
|
||||
<figure>
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/0/0a/Gold_coin_of_Boran%2C_daughter_of_Khusrau_II.jpg" alt="Gold coin of Boran">
|
||||
<figcaption><b>证据</b> MFA Boston 147596 转引,Boran 金币。<br><b>用</b> 女王翼冠、月牙球饰、耳坠项链。<br><b>不复制</b> 硬币脸和站像姿势。<br><a href="https://collections.mfa.org/objects/147596">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="https://images.metmuseum.org/CRDImages/an/web-large/DP247427.jpg" alt="Late Sasanian drachm">
|
||||
<figcaption><b>证据</b> The Met 36.30.306,萨珊晚期银币。<br><b>用</b> 火坛/铭文边的图案感。<br><b>不复制</b> 具体币文。<br><a href="https://www.metmuseum.org/art/collection/search/323485">source</a></figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="char">
|
||||
<div class="char-head">
|
||||
<h3>霍青娥</h3>
|
||||
<p class="role">相 / 布佐尔格米赫尔 Bozorgmehr</p>
|
||||
</div>
|
||||
<p class="thesis">设计一句话:把青娥做成哲人王身边的异端贤相,她把医术、星相、翻译、禁术和政治谋略全部包装成治国智慧。</p>
|
||||
<div class="design-grid">
|
||||
<div class="cell"><h4>1 基础元素</h4><p>保留蓝发、道士/仙人服、发簪、飘带、温柔但不可信的笑容,以及与芳香的操控关系。</p></div>
|
||||
<div class="cell"><h4>2 色彩剪影</h4><p class="palette"><span class="sw" style="background:#2e88a2"></span><span class="sw" style="background:#101820"></span><span class="sw" style="background:#d8c28a"></span><span class="sw" style="background:#f2ead6"></span>青蓝、黑、药石白、暗金;比神子更轻、更曲线。</p></div>
|
||||
<div class="cell"><h4>3 动作表情</h4><p>一手展开星盘或卷轴,一手牵出细线/符纸连向芳香。笑容要礼貌,但眼神像在试探。</p></div>
|
||||
<div class="cell"><h4>4 文明纹饰</h4><p>宫廷书记官腰带、药瓶、火坛小印、帕列维铭文感边饰、棋盘格。萨珊元素放在腰带、袖口、卷轴夹具上。</p></div>
|
||||
</div>
|
||||
<div class="priorities">
|
||||
<div class="priority"><h4>Must keep</h4><ul><li>青娥的蓝发和仙人感。</li><li>温柔危险的表情。</li><li>操控芳香的符线或药术联系。</li></ul></div>
|
||||
<div class="priority"><h4>Must add</h4><ul><li>贤相/智者的卷轴、星盘、药瓶。</li><li>宫廷顾问的腰带和印章。</li><li>萨珊火坛或银盘纹小徽记。</li></ul></div>
|
||||
<div class="priority"><h4>Can adjust</h4><ul><li>道服可改成长袍式开衫。</li><li>发簪可融合鸟翼或月牙。</li><li>鞋履可轻量化。</li></ul></div>
|
||||
<div class="priority"><h4>Avoid</h4><ul><li>过分邪恶导致不像相。</li><li>学者化到失去仙人魅力。</li><li>用宗教祭司形象压过东方道士识别。</li></ul></div>
|
||||
</div>
|
||||
<div class="refs">
|
||||
<figure>
|
||||
<img src="https://images.metmuseum.org/CRDImages/an/web-large/DP-42233-001.jpg" alt="Sasanian silver plate detail">
|
||||
<figcaption><b>用</b> 银盘边框和宫廷器物的高级感。<br><b>不复制</b> 王者狩猎主体。</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="https://openaccess-cdn.clevelandart.org/1963.258/1963.258_web.jpg" alt="Sasanian relief linework">
|
||||
<figcaption><b>用</b> 浮雕线条转化为袖口/卷轴边。<br><b>不复制</b> 狮猎故事。</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="char">
|
||||
<div class="char-head">
|
||||
<h3>物部布都</h3>
|
||||
<p class="role">马 / 巴赫拉姆·楚宾 Bahram Chobin</p>
|
||||
</div>
|
||||
<p class="thesis">设计一句话:把布都的古代祭祀行动派转成萨珊强弓骑将,白袍、火祭、骑射和反骨贵族感要同时成立。</p>
|
||||
<div class="design-grid">
|
||||
<div class="cell"><h4>1 基础元素</h4><p>保留布都帽子、短发、白绿服色、祭祀笏板/盘子感、向前冲的莽撞自信。</p></div>
|
||||
<div class="cell"><h4>2 色彩剪影</h4><p class="palette"><span class="sw" style="background:#f6f1df"></span><span class="sw" style="background:#6ea37b"></span><span class="sw" style="background:#b88932"></span><span class="sw" style="background:#7e3030"></span>白绿为主,旧金和酒红做军功色;轮廓要轻快、有斜线。</p></div>
|
||||
<div class="cell"><h4>3 动作表情</h4><p>身体前倾,像刚下令冲锋。可一手持短弓/火箭,一手持祭祀笏板;表情骄傲、亮眼、带一点不服管。</p></div>
|
||||
<div class="cell"><h4>4 文明纹饰</h4><p>萨珊骑兵甲片、弓袋、马具铃、火坛纹护臂。不要真的画马,最多让披帛和腰带呈马具节奏。</p></div>
|
||||
</div>
|
||||
<div class="priorities">
|
||||
<div class="priority"><h4>Must keep</h4><ul><li>布都帽子和白绿识别。</li><li>祭祀感和古代氏族感。</li><li>行动派、冲锋、骄傲。</li></ul></div>
|
||||
<div class="priority"><h4>Must add</h4><ul><li>萨珊强弓骑将元素。</li><li>甲片、弓袋、短披风。</li><li>火祭纹样和军功金饰。</li></ul></div>
|
||||
<div class="priority"><h4>Can adjust</h4><ul><li>盘子可改成火坛纹圆盾。</li><li>帽缘可加小型翼饰。</li><li>袖子可收窄利于运动。</li></ul></div>
|
||||
<div class="priority"><h4>Avoid</h4><ul><li>做成重甲车职阶。</li><li>过度男性化名将复刻。</li><li>完全丢掉布都的祭祀喜感。</li></ul></div>
|
||||
</div>
|
||||
<div class="refs">
|
||||
<figure>
|
||||
<img src="https://images.metmuseum.org/CRDImages/an/web-large/DP-42233-001.jpg" alt="Sasanian royal horse and hunting plate">
|
||||
<figcaption><b>用</b> 马具、弓袋、动态斜线。<br><b>不复制</b> 君王骑射构图。</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="https://images.metmuseum.org/CRDImages/an/web-large/DT235323.jpg" alt="Sasanian royal hunting plate">
|
||||
<figcaption><b>用</b> 军事披带、王者狩猎纹做护臂小图案。<br><b>不复制</b> 整体猎鹿动作。</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="char">
|
||||
<div class="char-head">
|
||||
<h3>宫古芳香</h3>
|
||||
<p class="role">车 / 罗斯塔姆·法罗赫扎德 Rostam Farrokhzad</p>
|
||||
</div>
|
||||
<p class="thesis">设计一句话:把芳香做成死后仍守住帝国边境的末代重装堡垒,僵尸的迟钝和萨珊重甲的厚度必须合成一个沉重剪影。</p>
|
||||
<div class="design-grid">
|
||||
<div class="cell"><h4>1 基础元素</h4><p>保留芳香僵尸符、青白肤色、僵硬姿势、红眼/呆滞感、被青娥控制的从属关系。</p></div>
|
||||
<div class="cell"><h4>2 色彩剪影</h4><p class="palette"><span class="sw" style="background:#a9c7bb"></span><span class="sw" style="background:#5f6568"></span><span class="sw" style="background:#7e3030"></span><span class="sw" style="background:#b88932"></span>尸青、铁灰、暗红、旧金;下盘稳,上半身像残破甲塔。</p></div>
|
||||
<div class="cell"><h4>3 动作表情</h4><p>双脚或衣摆沉重落地,手拖塔盾、断旗或长枪。表情空白,不需要热血;恐怖点来自“还在执行命令”。</p></div>
|
||||
<div class="cell"><h4>4 文明纹饰</h4><p>萨珊重甲、锁子甲、塔盾、破战旗、火坛残印。青娥符纸必须可见,说明她不是独立行动。</p></div>
|
||||
</div>
|
||||
<div class="priorities">
|
||||
<div class="priority"><h4>Must keep</h4><ul><li>芳香僵尸符和呆滞感。</li><li>青白尸色和僵硬动作。</li><li>与青娥的控制关系。</li></ul></div>
|
||||
<div class="priority"><h4>Must add</h4><ul><li>萨珊重甲与塔盾。</li><li>末代守国破旗。</li><li>旧金火坛残印。</li></ul></div>
|
||||
<div class="priority"><h4>Can adjust</h4><ul><li>手臂可加不对称护甲。</li><li>长枪可换成断旗杆。</li><li>符纸可写成伪帕列维纹样。</li></ul></div>
|
||||
<div class="priority"><h4>Avoid</h4><ul><li>画得太可爱、太轻。</li><li>与布都抢骑兵概念。</li><li>甲胄遮掉僵尸符和脸。</li></ul></div>
|
||||
</div>
|
||||
<div class="refs">
|
||||
<figure>
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/4/4a/Taq-e_Bostan_-_equestrian_statue.jpg" alt="Taq-e Bostan armored rider">
|
||||
<figcaption><b>证据</b> Taq-e Bostan 重甲骑士浮雕,Commons 展示。<br><b>用</b> 锁甲、重甲层次、厚重轮廓。<br><b>不复制</b> 骑马构图和霍斯劳二世身份。<br><a href="https://commons.wikimedia.org/wiki/File:Taq-e_Bostan_-_equestrian_statue.jpg">source</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="https://openaccess-cdn.clevelandart.org/1963.258/1963.258_web.jpg" alt="Sasanian relief heavy ornamental line">
|
||||
<figcaption><b>用</b> 浮雕线条转成盾面、护甲和断旗纹。<br><b>不复制</b> 狮猎故事。</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>全员共同规则</h2>
|
||||
<div class="note">
|
||||
五人必须能在头像裁切里辨认:神子看冠侧耳机,屠自古看绿发幽雷,青娥看蓝发仙人和符线,布都看白绿帽子与冲锋斜线,芳香看僵尸符和重甲塔盾。萨珊元素只负责帝国版本,不得替换东方角色本体。避免阿拉伯化、印度化、现代波斯民族服饰化、宗教嘲讽化、沙漠盗贼化。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>交付要求</h2>
|
||||
<div class="note">
|
||||
全身透明 PNG + 分层 PSD。每人至少分出:身体、头发、头饰、主道具、背后披帛/旗帜、特效。头像安全区需要覆盖头部到胸口,确保 256px UI 缩放仍能读出角色和职阶。外包阶段必须过三次检查:草图看识别点,色稿看阵营统一性,精修看纹样是否过密。参考图只取结构、材质和纹样逻辑,不照搬画面构图或具体人物脸。
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
630
Design/final/narrative/奇观设计/英格兰奇观草稿.html
Normal file
@ -0,0 +1,630 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>文案剧情 / 奇观设计 / 英格兰奇观草稿</title>
|
||||
<link rel="stylesheet" href="../../../shared/design-doc.css">
|
||||
<style>
|
||||
.wonder-hero { border-top: 5px solid #1d4ed8; }
|
||||
.wonder-nav { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
|
||||
.wonder-nav a {
|
||||
background: #fff;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
color: var(--ink);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
.spec-grid,
|
||||
.style-rule-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
.style-rule-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
.spec-grid article,
|
||||
.plan-card,
|
||||
.ref-card,
|
||||
.style-rule {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: #fbfdff;
|
||||
padding: 14px;
|
||||
}
|
||||
.spec-grid h3,
|
||||
.plan-card h3,
|
||||
.ref-card h3,
|
||||
.style-rule h3 {
|
||||
color: #1d4ed8;
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.spec-grid p,
|
||||
.plan-card p,
|
||||
.ref-card p,
|
||||
.style-rule p {
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
.wonder-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.12fr) minmax(320px, .88fr);
|
||||
gap: 18px;
|
||||
align-items: start;
|
||||
}
|
||||
.plan-list { display: grid; gap: 10px; }
|
||||
.plan-card ul {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
.plan-card li { margin: 4px 0; }
|
||||
.palette { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
|
||||
.swatch {
|
||||
width: 36px;
|
||||
height: 22px;
|
||||
border: 1px solid rgba(15,23,42,.22);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.ref-card { background: #f8fafc; }
|
||||
.reference-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.reference-grid a {
|
||||
display: flex;
|
||||
min-height: 104px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
color: var(--ink);
|
||||
padding: 10px;
|
||||
}
|
||||
.reference-grid a:hover {
|
||||
border-color: #93c5fd;
|
||||
text-decoration: none;
|
||||
}
|
||||
.reference-grid strong {
|
||||
color: #1d4ed8;
|
||||
font-size: 13px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.reference-grid span {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.35;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.source-links {
|
||||
margin: 8px 0 0;
|
||||
padding-left: 18px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
.source-links li { margin: 4px 0; }
|
||||
.search-block {
|
||||
margin-top: 10px;
|
||||
border-top: 1px dashed var(--line);
|
||||
padding-top: 10px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
.search-block code {
|
||||
display: inline-block;
|
||||
margin: 3px 4px 3px 0;
|
||||
background: #eef2f7;
|
||||
border-radius: 4px;
|
||||
color: #334155;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
.contact-sheet {
|
||||
width: 100%;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.prompt-block {
|
||||
margin-top: 8px;
|
||||
border: 1px solid #bfdbfe;
|
||||
border-radius: 6px;
|
||||
background: #eff6ff;
|
||||
color: #1e3a8a;
|
||||
font-size: 13px;
|
||||
line-height: 1.65;
|
||||
padding: 10px;
|
||||
}
|
||||
@media (max-width: 980px) {
|
||||
.spec-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.wonder-layout { grid-template-columns: 1fr; }
|
||||
.style-rule-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
@media (max-width: 620px) {
|
||||
.spec-grid,
|
||||
.reference-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell narrative-shell">
|
||||
<header class="doc-hero compact wonder-hero">
|
||||
<p class="eyebrow">Narrative / Wonder Draft / England</p>
|
||||
<h1>英格兰文明七大奇观美术需求草稿</h1>
|
||||
<p class="lead">本稿用于英格兰文明奇观建筑 Sprite 设计。规格参考维京奇观需求稿与现有落地奇观图:小体量、透明底、等距 3/4 俯视、单格地图可读,不做横向大场景插画。</p>
|
||||
<div class="meta-grid">
|
||||
<div><span>目标文明</span><strong>英格兰文明</strong></div>
|
||||
<div><span>枚举预留</span><strong>BritishByakuren 七类奇观</strong></div>
|
||||
<div><span>图像类型</span><strong>地图奇观建筑 Sprite</strong></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav class="wonder-nav" aria-label="英格兰奇观导航">
|
||||
<a href="#global">全局规格</a>
|
||||
<a href="#peace">和平</a>
|
||||
<a href="#knowledge">知识</a>
|
||||
<a href="#trade">贸易</a>
|
||||
<a href="#wealth">财富</a>
|
||||
<a href="#war">战争</a>
|
||||
<a href="#culture">文化</a>
|
||||
<a href="#explore">探索</a>
|
||||
<a href="#style">风格参考</a>
|
||||
</nav>
|
||||
|
||||
<section id="global" class="doc-section note">
|
||||
<h2>全局规格</h2>
|
||||
<p class="lead">英格兰奇观的核心不是写实复刻完整建筑,而是把“哥特塔楼、石堡、议会、学院、交易所、教堂、天文台”压缩成可读的等距地图件。远看先识别类别,近看再看到英格兰文明细节。</p>
|
||||
<div class="spec-grid">
|
||||
<article>
|
||||
<h3>画幅与主体</h3>
|
||||
<p>沿用现有奇观图规格:透明底,主体集中在中心,保留大透明边距。建筑主体不要铺满画面,避免变成 UI 插画或地标海报。</p>
|
||||
</article>
|
||||
<article>
|
||||
<h3>视角</h3>
|
||||
<p>等距 3/4 俯视。建筑底座统一朝同一方向,能看到屋顶、正面和侧面。禁止正视图、照片拼贴、远景城市天际线。</p>
|
||||
</article>
|
||||
<article>
|
||||
<h3>造型原则</h3>
|
||||
<p>每个奇观只保留 1 个主轮廓 + 2-4 个辅件。塔楼、尖拱、城垛、钟面、柱廊、草坪、旗帜只作为读图线索。</p>
|
||||
</article>
|
||||
<article>
|
||||
<h3>统一英格兰识别</h3>
|
||||
<p>浅灰石材、铅灰屋顶、深蓝阴影、王室红、少量金色、哥特尖塔、都铎木构、城堡垛口、狮纹与旗帜点缀。</p>
|
||||
</article>
|
||||
</div>
|
||||
<img class="contact-sheet" src="existing-wonders-contact.png" alt="现有四个阵营奇观 Sprite 对照图">
|
||||
</section>
|
||||
|
||||
<section id="peace" class="doc-section">
|
||||
<h2>和平奇观:威斯敏斯特宫</h2>
|
||||
<div class="wonder-layout">
|
||||
<div class="plan-list">
|
||||
<article class="plan-card">
|
||||
<h3>关键信息</h3>
|
||||
<p>英国议会所在地。主题是“议会、法治、公共秩序、制度连续性”,不要画成单纯钟楼旅游地标。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>图像长什么样</h3>
|
||||
<p>一个小型哥特议会建筑群:中央是横向议会大厅体块,右侧或后侧竖起伊丽莎白塔,屋顶为铅灰尖顶,正面有 3-5 个尖拱窗。底座可加泰晤士河蓝灰水边和一面小红蓝旗。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>关键模块设计</h3>
|
||||
<ul>
|
||||
<li>主轮廓:横向宫殿体块 + 一座高钟塔,钟面必须可读但不要细画数字。</li>
|
||||
<li>建筑语言:浅灰石墙、哥特尖拱、金色小尖顶、铅灰屋面。</li>
|
||||
<li>底座:一小段蓝灰河岸或石台阶,暗示伦敦与泰晤士河。</li>
|
||||
<li>和平感:少量旗帜和议会门廊即可,不要加军队或炮台。</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>配色</h3>
|
||||
<p>浅石灰、铅灰蓝、王室红、少量金色、泰晤士河蓝灰。整体要庄重、清晰,不要画得过暗或过密。</p>
|
||||
<div class="palette">
|
||||
<span class="swatch" style="background:#d1d5db"></span>
|
||||
<span class="swatch" style="background:#475569"></span>
|
||||
<span class="swatch" style="background:#991b1b"></span>
|
||||
<span class="swatch" style="background:#d6a446"></span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>不要这样画</h3>
|
||||
<p>不要只画大本钟单塔,不要画成现代伦敦明信片,也不要把建筑群铺满成横向全景。它应该是“议会制度建筑”而不是“钟楼图标”。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>生成提示核心句</h3>
|
||||
<div class="prompt-block">low-poly isometric Palace of Westminster map wonder, compact parliament hall with Elizabeth Tower, pale limestone, lead-gray roof, small clock face, tiny river edge, transparent background, TH1 strategy map sprite</div>
|
||||
</article>
|
||||
</div>
|
||||
<aside class="ref-card">
|
||||
<h3>明确参考入口</h3>
|
||||
<div class="reference-grid">
|
||||
<a href="https://whc.unesco.org/en/list/426"><strong>UNESCO:Westminster</strong><span>看威斯敏斯特宫、教堂与圣玛格丽特教堂的世界遗产定位。</span></a>
|
||||
<a href="https://www.parliament.uk/about/living-heritage/building/palace/"><strong>UK Parliament:Palace</strong><span>看议会建筑的制度属性与建筑说明。</span></a>
|
||||
<a href="https://commons.wikimedia.org/wiki/Category:Palace_of_Westminster"><strong>Commons 图库</strong><span>找伊丽莎白塔、宫殿横向轮廓和河岸角度。</span></a>
|
||||
</div>
|
||||
<ul class="source-links">
|
||||
<li>美术重点:钟塔只是识别符号,横向议会建筑必须一起出现。</li>
|
||||
<li>底座可以用一小块河岸,不要画完整伦敦城市背景。</li>
|
||||
</ul>
|
||||
<div class="search-block">搜索词:<code>Palace of Westminster isometric</code><code>Elizabeth Tower Palace of Westminster exterior</code><code>Westminster Gothic facade clock tower</code></div>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="knowledge" class="doc-section">
|
||||
<h2>知识奇观:牛津大学</h2>
|
||||
<div class="wonder-layout">
|
||||
<div class="plan-list">
|
||||
<article class="plan-card">
|
||||
<h3>关键信息</h3>
|
||||
<p>英格兰古老大学体系的代表。主题是“学院、图书馆、学术传统、石砌庭院”。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>图像长什么样</h3>
|
||||
<p>一个浓缩的牛津学院庭院:中心用拉德克利夫圆形阅览室或穹顶图书馆作为主轮廓,前方加小拱门和石院墙,两侧点缀尖塔与书卷。整体像“学院建筑模型”,不是单栋现代学校。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>关键模块设计</h3>
|
||||
<ul>
|
||||
<li>主轮廓:圆形阅览室/图书馆穹顶,配短柱廊和浅石墙。</li>
|
||||
<li>学院感:一小段四合院式石墙、拱门、草坪方块。</li>
|
||||
<li>知识符号:可放 1-2 个放大的书卷或蓝色小书,不要放文字。</li>
|
||||
<li>比例:穹顶与塔尖要大而简单,窗格只用少量深色切面表示。</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>配色</h3>
|
||||
<p>蜂蜜色石材、深灰屋顶、学院草坪绿、深蓝书本、少量金色高光。整体比威斯敏斯特更温暖、更学术。</p>
|
||||
<div class="palette">
|
||||
<span class="swatch" style="background:#d6c08f"></span>
|
||||
<span class="swatch" style="background:#334155"></span>
|
||||
<span class="swatch" style="background:#4d7c0f"></span>
|
||||
<span class="swatch" style="background:#1e3a8a"></span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>不要这样画</h3>
|
||||
<p>不要画成现代教学楼、图书馆书架室内图或抽象书本图标。建筑必须保留牛津学院石构和庭院气质。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>生成提示核心句</h3>
|
||||
<div class="prompt-block">low-poly isometric Oxford University wonder, compact Radcliffe Camera inspired round library, honey limestone, small college quad, arch gate, books as tiny props, transparent background</div>
|
||||
</article>
|
||||
</div>
|
||||
<aside class="ref-card">
|
||||
<h3>明确参考入口</h3>
|
||||
<div class="reference-grid">
|
||||
<a href="https://www.ox.ac.uk/visitors/visiting-oxford"><strong>University of Oxford</strong><span>看牛津大学整体访问入口和学院氛围。</span></a>
|
||||
<a href="https://visit.bodleian.ox.ac.uk/"><strong>Bodleian Libraries</strong><span>看图书馆与拉德克利夫阅览室的视觉语言。</span></a>
|
||||
<a href="https://commons.wikimedia.org/wiki/Category:Radcliffe_Camera"><strong>Commons:Radcliffe Camera</strong><span>找圆形图书馆、穹顶和学院石材参考。</span></a>
|
||||
</div>
|
||||
<ul class="source-links">
|
||||
<li>美术重点:用圆形图书馆解决“大学建筑群不好单格识别”的问题。</li>
|
||||
<li>庭院草坪只做底座,不要扩展成校园地图。</li>
|
||||
</ul>
|
||||
<div class="search-block">搜索词:<code>Radcliffe Camera Oxford exterior</code><code>Bodleian Library Oxford quadrangle</code><code>Oxford college honey stone arch</code></div>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="trade" class="doc-section">
|
||||
<h2>贸易奇观:伦敦皇家交易所</h2>
|
||||
<div class="wonder-layout">
|
||||
<div class="plan-list">
|
||||
<article class="plan-card">
|
||||
<h3>关键信息</h3>
|
||||
<p>伦敦商业与金融交易的地标。主题是“交易大厅、柱廊、商路汇聚、城市商业秩序”。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>图像长什么样</h3>
|
||||
<p>一个带三角山花和柱廊的小型交易所建筑,前方摆放货箱、账本、金币和两条交叉贸易路标。可在底座边缘加一小段码头木板,暗示伦敦海贸,但主体必须是交易所。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>关键模块设计</h3>
|
||||
<ul>
|
||||
<li>主轮廓:古典柱廊 + 三角屋顶,正面 4-6 根简化柱子。</li>
|
||||
<li>贸易符号:货箱、布包、金币盘、账册,不要堆太多小物。</li>
|
||||
<li>城市感:底座用石板街面和短台阶,而不是自然草地。</li>
|
||||
<li>英格兰感:可加红蓝小旗或金狮纹小盾牌,但不能变成国徽海报。</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>配色</h3>
|
||||
<p>浅石材、深蓝屋顶阴影、金币黄、木箱棕、王室红点缀。应比财富奇观更商业、更开放。</p>
|
||||
<div class="palette">
|
||||
<span class="swatch" style="background:#e5e7eb"></span>
|
||||
<span class="swatch" style="background:#1e3a8a"></span>
|
||||
<span class="swatch" style="background:#d97706"></span>
|
||||
<span class="swatch" style="background:#7c2d12"></span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>不要这样画</h3>
|
||||
<p>不要画成现代摩天楼金融区,不要用股票曲线、货币符号或大字招牌。贸易感应来自建筑、货物和道路。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>生成提示核心句</h3>
|
||||
<div class="prompt-block">low-poly isometric Royal Exchange London wonder, compact classical portico with columns and triangular pediment, crates, ledger, coins, stone street base, transparent background</div>
|
||||
</article>
|
||||
</div>
|
||||
<aside class="ref-card">
|
||||
<h3>明确参考入口</h3>
|
||||
<div class="reference-grid">
|
||||
<a href="https://www.theroyalexchange.co.uk/"><strong>Royal Exchange</strong><span>看皇家交易所入口、柱廊和商业地标形象。</span></a>
|
||||
<a href="https://historicengland.org.uk/listing/the-list/list-entry/1064713"><strong>Historic England</strong><span>看建筑登记信息和外观定位。</span></a>
|
||||
<a href="https://commons.wikimedia.org/wiki/Category:Royal_Exchange,_London"><strong>Commons 图库</strong><span>找柱廊正面、三角山花和城市街面参考。</span></a>
|
||||
</div>
|
||||
<ul class="source-links">
|
||||
<li>美术重点:柱廊和山花是第一识别点,货箱金币只是辅件。</li>
|
||||
<li>避免和财富奇观混淆:这里是“交易场所”,不是“王室宝库”。</li>
|
||||
</ul>
|
||||
<div class="search-block">搜索词:<code>Royal Exchange London portico</code><code>Royal Exchange London columns pediment</code><code>historic London trading hall exterior</code></div>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="wealth" class="doc-section">
|
||||
<h2>财富奇观:伦敦塔</h2>
|
||||
<div class="wonder-layout">
|
||||
<div class="plan-list">
|
||||
<article class="plan-card">
|
||||
<h3>关键信息</h3>
|
||||
<p>王权堡垒与王冠珠宝所在地。主题是“王室财富、宝库、权力收藏”,与战争奇观的军事堡垒分开。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>图像长什么样</h3>
|
||||
<p>中心是一座白塔式石堡,四角小塔清楚可见。前方放一个打开的金色宝箱或王冠展示台,底座可用短城墙围出宝库感。整体偏“皇家宝库”,不是攻城要塞。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>关键模块设计</h3>
|
||||
<ul>
|
||||
<li>主轮廓:白塔方形主楼 + 四角塔,顶部城垛简化为大块锯齿。</li>
|
||||
<li>财富符号:王冠、宝箱、金色珠宝托盘,数量少但尺寸要明显。</li>
|
||||
<li>防御符号:短城墙即可,不要加弩炮、火焰或大军旗。</li>
|
||||
<li>材质:浅灰白石墙,暗色窗洞,金色宝物形成视觉焦点。</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>配色</h3>
|
||||
<p>白灰石、深灰窗洞、王冠金、宝石红蓝、草地暗绿。财富焦点要亮,但不应把整张图画成金色。</p>
|
||||
<div class="palette">
|
||||
<span class="swatch" style="background:#e5e7eb"></span>
|
||||
<span class="swatch" style="background:#374151"></span>
|
||||
<span class="swatch" style="background:#f59e0b"></span>
|
||||
<span class="swatch" style="background:#1d4ed8"></span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>不要这样画</h3>
|
||||
<p>不要画成阴森监狱、刑场或纯军事城堡。财富类的视觉中心必须是王冠珠宝或宝库展示。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>生成提示核心句</h3>
|
||||
<div class="prompt-block">low-poly isometric Tower of London wealth wonder, compact White Tower keep, four corner turrets, small crown jewels display, treasure chest, pale stone, transparent background</div>
|
||||
</article>
|
||||
</div>
|
||||
<aside class="ref-card">
|
||||
<h3>明确参考入口</h3>
|
||||
<div class="reference-grid">
|
||||
<a href="https://www.hrp.org.uk/tower-of-london/"><strong>Historic Royal Palaces</strong><span>看伦敦塔官方入口和历史定位。</span></a>
|
||||
<a href="https://www.hrp.org.uk/tower-of-london/whats-on/the-crown-jewels/"><strong>Crown Jewels</strong><span>看王冠珠宝作为财富主题的依据。</span></a>
|
||||
<a href="https://commons.wikimedia.org/wiki/Category:White_Tower_(Tower_of_London)"><strong>Commons:White Tower</strong><span>找白塔方形堡垒、角塔和城垛参考。</span></a>
|
||||
</div>
|
||||
<ul class="source-links">
|
||||
<li>美术重点:白塔负责建筑识别,王冠珠宝负责“财富”分类识别。</li>
|
||||
<li>如果宝箱过俗,可改成红绒展示台上的王冠。</li>
|
||||
</ul>
|
||||
<div class="search-block">搜索词:<code>Tower of London White Tower isometric</code><code>White Tower four turrets</code><code>Tower of London Crown Jewels display</code></div>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="war" class="doc-section">
|
||||
<h2>战争奇观:多佛城堡</h2>
|
||||
<div class="wonder-layout">
|
||||
<div class="plan-list">
|
||||
<article class="plan-card">
|
||||
<h3>关键信息</h3>
|
||||
<p>英格兰海峡门户堡垒。主题是“海峡防御、城堡工程、边境守护”,避免和伦敦塔的宝库主题重复。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>图像长什么样</h3>
|
||||
<p>一座坐落在白色悬崖上的方形石堡:主塔在中央,外圈有半环形城墙和城门。底座一侧露出白崖与蓝灰海面,可加一面红十字小旗和一门极简炮台。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>关键模块设计</h3>
|
||||
<ul>
|
||||
<li>主轮廓:高石堡 + 环形城墙,城垛比伦敦塔更军事化。</li>
|
||||
<li>地理符号:白色悬崖和窄海面是多佛识别点。</li>
|
||||
<li>战争符号:小旗、城门、炮台或盾牌,控制在 1-2 个。</li>
|
||||
<li>体块:堡垒要厚重,墙面用大块明暗面,不画密集石缝。</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>配色</h3>
|
||||
<p>灰白石墙、白崖、深蓝海面、暗红军旗、铁灰阴影。整体比伦敦塔更硬、更冷。</p>
|
||||
<div class="palette">
|
||||
<span class="swatch" style="background:#d6d3d1"></span>
|
||||
<span class="swatch" style="background:#f8fafc"></span>
|
||||
<span class="swatch" style="background:#1e40af"></span>
|
||||
<span class="swatch" style="background:#7f1d1d"></span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>不要这样画</h3>
|
||||
<p>不要画成现代军事基地、二战雷达站或大海战场景。它应是单格可读的海峡石堡。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>生成提示核心句</h3>
|
||||
<div class="prompt-block">low-poly isometric Dover Castle war wonder, compact stone keep with ring wall, white cliffs base, small sea edge, red cross banner, strong defensive silhouette, transparent background</div>
|
||||
</article>
|
||||
</div>
|
||||
<aside class="ref-card">
|
||||
<h3>明确参考入口</h3>
|
||||
<div class="reference-grid">
|
||||
<a href="https://www.english-heritage.org.uk/visit/places/dover-castle/"><strong>English Heritage</strong><span>看多佛城堡官方资料和“Key to England”气质。</span></a>
|
||||
<a href="https://commons.wikimedia.org/wiki/Category:Dover_Castle"><strong>Commons:Dover Castle</strong><span>找主堡、城墙和白崖关系。</span></a>
|
||||
<a href="https://commons.wikimedia.org/wiki/Category:White_Cliffs_of_Dover"><strong>Commons:White Cliffs</strong><span>找白崖色块和海峡底座参考。</span></a>
|
||||
</div>
|
||||
<ul class="source-links">
|
||||
<li>美术重点:白崖底座能把多佛城堡和普通城堡区分开。</li>
|
||||
<li>战争类不需要大军队,城墙、城门、旗帜就够。</li>
|
||||
</ul>
|
||||
<div class="search-block">搜索词:<code>Dover Castle keep white cliffs</code><code>Dover Castle walls aerial</code><code>Dover Castle English Channel fortress</code></div>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="culture" class="doc-section">
|
||||
<h2>文化奇观:坎特伯雷大教堂</h2>
|
||||
<div class="wonder-layout">
|
||||
<div class="plan-list">
|
||||
<article class="plan-card">
|
||||
<h3>关键信息</h3>
|
||||
<p>英格兰宗教与文学传统地标。主题是“哥特教堂、朝圣、精神中心、文化记忆”。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>图像长什么样</h3>
|
||||
<p>一座小型哥特大教堂:中央高塔最醒目,左右有短翼廊和尖拱窗,前方可放一条朝圣石路或小十字。整体更精致、更竖向,和战争城堡的厚重体块区分开。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>关键模块设计</h3>
|
||||
<ul>
|
||||
<li>主轮廓:中央方塔 + 尖拱窗 + 长厅屋顶。</li>
|
||||
<li>文化符号:彩窗用 2-3 块蓝红色小面表示,朝圣路用浅石小径表示。</li>
|
||||
<li>材质:浅米石材和铅灰屋顶,线条比城堡更细致但不能密。</li>
|
||||
<li>精神感:可用很淡的金色窗光,不要用强烈神迹光束。</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>配色</h3>
|
||||
<p>米灰石材、铅灰屋顶、彩窗蓝红、草地绿、淡金窗光。比威斯敏斯特更宗教、更温和。</p>
|
||||
<div class="palette">
|
||||
<span class="swatch" style="background:#d8c7a3"></span>
|
||||
<span class="swatch" style="background:#475569"></span>
|
||||
<span class="swatch" style="background:#2563eb"></span>
|
||||
<span class="swatch" style="background:#b91c1c"></span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>不要这样画</h3>
|
||||
<p>不要画成巴黎圣母院复制品,不要过分强调十字或宗教人物。它应读作英格兰哥特大教堂和朝圣文化。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>生成提示核心句</h3>
|
||||
<div class="prompt-block">low-poly isometric Canterbury Cathedral culture wonder, compact English Gothic cathedral, central tower, pointed arches, small stained glass colors, pilgrim stone path, transparent background</div>
|
||||
</article>
|
||||
</div>
|
||||
<aside class="ref-card">
|
||||
<h3>明确参考入口</h3>
|
||||
<div class="reference-grid">
|
||||
<a href="https://www.canterbury-cathedral.org/"><strong>Canterbury Cathedral</strong><span>看教堂官方入口、建筑与朝圣文化定位。</span></a>
|
||||
<a href="https://whc.unesco.org/en/list/496"><strong>UNESCO:Canterbury</strong><span>看世界遗产说明与文化价值。</span></a>
|
||||
<a href="https://commons.wikimedia.org/wiki/Category:Canterbury_Cathedral"><strong>Commons 图库</strong><span>找中央塔、长厅、尖拱和彩窗参考。</span></a>
|
||||
</div>
|
||||
<ul class="source-links">
|
||||
<li>美术重点:中央塔和尖拱彩窗是第一识别点。</li>
|
||||
<li>如果需要更文学化,可在底座加一本极小打开的书,暗示《坎特伯雷故事集》。</li>
|
||||
</ul>
|
||||
<div class="search-block">搜索词:<code>Canterbury Cathedral central tower</code><code>Canterbury Cathedral Gothic exterior</code><code>Canterbury pilgrimage cathedral stained glass</code></div>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="explore" class="doc-section">
|
||||
<h2>探索奇观:格林尼治皇家天文台</h2>
|
||||
<div class="wonder-layout">
|
||||
<div class="plan-list">
|
||||
<article class="plan-card">
|
||||
<h3>关键信息</h3>
|
||||
<p>本初子午线与航海测量地标。主题是“时间、经线、航海、世界丈量”,不是一般科学馆。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>图像长什么样</h3>
|
||||
<p>一座小型白色天文台建筑,屋顶有红色时间球和小观测圆顶。底座上刻一条亮金色子午线,旁边放小航海仪或望远镜。可以在后方放一小片深蓝星盘,但不要画夜空背景。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>关键模块设计</h3>
|
||||
<ul>
|
||||
<li>主轮廓:白色天文台楼体 + 红色时间球杆 + 小圆顶。</li>
|
||||
<li>探索符号:金色子午线、望远镜、星盘、罗盘中选 1-2 个。</li>
|
||||
<li>底座:石台 + 草坪边缘,子午线必须从建筑前方穿过。</li>
|
||||
<li>区分巴黎天文台:重点放“经线/航海/时间球”,而不是单纯天文建筑。</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>配色</h3>
|
||||
<p>白色墙面、黑灰屋顶、红色时间球、金色经线、深蓝星盘。整体应明亮、精确、航海感强。</p>
|
||||
<div class="palette">
|
||||
<span class="swatch" style="background:#f8fafc"></span>
|
||||
<span class="swatch" style="background:#1f2937"></span>
|
||||
<span class="swatch" style="background:#b91c1c"></span>
|
||||
<span class="swatch" style="background:#d6a446"></span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>不要这样画</h3>
|
||||
<p>不要画成大型宇宙馆、现代天文台圆顶或星空插画。地图件要靠时间球和本初子午线读出格林尼治。</p>
|
||||
</article>
|
||||
<article class="plan-card">
|
||||
<h3>生成提示核心句</h3>
|
||||
<div class="prompt-block">low-poly isometric Royal Observatory Greenwich exploration wonder, compact white observatory, red time ball, golden prime meridian line on stone base, small telescope, transparent background</div>
|
||||
</article>
|
||||
</div>
|
||||
<aside class="ref-card">
|
||||
<h3>明确参考入口</h3>
|
||||
<div class="reference-grid">
|
||||
<a href="https://www.rmg.co.uk/royal-observatory"><strong>Royal Observatory Greenwich</strong><span>看天文台官方入口、时间球和参观信息。</span></a>
|
||||
<a href="https://whc.unesco.org/en/list/795"><strong>UNESCO:Maritime Greenwich</strong><span>看航海、天文与世界遗产定位。</span></a>
|
||||
<a href="https://commons.wikimedia.org/wiki/Category:Royal_Observatory,_Greenwich"><strong>Commons 图库</strong><span>找时间球、天文台建筑和子午线参考。</span></a>
|
||||
</div>
|
||||
<ul class="source-links">
|
||||
<li>美术重点:红色时间球 + 金色经线必须同时出现。</li>
|
||||
<li>这是英格兰探索奇观最强识别点,也能避免和法兰西巴黎天文台重复。</li>
|
||||
</ul>
|
||||
<div class="search-block">搜索词:<code>Royal Observatory Greenwich time ball</code><code>Greenwich Prime Meridian line</code><code>Royal Observatory Greenwich Flamsteed House</code></div>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="style" class="doc-section">
|
||||
<h2>风格参考:把英格兰味推满</h2>
|
||||
<p class="lead">以下是全套英格兰奇观可共用的视觉调味板。正式 Sprite 仍应保持小体量、透明底、等距 3/4 俯视,风格元素只能服务建筑识别。</p>
|
||||
<div class="style-rule-grid">
|
||||
<article class="style-rule">
|
||||
<h3>可以大胆吸收</h3>
|
||||
<p>浅灰石墙、铅灰屋顶、哥特尖拱、城垛、红蓝旗帜、金色王冠、狮纹盾牌、都铎木构、海军深蓝、白崖色块。这些元素能在小图尺寸下快速读出英格兰。</p>
|
||||
</article>
|
||||
<article class="style-rule">
|
||||
<h3>只取气氛,不画成插画</h3>
|
||||
<p>伦敦雾、泰晤士河、海峡、学院庭院、朝圣路、航海星图都可以做小底座或点缀。不要把单格奇观变成横向城市景观、海战场景或旅游海报。</p>
|
||||
</article>
|
||||
<article class="style-rule">
|
||||
<h3>分类差异</h3>
|
||||
<p>和平是议会秩序,知识是学院庭院,贸易是柱廊交易所,财富是王冠宝库,战争是白崖堡垒,文化是哥特教堂,探索是经线天文台。</p>
|
||||
</article>
|
||||
</div>
|
||||
<div class="search-block">进一步搜索词:<code>English Gothic architecture pointed arch</code><code>Tudor half timbered house England</code><code>English castle battlements low poly</code><code>British royal crown jewels</code><code>Royal Navy blue gold red palette</code><code>Prime Meridian Greenwich time ball</code></div>
|
||||
</section>
|
||||
|
||||
<section class="doc-section note">
|
||||
<h2>交付检查</h2>
|
||||
<ol class="rule-list">
|
||||
<li>每张图远看必须先识别类别:议会宫、大学图书馆、交易所、王室宝库、海峡堡垒、大教堂、天文台经线。</li>
|
||||
<li>英格兰文明统一色彩应以浅石、铅灰、深蓝、王室红、金色点缀为主,不要做成全红蓝国旗配色。</li>
|
||||
<li>威斯敏斯特宫、伦敦塔、多佛城堡都含权力建筑元素,必须用“议会 / 王冠珠宝 / 白崖防线”拉开差异。</li>
|
||||
<li>格林尼治皇家天文台必须突出本初子午线和时间球,避免与法兰西探索奇观“巴黎天文台”同质化。</li>
|
||||
<li>正式落地到 DataAsset 时,图片需进入对应 Resources/ArtResources 路径并由 GridAndResourceDataAssets 的 WonderInfoList 绑定 Sprite。</li>
|
||||
</ol>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
37
MD/AudioSfxRequests/2026-07-02-sfx-requests.md
Normal file
@ -0,0 +1,37 @@
|
||||
# 2026-07-02 音效需求记录
|
||||
|
||||
> 记录来源:用户口述需求与随附截图。
|
||||
> 关联背景:Dashboard 音频音效盘点显示当前音效覆盖率约 73%,高优先级缺口集中在 UI 切换、开局/提示、移动环境、探索发现、受击/攻击表现等场景。
|
||||
> 图片目录:`MD/AudioSfxRequests/images/`
|
||||
|
||||
## 需求清单
|
||||
|
||||
| 编号 | 音效需求 | 配图 | 建议 key | 触发场景 / 备注 |
|
||||
|---:|---|---|---|---|
|
||||
| 1 | 切换页面音效 | 无图 | `SFX/UI_tab_tick` | 用于页签、面板、列表分页等轻量 UI 切换。应比按钮点击更轻、更短,避免高频打扰。 |
|
||||
| 2 | 游戏开始音效 | [2026-07-02-02-game-start.png](images/2026-07-02-02-game-start.png) | `SFX/MATCH_start` | 对局进入加载/开局前后的正式开始提示。区别于当前 `SFX/start` 的通用回合提示用途。 |
|
||||
| 3 | 弹出中央提示框音效 | [2026-07-02-03-central-modal.png](images/2026-07-02-03-central-modal.png) | `SFX/UI_modal_open` | 用于中央确认框、重大提示框、开局说明框弹出。音色应有提示感但不要像警告。 |
|
||||
| 4 | 单位下海音效 | [2026-07-02-04-unit-enter-water.png](images/2026-07-02-04-unit-enter-water.png) | `SFX/UNIT_enter_water` | 单位从陆地进入水域/登船转换时播放。与普通 `SFX/UNIT_move` 区分,带轻水花或入水感。 |
|
||||
| 5 | 鼠标移动到一个按钮上的轻微音效 | 无图 | `SFX/UI_button_hover_light` | 高频 hover 反馈。当前已有 `SFX/UI_buttonHover`,新需求强调更轻微,可作为替换或变体。 |
|
||||
| 6 | 点击无效音效 | 无图 | `SFX/UI_invalid` | 不可执行、条件不足、按钮禁用、非法地块点击等场景。可复用现有 `SFX/UI_beep` 方向,但应做成短促弱警告。 |
|
||||
| 7 | 英雄出战音效 | 无图 | `SFX/HERO_deploy` | 英雄派遣、英雄登场、英雄出战确认时播放。应比普通单位出生更有仪式感。 |
|
||||
| 8 | 发现结界塔/村庄/遗迹音效 | [2026-07-02-08-discovery.png](images/2026-07-02-08-discovery.png) | `SFX/MAP_discovery` | 探索发现可交互点时播放。结界塔、村庄、遗迹可先共用,后续再按重要度拆分。 |
|
||||
| 9 | 点击雾气弹动的音效 | [2026-07-02-09-fog-bounce.png](images/2026-07-02-09-fog-bounce.png) | `SFX/MAP_fog_bounce` | 玩家点击未探索雾气/雾墙弹动反馈。应轻、软、带一点雾气或弹性,不像错误提示。 |
|
||||
| 10 | 更强大尖锐的攻击音效 | 无图 | `SFX/UNIT_attack_sharp_heavy` | 强攻击、暴击感、英雄攻击或更尖锐的近战/斩击反馈。当前基础攻击统一使用 `SFX/UNIT_attack`,此项用于增强层。 |
|
||||
|
||||
## 配图映射
|
||||
|
||||
| 图片 | 对应需求 | 文件 |
|
||||
|---|---:|---|
|
||||
| Image #1 | 2 | `MD/AudioSfxRequests/images/2026-07-02-02-game-start.png` |
|
||||
| Image #2 | 3 | `MD/AudioSfxRequests/images/2026-07-02-03-central-modal.png` |
|
||||
| Image #3 | 4 | `MD/AudioSfxRequests/images/2026-07-02-04-unit-enter-water.png` |
|
||||
| Image #4 | 8 | `MD/AudioSfxRequests/images/2026-07-02-08-discovery.png` |
|
||||
| Image #5 | 9 | `MD/AudioSfxRequests/images/2026-07-02-09-fog-bounce.png` |
|
||||
|
||||
## 接入注意
|
||||
|
||||
- 新增 SFX 时不要只放资源文件;需要同步注册 `AudioManager.Init()` key,并在明确触发点调用或配置。
|
||||
- UI 高频音效优先控制长度和响度,尤其是 hover、切换页面、无效点击。
|
||||
- 单位下海、发现点位、雾气弹动应挂在演出/交互反馈节点,避免 AI 或不可见行动触发本地声音。
|
||||
- 强攻击音效建议作为现有基础攻击音效的增强层或新 key,不直接替换全部普通攻击。
|
||||
41
MD/AudioSfxRequests/2026-07-03-sfx-requests.md
Normal file
@ -0,0 +1,41 @@
|
||||
# 2026-07-03 音效需求记录
|
||||
|
||||
> 记录来源:用户口述需求与随附截图。
|
||||
> 图片目录:`MD/AudioSfxRequests/images/`
|
||||
> 备注:本批包含若干“重大时刻 / 外交 / 研发 / 弹道 / 战斗反馈”音效。第 18 条“游戏开始音效”与 2026-07-02 第 2 条同类,保留为重复确认需求。
|
||||
|
||||
## 需求清单
|
||||
|
||||
| 编号 | 音效需求 | 配图 | 建议 key | 触发场景 / 备注 |
|
||||
|---:|---|---|---|---|
|
||||
| 1 | 振奋时刻 | [2026-07-03-01-inspiring-moment.png](images/2026-07-03-01-inspiring-moment.png) | `SFX/MOMENT_inspiring` | 类似“文明之握,占领一个村庄”的正面中等强度重大提示。应明亮、上扬、有奖励感。 |
|
||||
| 2 | 光荣时刻 | 无图 | `SFX/MOMENT_glorious` | 兴奋程度高于“振奋时刻”。用于更高价值的正面事件,音色更辉煌、更有仪式感。 |
|
||||
| 3 | 危难时刻 | 无图 | `SFX/MOMENT_crisis` | 负面紧张事件提示。应有压迫感,但不要直接等同失败。 |
|
||||
| 4 | 至暗时刻 | 无图 | `SFX/MOMENT_darkest` | 比“危难时刻”更严重的负面重大事件。应低沉、沉重,提示玩家局势严重恶化。 |
|
||||
| 5 | 弹道:投掷符卡音效 | 无图 | `SFX/PROJECTILE_spellcard_throw` | 符卡/札类投射物发射瞬间。与普通箭矢、魔法弹区分,带纸张/灵力划过感。 |
|
||||
| 6 | 弹道:投掷魔法弹音效 | 无图 | `SFX/PROJECTILE_magic_throw` | 魔法弹、光弹、弹幕发射瞬间。比箭矢更轻灵或能量化。 |
|
||||
| 7 | 范围 AOE 音效 | 无图 | `SFX/SKILL_aoe_impact` | 范围伤害、爆发、溅射、踩踏等结算时播放。Dashboard 也标记为核心缺口。 |
|
||||
| 8 | 转化一个单位变成自己的音效 | 无图 | `SFX/UNIT_convert` | 敌方/中立单位被转化为己方时播放。应有“归属改变”的清晰确认感。 |
|
||||
| 9 | 英雄升级音效 | 无图 | `SFX/HERO_levelup` | 英雄升级时播放。需要强于普通 `SFX/UNIT_levelup`,体现英雄成长。 |
|
||||
| 10 | 城市丢失经验音效 | 无图 | `SFX/CITY_exp_lost` | 城市经验减少、降级风险或惩罚结算时播放。应短促、下坠、负面但不刺耳。 |
|
||||
| 11 | 帝国陨落提示 | 无图 | `SFX/EMPIRE_fall` | 有一个国家阵亡/帝国被消灭时播放。应有全局公告感,严重度高。 |
|
||||
| 12 | 建立盟友提示 | 无图 | `SFX/DIPLOMACY_alliance_formed` | 外交关系建立盟友/结盟时播放。应温暖、确认、正面。 |
|
||||
| 13 | 撕毁盟友提示 | 无图 | `SFX/DIPLOMACY_alliance_broken` | 背盟、撕毁盟约、盟友关系破裂时播放。应有断裂或紧张感。 |
|
||||
| 14 | 不起眼的提示消息 | 无图 | `SFX/UI_notice_minor` | 低优先级提示、弱系统消息、轻提醒。应很短、低打扰。 |
|
||||
| 15 | 科技完成研发 | 无图 | `SFX/TECH_complete` | 科技研究完成时播放。当前已有未注册的 `UI_tech.WAV` 可参考或替换。 |
|
||||
| 16 | 法典版本 | 无图 | `SFX/CODEX_version` | 法典/版本/规则书类提示或打开法典版本信息时播放。音色可偏书页、印章、知识感。 |
|
||||
| 17 | 完成了英雄的选择 | 无图 | `SFX/HERO_select_confirmed` | 英雄选择完成、确认出战前的 UI/流程反馈。与第 9 条英雄升级、第 7 月 2 日“英雄出战”区分。 |
|
||||
| 18 | 游戏开始音效 | 无图 | `SFX/MATCH_start` | 对局正式开始音效。与 2026-07-02 第 2 条重复确认,可作为同一个需求实现。 |
|
||||
|
||||
## 配图映射
|
||||
|
||||
| 图片 | 对应需求 | 文件 |
|
||||
|---|---:|---|
|
||||
| Image #1 | 1 | `MD/AudioSfxRequests/images/2026-07-03-01-inspiring-moment.png` |
|
||||
|
||||
## 接入注意
|
||||
|
||||
- “振奋 / 光荣 / 危难 / 至暗”是一组重大时刻分层音效,建议保持同一音色家族,通过力度、音高、和声方向区分正负与严重程度。
|
||||
- “弹道:符卡 / 魔法弹”应优先配置到投射物数据或投射物创建阶段,避免散落到单个技能代码。
|
||||
- 外交和帝国陨落提示是全局信息反馈,触发时应避免重复播放,尤其是联机同步和回放场景。
|
||||
- “游戏开始音效”已在 2026-07-02 记录过,本次保留为重复需求,后续实现时合并到同一个 key。
|
||||
BIN
MD/AudioSfxRequests/TH1_SFX_Requirements_Vendor_2026-07-03.zip
Normal file
BIN
MD/AudioSfxRequests/images/2026-07-02-02-game-start.png
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
MD/AudioSfxRequests/images/2026-07-02-03-central-modal.png
Normal file
|
After Width: | Height: | Size: 535 KiB |
BIN
MD/AudioSfxRequests/images/2026-07-02-04-unit-enter-water.png
Normal file
|
After Width: | Height: | Size: 587 KiB |
BIN
MD/AudioSfxRequests/images/2026-07-02-08-discovery.png
Normal file
|
After Width: | Height: | Size: 521 KiB |
BIN
MD/AudioSfxRequests/images/2026-07-02-09-fog-bounce.png
Normal file
|
After Width: | Height: | Size: 301 KiB |
BIN
MD/AudioSfxRequests/images/2026-07-03-01-inspiring-moment.png
Normal file
|
After Width: | Height: | Size: 498 KiB |
550
MD/AudioSfxRequests/sfx-requirements-brief.html
Normal file
@ -0,0 +1,550 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 新音效需求清单</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f6f7fb;
|
||||
--paper: #ffffff;
|
||||
--ink: #1f2937;
|
||||
--muted: #667085;
|
||||
--line: #d9dee8;
|
||||
--head: #25324d;
|
||||
--blue: #2563eb;
|
||||
--green: #138a5b;
|
||||
--orange: #b45309;
|
||||
--red: #b42318;
|
||||
--purple: #7c3aed;
|
||||
--cyan: #087990;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
font-family: "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", Arial, sans-serif;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.page {
|
||||
width: min(1280px, calc(100vw - 40px));
|
||||
margin: 32px auto 48px;
|
||||
}
|
||||
|
||||
header {
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 24px 28px;
|
||||
box-shadow: 0 8px 24px rgba(31, 41, 55, 0.06);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 0 10px;
|
||||
color: var(--head);
|
||||
font-size: 30px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin: 18px 0 0;
|
||||
}
|
||||
|
||||
.metric {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 12px 14px;
|
||||
background: #fbfcff;
|
||||
}
|
||||
|
||||
.metric strong {
|
||||
display: block;
|
||||
color: var(--head);
|
||||
font-size: 22px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.metric span {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-top: 20px;
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 22px 24px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0 0 14px;
|
||||
color: var(--head);
|
||||
font-size: 20px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.notes {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.notes li { margin: 4px 0; }
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid var(--line);
|
||||
padding: 10px 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #eef2f8;
|
||||
color: var(--head);
|
||||
text-align: left;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(even) { background: #fbfcff; }
|
||||
|
||||
.col-id { width: 48px; text-align: center; }
|
||||
.col-title { width: 150px; }
|
||||
.col-priority { width: 72px; }
|
||||
.col-status { width: 118px; }
|
||||
.col-key { width: 170px; }
|
||||
.col-shot { width: 170px; }
|
||||
|
||||
code {
|
||||
color: #102a56;
|
||||
background: #eef4ff;
|
||||
border: 1px solid #d6e4ff;
|
||||
border-radius: 5px;
|
||||
padding: 1px 5px;
|
||||
font-family: Consolas, "Courier New", monospace;
|
||||
font-size: 12px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
min-width: 48px;
|
||||
border-radius: 999px;
|
||||
padding: 2px 8px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.p0 { background: var(--red); }
|
||||
.p1 { background: var(--orange); }
|
||||
.p2 { background: var(--blue); }
|
||||
.p3 { background: var(--green); }
|
||||
.status-new { background: var(--red); }
|
||||
.status-split { background: var(--orange); }
|
||||
.status-existing { background: var(--green); }
|
||||
.status-resource { background: var(--cyan); }
|
||||
.status-design { background: var(--purple); }
|
||||
|
||||
.thumb {
|
||||
display: block;
|
||||
width: 148px;
|
||||
max-height: 92px;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--line);
|
||||
background: #111827;
|
||||
}
|
||||
|
||||
.no-shot {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.small {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
margin: 18px 0 8px;
|
||||
color: var(--head);
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.page { width: min(100vw - 20px, 1280px); margin-top: 16px; }
|
||||
header, section { padding: 16px; }
|
||||
.summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
table { min-width: 1120px; }
|
||||
.table-wrap { overflow-x: auto; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="page">
|
||||
<header>
|
||||
<h1>TH1 新音效需求清单</h1>
|
||||
<p class="subtitle">整理时间:2026-07-03。来源:2026-07-02 与 2026-07-03 两批口述需求;结合当前音频系统快照与 Dashboard 音频盘点填写。</p>
|
||||
<div class="summary">
|
||||
<div class="metric"><strong>27</strong><span>合并后唯一需求项</span></div>
|
||||
<div class="metric"><strong>6</strong><span>带参考配图</span></div>
|
||||
<div class="metric"><strong>73%</strong><span>Dashboard 当前音效覆盖率</span></div>
|
||||
<div class="metric"><strong>20</strong><span>Dashboard 预期事件缺口</span></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<h2>制作口径</h2>
|
||||
<ul class="notes">
|
||||
<li>短音效建议优先交付 WAV,避免高频 UI 音效使用 MP3 带来的起播延迟和 padding 风险。</li>
|
||||
<li>当前工程已有基础按钮、移动、普通攻击、弓箭/魔法复用弹道、升级、占领、训练、宝藏、金币、胜负、聊天、玩家进入、少量环境音。</li>
|
||||
<li>新增音效需要同时落资源、注册 key,并挂到明确触发点。投射物优先走投射物配置,行动/战斗结果优先挂演出或 VFX 节点。</li>
|
||||
<li>“游戏开始音效”在两批需求中重复出现,本文合并为一项。</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>需求列表</h2>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-id">序号</th>
|
||||
<th class="col-title">音效需求</th>
|
||||
<th class="col-priority">优先级</th>
|
||||
<th class="col-status">当前情况</th>
|
||||
<th class="col-key">建议 key</th>
|
||||
<th>需求内容</th>
|
||||
<th>接入建议</th>
|
||||
<th class="col-shot">配图</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="col-id">1</td>
|
||||
<td>切换页面音效</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-split">复用需拆分</span><span class="small">当前多复用按钮点击。</span></td>
|
||||
<td><code>SFX/UI_tab_tick</code></td>
|
||||
<td>轻短的页签、面板、列表分页切换声。比按钮点击更薄、更短,不要有强确认感。</td>
|
||||
<td>接到 UI tab、分页、列表切换通用控件;避免每帧 hover 或滚动列表触发。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">2</td>
|
||||
<td>游戏开始音效</td>
|
||||
<td><span class="badge p0">P0</span></td>
|
||||
<td><span class="badge status-new">缺口</span><span class="small"><code>SFX/start</code> 现在用于回合提示,不等同开局。</span></td>
|
||||
<td><code>SFX/MATCH_start</code></td>
|
||||
<td>对局正式开始、进入战场或开局公告出现时播放。需要有“开幕”和行动开始的仪式感,长度约 1.0-2.0 秒。</td>
|
||||
<td>接到开局流程或开局重大提示展示点;7 月 2 日与 7 月 3 日重复提出,按同一项实现。</td>
|
||||
<td><img class="thumb" src="images/2026-07-02-02-game-start.png" alt="游戏开始音效参考图"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">3</td>
|
||||
<td>弹出中央提示框音效</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-new">缺口</span><span class="small">无专门弹窗开合音。</span></td>
|
||||
<td><code>SFX/UI_modal_open</code></td>
|
||||
<td>中央确认框、重大提示框、开局说明框弹出声。清晰但不吓人,偏“提示出现”。</td>
|
||||
<td>接到重大 UI 弹窗打开;关闭音可暂不做,后续需要再拆 <code>UI_modal_close</code>。</td>
|
||||
<td><img class="thumb" src="images/2026-07-02-03-central-modal.png" alt="中央提示框参考图"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">4</td>
|
||||
<td>单位下海音效</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-split">复用需拆分</span><span class="small">当前移动统一 <code>SFX/UNIT_move</code>。</span></td>
|
||||
<td><code>SFX/UNIT_enter_water</code></td>
|
||||
<td>单位从陆地进入水域、登船或水陆转换时的轻水花/入水声。不要做成长环境海声。</td>
|
||||
<td>接在移动 Fragment 判断地形变化的位置,只在可见行动播放。</td>
|
||||
<td><img class="thumb" src="images/2026-07-02-04-unit-enter-water.png" alt="单位下海参考图"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">5</td>
|
||||
<td>按钮轻微 hover</td>
|
||||
<td><span class="badge p2">P2</span></td>
|
||||
<td><span class="badge status-existing">已有基础</span><span class="small">已有 <code>SFX/UI_buttonHover</code>。</span></td>
|
||||
<td><code>SFX/UI_button_hover_light</code></td>
|
||||
<td>鼠标移动到按钮上的极轻反馈。比现有 hover 更小、更软,适合高频按钮。</td>
|
||||
<td>可先试听现有 hover;若偏重,再替换或增加 light 变体。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">6</td>
|
||||
<td>点击无效音效</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-resource">可参考现有</span><span class="small"><code>SFX/UI_beep</code> 只用于倒计时。</span></td>
|
||||
<td><code>SFX/UI_invalid</code></td>
|
||||
<td>不可执行、条件不足、按钮禁用、非法地块点击的短促弱警告。要明确但不要刺耳。</td>
|
||||
<td>接到统一不可操作提示入口;不要复用倒计时 beep 的高频语义。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">7</td>
|
||||
<td>英雄出战音效</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-split">复用需拆分</span><span class="small">普通训练使用 <code>SFX/UNIT_born</code>。</span></td>
|
||||
<td><code>SFX/HERO_deploy</code></td>
|
||||
<td>英雄派遣、登场、出战确认时播放。需要比普通单位出生更有角色登场感。</td>
|
||||
<td>接英雄派遣/生成表现节点;与英雄选择确认分开。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">8</td>
|
||||
<td>发现结界塔/村庄/遗迹</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-new">缺口</span><span class="small">领取奖励已有 <code>SFX/UNIT_treasure</code>,发现阶段缺独立声。</span></td>
|
||||
<td><code>SFX/MAP_discovery</code></td>
|
||||
<td>探索发现可交互点时播放。结界塔、村庄、遗迹可先共用;音色偏“发现目标”。</td>
|
||||
<td>接迷雾揭示后发现新 POI 的表现;不要和领取宝藏奖励混淆。</td>
|
||||
<td><img class="thumb" src="images/2026-07-02-08-discovery.png" alt="发现结界塔村庄遗迹参考图"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">9</td>
|
||||
<td>点击雾气弹动</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-new">缺口</span><span class="small">Fog VFX 有表现,缺交互音。</span></td>
|
||||
<td><code>SFX/MAP_fog_bounce</code></td>
|
||||
<td>点击未探索雾气/雾墙时的轻弹反馈。应软、轻、带雾气感,不像错误提示。</td>
|
||||
<td>接地图交互中点击雾气弹动表现;避免拖拽镜头时误触发。</td>
|
||||
<td><img class="thumb" src="images/2026-07-02-09-fog-bounce.png" alt="雾气弹动参考图"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">10</td>
|
||||
<td>更强大尖锐的攻击</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-split">复用需拆分</span><span class="small">普通攻击统一 <code>SFX/UNIT_attack</code>。</span></td>
|
||||
<td><code>SFX/UNIT_attack_sharp_heavy</code></td>
|
||||
<td>强攻击、英雄攻击、暴击感或尖锐斩击的增强层。更亮、更利,但不要覆盖所有普通攻击。</td>
|
||||
<td>作为攻击增强层或特殊攻击 key,按技能/单位类型选择播放。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">11</td>
|
||||
<td>振奋时刻</td>
|
||||
<td><span class="badge p0">P0</span></td>
|
||||
<td><span class="badge status-new">缺口</span><span class="small">重大时刻目前更多依赖 UI 和 BGM。</span></td>
|
||||
<td><code>SFX/MOMENT_inspiring</code></td>
|
||||
<td>正面中等强度重大提示,如“文明之握,占领一个村庄”。明亮、上扬、有奖励感。</td>
|
||||
<td>接重大时刻提示显示时;和“光荣时刻”保持同一音色家族但力度更低。</td>
|
||||
<td><img class="thumb" src="images/2026-07-03-01-inspiring-moment.png" alt="振奋时刻参考图"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">12</td>
|
||||
<td>光荣时刻</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-new">缺口</span></td>
|
||||
<td><code>SFX/MOMENT_glorious</code></td>
|
||||
<td>比振奋时刻更兴奋、更辉煌的正面重大事件。应有更强仪式感和胜利预感。</td>
|
||||
<td>接高价值重大时刻;与振奋、胜利结算音效区分。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">13</td>
|
||||
<td>危难时刻</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-new">缺口</span></td>
|
||||
<td><code>SFX/MOMENT_crisis</code></td>
|
||||
<td>负面紧张重大事件提示。有压迫感,但还不是失败或灭国。</td>
|
||||
<td>接重大负面事件提示;避免和普通无效点击或失败结算混淆。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">14</td>
|
||||
<td>至暗时刻</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-new">缺口</span></td>
|
||||
<td><code>SFX/MOMENT_darkest</code></td>
|
||||
<td>比危难时刻更严重的负面重大事件。低沉、沉重,提示局势严重恶化。</td>
|
||||
<td>接最高严重度重大负面提示;可与帝国陨落音效共享部分低频设计但不应完全相同。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">15</td>
|
||||
<td>弹道:投掷符卡</td>
|
||||
<td><span class="badge p0">P0</span></td>
|
||||
<td><span class="badge status-split">复用需拆分</span><span class="small">大量弹道复用 <code>UNIT_attack_arrow</code> / <code>UNIT_archer</code>。</span></td>
|
||||
<td><code>SFX/PROJECTILE_spellcard_throw</code></td>
|
||||
<td>符卡、札、纸符类投射物发射声。带纸张划过和灵力启动感,不像箭矢。</td>
|
||||
<td>优先配置到对应 ProjectileType;Reimu/Sanae 等符札系可优先替换。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">16</td>
|
||||
<td>弹道:投掷魔法弹</td>
|
||||
<td><span class="badge p0">P0</span></td>
|
||||
<td><span class="badge status-split">复用需拆分</span><span class="small">魔法弹与箭矢声纹未分层。</span></td>
|
||||
<td><code>SFX/PROJECTILE_magic_throw</code></td>
|
||||
<td>魔法弹、光弹、弹幕发射声。更能量化、轻灵,可用于 Patchouli、Reisen、Kaguya 等弹幕。</td>
|
||||
<td>优先配置到魔法/光弹 ProjectileType;避免在技能代码硬编码。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">17</td>
|
||||
<td>范围 AOE 音效</td>
|
||||
<td><span class="badge p0">P0</span></td>
|
||||
<td><span class="badge status-new">缺口</span><span class="small">Dashboard 标记核心缺口。</span></td>
|
||||
<td><code>SFX/SKILL_aoe_impact</code></td>
|
||||
<td>范围伤害、爆发、溅射、踩踏等结算的冲击声。需要明显命中感,可低频更足。</td>
|
||||
<td>接范围伤害演出或 VFX 命中节点;如果多段伤害,要限频。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">18</td>
|
||||
<td>转化单位为己方</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-new">缺口</span></td>
|
||||
<td><code>SFX/UNIT_convert</code></td>
|
||||
<td>敌方/中立单位被转化成己方时播放。要有归属改变和确认完成的感觉。</td>
|
||||
<td>接转化行动的可见演出;不可见或 AI 计算过程不播放。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">19</td>
|
||||
<td>英雄升级</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-split">复用需拆分</span><span class="small">现在复用 <code>SFX/UNIT_levelup</code>。</span></td>
|
||||
<td><code>SFX/HERO_levelup</code></td>
|
||||
<td>英雄升级时播放。应明显强于普通单位升级,有成长和关键角色强化感。</td>
|
||||
<td>接英雄升级分支;普通单位继续使用现有升级音。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">20</td>
|
||||
<td>城市丢失经验</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-new">缺口</span><span class="small">城市经验增长已有 <code>SFX/CITY_exp</code>。</span></td>
|
||||
<td><code>SFX/CITY_exp_lost</code></td>
|
||||
<td>城市经验减少、降级风险或惩罚结算时播放。短促、下坠、负面但不刺耳。</td>
|
||||
<td>接城市经验扣减的可见表现;与城市升级音效方向相反。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">21</td>
|
||||
<td>帝国陨落提示</td>
|
||||
<td><span class="badge p0">P0</span></td>
|
||||
<td><span class="badge status-new">缺口</span></td>
|
||||
<td><code>SFX/EMPIRE_fall</code></td>
|
||||
<td>有一个国家阵亡、帝国被消灭时的全局提示。严重、明确、有历史性。</td>
|
||||
<td>接玩家/国家淘汰公告;联机和回放需防重复播放。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">22</td>
|
||||
<td>建立盟友提示</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-new">缺口</span></td>
|
||||
<td><code>SFX/DIPLOMACY_alliance_formed</code></td>
|
||||
<td>建立盟友/结盟成功时播放。温暖、正面、有确认感。</td>
|
||||
<td>接外交关系变更展示;仅对本地玩家关心的关系播放。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">23</td>
|
||||
<td>撕毁盟友提示</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-new">缺口</span></td>
|
||||
<td><code>SFX/DIPLOMACY_alliance_broken</code></td>
|
||||
<td>背盟、撕毁盟约、盟友关系破裂时播放。有断裂感和紧张感。</td>
|
||||
<td>接外交关系从盟友转坏的展示;避免同一事件多处 UI 重复响。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">24</td>
|
||||
<td>不起眼的提示消息</td>
|
||||
<td><span class="badge p2">P2</span></td>
|
||||
<td><span class="badge status-resource">已有资源候选</span><span class="small"><code>UI_hintTiny.WAV</code> 未接入。</span></td>
|
||||
<td><code>SFX/UI_notice_minor</code></td>
|
||||
<td>低优先级提示、弱系统消息、轻提醒。很短、低打扰、不能抢玩家注意力。</td>
|
||||
<td>可评估未接入的 <code>UI_hintTiny</code>;接轻提示统一入口。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">25</td>
|
||||
<td>科技完成研发</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-resource">已有资源候选</span><span class="small"><code>UI_tech.WAV</code> 未注册/未播放。</span></td>
|
||||
<td><code>SFX/TECH_complete</code></td>
|
||||
<td>科技研发完成时播放。知识、解锁、完成感明确,强度中等。</td>
|
||||
<td>优先试听/复用 <code>Audio/SFX/UI_tech.WAV</code>,不合适再新做。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">26</td>
|
||||
<td>法典版本</td>
|
||||
<td><span class="badge p3">P3</span></td>
|
||||
<td><span class="badge status-design">新增设计</span></td>
|
||||
<td><code>SFX/CODEX_version</code></td>
|
||||
<td>法典/版本/规则书类提示,偏书页、印章、知识感。不要像科技完成那么兴奋。</td>
|
||||
<td>接法典版本信息、规则说明或类似入口;若使用频率低,可后置。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-id">27</td>
|
||||
<td>完成英雄选择</td>
|
||||
<td><span class="badge p1">P1</span></td>
|
||||
<td><span class="badge status-new">缺口</span></td>
|
||||
<td><code>SFX/HERO_select_confirmed</code></td>
|
||||
<td>完成英雄选择、确认英雄阵容时播放。是流程确认音,不是英雄出战或升级。</td>
|
||||
<td>接英雄选择 UI 的确认按钮或最终提交点;避免和普通按钮点击重复。</td>
|
||||
<td><span class="no-shot">无图</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>配图索引</h2>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>需求</th>
|
||||
<th>图片文件</th>
|
||||
<th>预览</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>游戏开始音效</td><td><code>images/2026-07-02-02-game-start.png</code></td><td><img class="thumb" src="images/2026-07-02-02-game-start.png" alt="游戏开始音效"></td></tr>
|
||||
<tr><td>弹出中央提示框音效</td><td><code>images/2026-07-02-03-central-modal.png</code></td><td><img class="thumb" src="images/2026-07-02-03-central-modal.png" alt="弹出中央提示框音效"></td></tr>
|
||||
<tr><td>单位下海音效</td><td><code>images/2026-07-02-04-unit-enter-water.png</code></td><td><img class="thumb" src="images/2026-07-02-04-unit-enter-water.png" alt="单位下海音效"></td></tr>
|
||||
<tr><td>发现结界塔/村庄/遗迹音效</td><td><code>images/2026-07-02-08-discovery.png</code></td><td><img class="thumb" src="images/2026-07-02-08-discovery.png" alt="发现结界塔村庄遗迹音效"></td></tr>
|
||||
<tr><td>点击雾气弹动的音效</td><td><code>images/2026-07-02-09-fog-bounce.png</code></td><td><img class="thumb" src="images/2026-07-02-09-fog-bounce.png" alt="点击雾气弹动音效"></td></tr>
|
||||
<tr><td>振奋时刻</td><td><code>images/2026-07-03-01-inspiring-moment.png</code></td><td><img class="thumb" src="images/2026-07-03-01-inspiring-moment.png" alt="振奋时刻"></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
202
MD/AudioSfxRequests/sfx-requirements-vendor.html
Normal file
@ -0,0 +1,202 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 音效制作需求清单</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f4f5f7;
|
||||
--paper: #fff;
|
||||
--ink: #1f2937;
|
||||
--muted: #667085;
|
||||
--line: #d7dce5;
|
||||
--head: #24314f;
|
||||
--accent: #315f8d;
|
||||
--soft: #edf3f8;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
font-family: "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", Arial, sans-serif;
|
||||
line-height: 1.55;
|
||||
}
|
||||
main {
|
||||
width: min(1180px, calc(100vw - 36px));
|
||||
margin: 28px auto 44px;
|
||||
}
|
||||
header, section {
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 20px rgba(31, 41, 55, 0.05);
|
||||
}
|
||||
header {
|
||||
padding: 24px 28px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 10px;
|
||||
color: var(--head);
|
||||
font-size: 30px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
.subtitle {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
.brief {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
.brief div {
|
||||
background: var(--soft);
|
||||
border: 1px solid #d9e5f0;
|
||||
border-radius: 8px;
|
||||
padding: 12px 14px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.brief strong {
|
||||
display: block;
|
||||
color: var(--head);
|
||||
font-size: 20px;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
section { padding: 22px 24px; margin-top: 18px; }
|
||||
h2 {
|
||||
margin: 0 0 14px;
|
||||
color: var(--head);
|
||||
font-size: 20px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
.note {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
font-size: 13px;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid var(--line);
|
||||
padding: 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
th {
|
||||
background: #eef2f6;
|
||||
color: var(--head);
|
||||
text-align: left;
|
||||
}
|
||||
tbody tr:nth-child(even) { background: #fbfcfe; }
|
||||
.id { width: 44px; text-align: center; }
|
||||
.name { width: 150px; font-weight: 700; color: var(--head); }
|
||||
.mood { width: 170px; }
|
||||
.shot { width: 170px; }
|
||||
.refs { width: 230px; }
|
||||
.thumb {
|
||||
display: block;
|
||||
width: 148px;
|
||||
max-height: 90px;
|
||||
object-fit: cover;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
background: #111827;
|
||||
}
|
||||
.none { color: var(--muted); font-size: 12px; }
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: #e9f1f7;
|
||||
border: 1px solid #cbd9e6;
|
||||
border-radius: 999px;
|
||||
padding: 2px 8px;
|
||||
margin: 0 4px 5px 0;
|
||||
color: #25445f;
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tag:hover { background: #dcecf7; }
|
||||
.table-wrap { overflow-x: auto; }
|
||||
@media (max-width: 900px) {
|
||||
main { width: min(100vw - 20px, 1180px); margin-top: 14px; }
|
||||
header, section { padding: 16px; }
|
||||
.brief { grid-template-columns: 1fr; }
|
||||
table { min-width: 1080px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header>
|
||||
<h1>TH1 音效制作需求清单</h1>
|
||||
<p class="subtitle">用途:给音效制作方使用。包内包含本 HTML、参考截图,以及可参考的现有游戏音效文件。</p>
|
||||
<div class="brief">
|
||||
<div><strong>27 项</strong>合并后的新音效需求</div>
|
||||
<div><strong>6 张</strong>需求参考截图</div>
|
||||
<div><strong>29 个</strong>现有音效参考文件</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<h2>制作说明</h2>
|
||||
<p class="note">整体风格需要适配低多边形、轻幻想、回合制策略游戏。UI 高频音效尽量短、轻、干净;重大时刻和战斗音效可以更有情绪,但避免过长或过于写实。参考音效仅用于理解当前游戏声纹和响度,不要求照抄。</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>需求列表</h2>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="id">序号</th>
|
||||
<th class="name">需求</th>
|
||||
<th>制作内容</th>
|
||||
<th class="mood">情绪 / 力度</th>
|
||||
<th class="refs">可参考现有音效</th>
|
||||
<th class="shot">参考图</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td class="id">1</td><td class="name">切换页面音效</td><td>页签、面板、列表分页切换时的轻量反馈。</td><td>轻、短、清爽;弱于按钮点击。</td><td><a class="tag" href="reference_audio/UI_buttonClick.WAV">UI_buttonClick</a><a class="tag" href="reference_audio/UI_hintTiny.WAV">UI_hintTiny</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">2</td><td class="name">游戏开始音效</td><td>对局正式开始、进入战场或开局公告出现时播放。</td><td>开幕感、振作、行动开始;约 1-2 秒。</td><td><a class="tag" href="reference_audio/start.mp3">start</a><a class="tag" href="reference_audio/MATCH_win.wav">MATCH_win</a></td><td><img class="thumb" src="images/2026-07-02-02-game-start.png" alt="游戏开始音效"></td></tr>
|
||||
<tr><td class="id">3</td><td class="name">中央提示框弹出</td><td>中央确认框、重大提示框、开局说明框弹出时播放。</td><td>提示出现、轻仪式感;不要像警告。</td><td><a class="tag" href="reference_audio/UI_bubbleTiny.WAV">UI_bubbleTiny</a><a class="tag" href="reference_audio/UI_checkOK.wav">UI_checkOK</a></td><td><img class="thumb" src="images/2026-07-02-03-central-modal.png" alt="中央提示框"></td></tr>
|
||||
<tr><td class="id">4</td><td class="name">单位下海音效</td><td>单位从陆地进入水域、登船或水陆转换时播放。</td><td>轻水花、入水感;不是长环境音。</td><td><a class="tag" href="reference_audio/UNIT_move.WAV">UNIT_move</a><a class="tag" href="reference_audio/ENV_sea.WAV">ENV_sea</a><a class="tag" href="reference_audio/ENV_ocean.wav">ENV_ocean</a></td><td><img class="thumb" src="images/2026-07-02-04-unit-enter-water.png" alt="单位下海"></td></tr>
|
||||
<tr><td class="id">5</td><td class="name">按钮轻微 hover</td><td>鼠标移动到按钮上的极轻反馈。</td><td>非常短、轻、低打扰。</td><td><a class="tag" href="reference_audio/UI_buttonHover.wav">UI_buttonHover</a><a class="tag" href="reference_audio/UI_hintTiny.WAV">UI_hintTiny</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">6</td><td class="name">点击无效音效</td><td>不可执行、条件不足、按钮禁用、非法地块点击的反馈。</td><td>短促弱警告;明确但不刺耳。</td><td><a class="tag" href="reference_audio/UI_beep.mp3">UI_beep</a><a class="tag" href="reference_audio/UI_hintTiny.WAV">UI_hintTiny</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">7</td><td class="name">英雄出战音效</td><td>英雄派遣、登场、出战确认时播放。</td><td>角色登场感;强于普通单位出生。</td><td><a class="tag" href="reference_audio/UNIT_born.mp3">UNIT_born</a><a class="tag" href="reference_audio/UNIT_levelup.wav">UNIT_levelup</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">8</td><td class="name">发现结界塔/村庄/遗迹</td><td>探索发现可交互点时播放,结界塔、村庄、遗迹可先共用。</td><td>发现目标、轻奖励感。</td><td><a class="tag" href="reference_audio/UNIT_treasure.wav">UNIT_treasure</a><a class="tag" href="reference_audio/UNIT_capture.wav">UNIT_capture</a></td><td><img class="thumb" src="images/2026-07-02-08-discovery.png" alt="发现结界塔村庄遗迹"></td></tr>
|
||||
<tr><td class="id">9</td><td class="name">点击雾气弹动</td><td>点击未探索雾气或雾墙时的轻弹反馈。</td><td>软、轻、有雾气或弹性;不像错误提示。</td><td><a class="tag" href="reference_audio/UI_hintTiny.WAV">UI_hintTiny</a><a class="tag" href="reference_audio/UNIT_move.WAV">UNIT_move</a></td><td><img class="thumb" src="images/2026-07-02-09-fog-bounce.png" alt="雾气弹动"></td></tr>
|
||||
<tr><td class="id">10</td><td class="name">强大尖锐攻击</td><td>强攻击、英雄攻击、暴击感或尖锐斩击的增强层。</td><td>更亮、更利、更有冲击;不要覆盖普通攻击。</td><td><a class="tag" href="reference_audio/UNIT_attack1.wav">UNIT_attack1</a><a class="tag" href="reference_audio/UNIT_attack.wav">UNIT_attack</a><a class="tag" href="reference_audio/UNIT_attack_arrow.wav">UNIT_attack_arrow</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">11</td><td class="name">振奋时刻</td><td>正面中等强度重大提示,例如占领一个村庄。</td><td>明亮、上扬、有奖励感。</td><td><a class="tag" href="reference_audio/UNIT_capture.wav">UNIT_capture</a><a class="tag" href="reference_audio/UNIT_levelup.wav">UNIT_levelup</a></td><td><img class="thumb" src="images/2026-07-03-01-inspiring-moment.png" alt="振奋时刻"></td></tr>
|
||||
<tr><td class="id">12</td><td class="name">光荣时刻</td><td>比振奋时刻更兴奋、更辉煌的正面重大事件。</td><td>辉煌、仪式感、胜利预感。</td><td><a class="tag" href="reference_audio/MATCH_win.wav">MATCH_win</a><a class="tag" href="reference_audio/UNIT_levelup.wav">UNIT_levelup</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">13</td><td class="name">危难时刻</td><td>负面紧张重大事件提示。</td><td>压迫、警觉;还不是失败。</td><td><a class="tag" href="reference_audio/MATCH_lose.wav">MATCH_lose</a><a class="tag" href="reference_audio/UI_beep.mp3">UI_beep</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">14</td><td class="name">至暗时刻</td><td>比危难时刻更严重的负面重大事件。</td><td>低沉、沉重、局势恶化。</td><td><a class="tag" href="reference_audio/MATCH_lose.wav">MATCH_lose</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">15</td><td class="name">投掷符卡弹道</td><td>符卡、札、纸符类投射物发射声。</td><td>纸张划过、灵力启动;区别于箭矢。</td><td><a class="tag" href="reference_audio/UNIT_attack_arrow.wav">UNIT_attack_arrow</a><a class="tag" href="reference_audio/UNIT_archer.WAV">UNIT_archer</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">16</td><td class="name">投掷魔法弹弹道</td><td>魔法弹、光弹、弹幕发射声。</td><td>能量化、轻灵、快速飞出。</td><td><a class="tag" href="reference_audio/UNIT_attack_arrow.wav">UNIT_attack_arrow</a><a class="tag" href="reference_audio/UNIT_archer.WAV">UNIT_archer</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">17</td><td class="name">范围 AOE</td><td>范围伤害、爆发、溅射、踩踏等结算的冲击声。</td><td>明显命中感、冲击、低频更足。</td><td><a class="tag" href="reference_audio/UNIT_attack_bomb.wav">UNIT_attack_bomb</a><a class="tag" href="reference_audio/UNIT_bomb.wav">UNIT_bomb</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">18</td><td class="name">转化单位为己方</td><td>敌方或中立单位被转化成己方时播放。</td><td>归属改变、确认完成、偏正面。</td><td><a class="tag" href="reference_audio/UNIT_capture.wav">UNIT_capture</a><a class="tag" href="reference_audio/UI_checkOK.wav">UI_checkOK</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">19</td><td class="name">英雄升级</td><td>英雄升级时播放。</td><td>强于普通升级,体现关键角色成长。</td><td><a class="tag" href="reference_audio/UNIT_levelup.wav">UNIT_levelup</a><a class="tag" href="reference_audio/MATCH_win.wav">MATCH_win</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">20</td><td class="name">城市丢失经验</td><td>城市经验减少、降级风险或惩罚结算时播放。</td><td>短促、下坠、负面但不刺耳。</td><td><a class="tag" href="reference_audio/CITY_exp.wav">CITY_exp</a><a class="tag" href="reference_audio/UI_cityExp.WAV">UI_cityExp</a><a class="tag" href="reference_audio/UI_cityUp.WAV">UI_cityUp</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">21</td><td class="name">帝国陨落提示</td><td>有一个国家阵亡、帝国被消灭时的全局提示。</td><td>严重、明确、有历史性。</td><td><a class="tag" href="reference_audio/MATCH_lose.wav">MATCH_lose</a><a class="tag" href="reference_audio/start.mp3">start</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">22</td><td class="name">建立盟友提示</td><td>建立盟友或结盟成功时播放。</td><td>温暖、正面、确认感。</td><td><a class="tag" href="reference_audio/UI_checkOK.wav">UI_checkOK</a><a class="tag" href="reference_audio/UI_playerenter.mp3">UI_playerenter</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">23</td><td class="name">撕毁盟友提示</td><td>背盟、撕毁盟约、盟友关系破裂时播放。</td><td>断裂、紧张、关系恶化。</td><td><a class="tag" href="reference_audio/MATCH_lose.wav">MATCH_lose</a><a class="tag" href="reference_audio/UI_beep.mp3">UI_beep</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">24</td><td class="name">不起眼提示消息</td><td>低优先级提示、弱系统消息、轻提醒。</td><td>很短、低打扰、不能抢注意力。</td><td><a class="tag" href="reference_audio/UI_hintTiny.WAV">UI_hintTiny</a><a class="tag" href="reference_audio/UI_message.mp3">UI_message</a><a class="tag" href="reference_audio/UI_bubbleTiny2.WAV">UI_bubbleTiny2</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">25</td><td class="name">科技完成研发</td><td>科技研发完成时播放。</td><td>知识、解锁、完成感;中等强度。</td><td><a class="tag" href="reference_audio/UI_tech.WAV">UI_tech</a><a class="tag" href="reference_audio/UI_checkOK.wav">UI_checkOK</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">26</td><td class="name">法典版本</td><td>法典、版本、规则书类提示。</td><td>书页、印章、知识感;不要太兴奋。</td><td><a class="tag" href="reference_audio/UI_tech.WAV">UI_tech</a><a class="tag" href="reference_audio/UI_bubbleTiny2.WAV">UI_bubbleTiny2</a></td><td><span class="none">无图</span></td></tr>
|
||||
<tr><td class="id">27</td><td class="name">完成英雄选择</td><td>完成英雄选择、确认英雄阵容时播放。</td><td>流程确认、轻仪式感;不是出战或升级。</td><td><a class="tag" href="reference_audio/UI_checkOK.wav">UI_checkOK</a><a class="tag" href="reference_audio/UNIT_levelup.wav">UNIT_levelup</a></td><td><span class="none">无图</span></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,11 @@
|
||||
# TH1-CI-2026-06-30-001 AttackGround Peace Wonder Semantics
|
||||
|
||||
- Status: fixed in code, guardrail added, pending Unity gameplay validation
|
||||
- First recorded date: 2026-06-30
|
||||
- Raw symptom and affected entrypoint: Sumireko's `UnitAttackGround` Occult Orb placement resets `PlayerData.TurnNoAttack`, interrupting Peace wonder progress. The user noted Suwako had a similar earlier one-off fix. Follow-up: successful `UnitActionType.Raid`, `PlayerActionType.DanegeldDemand`, and Infiltrate city-income theft also needed to count as Peace-breaking hostile economic actions.
|
||||
- Why this is recurring: Peace wonder progress was encoded as a negative counter named `TurnNoAttack`, so implementation sites kept reasoning from entrypoint names such as Attack or AttackGround. That made non-combat ground interactions too aggressive, while non-attack hostile economic actions could be missed.
|
||||
- Root cause: `UnitAttackGroundAction.Execute` called `ActiveAttackActionRecorder.MarkStarted` whenever a skill handled ground targeting through `AttackGroundExecute`. That conflated active attacks with non-damaging ground interactions such as Sumireko orb placement and Kasen beast-guide placement. Separately, successful raiding, Danegeld demand, and Infiltrate theft had no named shared Peace-breaking entrypoint to call because the only existing helper was attack-named.
|
||||
- Root-cause fix: added `SkillBase.AttackGroundCountsAsActiveAttack(...)` and routed `UnitData.AttackGroundExecute` to return that semantic result to `UnitAttackGroundAction`. Added `PeaceWonderProgressRecorder.BreakProgress(...)` as the shared Peace progress reset entrypoint. `UnitAttackGroundAction` now resets Peace progress only when the executed skill declares that it counts as an active attack. Successful raid payouts, Danegeld demands, Infiltrate stolen-city-income payouts, and active damage settlement all use the shared Peace-breaking entrypoint.
|
||||
- Guardrail added: `Tools/CheckAttackGroundPeaceSemantics.ps1` requires every skill overriding `AttackGroundExecute` to explicitly declare `AttackGroundCountsAsActiveAttack`, and `Tools/CheckPeaceWonderHostileActionSemantics.ps1` verifies the shared Peace-breaking entrypoint plus raid, Danegeld demand, Infiltrate theft, and damage-settlement hooks. `Tools/GitCheckpoint.ps1` runs the relevant checks when touched files change.
|
||||
- Verification performed: `Tools/CheckAttackGroundPeaceSemantics.ps1`; `Tools/CheckPeaceWonderHostileActionSemantics.ps1`; `Tools/CheckSuikaFallingSplashAnimation.ps1`; `Tools/GitCheckpoint.ps1 -Strict`; `dotnet build Unity/TH1.Hotfix.csproj --no-restore`.
|
||||
- Remaining validation gaps: Unity Editor gameplay should verify Sumireko orb placement and Kasen beast-guide placement preserve Peace progress, while Suika falling splash/throw, Suwako occupied-grid interaction, Infiltrate city theft, successful Raid, Danegeld demand, and ordinary `UnitAttack` reset it. Danegeld pay/reject should not reset the responding player's Peace progress.
|
||||
@ -0,0 +1,43 @@
|
||||
# TH1-CI-2026-06-30-003 Hakurei Statue Action Icon Context
|
||||
|
||||
- Status: Fixed in code; guardrail added; Unity validation pending
|
||||
- First recorded date: 2026-06-30
|
||||
- Severity: High
|
||||
|
||||
## Raw Symptom
|
||||
|
||||
Player report: Hakurei empire statue icons were corrected on the map/resource side, but the build action for the horse statue still displayed the wrong icon.
|
||||
|
||||
## Why This Recurs
|
||||
|
||||
Hakurei/Reimu statue sprites are civilization and force variants in `ActionDataAssets.IconList`, not the top-level `ActionInfo.Icon`. Any UI path that renders an action by reading `actionInfo.Icon` directly can still show the default or another civilization's statue icon even after the data row has the correct `CivId: 4` / `ForceId: 4` variant.
|
||||
|
||||
## Root Cause
|
||||
|
||||
`HeroHintPanelCommonCircleMono.SetContent(CommonActionId)` assigned `SkillIcon.sprite = actionInfo.Icon`. This bypassed `ActionInfo.GetIcon(...)`, so action circles shown in the hero hint/action panel ignored the current player's civilization and force.
|
||||
|
||||
## Root-Cause Fix
|
||||
|
||||
- `HeroHintPanelCommonCircleMono` now resolves action icons through the current self player's `CivEnum` and `ForceEnum`.
|
||||
- When no match map/self player exists, it falls back to the common `0/0` table icon, then the legacy top-level `Icon`.
|
||||
- The fix keeps default action icons intact for other civilizations instead of hardcoding Hakurei sprites into `ActionInfo.Icon`.
|
||||
|
||||
## Guardrail Added
|
||||
|
||||
- `Tools/CheckHakureiStatueActionIcons.ps1` checks that:
|
||||
- Hakurei/Reimu statue build actions for horse, mountain, water, peace, and forest use the expected `CivId: 4` / `ForceId: 4` icons in both source and export ActionDataAssets.
|
||||
- Hakurei statue GUIDs are not assigned to the wrong `CivId: 5` / `ForceId: 5` action entries.
|
||||
- `HeroHintPanelCommonCircleMono` no longer assigns action circles from `actionInfo.Icon` directly and contains variant-aware fallback logic.
|
||||
|
||||
## Verification Performed
|
||||
|
||||
- `Tools/CheckHakureiStatueActionIcons.ps1`
|
||||
- `git diff --check`
|
||||
- `dotnet build Unity/TH1.Hotfix.csproj --no-restore`
|
||||
|
||||
## Remaining Validation Gaps
|
||||
|
||||
- Unity Editor visual validation is still required for Hakurei/Reimu:
|
||||
1. Open the hero/action hint panel that lists build actions.
|
||||
2. Confirm the horse statue action circle uses the Hakurei/Reimu horse statue icon.
|
||||
3. Confirm the map build action button and completed map statue still use the same faction-specific horse statue icon.
|
||||
@ -0,0 +1,53 @@
|
||||
# TH1-CI-2026-06-30-002 Move Transform Visual Refresh
|
||||
|
||||
- Status: Fixed in code; guardrail added; Unity validation pending
|
||||
- First recorded date: 2026-06-30
|
||||
- Severity: High
|
||||
|
||||
## Raw Symptom
|
||||
|
||||
Player report: a unit passively displaced by a giant into a port-water tile looked like it was still a ship after later moving ashore.
|
||||
|
||||
Existing local evidence:
|
||||
|
||||
- `DOC/bugs.json` has an open report: "船移动上岸但是没有立刻变成陆地单位".
|
||||
- CrashSight summaries include repeated `BoatUnitOnLandState` / `BoatUnitOnLandBeforeAction` diagnostics around move fragments and landing-related paths.
|
||||
|
||||
## Why This Recurs
|
||||
|
||||
TH1 movement mutates authoritative data before the move Fragment plays. `UnitLogic.MoveToLogic` correctly handles:
|
||||
|
||||
- `LandAndPort` unit entering `ResourceType.Port` through `LandToBoat`.
|
||||
- `WaterAndAshore` unit entering `TerrainType.Land` through `BoatToLand`.
|
||||
|
||||
However, `UnitTypeTransform` only changes data and skill state. Renderer sprite refresh normally happens at the end of `FragmentMove`, so during the move animation the visible unit can still use the pre-transform sprite. This is especially visible after passive displacement into a port followed by landing.
|
||||
|
||||
## Root Cause
|
||||
|
||||
`FragmentMove` started the move animation before refreshing the `UnitRenderer` image from already-mutated `UnitData`. When the action had already converted a unit between land and ship forms, the renderer could animate with the stale sprite until the fragment settle step.
|
||||
|
||||
## Root-Cause Fix
|
||||
|
||||
- Added `UnitRenderer.InstantUpdateUnitImageOnly()` for image/status refresh without changing visibility or position.
|
||||
- `FragmentMove` now calls that method before enqueuing the move atom animation, so movement involving `LandToBoat` / `BoatToLand` starts with the current data form while preserving the existing final settle refresh.
|
||||
|
||||
## Guardrail Added
|
||||
|
||||
- `Tools/CheckMoveTransformVisualRefresh.ps1` checks that:
|
||||
- `UnitRenderer` exposes the image-only refresh method.
|
||||
- `FragmentMove` refreshes transformed unit image before starting `UnitAtomAnimType.Move`.
|
||||
- `FragmentMove` still performs its final `InstantUpdateUnit(true)` settle refresh.
|
||||
|
||||
## Verification Performed
|
||||
|
||||
- Pending in this work item:
|
||||
- `Tools/CheckMoveTransformVisualRefresh.ps1`
|
||||
- `dotnet build Unity/TH1.Hotfix.csproj --no-restore`
|
||||
|
||||
## Remaining Validation Gaps
|
||||
|
||||
- Unity Editor visual validation is still required for the exact scenario:
|
||||
1. A giant spawns or moves onto an occupied tile.
|
||||
2. The displaced land unit passively moves into an allied port-water tile.
|
||||
3. The resulting ship moves ashore.
|
||||
4. During and after the landing animation, the unit sprite matches its land form.
|
||||
@ -0,0 +1,54 @@
|
||||
# TH1-CI-2026-07-01-001 Civ/Force Enum-Id Confusion
|
||||
|
||||
- Status: Workflow rule added; memory/rule guardrail added
|
||||
- First recorded date: 2026-07-01
|
||||
- Severity: Critical
|
||||
|
||||
## Raw Symptom
|
||||
|
||||
User report: Codex repeatedly confused `CivEnum`, `CivId`, `ForceEnum`, and `ForceId`, including assigning Hakurei/Reimu content to `CivId: 5` / `ForceId: 5` when the correct table ids are `4/4`.
|
||||
|
||||
## Why This Recurs
|
||||
|
||||
TH1 has two numeric layers that look similar:
|
||||
|
||||
- `CivEnum` / `ForceEnum` are runtime enums and real empires start at 1 because `Common = 0`.
|
||||
- `CivId` / `ForceId` in DataAssets, YAML, `PlayerData`, and match slots are 0-based `uint` table ids.
|
||||
|
||||
Without an explicit remembered rule, it is easy to read `CivEnum.Norway = 5` / `ForceEnum.Reimu = 5` and incorrectly write `CivId = 5` / `ForceId = 5`.
|
||||
|
||||
## Root Cause
|
||||
|
||||
Codex treated enum numeric values and table ids as interchangeable instead of using the project conversion API:
|
||||
|
||||
- `Table.Instance.TransCivEnumToCivId`
|
||||
- `Table.Instance.TransCivIdToCivEnum`
|
||||
- `Table.Instance.TransForceEnumToForceId`
|
||||
- `Table.Instance.TransForceIdToForceEnum`
|
||||
|
||||
## Root-Cause Fix
|
||||
|
||||
- Added the Civ/Force enum-id invariant to the user-level Codex rules file.
|
||||
- Added a user-level memory note under `.codex/memories`.
|
||||
- Added the same invariant to repository `AGENTS.md`.
|
||||
- Added the same invariant to `.codex/skills/th1-base/SKILL.md`, which is the baseline skill for broad TH1 work.
|
||||
|
||||
## Guardrail Added
|
||||
|
||||
Future TH1 work must apply this rule before touching any field or API involving civilizations, forces, players, empires, DataAssets, YAML, icons, units, resources, wonders, tech, map slots, or content gating:
|
||||
|
||||
- Never copy enum numeric values directly into `CivId` / `ForceId`.
|
||||
- Use conversion APIs or existing `PlayerCivId` / `PlayerForceId`.
|
||||
- Hakurei/Reimu is `CivEnum.Norway = 5` / `ForceEnum.Reimu = 5`, mapping to `CivId = 4` / `ForceId = 4`.
|
||||
- Treat `CivId = 5` / `ForceId = 5` as not Hakurei/Reimu.
|
||||
- Avoid enum-to-id conversion for `Common = 0` because it underflows to `uint.MaxValue`; use explicit `0u` only when common table ids are required.
|
||||
|
||||
## Verification Performed
|
||||
|
||||
- Inspected `Table.Instance` conversion methods in `Unity/Assets/Scripts/TH1_Instance/Table.cs`.
|
||||
- Inspected `CivEnum` / `ForceEnum` values in `Unity/Assets/Scripts/TH1_DataAssetsScript/PlayerDataAssets.cs`.
|
||||
|
||||
## Remaining Validation Gaps
|
||||
|
||||
- This is a workflow/memory guardrail, not a runtime code change.
|
||||
- Existing data-specific validators, such as `Tools/CheckHakureiStatueActionIcons.ps1`, should still be used for concrete asset families.
|
||||
@ -0,0 +1,64 @@
|
||||
# TH1-CI-2026-07-01-004 GrowForest Under-Building Farm
|
||||
|
||||
## Status
|
||||
|
||||
Fixed in code; guardrail added; Unity validation pending.
|
||||
|
||||
## First Recorded Date
|
||||
|
||||
2026-07-01
|
||||
|
||||
## Raw Symptom And Affected Path
|
||||
|
||||
玩家反馈“现在还是会出现庄稼上种树的 bug”,怀疑没有处理 `underGroundResource=farm` 同时种树的情况。
|
||||
|
||||
Affected path:
|
||||
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Action/GridMiscActionLogic.cs`
|
||||
- `GridMiscActionType.GrowForest`
|
||||
- Kaguya French Napoleonic Code path that allows planting forest on existing buildings.
|
||||
|
||||
## Why This Is Recurring
|
||||
|
||||
GrowForest 的常规合法性只看 `GridData.Resource`,法典特例还允许在 `Windmill/Sawmill/Forge/Market/Wonder/Military...` 等已有建筑上种树。TH1 的建筑层会把原资源放入 `GridData.ResourceUnderBuilding`,例如谷仓/其他建筑下可能保留 `Farm`。此前判断没有检查 `ResourceUnderBuilding`,因此 UI 展示和最终执行都可能放行“建筑表层 + 农田底层”的地块。
|
||||
|
||||
## Root Cause
|
||||
|
||||
`CheckCan` 和 `CheckShow` 各自内联 GrowForest 判断,且都没有把 `ResourceUnderBuilding != ResourceType.None` 视为非法种树条件。法典特例在已有建筑上种树时不会清空 `Resource`,所以隐藏底层资源会持续影响地块状态。
|
||||
|
||||
## Root-Cause Fix
|
||||
|
||||
抽出 `CanGrowForestOnGrid(CommonActionParams actionParams)` 作为唯一 GrowForest 合法性谓词,并在其中要求:
|
||||
|
||||
- terrain 是陆地;
|
||||
- feature 不是山;
|
||||
- 当前没有树林;
|
||||
- `ResourceUnderBuilding == ResourceType.None`;
|
||||
- 常规资源只允许 `None/Crop/Fruit`;
|
||||
- 法典特例仍只允许既定建筑白名单。
|
||||
|
||||
`CheckCan` 和 `CheckShow` 均调用该共享谓词。
|
||||
|
||||
## Guardrail Added
|
||||
|
||||
Added `Tools/CheckGrowForestUnderBuildingResource.ps1` to assert:
|
||||
|
||||
- GrowForest uses a shared `CanGrowForestOnGrid` predicate;
|
||||
- the predicate checks `ResourceUnderBuilding != ResourceType.None`;
|
||||
- `CheckCan` and `CheckShow` both call the predicate.
|
||||
|
||||
`Tools/GitCheckpoint.ps1` now runs the guardrail when `GridMiscActionLogic.cs` or the check script changes.
|
||||
|
||||
## Verification Performed
|
||||
|
||||
- `Tools/CheckGrowForestUnderBuildingResource.ps1` passed.
|
||||
- `Tools/GitCheckpoint.ps1` passed and triggered the GrowForest guardrail.
|
||||
- `dotnet build Unity/TH1.Hotfix.csproj --no-restore` passed with existing warnings.
|
||||
|
||||
## Remaining Validation Gaps
|
||||
|
||||
Unity Editor manual validation is still needed for the exact player-reported scenario:
|
||||
|
||||
- build or load a grid whose visible building has `ResourceUnderBuilding = Farm`;
|
||||
- confirm GrowForest action does not show and cannot execute;
|
||||
- confirm normal empty/crop/fruit GrowForest and intended Napoleonic Code building cases still work when `ResourceUnderBuilding = None`.
|
||||
@ -0,0 +1,60 @@
|
||||
# TH1-CI-2026-07-01-002 Hakurei Statue Obsolete Asset Family
|
||||
|
||||
- Status: Fixed in assets; guardrail added; Unity validation pending
|
||||
- First recorded date: 2026-07-01
|
||||
- Severity: Critical
|
||||
|
||||
## Raw Symptom
|
||||
|
||||
User report: Hakurei empire statue buildings and build actions still used wrong icons, including the horse statue build action. The five `ReimuForces_Norway_Statue_*` PNGs were not just assigned to the wrong rows; they were invalid assets that should not exist in the game.
|
||||
|
||||
Affected files:
|
||||
|
||||
- `Unity/Assets/BundleResources/ArtResources/TH1Buildings/TH1BuildingsReimuForces/Norway/ReimuForces_Norway_Statue_*.png`
|
||||
- `Unity/Assets/BundleResources/DataAssets/ActionDataAssets.asset`
|
||||
- `Unity/Assets/BundleResources/DataAssets/GridAndResourceDataAssets.asset`
|
||||
- `Unity/Assets/BundleResources/DataAssets/CultureCardDataAssets.asset`
|
||||
- Matching `Unity/Assets/BundleResources/Export/*` runtime assets
|
||||
|
||||
## Why This Recurs
|
||||
|
||||
The prior fix treated newly introduced statue PNGs as the expected Hakurei/Reimu art instead of comparing against existing civilization temple/statue rows. That also caused the guardrail script to encode the wrong GUIDs as expected values, so future checks would have preserved the bad assets instead of rejecting them.
|
||||
|
||||
## Root Cause
|
||||
|
||||
Two mistakes compounded:
|
||||
|
||||
- The asset family `TH1BuildingsReimuForces/Norway/ReimuForces_Norway_Statue_*` was introduced even though Hakurei/Reimu already has the correct temple/statue sprite family under `TH1BuildingsCommon/Temple/Hakurei`.
|
||||
- The 1-based enum value `CivEnum.Norway = 5` / `ForceEnum.Reimu = 5` was confused with DataAsset ids, polluting `CivId: 5` / `ForceId: 5` rows even though Hakurei/Reimu is `CivId: 4` / `ForceId: 4`.
|
||||
|
||||
## Root-Cause Fix
|
||||
|
||||
- Removed the invalid `ReimuForces_Norway_Statue_Horse/Queen/Chariot/Minister/King` PNGs and their `.meta` files.
|
||||
- Repointed Hakurei/Reimu `CivId: 4` / `ForceId: 4` action, map-resource, and culture-card sprite references to the existing `TH1BuildingsCommon/Temple/Hakurei` series:
|
||||
- Horse statue: `NorwayAunn_Temple1..5`
|
||||
- Queen statue: `NorwaySumireko_Temple1..5`
|
||||
- Chariot statue: `NorwaySuika_Temple1..5`
|
||||
- Minister statue: `NorwayKasen_Temple1..5`
|
||||
- King statue: `NorwayReimu_Temple1..5`
|
||||
- Removed the erroneous `CivId: 5` / `ForceId: 5` Hakurei temple rows from grid/resource data.
|
||||
|
||||
## Guardrail Added
|
||||
|
||||
`Tools/CheckHakureiStatueActionIcons.ps1` now fails if:
|
||||
|
||||
- Any obsolete `ReimuForces_Norway_Statue_*` file exists.
|
||||
- Any DataAssets or Export asset still references the obsolete GUIDs.
|
||||
- Hakurei/Reimu statue action, map-resource, or culture-card rows for `CivId: 4` / `ForceId: 4` do not use the existing `TH1BuildingsCommon/Temple/Hakurei` GUIDs.
|
||||
- Any Hakurei temple GUID leaks into `CivId: 5` / `ForceId: 5`.
|
||||
- The hero action hint UI bypasses `ActionInfo.GetIcon`.
|
||||
|
||||
## Verification Performed
|
||||
|
||||
- `Tools/CheckHakureiStatueActionIcons.ps1`
|
||||
- `Tools/CheckUnityAssetReferenceIntegrity.ps1`
|
||||
- `rg` scan for obsolete GUIDs under `Unity/Assets/BundleResources`
|
||||
- `git diff --check`
|
||||
|
||||
## Remaining Validation Gaps
|
||||
|
||||
- Unity Editor visual validation is still required to confirm all five Hakurei/Reimu statue build actions, culture-card icons, and completed map resources render with the intended Aunn/Sumireko/Suika/Kasen/Reimu temple series.
|
||||
@ -0,0 +1,11 @@
|
||||
# TH1-CI-2026-07-01-003 Suika Falling Splash Death-Replacement Landing
|
||||
|
||||
- Status: Fixed in code; guardrail added; Unity validation pending
|
||||
- First recorded date: 2026-07-01
|
||||
- Raw symptom and affected entrypoint: player reported that when Lv4 Suika Ibuki jumps exactly 3 grids and kills a death-replacement target such as Mokou or Koakuma, Suika disappears. Affected entrypoint is `SuikaFallingSplashSkill.AfterActiveAttackOther` -> `HakureiNorwayHeroSkillUtil.TryExecuteSuikaFlyAfterAttack`.
|
||||
- Why this is recurring: Suika Lv4 falling splash has repeatedly needed special handling for jump timing, final landing, fog refresh, water targeting, and now death-replacement occupants. The dedicated Fragment fixed presentation timing, but the landing selector still assumed a killed target leaves the target grid available.
|
||||
- Root cause: death-replacement skills run during `DamageSettlement` and can immediately add a new unit on `attackInfo.DamageTargetGrid`. After the splash damage settlement, Suika's landing logic still preferred the original target grid if terrain checks passed. `SetUnitIdToGridId` can overwrite the grid occupancy mapping, so Suika and the replacement unit diverge between data and renderer state, presenting as Suika disappearing or the target grid becoming inconsistent.
|
||||
- Root-cause fix: `TryExecuteSuikaFlyAfterAttack` now treats any non-Suika occupant on the target grid after splash settlement as a blocker and chooses a legal surrounding empty landing grid before repositioning Suika.
|
||||
- Guardrail added: `Tools/CheckSuikaFallingSplashAnimation.ps1` now asserts that the attack-after-splash path settles damage, checks `TargetGridHasOtherUnit`, falls back through `TryFindRandomEmptyAround`, then caches sight refresh grids and repositions Suika.
|
||||
- Verification performed: `Tools/CheckSuikaFallingSplashAnimation.ps1` passed. `dotnet build Unity/TH1.Hotfix.csproj --no-restore` passed with 0 errors and existing warnings.
|
||||
- Remaining validation gaps: Unity Editor gameplay validation should cover Lv4 Suika jumping 3 grids to kill Mokou egg replacement and Scarlet Koakuma lion replacement; verify Suika lands on an adjacent legal grid, the replacement unit remains on the target grid, both renderers remain visible, and fog/highlights refresh.
|
||||
@ -6,6 +6,13 @@
|
||||
|
||||
| ID | 日期 | 状态 | 严重度 | 问题 | 根治方向 | 记录 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| TH1-CI-2026-07-01-004 | 2026-07-01 | Fixed in code; guardrail added; Unity validation pending | High | 法典种树允许在已有建筑上叠森林,但未检查 `ResourceUnderBuilding=Farm`,导致农田/庄稼底层资源仍可能被种树覆盖显示 | GrowForest 的 CheckCan/CheckShow 统一走共享谓词,并拒绝任何非空底层资源;GitCheckpoint 接入守护脚本 | [record](2026-07-01-growforest-under-building-farm.md) |
|
||||
| TH1-CI-2026-07-01-003 | 2026-07-01 | Fixed in code; guardrail added; Unity validation pending | High | 萃香 Lv4 三格坠落击杀妹红/小恶魔等死亡替换目标后,落点仍可选中已生成新单位的目标格,导致萃香消失或格子渲染错乱 | 坠落伤害结算后重新检查目标格真实占位;死亡替换生成单位时改落周围合法空格,并用守护脚本锁定顺序 | [record](2026-07-01-suika-falling-splash-death-replacement-landing.md) |
|
||||
| TH1-CI-2026-07-01-002 | 2026-07-01 | Fixed in assets; guardrail added; Unity validation pending | Critical | 博丽/灵梦神像错误 PNG 资源族被引入,并被 action、地图资源、文化卡引用,守护脚本还把错误 GUID 当期望值 | 删除无效 `ReimuForces_Norway_Statue_*` 资源,统一回到现有 `TH1BuildingsCommon/Temple/Hakurei` 系列,并黑名单旧 GUID/文件与 5/5 污染 | [record](2026-07-01-hakurei-statue-obsolete-assets.md) |
|
||||
| TH1-CI-2026-07-01-001 | 2026-07-01 | Workflow rule added; memory/rule guardrail added | Critical | Codex repeatedly confused 1-based `CivEnum`/`ForceEnum` with 0-based `CivId`/`ForceId`, causing wrong Hakurei/Reimu table ids | Encode enum-id conversion as a hard rule in global Codex rules, user memory, repo `AGENTS.md`, and `th1-base`; require conversion APIs before editing related data | [record](2026-07-01-civ-force-enum-id-confusion.md) |
|
||||
| TH1-CI-2026-06-30-003 | 2026-06-30 | Fixed in code; guardrail added; Unity validation pending | High | 博丽/灵梦神像 action 配置已有变体图标,但英雄提示 action 圆按钮仍绕过变体选择使用默认图 | 所有 action 图标展示入口必须通过 `ActionInfo.GetIcon` 按当前玩家文明/势力解析,并增加配置/UI 双重校验 | [record](2026-06-30-hakurei-statue-action-icon-context.md) |
|
||||
| TH1-CI-2026-06-30-002 | 2026-06-30 | Fixed in code; guardrail added; Unity validation pending | High | 上下船移动的数据形态已变化,但 `FragmentMove` 开始动画时仍可能显示旧船/旧陆地外观 | 在移动 Fragment 起步前用当前 `UnitData` 刷新单位图片,同时保留终点 settle 刷新 | [record](2026-06-30-move-transform-visual-refresh.md) |
|
||||
| TH1-CI-2026-06-30-001 | 2026-06-30 | Fixed in code; guardrail added; Unity validation pending | High | `UnitAttackGround` support/place skills can reset Peace wonder progress because the entrypoint name is treated as active attack | Centralize ground-target active-attack semantics and require every `AttackGroundExecute` skill to declare whether it counts as an attack | [record](2026-06-30-attackground-peace-wonder-semantics.md) |
|
||||
| TH1-CI-2026-06-29-001 | 2026-06-29 | Fixed in code; 17-player 10-turn batch passed | Critical | AI Director fallback can repeat zero-effect actions until the AI loop guard fires | Filter no-progress fallback actions, align BuildWonder CheckCan with Execute prerequisites, and keep batch diagnostics compact | [record](2026-06-29-ai-director-zero-effect-action-loop.md) |
|
||||
| TH1-CI-2026-06-27-001 | 2026-06-27 | Fixed in code; guardrail added; Unity validation pending | High | Suika Lv4 falling splash water restriction applied to empty grids but not unit targets on water | Share falling-splash water target validation across ground targeting, unit targeting, and `UnitAttack` CheckCan filtering | [record](2026-06-27-suika-falling-splash-water-targeting.md) |
|
||||
| TH1-CI-2026-06-26-002 | 2026-06-26 | Fixed in code; guardrail added; Unity validation pending | Critical | Aunn shared HP ignored `DamageBearer` substitute-damage paths | Treat shared HP as an invariant over the actual mutated unit, `DamageBearer ?? DamageTarget`, with entrypoint fallback and shared-death cleanup | [record](2026-06-26-aunn-shared-health-damage-bearer.md) |
|
||||
|
||||
@ -1,33 +1,47 @@
|
||||
# 12 - 音频系统与当前音效设计清单
|
||||
# 12 - 音频系统与当前音效清单
|
||||
|
||||
> 记录时间:2026-06-27
|
||||
> 记录时间:2026-06-29
|
||||
> 代码入口:`Unity/Assets/Scripts/TH1_Audio/AudioManager.cs`
|
||||
> 音频资源:`Unity/Assets/BundleResources/Audio/`
|
||||
> 音乐配置:`Unity/Assets/BundleResources/DataAssets/MusicDataAssets.asset`、`PlayerDataAssets.asset`
|
||||
> 投射物音效配置:`Unity/Assets/BundleResources/DataAssets/ProjectileTypeDataAssets.asset`
|
||||
|
||||
本文档记录当前工程内已经接入的 BGM / SFX、资源存在但尚未直接接入的音频,以及各音效的实际播放时刻。
|
||||
本文档是当前工程音频资源和实际播放点的快照。统计口径以游戏正式资源 `Unity/Assets/BundleResources/Audio` 为主;`Unity/Assets/Plugins` 下的第三方示例音频不计入游戏正式音效。
|
||||
|
||||
---
|
||||
|
||||
## 1. 播放系统规则
|
||||
|
||||
- `AudioManager.Init()` 在启动时预加载固定音频 key 到 `_clips` 缓存,播放时使用 `PlayMusic(key, fadeIn, fadeOut, isLoop, isContinue)` 或 `PlayAudio(key, fadeIn, fadeOut, isLoop)`。
|
||||
- BGM 使用 `MusicVolume`,SFX 使用 `AudioVolume`;二者都来自 `ConfigManager.Instance.Config`。
|
||||
- BGM 切换走淡入淡出。旧 BGM 会先 `FadeOut`,新 BGM 使用新的 `AudioPlayer` crossfade,避免复用同一个 `AudioSource` 导致硬切。
|
||||
- BGM 播放器底层 `AudioSource.loop` 会被强制打开,真正是否循环由 `AudioPlayer.IsLoop` 和状态机控制,目的是避免 Unity 播放到 clip 末尾时提前停声。
|
||||
- SFX 使用播放器池,最多 16 个 `AudioPlayer`;超过时会尝试复用最早的播放器。
|
||||
- `AudioClipConfig` 可额外把场景内 `AudioClip` 按 `clip.name` 注册进缓存,但当前主要音频都来自 `BundleResources/Audio`。
|
||||
- `AudioManager.Init()` 启动时把固定音频 key 加入 `_clips` 缓存,播放时通过 `PlayMusic(key, fadeIn, fadeOut, isLoop, isContinue)` 或 `PlayAudio(key, fadeIn, fadeOut, isLoop)`。
|
||||
- BGM 使用 `ConfigManager.Instance.Config.MusicVolume`,SFX 使用 `ConfigManager.Instance.Config.AudioVolume`。
|
||||
- BGM 切换有淡入淡出。旧 BGM 进入 `FadeOut`,新 BGM 用新的 `AudioPlayer` crossfade,避免复用同一个 `AudioSource` 造成硬切。
|
||||
- BGM 底层 `AudioSource.loop` 会被强制打开,实际是否循环由 `AudioPlayer.IsLoop` 和状态机控制,避免 Unity 在 clip 末尾提前停声导致淡出丢失。
|
||||
- SFX 使用播放器池,最多 16 个 `AudioPlayer`;超过时尝试复用最早的播放器。
|
||||
- `AudioClipConfig` 可以把场景内 `AudioClip` 按 `clip.name` 额外注册进缓存,但当前主要正式音频都来自 `BundleResources/Audio`。
|
||||
- 很多 prefab 仍保留 `AudioSource`、`ButtonHoverEffect.hoverSound` / `clickSound` 序列化字段,但当前 `ButtonHoverEffect` 实际调用的是 `AudioManager.PlayAudio("SFX/UI_buttonHover")` / `PlayAudio("SFX/UI_buttonClick")`,不是 `AudioSource.PlayOneShot`。
|
||||
|
||||
---
|
||||
|
||||
## 2. BGM 清单
|
||||
## 2. 当前音频资源规模
|
||||
|
||||
- 正式 BGM:18 个 `.wav`,位于 `Unity/Assets/BundleResources/Audio` 根目录。
|
||||
- 正式 SFX:40 个文件,位于 `Unity/Assets/BundleResources/Audio/SFX`。
|
||||
- `AudioManager.Init()` 当前注册:
|
||||
- 18 个 BGM key。
|
||||
- 28 个 SFX key。
|
||||
- 当前代码 / 配置实际会触发播放的 SFX key:26 个。
|
||||
- 当前注册但没有找到实际播放点的 SFX key:`SFX/UNIT_bomb`、`SFX/UNIT_hurt`。
|
||||
- 当前资源存在但未注册、未直接播放的 SFX 文件:12 个,见第 7 节。
|
||||
|
||||
---
|
||||
|
||||
## 3. BGM 清单
|
||||
|
||||
| BGM key | 资源文件 | 用途 / 归属 | 主要播放时刻 |
|
||||
|---|---|---|---|
|
||||
| `Main` | `Audio/Main.wav` | 主菜单 BGM | 游戏启动后、主菜单打开时、序章界面关闭后播放。 |
|
||||
| `Story` | `Audio/Game.wav` | 序章 / 故事模式 BGM | 打开序章剧情界面时播放。 |
|
||||
| `RemiliaEgyptian` | `Audio/RemiliaEgyptian.wav` | 斯卡雷特帝国 / 埃及 | 选择该文明、开局重要时刻、首次遭遇、外交/科技/英雄等对应文明信息界面播放;可进入轮播池。 |
|
||||
| `RemiliaEgyptian` | `Audio/RemiliaEgyptian.wav` | 斯卡雷特帝国 / 埃及 | 选择该文明、开局重要时刻、首次遭遇、外交 / 科技 / 英雄等对应文明信息界面播放;可进入轮播池。 |
|
||||
| `KaguyaFrench` | `Audio/KaguyaFrench.wav` | 蓬莱山帝国 / 法兰西 | 同上。 |
|
||||
| `KanakoGermany` | `Audio/KanakoGermany.wav` | 守矢帝国 / 德意志 | 同上。 |
|
||||
| `SatoriIndian` | `Audio/SatoriIndian.wav` | 古明地帝国 / 印度 | 同上。 |
|
||||
@ -35,83 +49,83 @@
|
||||
| `ByakurenBritish` | `Audio/ByakurenBritish.wav` | 命莲帝国 / 英格兰 | 同上。 |
|
||||
| `MikoPersian` | `Audio/MikoPersian.wav` | 丰聪耳帝国 / 波斯 | 同上。 |
|
||||
| `ZanmuByzantine` | `Audio/ZanmuByzantine.wav` | 日白帝国 / 拜占庭 | 同上。 |
|
||||
| `SaigyoujiSumerian` | `Audio/SaigyoujiSumerian.wav` | 白玉帝国 / 苏美尔 | 同上;由 `PlayerDataAssets.MusicName` 和 `MusicDataAssets` 接入。 |
|
||||
| `SaigyoujiSumerian` | `Audio/SaigyoujiSumerian.wav` | 白玉帝国 / 苏美尔 | 同上。 |
|
||||
| `LapisMayan` | `Audio/LapisMayan.wav` | 拉碧斯帝国 / 玛雅 | 同上。 |
|
||||
| `IizunamaruMalian` | `Audio/IizunamaruMalian.wav` | 天狗帝国 / 马里 | 同上。 |
|
||||
| `CirnoGreek` | `Audio/CirnoGreek.wav` | 无敌厉害帝国 / 希腊 | 同上。 |
|
||||
| `WatatsukiKhmer` | `Audio/WatatsukiKhmer.wav` | 月之帝国 / 高棉 | 同上。 |
|
||||
| `HinanawiAztec` | `Audio/HinanawiAztec.wav` | 比那名居帝国 / 阿兹特克 | 同上。 |
|
||||
| `ChirizukaIncan` | `Audio/ChirizukaIncan.wav` | 圣域帝国 / 印加 | 同上。 |
|
||||
| `KijinMogolian` | `Audio/KijinMogolian.wav` | 国帝邪天 / 蒙古 | 同上。 |
|
||||
| `KijinMogolian` | `Audio/KijinMogolian.wav` | 天邪帝国 / 蒙古 | 同上。 |
|
||||
|
||||
### BGM 轮播规则
|
||||
|
||||
- 轮播开关为 `Config.BgmContinuousPlay`,默认值为 `true`。
|
||||
- 轮播候选来自当前真人玩家 `MeetPlayers` 中已遭遇玩家的 `PlayerDataAssets.MusicName`,并过滤掉 AudioManager 未注册的 key。
|
||||
- 轮播开关为 `Config.BgmContinuousPlay`,当前默认开启。
|
||||
- 轮播候选来自当前真人玩家 `MeetPlayers` 中已遭遇玩家的 `PlayerDataAssets.MusicName`,并过滤掉 `AudioManager` 未注册的 key。
|
||||
- 轮播列表会随机打乱,并避免连续两次选到同一首。
|
||||
- 从临时 BGM 场景返回时会调用 `ResumeBgmRotationIfEnabled()`,例如:重要时刻弹窗关闭、外交/科技/英雄/地块信息页关闭。
|
||||
- 从临时 BGM 场景返回时会调用 `ResumeBgmRotationIfEnabled()`,例如重要时刻弹窗关闭、外交 / 科技 / 英雄 / 地块信息页关闭。
|
||||
- 音乐鉴赏室会暂存进入前的 BGM,试听曲目结束或离开 Music sheet 时恢复原 BGM。
|
||||
|
||||
---
|
||||
|
||||
## 3. 已接入 SFX 清单
|
||||
## 4. 实际播放中的 SFX
|
||||
|
||||
### 3.1 UI 与提示
|
||||
### 4.1 UI 与联网提示
|
||||
|
||||
| SFX key | 资源文件 | 播放时刻 |
|
||||
| SFX key | 资源文件 | 实际播放点 |
|
||||
|---|---|---|
|
||||
| `SFX/UI_buttonHover` | `Audio/SFX/UI_buttonHover.wav` | `ButtonSFX.OnPointerEnter`、`ButtonHoverEffect.OnPointerEnter`;多数按钮鼠标悬停时播放。 |
|
||||
| `SFX/UI_buttonClick` | `Audio/SFX/UI_buttonClick.WAV` | `ButtonSFX.OnPointerClick`、`ButtonHoverEffect.OnPointerDown`;多数按钮点击 / 按下时播放。 |
|
||||
| `SFX/UI_beep` | `Audio/SFX/UI_beep.mp3` | 底部栏切换特殊面板时播放。 |
|
||||
| `SFX/UI_message` | `Audio/SFX/UI_message.mp3` | 聊天区收到 / 显示消息时播放。 |
|
||||
| `SFX/UI_playerenter` | `Audio/SFX/UI_playerenter.mp3` | 联机大厅玩家进入、房间列表 / 底部联网提示刷新相关入口播放。 |
|
||||
| `SFX/start` | `Audio/SFX/start.mp3` | 回合提示 `TurnHint` 通知显示时播放。 |
|
||||
| `SFX/UI_buttonHover` | `Audio/SFX/UI_buttonHover.wav` | `ButtonSFX.OnPointerEnter`、`ButtonHoverEffect.OnPointerEnter`。多数按钮鼠标悬停时播放。 |
|
||||
| `SFX/UI_buttonClick` | `Audio/SFX/UI_buttonClick.WAV` | `ButtonSFX.OnPointerClick`、`ButtonHoverEffect.OnPointerDown`。多数按钮点击 / 按下时播放。 |
|
||||
| `SFX/UI_beep` | `Audio/SFX/UI_beep.mp3` | 联机限时回合中,当前玩家回合且底部 NextTurn 按钮可见时,剩余 10 到 1 秒每个秒数播放一次。 |
|
||||
| `SFX/UI_message` | `Audio/SFX/UI_message.mp3` | `UIChatAreaMono.RefreshChatBox()` 增量显示新聊天消息时播放。大厅聊天和游戏内聊天共用该聊天组件。 |
|
||||
| `SFX/UI_playerenter` | `Audio/SFX/UI_playerenter.mp3` | `UIOutsideMultiplayView` 大厅房间成员数增加时播放;`UIBottomNetView` 游戏内联网成员数增加时播放。首次进入 / 初始化计数不会响。 |
|
||||
| `SFX/start` | `Audio/SFX/start.mp3` | `UINotifyCommonView` 显示 `TurnHint` 回合提示通知时播放。 |
|
||||
|
||||
### 3.2 地图与单位交互
|
||||
### 4.2 单位、移动、攻击与技能演出
|
||||
|
||||
| SFX key | 资源文件 | 播放时刻 |
|
||||
| SFX key | 资源文件 | 实际播放点 |
|
||||
|---|---|---|
|
||||
| `SFX/UNIT_click` | `Audio/SFX/UNIT_click.WAV` | 玩家点击地图格 / 单位选择逻辑中播放。 |
|
||||
| `SFX/UNIT_move` | `Audio/SFX/UNIT_move.WAV` | 单位普通移动 Fragment、探索者移动 Fragment 的移动段播放;移动路径动画可能多次触发。 |
|
||||
| `SFX/UNIT_attack` | `Audio/SFX/UNIT_attack1.wav` | 单位近战攻击 atom 播放时触发;部分技能演出如萃香下落水花也复用该音效。注意资源中另有 `UNIT_attack.wav`,但当前 key 指向 `UNIT_attack1.wav`。 |
|
||||
| `SFX/UNIT_attack_arrow` | `Audio/SFX/UNIT_attack_arrow.wav` | 投射物创建时播放,用于 Arrow、PatchouliAttack、MokouAttack、ReisenAttack、KaguyaAttack、SanaeAttack、ReimuAttack 等配置了箭矢类音效的投射物。 |
|
||||
| `SFX/UNIT_attack_bomb` | `Audio/SFX/UNIT_attack_bomb.wav` | Bomb 投射物播放。当前配置 `HasSFXDelay = true`,延迟 `AnimTime` 0.5 秒后播放。 |
|
||||
| `SFX/UNIT_archer` | `Audio/SFX/UNIT_archer.WAV` | SuwakoAttack、SatoriAttack 等投射物创建时播放。 |
|
||||
| `SFX/UNIT_capture` | `Audio/SFX/UNIT_capture.wav` | 我方可见范围内占领村庄 / 城市时播放;`PlayerLogic.OccupyCity_LogicView` 中也会在占领城市可见时播放。 |
|
||||
| `SFX/UNIT_levelup` | `Audio/SFX/UNIT_levelup.wav` | 单位升阶、船只升级、英雄升级、教程任务完成反馈时播放。 |
|
||||
| `SFX/UNIT_born` | `Audio/SFX/UNIT_born.mp3` | 训练单位完成并生成单位时播放。 |
|
||||
| `SFX/UNIT_treasure` | `Audio/SFX/UNIT_treasure.wav` | 挖掘遗迹 / 宝藏并进入选项结算时播放。 |
|
||||
| `SFX/UNIT_click` | `Audio/SFX/UNIT_click.WAV` | `MapInteraction.OnTileClicked()` 地图格 / 单位点击选择时播放。 |
|
||||
| `SFX/UNIT_move` | `Audio/SFX/UNIT_move.WAV` | `FragmentMove`、`FragmentMoveExplorer` 的移动演出段播放;长路径可能多次触发。 |
|
||||
| `SFX/UNIT_attack` | `Audio/SFX/UNIT_attack1.wav` | `UnitAtomAnimAttack` 普通近战攻击播放;`FragmentSuikaFallingSplash` 也复用该音效。注意 key 指向 `UNIT_attack1.wav`,不是同目录下的 `UNIT_attack.wav`。 |
|
||||
| `SFX/UNIT_attack_arrow` | `Audio/SFX/UNIT_attack_arrow.wav` | 由投射物配置触发,常用于箭矢 / 弹幕类投射物。详见第 5 节。 |
|
||||
| `SFX/UNIT_attack_bomb` | `Audio/SFX/UNIT_attack_bomb.wav` | 由 `Bomb` 投射物配置触发,延迟 `AnimTime` 后播放。 |
|
||||
| `SFX/UNIT_archer` | `Audio/SFX/UNIT_archer.WAV` | 由部分投射物配置触发,如 `SuwakoAttack`、`SanaeOmikuji`。 |
|
||||
| `SFX/UNIT_capture` | `Audio/SFX/UNIT_capture.wav` | `UnitActionLogic`、`PlayerLogic.OccupyCity_LogicView()` 中,可见范围内占领村庄 / 城市时播放。 |
|
||||
| `SFX/UNIT_levelup` | `Audio/SFX/UNIT_levelup.wav` | 单位升级、船只升级、英雄升级、教程任务完成反馈等场景播放。 |
|
||||
| `SFX/UNIT_born` | `Audio/SFX/UNIT_born.mp3` | `TrainUnitActionLogic` 训练完成并生成单位时播放。 |
|
||||
| `SFX/UNIT_treasure` | `Audio/SFX/UNIT_treasure.wav` | `ActionLogic` / `UnitActionLogic` 中挖掘遗迹、宝藏结算进入选项时播放。 |
|
||||
| `SFX/UNIT_die` | `Audio/SFX/UNIT_die.wav` | `GridVFXRenderer` 播放 `GridVFXType.Die` 时触发。 |
|
||||
| `SFX/UNIT_heal` | `Audio/SFX/UNIT_heal.mp3` | `GridVFXRenderer` 播放 `GridVFXType.Heal` 时触发。 |
|
||||
|
||||
### 3.3 城市、资源与结算
|
||||
### 4.3 城市、资源、结算
|
||||
|
||||
| SFX key | 资源文件 | 播放时刻 |
|
||||
| SFX key | 资源文件 | 实际播放点 |
|
||||
|---|---|---|
|
||||
| `SFX/CITY_levelup` | `Audio/SFX/CITY_levelup.wav` | 城市升级 Fragment 播放时触发。 |
|
||||
| `SFX/CITY_exp` | `Audio/SFX/CITY_exp.wav` | CityExp 投射物创建时播放。 |
|
||||
| `SFX/GRID_build` | `Audio/SFX/GRID_build.wav` | 地块更新 Fragment 中检测到建筑变化并播放建造表现时触发。 |
|
||||
| `SFX/PLAYER_coin` | `Audio/SFX/PLAYER_coin.wav` | 真人自己的玩家获得金币且有可视来源位置时播放,同时触发金币飞行动画;首次遭遇奖励关闭弹窗时也通过 `AddCoin_ViewOnly` 播放。 |
|
||||
| `SFX/MATCH_win` | `Audio/SFX/MATCH_win.wav` | 结算胜利界面显示时播放。 |
|
||||
| `SFX/MATCH_lose` | `Audio/SFX/MATCH_lose.wav` | 结算失败界面显示时播放。 |
|
||||
| `SFX/CITY_levelup` | `Audio/SFX/CITY_levelup.wav` | `FragmentCityLevelUp` 城市升级演出时播放。 |
|
||||
| `SFX/CITY_exp` | `Audio/SFX/CITY_exp.wav` | `ProjectileType.CityExp` 投射物创建时播放。 |
|
||||
| `SFX/GRID_build` | `Audio/SFX/GRID_build.wav` | `FragmentGridUpdate` 检测到建筑变化并播放建造表现时触发。 |
|
||||
| `SFX/PLAYER_coin` | `Audio/SFX/PLAYER_coin.wav` | 真人自己获得金币且有可视来源位置时播放,同时触发金币飞行动画;首次遭遇奖励关闭弹窗时也会经 `AddCoin_ViewOnly` 播放。 |
|
||||
| `SFX/MATCH_win` | `Audio/SFX/MATCH_win.wav` | `UITopWinView` 结算胜利界面显示时播放。 |
|
||||
| `SFX/MATCH_lose` | `Audio/SFX/MATCH_lose.wav` | `UITopWinView` 结算失败界面显示时播放。 |
|
||||
|
||||
### 3.4 环境音
|
||||
### 4.4 环境音
|
||||
|
||||
| SFX key | 资源文件 | 播放时刻 |
|
||||
| SFX key | 资源文件 | 实际播放点 |
|
||||
|---|---|---|
|
||||
| `SFX/ENV_sea` | `Audio/SFX/ENV_sea.WAV` | 真人玩家回合开始时调用 `CalculateAndPlayAmbient()`;先有 40% 概率直接不播,若当前玩家领土内有非陆地格,再以 60% 概率播放海洋环境音。 |
|
||||
| `SFX/ENV_forest` | `Audio/SFX/ENV_forest.wav` | 真人玩家回合开始时调用 `CalculateAndPlayAmbient()`;若未播放海洋音且领土内有树林,则播放森林环境音。 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 投射物音效配置
|
||||
## 5. 投射物音效配置
|
||||
|
||||
投射物音效由 `ProjectileTypeDataAssets.asset` 控制。`ProjectileManager.CreateProjectile()` 创建投射物后,如果 `HasSFX = true`,立即播放 `SFXName`;如果 `HasSFXDelay = true`,则延迟 `AnimTime` 秒播放。
|
||||
|
||||
| ProjectileType | 当前 SFX | 延迟 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `Arrow` | `SFX/UNIT_attack_arrow` | 否 | 标准箭矢。 |
|
||||
| `Bomb` | `SFX/UNIT_attack_bomb` | 是,0.5 秒 | 抛物线炸弹落点音效。 |
|
||||
| `Bomb` | `SFX/UNIT_attack_bomb` | 是,0.5 秒 | 抛物线炸弹音效。 |
|
||||
| `PatchouliAttack` | `SFX/UNIT_attack_arrow` | 否 | 复用箭矢类发射音。 |
|
||||
| `MokouAttack` | `SFX/UNIT_attack_arrow` | 否 | 复用箭矢类发射音。 |
|
||||
| `ReisenAttack` | `SFX/UNIT_attack_arrow` | 否 | 复用箭矢类发射音。 |
|
||||
@ -126,9 +140,20 @@
|
||||
|
||||
---
|
||||
|
||||
## 5. 资源存在但当前未直接接入的 SFX
|
||||
## 6. 已注册但当前没有实际播放点的 SFX
|
||||
|
||||
以下文件位于 `BundleResources/Audio/SFX`,但当前未在 `AudioManager.Init()` 注册为固定 key,也没有被代码中的 `PlayAudio("...")` 或投射物配置直接调用。若策划要使用,需要先明确 key 与触发时刻,再接入 `AudioManager` 或改为配置化调用。
|
||||
这些 key 在 `AudioManager.Init()` 中已注册,资源也存在,但当前未在代码直接调用,也未被启用的投射物配置触发。
|
||||
|
||||
| SFX key | 资源文件 | 备注 |
|
||||
|---|---|---|
|
||||
| `SFX/UNIT_bomb` | `Audio/SFX/UNIT_bomb.wav` | 目前炸弹投射物实际使用 `SFX/UNIT_attack_bomb`。 |
|
||||
| `SFX/UNIT_hurt` | `Audio/SFX/UNIT_hurt.WAV` | 目前受击表现没有找到播放点;死亡 / 治疗 VFX 有独立音效。 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 资源存在但当前未接入的 SFX
|
||||
|
||||
以下文件位于 `Unity/Assets/BundleResources/Audio/SFX`,但当前未在 `AudioManager.Init()` 注册为固定 key,也没有被代码中的 `PlayAudio("...")` 或投射物配置直接调用。
|
||||
|
||||
| 资源文件 | 备注 |
|
||||
|---|---|
|
||||
@ -142,17 +167,27 @@
|
||||
| `Audio/SFX/UI_cityUp.WAV` | 资源存在,当前未注册 / 未播放;城市升级当前使用 `CITY_levelup`。 |
|
||||
| `Audio/SFX/UI_hintTiny.WAV` | 资源存在,当前未注册 / 未播放。 |
|
||||
| `Audio/SFX/UI_tech.WAV` | 资源存在,当前未注册 / 未播放。 |
|
||||
| `Audio/SFX/UNIT_attack.wav` | 资源存在但当前 `SFX/UNIT_attack` key 指向 `UNIT_attack1.wav`。 |
|
||||
| `Audio/SFX/UNIT_bomb.wav` | 已注册为 `SFX/UNIT_bomb`,但当前没有直接播放点。 |
|
||||
| `Audio/SFX/UNIT_attack.wav` | 资源存在,但当前 `SFX/UNIT_attack` key 指向 `UNIT_attack1.wav`。 |
|
||||
| `Audio/SFX/UNIT_heal1.mp3` | 资源存在,当前未注册 / 未播放;治疗当前使用 `UNIT_heal.mp3`。 |
|
||||
| `Audio/SFX/UNIT_hurt.WAV` | 已注册为 `SFX/UNIT_hurt`,但当前没有直接播放点。 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 维护注意事项
|
||||
## 8. 当前音效情况归纳
|
||||
|
||||
- 新增 BGM 时,需要同时检查三处:`BundleResources/Audio` 资源文件、`AudioManager.Init()` 的 key 注册、`MusicDataAssets.asset` / `PlayerDataAssets.asset` 的音乐信息与文明归属。
|
||||
- 新增 SFX 时,优先给出明确触发时刻:UI 交互、行动执行、Fragment 演出、投射物创建、VFX 播放、回合通知或结算界面。不要只放资源文件。
|
||||
- 音效覆盖集中在“按钮反馈、移动、基础攻击、投射物发射、升级、占领、训练、宝藏、金币、胜负、少量联网提示、少量环境音”。
|
||||
- 单位战斗音效仍偏基础化:近战统一 `UNIT_attack`,大量弹幕 / 法术类投射物复用 `UNIT_attack_arrow` 或 `UNIT_archer`,角色特色技能缺少独立声纹。
|
||||
- 城市 / 经济类已有基础反馈,但同类资源存在两套命名:例如 `CITY_exp` 与未接入的 `UI_cityExp`、`CITY_levelup` 与未接入的 `UI_cityUp`。
|
||||
- UI 通知资源储备多于实际接入:`UI_bubbleTiny`、`UI_checkOK`、`UI_hintTiny`、`UI_tech` 等资源存在,但还没有进入统一触发点。
|
||||
- 环境音只有回合开始时的随机海洋 / 森林,暂时没有动物、海洋替代版本、地形常驻氛围或镜头区域变化触发。
|
||||
- 部分旧 prefab 上仍有 `AudioSource` / `hoverSound` 引用,但实际播放已统一走 `AudioManager`,这类字段更像历史残留。
|
||||
- SFX 中仍有少量短提示音使用 `.mp3`:`UI_beep`、`UI_message`、`UI_playerenter`、`UNIT_born`、`UNIT_heal`、`start`。其中 `UI_beep` 是高频短音,后续更适合换成 `.wav` 以减少起播延迟和 MP3 padding 风险。
|
||||
|
||||
---
|
||||
|
||||
## 9. 维护注意事项
|
||||
|
||||
- 新增 BGM 时,需要同时检查:`BundleResources/Audio` 资源文件、`AudioManager.Init()` key 注册、`MusicDataAssets.asset` / `PlayerDataAssets.asset` 的音乐信息与文明归属。
|
||||
- 新增 SFX 时,先明确触发时刻:UI 交互、行动执行、Fragment 演出、投射物创建、VFX 播放、回合通知或结算界面。不要只放资源文件。
|
||||
- 投射物类音效优先配置在 `ProjectileTypeDataAssets.asset`,避免在技能代码里散落硬编码。
|
||||
- 行动结果音效应尽量挂在真实执行或演出节点上,并保留视野判断,避免 AI 沙盘、不可见行动或网络接收过程播放本地音效。
|
||||
- 当前 `GridVFXInfoDataAssets.asset` 存在 `AudioClipPath` 字段,但所有条目为空;实际 VFX 音效仍由 `GridVFXRenderer` 针对 `Die` / `Heal` 类型硬编码播放。
|
||||
|
||||
538
MD/GameMDFramework/17-服务端迁移交接文档.md
Normal file
@ -0,0 +1,538 @@
|
||||
# 17 - 服务端迁移交接文档
|
||||
|
||||
> 面向对象:新加入的服务端/运维同事。
|
||||
> 目标:说明 TH1 当前服务器架构、使用的阿里云服务、各服务怎么协作,以及完整迁移时需要迁移和验证什么。
|
||||
> 更新时间:2026-06-30。
|
||||
> 事实来源:`Tools/OSS/game-upload-function/`、`Unity/Assets/Scripts/TH1_Logic/Oss/`、`MD/GameMDFramework/15-服务端-GameUploadFunction.md`。
|
||||
|
||||
---
|
||||
|
||||
## 1. 当前服务端定位
|
||||
|
||||
TH1 目前没有传统意义上的常驻游戏逻辑服务器。回合逻辑、联机同步和战斗结算主要在 Unity 客户端侧完成,联机链路依赖 Steam 相关能力。当前阿里云后端的核心职责是:
|
||||
|
||||
- 验证 Steam 玩家身份。
|
||||
- 为客户端签发有限权限的 OSS 上传凭证。
|
||||
- 让客户端把对局数据、统计数据、玩家问题汇报、问卷答卷等文件直接上传到 OSS。
|
||||
- 使用 Tablestore 做短时缓存,减少 Steam Web API 和 STS 调用次数。
|
||||
- 提供开发者本地工具从 OSS 拉取数据并查看。
|
||||
|
||||
因此,迁移重点不是迁移一套业务 API 集群,而是迁移“函数计算 + OSS + STS/RAM + Tablestore + 硬编码客户端入口”的上传基础设施。
|
||||
|
||||
---
|
||||
|
||||
## 2. 一页架构图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Unity["Unity 客户端<br/>OssManager / StsTokenService"] -->|POST JSON<br/>steamId, steamAppId, authTicket, type, version| FC["阿里云函数计算<br/>game-upload-function<br/>Node.js, port 9000"]
|
||||
FC -->|AuthenticateUserTicket| Steam["Steam Web API"]
|
||||
FC -->|getRow / putRow| OTS["阿里云 Tablestore<br/>Players 表"]
|
||||
FC -->|AssumeRole| STS["阿里云 STS / RAM Role"]
|
||||
STS --> FC
|
||||
FC -->|STS + Post Policy<br/>exact objectKey| Unity
|
||||
Unity -->|PostObject multipart/form-data| OSS["阿里云 OSS<br/>Bucket: th1-oss<br/>endpoint: oss-cn-shanghai.aliyuncs.com"]
|
||||
Tools["Unity 编辑器工具 / Python 查看器"] -->|ListObjects / GetObject| OSS
|
||||
```
|
||||
|
||||
关键点:
|
||||
|
||||
- Function Compute 不接收游戏数据文件,只返回上传凭证。
|
||||
- 文件上传是 `Unity -> OSS` 直传。
|
||||
- STS 和 Post Policy 都限制到一个精确 `objectKey`,不是给客户端开放整个 Bucket 写权限。
|
||||
- Tablestore 的数据主要是短时缓存,不是永久业务数据源。
|
||||
|
||||
---
|
||||
|
||||
## 3. 使用的阿里云服务
|
||||
|
||||
| 服务 | 当前用途 | 迁移时要做什么 |
|
||||
|------|----------|----------------|
|
||||
| 函数计算 Function Compute | 承载 `game-upload-function` HTTP 服务。公开入口当前硬编码在 Unity:`https://get-sts-token-qltjykaafr.cn-shanghai.fcapp.run`。 | 新建或迁移函数应用,Node.js 运行时需支持 >=18,启动 `index.js`,监听端口 9000。若 URL 变化,要同步客户端代码或通过域名/代理保持老 URL 可用。 |
|
||||
| OSS 对象存储 | 存放玩家上传文件。当前默认 Endpoint 是 `oss-cn-shanghai.aliyuncs.com`,工具默认 Bucket 是 `th1-oss`,服务端实际 Bucket 来自 `BUCKET_NAME`。 | 创建目标 Bucket,复制全部对象,保持 objectKey 不变。重点前缀包括版本根目录、`common/`、`collect/`、`bugreport/`、`multilingualreport/`、`questionnaire/`。 |
|
||||
| STS | 函数调用 `AssumeRole` 获取临时凭证。临时凭证有效期 900 秒。 | 创建/迁移 RAM Role,允许被函数使用的 RAM 身份扮演;Role 本身需要允许对目标 Bucket 执行 `oss:PutObject`,函数再用 inline policy 缩到单个 objectKey。 |
|
||||
| RAM | 保存函数调用 STS、Tablestore 的 AccessKey 身份,以及被 STS 扮演的 Role。 | 重建最小权限 RAM 用户/角色策略。不要把 AccessKey、Secret、Steam API Key 写入仓库或文档。 |
|
||||
| Tablestore | `Players` 表,主键 `PlayerId`。缓存 Steam 身份和 STS 上传凭证。 | 创建实例和 `Players` 表。缓存都是分钟级,通常可以不迁移历史行,空表上线只会让首次请求重新验证/签发。 |
|
||||
|
||||
当前未看到 ECS、RDS、Redis、CDN、独立 API Gateway 作为这条链路的必需依赖。
|
||||
|
||||
---
|
||||
|
||||
## 4. 代码和文档入口
|
||||
|
||||
服务端源码:
|
||||
|
||||
- `Tools/OSS/game-upload-function/index.js`:后端事实来源,包含接口、缓存、Steam 验证、STS 签发、OSS Post Policy。
|
||||
- `Tools/OSS/game-upload-function/package.json`:Node 依赖和运行时要求。
|
||||
- `Tools/OSS/game-upload-function/README.md`:函数服务说明。
|
||||
|
||||
架构文档:
|
||||
|
||||
- `MD/GameMDFramework/15-服务端-GameUploadFunction.md`:接口和流程的详细说明。
|
||||
- 本文档:迁移和交接总览。
|
||||
|
||||
Unity 运行时客户端:
|
||||
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Oss/OssManager.cs`:上传入口、Steam 预校验调度、各上传类型调用。
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Oss/StsTokenService.cs`:向函数计算请求 STS 凭证。
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Oss/OssUploadService.cs`:用 PostObject 直传 OSS。
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Oss/PlayerBugReportService.cs`:玩家 Bug 汇报 zip 打包。
|
||||
|
||||
开发者工具:
|
||||
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Editor/OssEditorWindow.cs`:Unity 编辑器内 OSS 拉取、上传测试、统计导出入口。
|
||||
- `Tools/PlayerBugViewer/`:拉取和查看 `bugreport/`。
|
||||
- `Tools/PlayerMultilingualReportViewer/`:拉取和查看 `multilingualreport/`。
|
||||
- `Tools/PlayerQuestionnaireViewer/`:拉取和查看 `questionnaire/`。
|
||||
|
||||
---
|
||||
|
||||
## 5. Function Compute 服务
|
||||
|
||||
当前服务是一个 Node.js CommonJS HTTP 服务:
|
||||
|
||||
- 入口文件:`Tools/OSS/game-upload-function/index.js`
|
||||
- 监听端口:`9000`
|
||||
- Node 版本:`>=18.0.0`
|
||||
- 依赖:
|
||||
- `@alicloud/pop-core`:调用 STS `AssumeRole`
|
||||
- `tablestore`:读写 Tablestore
|
||||
|
||||
函数接收 `POST /` JSON 请求,最大请求体 1024 字节。`OPTIONS` 用于 CORS 预检,其他非 POST 请求返回 405。
|
||||
|
||||
### 5.1 必需环境变量
|
||||
|
||||
这些值必须从阿里云/Steam 控制台配置到函数计算环境变量,不得提交到仓库:
|
||||
|
||||
| 变量 | 用途 |
|
||||
|------|------|
|
||||
| `ACCESS_KEY_ID` | 函数使用的 RAM AccessKey ID。 |
|
||||
| `ACCESS_KEY_SECRET` | 函数使用的 RAM AccessKey Secret。 |
|
||||
| `ROLE_ARN` | STS `AssumeRole` 的目标 RAM Role ARN。 |
|
||||
| `BUCKET_NAME` | 目标 OSS Bucket。 |
|
||||
| `STEAM_API_KEY` | Steam Web API Key。 |
|
||||
| `OTS_ENDPOINT` | Tablestore Endpoint。 |
|
||||
| `OTS_INSTANCE` | Tablestore 实例名。 |
|
||||
|
||||
兼容/可选变量:
|
||||
|
||||
| 变量 | 用途 |
|
||||
|------|------|
|
||||
| `STEAM_APP_ID` | 老客户端未传 `steamAppId` 时的默认 AppID。生产环境建议显式配置。 |
|
||||
| `STEAM_ALLOWED_APP_IDS` 或 `STEAM_APP_IDS` | 允许的 Steam AppID 列表。未配置时默认包含 `3774440,3887950`。 |
|
||||
|
||||
### 5.2 接口请求字段
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `steamId` | 是 | 玩家 Steam ID。 |
|
||||
| `steamAppId` | 否 | 客户端当前 Steam AppID。新客户端会传,老客户端可能不传。 |
|
||||
| `authTicket` | 条件必填 | Steam Auth Ticket。身份缓存未命中时必需。 |
|
||||
| `version` | 否 | 客户端版本。不传时使用 `common` 路径段。 |
|
||||
| `action` | 否 | `steamauth` 表示只做 Steam 身份预校验。 |
|
||||
| `type` | 否 | 上传类型,默认 `ossdata`。 |
|
||||
|
||||
上传凭证响应直接映射 Unity `StsCredentials`:
|
||||
|
||||
```json
|
||||
{
|
||||
"accessKeyId": "STS.xxx",
|
||||
"accessKeySecret": "xxx",
|
||||
"securityToken": "xxx",
|
||||
"steamAppId": "3774440",
|
||||
"endpoint": "oss-cn-shanghai.aliyuncs.com",
|
||||
"bucket": "th1-oss",
|
||||
"objectKey": "0.7.5/76561198xxx/1780000000000.dat",
|
||||
"policy": "base64...",
|
||||
"signature": "hmac-sha1...",
|
||||
"expiresIn": 900
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 运行时请求流程
|
||||
|
||||
### 6.1 Steam 预校验
|
||||
|
||||
客户端 Steam 就绪后会周期性调用:
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "steamauth",
|
||||
"steamId": "...",
|
||||
"steamAppId": "...",
|
||||
"authTicket": "...",
|
||||
"version": "0.7.5"
|
||||
}
|
||||
```
|
||||
|
||||
服务端流程:
|
||||
|
||||
1. 查询 Tablestore 是否已有 `{steamId}#steamauth#{steamAppId}` 身份缓存。
|
||||
2. 缓存命中则直接返回剩余秒数。
|
||||
3. 缓存未命中则调用 Steam Web API `ISteamUserAuth/AuthenticateUserTicket/v1/`。
|
||||
4. 校验通过后写入 Tablestore,缓存 10 分钟。
|
||||
|
||||
这个缓存只代表玩家身份近期通过验证,不直接授权 OSS 写入。
|
||||
|
||||
### 6.2 上传凭证签发
|
||||
|
||||
客户端请求 `type=ossdata|collectdata|bugreport|multilingualreport|questionnaire`。
|
||||
|
||||
服务端流程:
|
||||
|
||||
1. 对可缓存类型先查 STS 上传凭证缓存。
|
||||
2. 如果未命中,检查 Steam 身份缓存。
|
||||
3. 身份缓存也未命中时,用 `authTicket` 调 Steam Web API。
|
||||
4. 调阿里云 STS `AssumeRole` 获取临时凭证。
|
||||
5. 按上传类型和版本生成一个精确 OSS `objectKey`。
|
||||
6. 用 STS 临时密钥生成 Post Policy 和 HMAC-SHA1 签名。
|
||||
7. 可缓存类型写回 Tablestore。
|
||||
8. 返回凭证给 Unity。
|
||||
9. Unity 使用 multipart/form-data 直接 POST 到 `https://{bucket}.{endpoint}`。
|
||||
|
||||
---
|
||||
|
||||
## 7. 上传类型和 OSS 路径
|
||||
|
||||
| type | 文件 | 是否缓存 STS | 大小限制 | OSS 路径 |
|
||||
|------|------|--------------|----------|----------|
|
||||
| `ossdata` | `.dat` | 是 | 3 MB | `{version}/{steamId}/{timestamp}.dat` 或 `common/{steamId}/{timestamp}.dat` |
|
||||
| `collectdata` | `.dat` | 是 | 3 MB | `collect/{version}/{steamId}/{timestamp}.dat` 或 `collect/common/{steamId}/{timestamp}.dat` |
|
||||
| `bugreport` | `.zip` | 否 | 10 MB | `bugreport/{version}/{steamId}/{timestamp}-{random}.zip` |
|
||||
| `multilingualreport` | `.zip` | 否 | 1 MB | `multilingualreport/{version}/{steamId}/{timestamp}-{random}.zip` |
|
||||
| `questionnaire` | `.json` | 否 | 512 KB | `questionnaire/{version}/{steamId}/{timestamp}-{random}.json` |
|
||||
|
||||
迁移 OSS 时不要只复制显式前缀。`ossdata` 直接放在 Bucket 根部的版本目录下,例如 `0.7.5/{steamId}/{timestamp}.dat`,因此最稳妥策略是整桶复制,保持 objectKey 完全一致。
|
||||
|
||||
---
|
||||
|
||||
## 8. Tablestore 数据模型
|
||||
|
||||
Tablestore 表:
|
||||
|
||||
- 表名:`Players`
|
||||
- 主键:`PlayerId`,字符串类型
|
||||
|
||||
### 8.1 Steam 身份缓存
|
||||
|
||||
主键:
|
||||
|
||||
```text
|
||||
{steamId}#steamauth#{steamAppId}
|
||||
```
|
||||
|
||||
字段:
|
||||
|
||||
- `verifiedAt`:验证通过时间,毫秒时间戳。
|
||||
- `authExpireAt`:身份缓存过期时间,毫秒时间戳。
|
||||
- `steamAppId`:验证使用的 Steam AppID。
|
||||
- `version`:客户端版本。
|
||||
|
||||
有效期:10 分钟。
|
||||
|
||||
### 8.2 STS 上传凭证缓存
|
||||
|
||||
主键:
|
||||
|
||||
```text
|
||||
{steamId}#{type}#{steamAppId}
|
||||
```
|
||||
|
||||
字段:
|
||||
|
||||
- `accessKeyId`
|
||||
- `accessKeySecret`
|
||||
- `securityToken`
|
||||
- `endpoint`
|
||||
- `bucket`
|
||||
- `objectKey`
|
||||
- `policy`
|
||||
- `signature`
|
||||
- `issuedAt`
|
||||
- `stsExpireAt`
|
||||
- `steamAppId`
|
||||
- `version`
|
||||
|
||||
命中条件:
|
||||
|
||||
- 请求版本和缓存版本一致。
|
||||
- 签发时间在 5 分钟内。
|
||||
- STS 剩余有效期大于 2 分钟。
|
||||
|
||||
`bugreport`、`multilingualreport`、`questionnaire` 不使用 STS 缓存,因为每次提交必须生成唯一 objectKey,避免覆盖。
|
||||
|
||||
迁移建议:Tablestore 只存短时缓存,可以在新环境创建空表。迁移历史缓存意义不大。
|
||||
|
||||
---
|
||||
|
||||
## 9. RAM / STS 权限模型
|
||||
|
||||
当前函数使用环境变量里的 `ACCESS_KEY_ID` / `ACCESS_KEY_SECRET`:
|
||||
|
||||
- 调用 Tablestore `getRow` / `putRow`。
|
||||
- 调用 STS `AssumeRole`。
|
||||
|
||||
STS 临时凭证来源于 `ROLE_ARN`,签发时会附带 inline policy:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "1",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": ["oss:PutObject"],
|
||||
"Resource": ["acs:oss:*:*:{BUCKET_NAME}/{objectKey}"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
迁移时至少需要两层权限:
|
||||
|
||||
1. 函数自身使用的 RAM 身份:
|
||||
- 可以 `sts:AssumeRole` 到 `ROLE_ARN`。
|
||||
- 可以读写目标 Tablestore `Players` 表。
|
||||
2. 被扮演的 RAM Role:
|
||||
- 基础权限允许对目标 Bucket 执行 `oss:PutObject`。
|
||||
- 实际下发给客户端的权限由 inline policy 缩小到单个 objectKey。
|
||||
|
||||
不要为了省事把客户端 STS 权限放宽到整个 Bucket。当前安全边界依赖“STS exact objectKey + Post Policy exact key + 文件大小限制”共同生效。
|
||||
|
||||
---
|
||||
|
||||
## 10. OSS 读写和本地工具
|
||||
|
||||
客户端写入:
|
||||
|
||||
- Unity 先向函数计算拿 `StsCredentials`。
|
||||
- 再用 `OssUploadService` 直传到 `https://{bucket}.{endpoint}`。
|
||||
- multipart 字段包括 `key`、`OSSAccessKeyId`、`policy`、`Signature`、`x-oss-security-token`,最后是 `file`。
|
||||
|
||||
开发者读取:
|
||||
|
||||
- Unity `Tools/Oss 编辑器` 可以从 `collect/` 拉取统计数据到 `Tools/OSS/Data/`。
|
||||
- `Tools/PlayerBugViewer` 从 `bugreport/` 拉取 zip。
|
||||
- `Tools/PlayerMultilingualReportViewer` 从 `multilingualreport/` 拉取 zip。
|
||||
- `Tools/PlayerQuestionnaireViewer` 从 `questionnaire/` 拉取 JSON。
|
||||
|
||||
这些本地 `Data/` 目录是开发者缓存,不是迁移源。迁移应以 OSS Bucket 为准。
|
||||
|
||||
---
|
||||
|
||||
## 11. 客户端硬编码入口
|
||||
|
||||
当前函数 URL 在以下代码里硬编码:
|
||||
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Oss/OssManager.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Editor/OssEditorWindow.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Editor/SteamEditorWindow.cs`
|
||||
|
||||
当前 URL:
|
||||
|
||||
```text
|
||||
https://get-sts-token-qltjykaafr.cn-shanghai.fcapp.run
|
||||
```
|
||||
|
||||
迁移风险:
|
||||
|
||||
- 已发布老客户端不会自动知道新函数 URL。
|
||||
- 如果直接换新的 fcapp.run 地址,老版本上传会失败。
|
||||
|
||||
推荐策略:
|
||||
|
||||
1. 如果可以,给函数计算接入项目自有域名,并让客户端以后只写自有域名。
|
||||
2. 迁移期间保留旧函数或让旧 URL 代理到新环境,直到不再需要兼容老客户端。
|
||||
3. 新版本客户端再修改 `FunctionUrl`,并通过 Steam 上传流程测试器验证。
|
||||
|
||||
---
|
||||
|
||||
## 12. 完整迁移步骤
|
||||
|
||||
### 12.1 迁移前盘点
|
||||
|
||||
从旧环境记录以下信息,记录时不要把密钥值写入仓库:
|
||||
|
||||
- Function Compute:
|
||||
- 地域、函数/应用名、运行时、启动命令、端口、HTTP 触发器、公开 URL。
|
||||
- 环境变量名称和值来源。
|
||||
- 日志查看位置和错误报警方式。
|
||||
- OSS:
|
||||
- Bucket 名称、地域、Endpoint。
|
||||
- 对象总量、总大小、关键前缀。
|
||||
- 是否配置生命周期、跨域、版本控制、服务端加密。
|
||||
- RAM / STS:
|
||||
- 函数使用的 RAM 身份。
|
||||
- `ROLE_ARN` 对应角色。
|
||||
- 角色信任策略和权限策略。
|
||||
- Tablestore:
|
||||
- 实例名、Endpoint、表 `Players` 的主键结构。
|
||||
- Steam:
|
||||
- Steam Web API Key 管理账号。
|
||||
- 允许的 AppID:当前默认兼容 `3774440`、`3887950`。
|
||||
- Unity 客户端:
|
||||
- 当前发布版本是否还能接受修改函数 URL。
|
||||
- 是否需要保留旧 URL 兼容历史版本。
|
||||
|
||||
### 12.2 新环境资源创建
|
||||
|
||||
建议优先保持同地域 `cn-shanghai`,减少 Endpoint、延迟和配置差异。
|
||||
|
||||
1. 创建 OSS Bucket。
|
||||
- 名称可以沿用或换新,但如果换新,要同步 `BUCKET_NAME` 和工具默认值。
|
||||
- Endpoint 目前按 `oss-cn-shanghai.aliyuncs.com` 返回给客户端。
|
||||
2. 创建 Tablestore 实例。
|
||||
- 创建 `Players` 表。
|
||||
- 主键:`PlayerId`,字符串。
|
||||
3. 创建 RAM 身份和 Role。
|
||||
- 函数 RAM 身份:允许 `sts:AssumeRole`,允许访问 Tablestore `Players`。
|
||||
- STS Role:允许 OSS `PutObject` 到目标 Bucket,最终权限由 inline policy 缩小。
|
||||
4. 创建 Function Compute 应用/函数。
|
||||
- 上传 `Tools/OSS/game-upload-function/` 代码包。
|
||||
- 安装依赖或随包部署 `node_modules`,以目标平台部署规范为准。
|
||||
- 启动 `node index.js`。
|
||||
- 监听端口 9000。
|
||||
- 配置 HTTP 触发器。
|
||||
5. 配置函数环境变量。
|
||||
- 填入第 5.1 节列出的必需变量。
|
||||
- 显式配置 `STEAM_APP_ID` 和允许 AppID 列表,避免老客户端行为不确定。
|
||||
|
||||
### 12.3 OSS 数据迁移
|
||||
|
||||
必须保持 objectKey 不变。迁移对象包括:
|
||||
|
||||
- `0.x.x/{steamId}/{timestamp}.dat` 等版本根目录。
|
||||
- `common/`
|
||||
- `collect/`
|
||||
- `bugreport/`
|
||||
- `multilingualreport/`
|
||||
- `questionnaire/`
|
||||
|
||||
推荐做法:
|
||||
|
||||
- 使用阿里云官方迁移工具或 `ossutil` 整桶复制。
|
||||
- 复制后按前缀抽样比对对象数量和文件大小。
|
||||
- 不要通过 Git 或本地 `Tools/OSS/Data` 反向恢复 OSS;本地目录只是缓存。
|
||||
|
||||
### 12.4 Tablestore 迁移
|
||||
|
||||
通常不迁移旧缓存:
|
||||
|
||||
- Steam 身份缓存只有 10 分钟。
|
||||
- STS 上传凭证缓存最多复用 5 分钟,STS 自身 15 分钟过期。
|
||||
- 空表上线后,第一次请求会重新走 Steam 验证和 STS 签发。
|
||||
|
||||
只有在做无感切换且非常在意首次请求延迟时,才考虑导出导入缓存。即便不迁移,功能正确性不受影响。
|
||||
|
||||
### 12.5 切流
|
||||
|
||||
优先选择可回滚方案:
|
||||
|
||||
1. 新函数先用独立测试 URL 验证完整链路。
|
||||
2. 新 OSS Bucket 先用测试客户端验证上传和拉取。
|
||||
3. 如果使用自有域名,切 CNAME/路由到新函数。
|
||||
4. 如果必须改 Unity 硬编码 URL,先发测试包,用 `Tools/Steam 上传流程测试器` 验证。
|
||||
5. 保留旧函数和旧 Bucket 一段时间,直到确认没有老客户端继续写入。
|
||||
|
||||
### 12.6 回滚
|
||||
|
||||
回滚条件:
|
||||
|
||||
- 大量 `STS request failed`。
|
||||
- `Steam verification failed` 异常升高。
|
||||
- OSS PostObject 403/Policy 失败。
|
||||
- 查看器无法拉取新对象。
|
||||
|
||||
回滚方式:
|
||||
|
||||
- 域名切回旧函数。
|
||||
- 或客户端测试包退回旧 `FunctionUrl`。
|
||||
- OSS 写入如果已经分散到新旧 Bucket,需要按时间窗口补复制新 Bucket 新增对象回旧 Bucket。
|
||||
|
||||
---
|
||||
|
||||
## 13. 上线验证清单
|
||||
|
||||
### 13.1 本地代码检查
|
||||
|
||||
在函数目录运行:
|
||||
|
||||
```powershell
|
||||
cd Tools/OSS/game-upload-function
|
||||
npm run check
|
||||
```
|
||||
|
||||
该命令会执行:
|
||||
|
||||
- `node -c index.js`
|
||||
- `node check-upload-contract.js`
|
||||
|
||||
### 13.2 云端接口验证
|
||||
|
||||
需要 Steam 登录态和真实 Auth Ticket 的测试,建议用 Unity 的 `Tools/Steam 上传流程测试器`:
|
||||
|
||||
- `action=steamauth` 预校验成功。
|
||||
- `type=ossdata` 上传成功。
|
||||
- `type=collectdata` 上传成功。
|
||||
- `type=bugreport` 返回 `bugreport/...zip` 且每次 key 不同。
|
||||
- `type=multilingualreport` 返回 `multilingualreport/...zip` 且每次 key 不同。
|
||||
- `type=questionnaire` 返回 `questionnaire/...json` 且每次 key 不同。
|
||||
- 错误 objectKey 上传被 OSS 拒绝。
|
||||
- 超大文件被客户端或 OSS Policy 拒绝。
|
||||
|
||||
### 13.3 OSS 验证
|
||||
|
||||
- OSS 控制台能看到新对象。
|
||||
- `collect/` 可以被 Unity OSS 编辑器拉取。
|
||||
- `bugreport/` 可以被 PlayerBugViewer 拉取和解析。
|
||||
- `multilingualreport/` 可以被对应查看器拉取和解析。
|
||||
- `questionnaire/` 可以被对应查看器拉取和解析。
|
||||
|
||||
### 13.4 日志验证
|
||||
|
||||
Function Compute 日志里应能看到:
|
||||
|
||||
- `[服务启动]`
|
||||
- `[请求开始]`
|
||||
- `[Steam验证]`
|
||||
- `[STS签发]`
|
||||
- `[PostPolicy]`
|
||||
- `[请求完成]`
|
||||
|
||||
异常日志要重点看:
|
||||
|
||||
- 缺少环境变量。
|
||||
- Steam API 请求超时或 `Ticket for other app`。
|
||||
- Tablestore 读写失败。
|
||||
- STS `AssumeRole` 失败。
|
||||
- OSS PostObject 403。
|
||||
|
||||
---
|
||||
|
||||
## 14. 常见问题定位
|
||||
|
||||
| 现象 | 优先检查 |
|
||||
|------|----------|
|
||||
| STS 请求 500 | 函数环境变量、RAM 权限、`ROLE_ARN`、Tablestore Endpoint/实例名。 |
|
||||
| STS 请求 403,Steam 验证失败 | `STEAM_API_KEY`、`steamAppId`、允许 AppID、客户端生成的 Auth Ticket 是否属于该 App。 |
|
||||
| `Ticket for other app` | 当前客户端 AppID 与服务端允许列表不一致,或测试包/正式包 Steam AppID 混用。 |
|
||||
| OSS PostObject 403 | `objectKey` 是否被客户端改动,Post Policy 是否过期,Bucket/Endpoint 是否匹配,STS Role 是否有 PutObject。 |
|
||||
| 上传大文件失败 | 对应类型大小限制:普通/collect 3MB,bugreport 10MB,multilingualreport 1MB,questionnaire 512KB。 |
|
||||
| 新环境首次上传变慢 | Tablestore 缓存为空是正常现象;第一次会重新调用 Steam 和 STS。 |
|
||||
| 老版本客户端上传失败 | 检查旧 `FunctionUrl` 是否仍可用,或是否已发布包含新 URL 的客户端。 |
|
||||
|
||||
---
|
||||
|
||||
## 15. 交接结论
|
||||
|
||||
服务端同事需要优先理解以下事实:
|
||||
|
||||
1. 当前后端是“上传授权服务”,不是游戏权威服务器。
|
||||
2. Function Compute 只签发凭证,不保存玩家文件。
|
||||
3. OSS 是永久文件数据源,迁移时要整桶/全前缀保留 objectKey。
|
||||
4. Tablestore 是短时缓存,表结构必须有,但缓存行通常不用迁移。
|
||||
5. RAM / STS 是安全核心,不能放宽到全桶长期写权限。
|
||||
6. 函数 URL 已硬编码到客户端,迁移切流必须考虑老版本兼容。
|
||||
7. 任何迁移验证都必须覆盖 Steam 预校验、STS 签发、OSS 直传、开发者查看器拉取四段链路。
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
# TH1-SI-2026-06-29-001 Hakurei Karvi Landing Dash AP Regression
|
||||
|
||||
## Basic Info
|
||||
|
||||
- Severity: S1
|
||||
- Status: Fixed in workspace; guardrail and Hotfix build passed; Unity runtime validation pending
|
||||
- First reported: 2026-06-29
|
||||
- Reporter/source: User gameplay report in Codex thread
|
||||
- Affected area: Hakurei Karvi/Longship landing, Dash movement-attack action economy, UnitMove/BoatToLand transform flow
|
||||
|
||||
## Symptom
|
||||
|
||||
Hakurei Karvi ship landing stopped granting an attack action point to units that have the movement-attack ability.
|
||||
|
||||
Expected behavior: the `HakureiKarviEmbark` / `岸袭` contract says units with `DASH` / `移动攻击` gain an `ActionPointType.Attack` point when they actively land from the Karvi/Longship boat state.
|
||||
|
||||
## Impact
|
||||
|
||||
This breaks a core Hakurei faction naval-to-land tempo mechanic. A valid active landing leaves movement-attack units unable to perform the intended follow-up attack, changing action economy and combat outcomes.
|
||||
|
||||
## Reproduction Or Detection Path
|
||||
|
||||
Static reproduction path from current code:
|
||||
|
||||
1. A regular Hakurei land-and-port unit with `SkillType.DASH` and `SkillType.HakureiKarviEmbark` actively moves from land to shallow sea.
|
||||
2. `UnitLogic.MoveToLogic` calls `unitData.OnMove`, so `DashSkill.TryTriggerMoveBonus` consumes the Dash charge and grants an attack point.
|
||||
3. `UnitLogic.LandToBoat` then transforms the unit to `HakureiKarvi`/`HakureiLongship` and clears action points. `UnitTypeTransform` moves the consumed `DashSkill` instance into `SkillCache`.
|
||||
4. The same unit actively moves from boat state to land.
|
||||
5. `UnitLogic.BoatToLand` transforms it back to the carried land type. Because `FullTypeCache` matches the target land type, `UnitTypeTransform` can reuse the cached `DashSkill` with `Level == 0`.
|
||||
6. The old `HakureiKarviEmbarkSkill.AfterTransformFromBoat` called `dashSkill.TryTriggerMoveBonus`, which returned false because the cached Dash charge had already been consumed.
|
||||
|
||||
## Root Cause
|
||||
|
||||
The landing reward was coupled to Dash's per-turn charge state instead of treating landing as the independent reward described by `HakureiKarviEmbark`.
|
||||
|
||||
`HakureiKarviEmbarkSkill.AfterTransformFromBoat` used `DashSkill.TryTriggerMoveBonus`. That was fragile from the start because the same unit consumes Dash during embark before boat transformation clears the temporary attack point. The later Dash charge refactor made this fragility visible: consumed Dash state is represented by `SkillBase.Level == 0` and can be reused from `UnitData.SkillCache` when returning to the same land unit type.
|
||||
|
||||
## Introducing Commits
|
||||
|
||||
- Fragile dependency introduced by commit `40b5d0d1703757ca08a196e2ab5e2b8b036a407a`
|
||||
- Author date: 2026-06-20 16:53:20 +0800
|
||||
- Message: `博丽帝国开发2`
|
||||
- Evidence: `git log -S "dashSkill.TryTriggerMoveBonus" -- Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill/HakureiKarviEmbarkSkill.cs` points to this commit. The commit added `AfterTransformFromBoat` and routed landing through `DashSkill.TryTriggerMoveBonus`.
|
||||
- Symptom-triggering change from commit `f0a8cd5316612cce1d7803b8e51805b4889c78a8`
|
||||
- Author date: 2026-06-28 20:52:51 +0800
|
||||
- Message: `fix: use charges for dash escape counters`
|
||||
- Evidence: this commit changed `DashSkill` from `IsTrigger` to charge state stored in `SkillBase.Level`, and `TryTriggerMoveBonus` now returns false when `Level <= 0`.
|
||||
|
||||
## Fix
|
||||
|
||||
`HakureiKarviEmbarkSkill.AfterTransformFromBoat` now keeps the intended qualification check, but decouples the landing reward from Dash charge state:
|
||||
|
||||
- Require valid Hakurei regular land-and-port owner.
|
||||
- Require active landing movement.
|
||||
- Require the transformed land unit to have `SkillType.DASH`.
|
||||
- Grant `ActionPointType.Attack` directly.
|
||||
|
||||
Changed files:
|
||||
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill/HakureiKarviEmbarkSkill.cs`
|
||||
- `Tools/CheckHakureiKarviLandingDash.ps1`
|
||||
- `Tools/GitCheckpoint.ps1`
|
||||
- `MD/SeriousIncidentLog/2026-06-29-hakurei-karvi-landing-dash-ap.md`
|
||||
- `MD/SeriousIncidentLog/index.md`
|
||||
|
||||
## Verification
|
||||
|
||||
- `powershell -ExecutionPolicy Bypass -File Tools/CheckHakureiKarviLandingDash.ps1`
|
||||
- `git diff --check -- Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill/HakureiKarviEmbarkSkill.cs Tools/CheckHakureiKarviLandingDash.ps1`
|
||||
- `dotnet build Unity/TH1.Hotfix.csproj --no-restore`
|
||||
|
||||
## Remaining Validation Gaps
|
||||
|
||||
- Unity Editor gameplay validation pending: with Hakurei, actively embark a `DASH` unit into Karvi/Longship state, then actively land and confirm it receives exactly one attack action point.
|
||||
- Multiplayer/replay validation pending for the same action sequence.
|
||||
|
||||
## Guardrail
|
||||
|
||||
`Tools/CheckHakureiKarviLandingDash.ps1` fails if `HakureiKarviEmbarkSkill.AfterTransformFromBoat` reintroduces dependency on `DashSkill.TryTriggerMoveBonus`, `DashSkill` type checks, `Level <= 0`, or `HasCharge()` for the landing reward.
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
| ID | 日期 | 严重度 | 状态 | 事件 | 根因提交 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| TH1-SI-2026-06-29-001 | 2026-06-29 | S1 | Fixed in workspace; guardrail and Hotfix build passed; Unity runtime validation pending | Hakurei Karvi landing no longer grants Dash attack AP | Fragile dependency `40b5d0d17`; symptom trigger `f0a8cd531` |
|
||||
| TH1-SI-2026-06-25-003 | 2026-06-25 | S1 | Fixed in workspace; guardrails passed; Unity runtime validation pending | DataAssets/Export icon references and Aunn localization/icon corruption | `8f9d23e0c`; guardrail gap after `79ac873c`; historical missing GUID refs |
|
||||
| TH1-SI-2026-06-25-002 | 2026-06-25 | S2 | Fixed in workspace; Unity/export validation pending | Hakurei Round Shieldman lacks Feudal Fief culture upgrade option | `97f05e686481855e67196c6551d7d6626e1fb2a0` |
|
||||
| TH1-SI-2026-06-25-001 | 2026-06-25 | S2 | Fixed in workspace; Unity/export validation pending | Suika Lv.2 -> Lv.3 grants a free Suika Shadow stack | `1519d4cca418ec37d4b153f387203584527878ee` |
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
# QQ 群聊天记录临时副本(2026-07-04)
|
||||
|
||||
此目录是为了本次上传、交接和复核临时放入 git 的聊天记录副本。
|
||||
|
||||
- 群:帝幻前瞻群~074发布(678575841)
|
||||
- 数据范围:2026-07-01 05:34:36 至 2026-07-04 19:33:45
|
||||
- 原始本地来源:`D:\TH1_本地目录\Q群每日\聊天记录\Exports\2026-07-04_帝幻前瞻群_678575841`
|
||||
- `group_678575841_20260704_193345_livefetch.json`:QQ Chat Exporter 实时抓取的完整 JSON
|
||||
- `group_678575841_20260704_193345_text.tsv`:从 JSON 抽取出的文字/回复文本表,供快速检索
|
||||
|
||||
这里不是长期权威数据源。完成上传或迁移到正式数据位置后,可以从仓库中清理。
|
||||
|
||||
9
MD/TempData/QQChatExports/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# 临时数据存储说明
|
||||
|
||||
此目录用于临时存放需要上传、交接或复核的外部导出数据。
|
||||
|
||||
- 这里的文件不是长期权威数据源。
|
||||
- 原始数据仍以本地导出目录、QQ Chat Exporter 或后续正式数据仓库为准。
|
||||
- 放入此目录的数据通常体积较大,完成上传或迁移后可以清理。
|
||||
- 新增数据时请按日期和来源建子目录,并在相关分析文档中记录来源范围。
|
||||
|
||||
189
MD/hakurei_viking_qq_feedback_20260701.md
Normal file
@ -0,0 +1,189 @@
|
||||
# QQ 群博丽帝国维京文明反馈整理(2026-07-01)
|
||||
|
||||
## 数据来源
|
||||
|
||||
- 群:帝幻前瞻群~074发布(678575841)
|
||||
- 导出文件:`D:\TH1_本地目录\Q群每日\聊天记录\Exports\2026-07-01_帝幻前瞻群_678575841\group_678575841_20260701_203346.json`
|
||||
- 导出时间:2026-07-01 20:35:16
|
||||
- 消息范围:1261 条,群消息本地时间约 2026-06-29 16:32:05 至 2026-07-01 05:34:36
|
||||
- 本整理只覆盖文字消息。导出 manifest 记录 119 个图片/媒体资源下载失败,因此图片里的意见无法可靠判读。
|
||||
- 文本中没有直接出现“维京”,相关讨论主要用“博丽”“玻璃”“雷霆”“长船”“龙船”“女武神”“堇子”“阿吽”“华扇”“萃香”等指代。
|
||||
|
||||
## 总体判断
|
||||
|
||||
群友对博丽帝国维京文明的反馈集中在四条主线:
|
||||
|
||||
1. 水图和海战节奏过强:三速船、早帆船、捕鲸、海权、岸袭等形成明显先手优势。
|
||||
2. 堇子/阿吽/雷霆单位的防御转攻击、传送、女武神高防组合让反制手段变窄。
|
||||
3. 地图资源分布很影响体验:树太多会卡陆战和华扇/港口玩法,但水多又会直接转捕鲸海战。
|
||||
4. 英雄组合上灵梦存在感低,堇子、华扇、萃香、阿吽承担主流强度;但各自强弱高度依赖地图类型。
|
||||
|
||||
## 单位与舰船
|
||||
|
||||
### 长船 / 龙船 / 帆船
|
||||
|
||||
- 长船 3 移动被认为“很超模”,尤其在博丽仍有正常路的前提下,机动性评价偏高。来源:#0083-#0088。
|
||||
- 群友纠正“推人的是龙船,长船不是龙船”,说明当前舰船差异需要在 UI 或技能描述中更清晰。来源:#0089-#0095。
|
||||
- 海上成群小帆船被评价为“太恐怖”,水图中“帆船大战”成为核心节奏。来源:#0109、#0310。
|
||||
- 早爆帆船被认为会形成大优势,但也有人质疑前期是否会牺牲经济,尤其“帆船还要 5 块钱”。来源:#0287-#0319。
|
||||
- 有人提出合理节奏应是先让经济跑起来,再转帆船;也有人强调博丽再慢也比别阵营快。来源:#0292-#0307。
|
||||
|
||||
### 岸袭 / 上岸攻击点 / 卡维船
|
||||
|
||||
- 帆船上岸后仍有再攻/攻击点;炮舰、龙船、长船也被观察到仍有攻击点。来源:#0409-#0418。
|
||||
- 只有卡维船没有上岸攻击点,被进一步说明为“没港口的船”。来源:#0419-#0425。
|
||||
- 玩家直接反馈“卡维船有 bug,不能获得岸袭的攻击点”。如果这是设计意图,需要在规则说明里解释;如果不是,需要查岸袭和船来源判定。来源:#0454-#0457。
|
||||
|
||||
### 女武神 / 雷霆单位 / 大盾
|
||||
|
||||
- 小帆船打女武神只打 2,女武神高防在海战和岸边交火中压迫感强。来源:#0258。
|
||||
- 堇子阿吽可以做出“雷霆 7.5 攻击力女武神”,被评价为“没有车打得过”。来源:#0936-#0938。
|
||||
- 雷霆大盾被指出不能移动攻击,剑士仍因移动攻击有价值。来源:#0504-#0508。
|
||||
- 山防、林防叠加后出现“全员无敌”“往哪一站全是 0”的体验反馈,说明地形防御和雷霆高防组合需要重点看伤害下限、反制和 UI 预期。来源:#0511-#0516。
|
||||
|
||||
## 经济、建设与发展
|
||||
|
||||
### 树、陆战与资源分布
|
||||
|
||||
- 博丽陆战被质疑“不行吗”,主要原因是“太多树卡脚”。来源:#0050-#0058。
|
||||
- 有人建议“全砍了”,也有人认为只要把博丽树量降到红魔馆一样就不会卡。来源:#0055-#0059。
|
||||
- 反向意见是树多方便放“丹麦珠”,树量既是阻碍也是机制资源。来源:#0060。
|
||||
- 华扇与港口玩法会因为树多被阻断,玩家表示“树多没机会玩华扇与港口”。来源:#0063-#0074。
|
||||
|
||||
### 捕鲸、鲸鱼与海图经济
|
||||
|
||||
- 水图对博丽的核心发展路线被总结为“海多就直线捕鲸,树多直接伐木巨人”。来源:#0620。
|
||||
- 水图对局中有人吃了 5、6 个鲸鱼,但仍反馈前期出现“过山车式劣势”,说明鲸鱼经济很强但也可能被城市数量、人口和前线转换效率卡住。来源:#0602-#0612。
|
||||
- “捕鲸遗迹出经济”被作为早帆船/水图经济理由。来源:#0310-#0311。
|
||||
- 博丽内战被吐槽成“谁鲸鱼多谁赢”,地图随机性对胜负权重偏高。来源:#0269-#0270。
|
||||
|
||||
### 军营 / 军港 / 出兵路线
|
||||
|
||||
- 一局复盘中玩家认为第一个骑士不该出,应该用“军营军港联动造帆船”。来源:#0606-#0608。
|
||||
- 军港相关讨论带有强烈负面情绪:“删除军港”“被军港舰船打爆了”。虽然不完全限于博丽,但它直接影响当前海战环境。来源:#0236-#0253。
|
||||
- 有人提出“改成不可当回合拆卸也可以”,这是对军港/设施即时转换收益的具体平衡建议。来源:#0261。
|
||||
|
||||
### 奇观与建设
|
||||
|
||||
- 有玩家反馈最近玩博丽时“时不时就能点出四角奇观,以前没那么好点”。需要核查是否来自博丽地形、视野、科技、资源分布或奇观判定变化。来源:#0404-#0407。
|
||||
|
||||
## 战斗、对局与反制
|
||||
|
||||
### 海战后期形态
|
||||
|
||||
- 群友认为海战后期本质仍是经济比拼,发展到“炮艇与炮兵互相压岸”,上下海都会被惩罚。来源:#0114-#0116。
|
||||
- 这说明海战不是单纯船强弱问题,而是经济、岸边火力、登陆惩罚、单位突破点共同决定。
|
||||
|
||||
### 反制堇子阿吽 / 高防体系
|
||||
|
||||
- 对抗堇子阿吽的明确建议是“把树都砍了”,红魔馆自带,守矢也能全砍,其他阵营需要专门对策。来源:#0516-#0519。
|
||||
- 也有人认为堇子阿吽如果没有持续压制起来就会废,且“主要跑得有点慢”。来源:#0524-#0525。
|
||||
- 因此堇子阿吽更像高上限压制体系,而不是无脑稳定体系;平衡时要区分开局压制、地图支撑、操作负担和失速后的下限。
|
||||
|
||||
### 地图选择成为反制
|
||||
|
||||
- 有玩家总结“现在看到博丽,你不是博丽就开旱地”。来源:#0278-#0279。
|
||||
- 这是明显红旗:如果常规反制依赖开特定地图,而不是局内工具,说明水图博丽的普适反制可能不足。
|
||||
|
||||
### 符文与连杀
|
||||
|
||||
- 有玩家问“符文禁止连杀 bug 修了吗”,并反馈此前芙兰打博丽“一碰到符文直接烂了”。来源:#1178-#1182。
|
||||
- 这项应先按 bug 处理,再谈芙兰 vs 博丽的真实平衡。
|
||||
|
||||
## 平衡风险点
|
||||
|
||||
1. 三速船 + 正常路 + 早帆船 + 捕鲸,可能让博丽在水图启动节奏过快。来源:#0083-#0088、#0287-#0311。
|
||||
2. 堇子防御转攻击被评价为“无责任”“无敌”,尤其配合阿吽、女武神、雷霆属性时反制过窄。来源:#0257-#0264、#0937-#0942。
|
||||
3. 华扇“全靠机制”,削弱前强到“别的阵营要灭亡”,说明机制本身仍需持续观察。来源:#0440-#0445。
|
||||
4. 博丽内战/水图胜负可能过度依赖鲸鱼数量,地图随机性过高。来源:#0269-#0270。
|
||||
5. 树和水分别把博丽导向完全不同强势路线:树多伐木巨人,水多捕鲸;缺水缺树则玩家玩笑称“3 速魔法下一把”。来源:#0620-#0625。
|
||||
|
||||
## 英雄相关
|
||||
|
||||
### 主流组合与出场倾向
|
||||
|
||||
- 早期回答“现在博丽一般英雄组合”时,有人答“鬼王鬼王”。来源:#0108-#0121。
|
||||
- 后续更细化为“堇子阿吽,或者鬼王鬼王”。来源:#0932-#0937。
|
||||
- 有玩家平时“西瓜和华扇必出,剩下一个基本给堇子”。来源:#0482-#0485。
|
||||
- “反正不是灵梦,排列组合 3 个”“除了灵梦排列组合一下都能玩”反复出现,说明灵梦当前吸引力或功能性不足。来源:#0485、#0944。
|
||||
|
||||
### 堇子
|
||||
|
||||
- 堇子被认为海战强,“海战还是堇子好”“汪洋是得堇子”。来源:#0342-#0345。
|
||||
- 堇子削弱点被提到:珠子上限等于等级,范围变 2。来源:#0126-#0132。
|
||||
- 堇子开局被评价比华扇轻松,因为不用数三格;但也有人觉得需要记忆,不适合忘记珠子操作。来源:#0590-#0599。
|
||||
- 海战中堇子比鬼王更能吃鲸、也更能让帆船成形。来源:#0947、#0962。
|
||||
- UI 建议:珠子需要独立于其他单位的显示,避免战场复杂时漏看。来源:#0616-#0618。
|
||||
|
||||
### 阿吽
|
||||
|
||||
- 阿吽可作为防城旁或军营旁的传送点,把女武神、剑士等持续送上前线。来源:#0492-#0496。
|
||||
- 范围 8 在 1v1 基本不会超,2v2 才更明显。来源:#0498-#0501。
|
||||
- 负面评价是“阿吽已经跟不上其他三个了”,lv2 没有成长空间,最终效果像“3 攻小马和 +1 防”。来源:#0939、#0948-#0950。
|
||||
- 结论:阿吽单体成长可能弱,但与堇子和高防单位的体系配合仍能形成压制。
|
||||
|
||||
### 华扇
|
||||
|
||||
- 华扇面板数值看起来弱,但强度主要靠机制;玩家认为已经削过,且不削会压垮其他阵营。来源:#0440-#0445。
|
||||
- 水图差异明显:标准图华扇通常能跨过去,汪洋图没有传送点。来源:#0345-#0347。
|
||||
- 华扇与港口玩法受树量影响,树多会限制体验。来源:#0074。
|
||||
|
||||
### 萃香 / 鬼王
|
||||
|
||||
- 鬼王组合被多次提为常见选择,萃香也被称作“4 速萃香”。来源:#0928、#0936。
|
||||
- 萃香在局部战斗中表现很肉,但也有被单击击杀的反馈,说明面板、防御、雷霆、地形和敌方爆发互动复杂。来源:#0461-#0487。
|
||||
- 鬼王标准海图可以玩,但纯海弱,因为没有落点,等于没技能。来源:#0951-#0956。
|
||||
|
||||
### 灵梦
|
||||
|
||||
- 灵梦被明确排除在常用排列组合外。来源:#0485、#0944。
|
||||
- 玩家开头提到“我的灵梦容易旱梦”,但后续讨论核心转向地图树量和海战,并没有形成明确灵梦加强方案。来源:#0050-#0063。
|
||||
- 建议后续单独看灵梦定位:她是被地图拖累,还是英雄机制本身没有足够战略理由。
|
||||
|
||||
## UI、说明与可读性
|
||||
|
||||
- 珠子独立显示:玩家明确请求“能不能给珠子独立与其他单位的显示”。来源:#0616-#0618。
|
||||
- 英灵符文对普通单位效果不清楚,有玩家表示“没看出来什么效果”。来源:#0450-#0451。
|
||||
- 岸袭是否基于船、哪些船能拿攻击点、卡维船为何不同,需要在技能说明或战斗预览中解释。来源:#0413-#0425、#0454-#0457。
|
||||
- 挖坟出来不是龙船的结果让玩家困惑,可能需要明确随机池或产物规则。来源:#0448。
|
||||
|
||||
## 可转任务清单
|
||||
|
||||
### 高优先级
|
||||
|
||||
1. 验证并修复“符文禁止连杀”相关 bug;该 bug 会污染博丽对芙兰等连杀英雄的平衡判断。
|
||||
2. 验证卡维船岸袭/上岸攻击点逻辑:区分设计意图、船来源、港口条件和 UI 表现。
|
||||
3. 为堇子珠子增加独立显示或更明显的战场提示,降低漏操作。
|
||||
4. 拉一轮真实数据:博丽英雄出场率、胜率、地图类型、鲸鱼数量、前 10 回合科技/单位路径。
|
||||
|
||||
### 中优先级
|
||||
|
||||
1. 评估长船 3 移动与正常道路、三速船体系叠加后的实际可达范围。
|
||||
2. 评估堇子防御转攻击、阿吽传送点、雷霆女武神组合的反制宽度。
|
||||
3. 检查博丽水图捕鲸经济:早帆船是否过快,鲸鱼数量是否过度决定胜负。
|
||||
4. 检查博丽树量:减少陆战卡脚会削弱丹麦珠/伐木巨人路线,需要配套补偿或改成地图生成权重微调。
|
||||
|
||||
### 低优先级 / 需更多样本
|
||||
|
||||
1. 四角奇观更容易点出的体感,需要实际复盘或配置检查。
|
||||
2. 灵梦弱存在感需要单独测试,不应只凭本次群聊结论直接加强。
|
||||
3. 军港即时拆卸、军营军港联动造船属于更大海战系统问题,可与博丽海战一起复盘。
|
||||
|
||||
## 代表性消息索引
|
||||
|
||||
- 树、陆战、丹麦珠:#0050-#0060
|
||||
- 华扇与港口受树影响:#0063-#0074
|
||||
- 三速船、长船、龙船:#0083-#0095
|
||||
- 海战经济与炮艇/炮兵压岸:#0109-#0116
|
||||
- 军港、堇子、女武神、高防:#0236-#0264
|
||||
- 博丽内战和鲸鱼随机性:#0269-#0270
|
||||
- 地图反制“开旱地”:#0278-#0279
|
||||
- 早帆船与经济争论:#0287-#0321
|
||||
- 海战堇子、华扇传送点:#0342-#0347
|
||||
- 卡维船和岸袭攻击点:#0409-#0425、#0454-#0457
|
||||
- 华扇机制强度:#0440-#0445
|
||||
- 英灵符文说明不清:#0450-#0451
|
||||
- 堇子阿吽、传送女武神、地形高防:#0482-#0525
|
||||
- 珠子 UI、鲸鱼与军营军港造帆船:#0590-#0620
|
||||
- 主流英雄组合与灵梦低存在感:#0928-#0962
|
||||
- 符文连杀 bug:#1178-#1182
|
||||
205
MD/hakurei_viking_qq_feedback_20260704.md
Normal file
@ -0,0 +1,205 @@
|
||||
# QQ 群博丽帝国维京文明反馈整理(累计至 2026-07-04)
|
||||
|
||||
## 数据范围
|
||||
|
||||
- 群:帝幻前瞻群~074发布(678575841)
|
||||
- 旧导出:`D:\TH1_本地目录\Q群每日\聊天记录\Exports\2026-07-01_帝幻前瞻群_678575841\group_678575841_20260701_203346.json`
|
||||
- 旧导出范围:1261 条,约 2026-06-29 16:32:05 至 2026-07-01 05:34:36
|
||||
- 新导出:`D:\TH1_本地目录\Q群每日\聊天记录\Exports\2026-07-04_帝幻前瞻群_678575841\group_678575841_20260704_193345_livefetch.json`
|
||||
- 新文字表:`D:\TH1_本地目录\Q群每日\聊天记录\Exports\2026-07-04_帝幻前瞻群_678575841\group_678575841_20260704_193345_text.tsv`
|
||||
- 新导出范围:1652 条,2026-07-01 05:34:36 至 2026-07-04 19:33:45
|
||||
- 累计范围:约 2912 条可见群消息,边界消息重合 1 条
|
||||
- 来源编号说明:`#0000` 形式的旧编号沿用 7 月 1 日整理报告的消息索引;`#92509` 这类五位编号为新抓取中的 QQ `msgSeq`。
|
||||
- 本整理只可靠覆盖文字、回复文本和能解析出的文本元素;图片、表情包、截图里的意见没有纳入明确结论。
|
||||
- 群聊中很少直接说“维京”,相关讨论主要用“博丽”“玻璃”“灵梦”“堇子”“阿吽”“华扇”“萃香”“雷霆”“女武神”“长船”“龙船”“帆船”“捕鲸”“旱地”等词指代。
|
||||
|
||||
## 这次新增的核心变化
|
||||
|
||||
7 月 1 日报告的核心是“水图海战和堇子/阿吽/雷霆组合太强、灵梦存在感低”。7 月 3 日至 7 月 4 日的新讨论把问题进一步收束成两条:
|
||||
|
||||
1. 玩家不再只把博丽看成某个数值或某条船过强,而是把它看作一整套节奏包:雷霆女武神、华扇扇子、萃香、堇子、阿吽、帆船、捕鲸、偷钱、狂暴、高密度资源叠在一起。来源:#93501-#93503。
|
||||
2. 标准图对堇子珠子和浅海/海上压力缺少局内反制。有玩家直接说“遇到博丽开标准就是想输了”,并提出“消除浅海科技”作为反制想法。来源:#93970、#93986-#93991。
|
||||
|
||||
从 4X/战棋设计角度看,当前风险不是单点超模,而是经济启动、机动范围、海岸控制、隐蔽召唤物、高防单位和地图资源权重共同压缩了对手的有效选择。
|
||||
|
||||
## 总体判断
|
||||
|
||||
博丽帝国维京文明目前给玩家的强势感来自五个叠加层:
|
||||
|
||||
1. 经济层:捕鲸、海图收益、高密度资源、伐木巨人路线让启动很快,水多和树多都可能转成收益。来源:#0269-#0270、#0310-#0311、#0620。
|
||||
2. 机动层:三速船、帆船群、华扇传送、萃香大跳、阿吽信标让博丽能快速投送压力。来源:#0083-#0095、#0342-#0347、#0492-#0501、#93537-#93569。
|
||||
3. 战斗层:堇子珠子、雷霆女武神、高防地形和防御转攻击造成对手伤害下限低、拆解成本高。来源:#0257-#0264、#0511-#0516、#0937-#0942、#93970-#94003。
|
||||
4. 地图层:标准图、汪洋图、旱地图对博丽体验差异极大,甚至出现“非博丽就开旱地”的玩家习惯。来源:#0278-#0279、#93970、#93986。
|
||||
5. 可读性层:堇子珠子、堇子技能拆分、萃香投掷/跳跃、长船/龙船/岸袭规则都存在理解成本。来源:#0616-#0618、#93647-#93666、#93539-#93547、#93976-#93983。
|
||||
|
||||
因此,平衡不建议只砍某个数值。更稳妥的方向是先定义“标准图中非博丽玩家应该有哪些局内反制工具”,再回头判断哪些博丽优势属于特色,哪些优势叠加后破坏反制。
|
||||
|
||||
## 单位与舰船
|
||||
|
||||
### 长船 / 龙船 / 战舰 / 帆船
|
||||
|
||||
- 长船 3 移动仍被认为机动超标,尤其博丽还有正常道路体系时,海陆转线速度评价偏高。来源:#0083-#0088。
|
||||
- 玩家多次把“长船”和“龙船”混用。旧记录里有人纠正“推人的是龙船,长船不是龙船”;新记录里也出现“长船能把船推上岸”后被纠正为“龙船”。来源:#0089-#0095、#93976-#93983。
|
||||
- 15 块钱战舰被提到“一个没看住就没了”,但也有人指出它速度只有 2,需要区分是舰船本身脆弱、雾战惩罚,还是玩家操作风险。来源:#93977-#93985。
|
||||
- 小帆船群和早帆船依旧是水图核心压力点。旧讨论里“帆船大战”“捕鲸遗迹出经济”“早帆船能否牺牲经济”等都说明海战启动节奏需要复盘。来源:#0109、#0287-#0319。
|
||||
|
||||
### 卡维船与岸袭
|
||||
|
||||
- 旧记录里玩家反馈帆船、炮舰、龙船、长船上岸后仍有攻击点,只有卡维船没有。来源:#0409-#0425。
|
||||
- 有玩家直接认为“卡维船有 bug,不能获得岸袭的攻击点”。如果这是设计差异,说明需要 UI/说明明确;如果不是设计差异,应按 bug 查船来源、港口条件和岸袭判定。来源:#0454-#0457。
|
||||
|
||||
### 女武神与雷霆高防
|
||||
|
||||
- 女武神高防在海岸和水图交火中压迫感很强,小帆船打女武神只打 2 的反馈仍有代表性。来源:#0258。
|
||||
- 玩家质疑“长船 2-2-2”与“女武神 0-4”在和堇子配合时强度差异过大,认为二者与堇子的适配度“一个天一个地”。来源:#93971-#93975。
|
||||
- 雷霆女武神和堇子/阿吽组合可形成高攻高防单位,玩家曾评价为“没有车打得过”。来源:#0936-#0938。
|
||||
|
||||
## 经济、建设与发展
|
||||
|
||||
### 捕鲸与水图经济
|
||||
|
||||
- 水图核心路线被玩家总结为“海多就直线捕鲸,树多直接伐木巨人”。来源:#0620。
|
||||
- 博丽内战被吐槽成“谁鲸鱼多谁赢”,说明鲸鱼数量对胜负权重可能过高。来源:#0269-#0270。
|
||||
- 早帆船是否过快仍有争论:支持者认为捕鲸和水图遗迹能反哺经济,质疑者认为帆船前期仍要花钱。来源:#0287-#0319。
|
||||
- 新讨论里,玩家把博丽经济与旧阵营比较,认为博丽让红魔馆从经济节奏、英雄、巨人、阵营特色都显得落后。来源:#92509-#92515。
|
||||
|
||||
### 树、陆战与资源密度
|
||||
|
||||
- 树多一方面卡脚,影响陆战、华扇和港口;另一方面又是丹麦珠、伐木巨人等机制资源。来源:#0050-#0074。
|
||||
- 这意味着“减少树”不是纯削或纯加强:它可能改善陆战体验,但会改变堇子/资源路线和伐木巨人节奏。
|
||||
- 新讨论把“高密度资源”直接列入博丽强势包,说明玩家感受中的问题不仅是水,而是地图给博丽的可转化资源过多。来源:#93501-#93503。
|
||||
|
||||
### 路线与科技选择
|
||||
|
||||
- 有玩家提出堇子路线:堇子捕猎点到种树,然后持续出棒子,一个珠子加速,一个珠子加攻。来源:#92823。
|
||||
- 这条路线值得单独复盘,因为它说明堇子不是只在海图吃鲸强,也能通过低级兵和珠子加速/加攻打出陆上节奏。
|
||||
- PVE 速通讨论里,玩家认为不要过度养经济,差不多够启动就应抢城、抢见面金;博丽当前极限记录被提到 16t,可能有 15t。来源:#93142-#93224。
|
||||
- 速通数据不等于 PVP 平衡证据,但能提示“启动回合”“爆发回合”和“地图凹度”在哪些阵营上更突出。
|
||||
|
||||
### 后续阵营设计警示
|
||||
|
||||
- 玩家在比较红魔馆时提出一个更通用的问题:如何让一个阵营有经济强度,同时避免只是 T2 经济建筑换皮。来源:#92543-#92551。
|
||||
- 这对博丽也有参考意义:如果博丽强在捕鲸、资源密度和多层英雄机制,后续阵营不能只靠更高经济补数值,否则会继续推高整体节奏。
|
||||
|
||||
## 战斗与反制
|
||||
|
||||
### 标准图反制不足
|
||||
|
||||
- 旧记录里已有“看到博丽,不是博丽就开旱地”的反馈。来源:#0278-#0279。
|
||||
- 新记录进一步升级为“标准图遇到博丽就是想输了”,并把矛头指向堇子珠子在标准图和浅海环境里难以处理。来源:#93970、#93986-#93991。
|
||||
- 有玩家提议加入“消除浅海的科技”,本质是在请求局内地形反制工具,而不是继续用开局选图反制。来源:#93970。
|
||||
- 也有玩家补充“不只是堇子的问题”,指出裸下海和海中珠子都让标准图处理困难。来源:#93990-#93996。
|
||||
|
||||
### 堇子珠子的可选改法
|
||||
|
||||
群聊里出现的具体想法:
|
||||
|
||||
1. 增加消除浅海科技,让标准图有地形层面的反制。来源:#93970。
|
||||
2. 把堇子改成对单 buff,直接给单位上珠子,减少地图召唤物带来的海上不可拆问题。来源:#93995-#93997。
|
||||
3. 删除珠子隐身。来源:#93998。
|
||||
4. 部署当回合无效,避免回合内即时收益过强。来源:#93999。
|
||||
|
||||
这些建议里,2 是机制重做,1 是环境反制,3 和 4 是直接削珠子。群里也立刻有人担心“部署当回合无效”会让跨回合收益不足,说明直接砍即时性可能会过削,需要先测标准图反制宽度。来源:#94000-#94003。
|
||||
|
||||
### 高防与防御转攻击
|
||||
|
||||
- 堇子、阿吽、雷霆、女武神、地形防御叠加后,玩家曾反馈出现“全员无敌”“往哪一站全是 0”的体验。来源:#0511-#0516。
|
||||
- 玩家对堇子阿吽的反制建议之一是“把树都砍了”,但这对不是所有阵营都公平可用。来源:#0516-#0519。
|
||||
- 因此应区分两件事:堇子/阿吽体系是否上限太高,以及非特定阵营是否缺少足够清晰、低成本的反制。
|
||||
|
||||
### 符文与连杀
|
||||
|
||||
- 旧记录里玩家问“符文禁止连杀 bug 修了吗”,并反馈此前芙兰打博丽“一碰到符文直接烂了”。来源:#1178-#1182。
|
||||
- 这应优先按 bug 或规则一致性处理;如果符文会异常阻断连杀,就会污染所有关于博丽克制连杀英雄的平衡判断。
|
||||
|
||||
## 英雄相关
|
||||
|
||||
### 灵梦
|
||||
|
||||
- 旧记录中灵梦经常被排除在主流组合之外,玩家说“反正不是灵梦”“除了灵梦排列组合一下都能玩”。来源:#0485、#0944。
|
||||
- 新记录里出现了更细的反向意见:灵梦因为雷霆女武神和扇子保护,实际没那么差;对面也不容易解掉灵梦。来源:#93471-#93473。
|
||||
- 但灵梦仍被指出移速慢,输出依赖对面打她;有玩家认为光环 buff 单位通常不能移速太低,否则容易从攻坚单位退化为守家单位。来源:#93474-#93496。
|
||||
- 结论:灵梦不应只按单体弱来加强。应测试“灵梦 + 雷霆女武神 + 华扇/扇子 + 萃香投掷”的完整组合,再判断是移速问题、触发条件问题,还是只是玩家未充分开发。
|
||||
|
||||
### 堇子
|
||||
|
||||
- 堇子仍是讨论最多的核心英雄。旧记录里她在海战、吃鲸、帆船成形、阿吽组合中评价很高。来源:#0342-#0345、#0947、#0962。
|
||||
- 新记录给出具体路线:捕猎到种树,出棒子,一个珠子加速,一个珠子加攻。来源:#92823。
|
||||
- 玩家质疑堇子“明显 3 个技能却写到一起”,认为三个效果完全独立,按等级逐个给,应该分成技能或至少说明更清楚。来源:#93647-#93666。
|
||||
- 也有人指出分开放会让 UI 显得杂、显得技能多和超模。这个不是纯 UI 问题,而是技能复杂度和强度感知问题。来源:#93665-#93666。
|
||||
- 珠子独立显示仍是明确 UI 需求,旧记录里玩家已经请求“给珠子独立于其他单位的显示”。来源:#0616-#0618。
|
||||
|
||||
### 阿吽
|
||||
|
||||
- 阿吽可作为防城旁或军营旁的传送点,把女武神、剑士等持续送上前线。来源:#0492-#0496。
|
||||
- 阿吽单体被评价“跟不上其他三个”,二级成长空间有限;但与堇子和高防单位配合仍有压制力。来源:#0939、#0948-#0950。
|
||||
- 处理阿吽时要避免只看单体弱而忽略它在“传送 + 高防 + 珠子 + 海岸”的体系价值。
|
||||
|
||||
### 华扇
|
||||
|
||||
- 华扇强度主要来自机制,不是面板。玩家认为此前不削会压垮其他阵营。来源:#0440-#0445。
|
||||
- 水图差异明显:标准图通常能跨过去,汪洋图没有传送点;树多也会限制华扇和港口玩法。来源:#0345-#0347、#0074。
|
||||
- 新记录中“扇子”被列入博丽强势包,说明它仍被玩家视为关键保护/逃脱组件。来源:#93501-#93503。
|
||||
|
||||
### 萃香 / 鬼王
|
||||
|
||||
- 旧记录里萃香和鬼王组合是常见选择,局部战斗表现很肉,但纯海图弱,因为没有落点。来源:#0928、#0936、#0951-#0956。
|
||||
- 新记录补充了萃香与灵梦的联动:3 级萃香可以把灵梦扔出去,被玩家类比成“王车易位”。来源:#93537-#93538。
|
||||
- 萃香投掷操作可读性不足,有玩家表示找不到扔人的键位,随后被说明“点一下萃香”“和攻击一样”“消耗攻击点”。来源:#93539-#93547。
|
||||
- 萃香大跳被确认可在控制区里跳,也能跳进别人城里;只要是三格能走的地都能,还能砸到单位身上。来源:#93560-#93571。
|
||||
- 这组规则应重点验证。若它绕过控制区、城防、占城和单位阻挡的预期太多,会显著增加博丽的战术爆发和 UI 误判。
|
||||
|
||||
## UI、说明与可读性
|
||||
|
||||
- 舰船命名:长船、龙船、战舰、卡维船的技能差异需要更清楚,尤其“推船上岸”和“上岸攻击点”。来源:#0089-#0095、#0409-#0425、#93976-#93983。
|
||||
- 堇子珠子:需要独立显示或更明显提示,否则战场复杂时容易漏看。来源:#0616-#0618。
|
||||
- 堇子技能:当前把多个独立效果写在一起,玩家会认为是在隐藏超模;但拆开又可能造成 UI 杂。来源:#93647-#93666。
|
||||
- 萃香操作:投掷和大跳需要更明确的目标提示、控制区/敌城规则说明和预览反馈。来源:#93539-#93571。
|
||||
- 英灵符文和岸袭规则旧记录里也被认为不够清楚。来源:#0450-#0457。
|
||||
|
||||
## 可转任务清单
|
||||
|
||||
### 高优先级
|
||||
|
||||
1. 定义标准图对博丽的反制契约:非博丽玩家不应只能靠开旱地图反制;需要明确浅海、海中珠子、捕鲸和帆船群分别有哪些局内答案。
|
||||
2. 专项复盘堇子珠子:测试“消除浅海科技”“对单 buff 化”“去隐身”“部署当回合无效”四类改法,先评估反制宽度,再评估强度削减。
|
||||
3. 验证萃香投掷/大跳规则:控制区内能否跳、能否进敌城、能否砸单位、是否消耗攻击点、是否影响占城与城防预期。
|
||||
4. 验证并修复符文禁止连杀相关 bug,避免它继续污染芙兰等连杀英雄对博丽的平衡判断。
|
||||
5. 梳理长船/龙船/卡维船/战舰说明,特别是推船、岸袭、上岸后攻击点、船来源差异。
|
||||
|
||||
### 中优先级
|
||||
|
||||
1. 用组合而不是单体评估灵梦:灵梦 + 雷霆女武神 + 华扇扇子 + 萃香投掷是否已经足够强,移速慢是否仍是关键短板。
|
||||
2. 评估“博丽强势包”中每一层的必要性:三速船、捕鲸、堇子珠子、阿吽传送、华扇逃脱、萃香跳投、雷霆女武神、高密度资源不能全部按独立特色放行。
|
||||
3. 检查博丽资源生成权重:水、树、鲸鱼、高密度资源是否让不同地图都能转成强势路线。
|
||||
4. 对比旧阵营节奏:玩家认为博丽让红魔馆等旧阵营显得落后,应拆分是旧阵营弱、博丽强,还是新阵营整体设计节奏抬高。
|
||||
|
||||
### 低优先级 / 需要更多样本
|
||||
|
||||
1. 博丽 16t/15t 速通记录只作为启动速度提示,不直接作为 PVP 平衡证据。
|
||||
2. 四角奇观更容易点出的体感需要复盘或配置检查。来源:#0404-#0407。
|
||||
3. 军港即时拆卸、军营军港联动造船属于更大海战系统问题,可与博丽海战一起复盘。来源:#0236-#0261、#0606-#0608。
|
||||
|
||||
## 代表性消息索引
|
||||
|
||||
- 树、陆战、丹麦珠:#0050-#0060
|
||||
- 华扇与港口受树影响:#0063-#0074
|
||||
- 三速船、长船、龙船:#0083-#0095
|
||||
- 海战经济与炮艇/炮兵压岸:#0109-#0116
|
||||
- 军港、堇子、女武神、高防:#0236-#0264
|
||||
- 博丽内战和鲸鱼随机性:#0269-#0270
|
||||
- 地图反制“开旱地”:#0278-#0279
|
||||
- 早帆船与经济争论:#0287-#0321
|
||||
- 卡维船和岸袭攻击点:#0409-#0425、#0454-#0457
|
||||
- 堇子阿吽、传送女武神、地形高防:#0482-#0525
|
||||
- 珠子 UI、鲸鱼与军营军港造帆船:#0590-#0620
|
||||
- 主流英雄组合与灵梦低存在感:#0928-#0962
|
||||
- 符文连杀 bug:#1178-#1182
|
||||
- 博丽压旧阵营节奏、后续经济设计问题:#92509-#92551
|
||||
- 堇子捕猎种树路线:#92823
|
||||
- 地图生成和 PVE 速通上下文:#93094-#93224
|
||||
- 灵梦与雷霆女武神、扇子保护:#93471-#93496
|
||||
- 博丽强势包玩家概括:#93501-#93503
|
||||
- 萃香扔灵梦、投掷和大跳规则:#93537-#93571
|
||||
- 堇子技能拆分与 UI 争论:#93647-#93666
|
||||
- 标准图、浅海、堇子珠子反制建议:#93970-#94003
|
||||
103
Tools/CheckAttackGroundPeaceSemantics.ps1
Normal file
@ -0,0 +1,103 @@
|
||||
param()
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
$failures = @()
|
||||
|
||||
function Add-Failure {
|
||||
param([string]$Message)
|
||||
$script:failures += $Message
|
||||
}
|
||||
|
||||
function Read-RepoText {
|
||||
param([string]$RelativePath)
|
||||
$path = Join-Path $repoRoot $RelativePath
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
Add-Failure "Missing file: $RelativePath"
|
||||
return ""
|
||||
}
|
||||
return Get-Content -LiteralPath $path -Raw
|
||||
}
|
||||
|
||||
$skillBase = Read-RepoText "Unity/Assets/Scripts/TH1_Logic/Skill/SkillBase.cs"
|
||||
$unitData = Read-RepoText "Unity/Assets/Scripts/TH1_Data/UnitData.cs"
|
||||
$actionLogic = Read-RepoText "Unity/Assets/Scripts/TH1_Logic/Action/ActionLogic.cs"
|
||||
$hakureiNorwayHeroSkill = Read-RepoText "Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill/HakureiNorwayHeroSkill.cs"
|
||||
|
||||
if ($skillBase -notmatch "public bool AttackGroundCountsAsActiveAttack\(MapData map,UnitData self, GridData target\);") {
|
||||
Add-Failure "ISkill must expose AttackGroundCountsAsActiveAttack."
|
||||
}
|
||||
|
||||
if ($skillBase -notmatch "public virtual bool AttackGroundCountsAsActiveAttack\(MapData map, UnitData self, GridData target\)\s*\{\s*return true;\s*\}") {
|
||||
Add-Failure "SkillBase default AttackGroundCountsAsActiveAttack must remain true."
|
||||
}
|
||||
|
||||
if ($unitData -notmatch "out bool countsAsActiveAttack") {
|
||||
Add-Failure "UnitData.AttackGroundExecute must return countsAsActiveAttack."
|
||||
}
|
||||
|
||||
if ($unitData -notmatch "skill\.AttackGroundCountsAsActiveAttack\(map, this, target\)") {
|
||||
Add-Failure "UnitData.AttackGroundExecute must read skill-level active-attack semantics before executing."
|
||||
}
|
||||
|
||||
if ($actionLogic -notmatch "out skillAttackGroundCountsAsActiveAttack") {
|
||||
Add-Failure "UnitAttackGroundAction must receive the skill active-attack semantic result."
|
||||
}
|
||||
|
||||
if ($actionLogic -notmatch "if \(skillAttackGroundCountsAsActiveAttack\)\s*ActiveAttackActionRecorder\.MarkStarted") {
|
||||
Add-Failure "UnitAttackGroundAction must only reset peace progress for skill ground actions that count as active attacks."
|
||||
}
|
||||
|
||||
if ($actionLogic -notmatch "if \(!handledBySkillAttackGround && hasSuwakoAttack\)" -or
|
||||
$actionLogic -notmatch "(?s)targetGrid\.RealUnit\(actionParams\.MapData, out var targetGridUnit\).*?ActiveAttackActionRecorder\.MarkStarted" -or
|
||||
$actionLogic -notmatch "(?s)unit1\.ClearActionPoint\(\).*?actionParams\.MapData\.AddUnitData") {
|
||||
Add-Failure "Suwako occupied-grid interaction and empty-grid summon paths must stay separated."
|
||||
}
|
||||
|
||||
$skillFiles = Get-ChildItem -LiteralPath (Join-Path $repoRoot "Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill") -Recurse -Filter "*.cs"
|
||||
foreach ($file in $skillFiles) {
|
||||
$text = Get-Content -LiteralPath $file.FullName -Raw
|
||||
if ($text -notmatch "override\s+bool\s+AttackGroundExecute\s*\(") {
|
||||
continue
|
||||
}
|
||||
|
||||
if ($text -notmatch "override\s+bool\s+AttackGroundCountsAsActiveAttack\s*\(") {
|
||||
$relative = [System.IO.Path]::GetRelativePath($repoRoot, $file.FullName)
|
||||
Add-Failure "$relative overrides AttackGroundExecute but does not explicitly declare AttackGroundCountsAsActiveAttack."
|
||||
}
|
||||
}
|
||||
|
||||
$expectedFalseSkills = @(
|
||||
"SumirekoOccultOrbOwnerSkill",
|
||||
"KasenBeastGuideReserveSkill"
|
||||
)
|
||||
|
||||
foreach ($className in $expectedFalseSkills) {
|
||||
$pattern = "(?s)class\s+$className\b.*?override\s+bool\s+AttackGroundCountsAsActiveAttack\s*\([^)]*\)\s*\{\s*return\s+false;\s*\}"
|
||||
if ($hakureiNorwayHeroSkill -notmatch $pattern) {
|
||||
Add-Failure "$className must declare ground targeting as non-attack so peace wonder progress is preserved."
|
||||
}
|
||||
}
|
||||
|
||||
$expectedTrueSkills = @(
|
||||
"SuikaFallingSplashSkill",
|
||||
"SuikaThrowReadySkill"
|
||||
)
|
||||
|
||||
foreach ($className in $expectedTrueSkills) {
|
||||
$pattern = "(?s)class\s+$className\b.*?override\s+bool\s+AttackGroundCountsAsActiveAttack\s*\([^)]*\)\s*\{\s*return\s+true;\s*\}"
|
||||
if ($hakureiNorwayHeroSkill -notmatch $pattern) {
|
||||
Add-Failure "$className must explicitly declare ground targeting as active attack."
|
||||
}
|
||||
}
|
||||
|
||||
if ($failures.Count -gt 0) {
|
||||
Write-Host "AttackGround peace semantics check failed:" -ForegroundColor Red
|
||||
foreach ($failure in $failures) {
|
||||
Write-Host " - $failure" -ForegroundColor Red
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "AttackGround peace semantics checks passed."
|
||||
52
Tools/CheckGrowForestUnderBuildingResource.ps1
Normal file
@ -0,0 +1,52 @@
|
||||
param(
|
||||
[string]$GridMiscActionFile = "Unity/Assets/Scripts/TH1_Logic/Action/GridMiscActionLogic.cs"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = git rev-parse --show-toplevel 2>$null
|
||||
if (-not $repoRoot) {
|
||||
throw "Not inside a git repository."
|
||||
}
|
||||
$repoRoot = [System.IO.Path]::GetFullPath($repoRoot.Trim())
|
||||
|
||||
function Read-RepoFile([string]$relativePath) {
|
||||
$fullPath = Join-Path $repoRoot $relativePath
|
||||
if (-not (Test-Path -LiteralPath $fullPath)) {
|
||||
throw "Required file not found: $fullPath"
|
||||
}
|
||||
return Get-Content -LiteralPath $fullPath -Raw -Encoding UTF8
|
||||
}
|
||||
|
||||
$text = Read-RepoFile $GridMiscActionFile
|
||||
|
||||
if ($text -notmatch 'private static bool CanGrowForestOnGrid\(CommonActionParams actionParams\)') {
|
||||
throw "GrowForest under-building guardrail failed: missing shared CanGrowForestOnGrid predicate."
|
||||
}
|
||||
|
||||
$method = [regex]::Match($text, '(?ms)private static bool CanGrowForestOnGrid\(CommonActionParams actionParams\).*?^\s*\}')
|
||||
if (-not $method.Success) {
|
||||
throw "GrowForest under-building guardrail failed: cannot locate CanGrowForestOnGrid body."
|
||||
}
|
||||
|
||||
foreach ($needle in @(
|
||||
'grid.ResourceUnderBuilding != ResourceType.None',
|
||||
'grid.Resource is ResourceType.None or ResourceType.Crop or ResourceType.Fruit',
|
||||
'TechAtom.KaguyaFrenchNapoleonicCode'
|
||||
)) {
|
||||
if (-not $method.Value.Contains($needle)) {
|
||||
throw "GrowForest under-building guardrail failed: CanGrowForestOnGrid missing '$needle'."
|
||||
}
|
||||
}
|
||||
|
||||
$checkCanBlock = [regex]::Match($text, '(?ms)public override bool CheckCan\(CommonActionParams actionParams\).*?public override bool CheckShow')
|
||||
if (-not $checkCanBlock.Success -or -not $checkCanBlock.Value.Contains('return CanGrowForestOnGrid(actionParams);')) {
|
||||
throw "GrowForest under-building guardrail failed: CheckCan must use CanGrowForestOnGrid."
|
||||
}
|
||||
|
||||
$checkShowBlock = [regex]::Match($text, '(?ms)public override bool CheckShow\(CommonActionParams actionParams,out ShowType showType\).*?protected bool GridMiscActionPayMoney')
|
||||
if (-not $checkShowBlock.Success -or -not $checkShowBlock.Value.Contains('return CanGrowForestOnGrid(actionParams);')) {
|
||||
throw "GrowForest under-building guardrail failed: CheckShow must use CanGrowForestOnGrid."
|
||||
}
|
||||
|
||||
Write-Host "GrowForest under-building resource guardrail passed."
|
||||
66
Tools/CheckHakureiKarviLandingDash.ps1
Normal file
@ -0,0 +1,66 @@
|
||||
param(
|
||||
[string]$SkillFile = "Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill/HakureiKarviEmbarkSkill.cs",
|
||||
[string]$UnitLogicFile = "Unity/Assets/Scripts/TH1_Logic/Unit/UnitLogic.cs"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = git rev-parse --show-toplevel 2>$null
|
||||
if (-not $repoRoot) {
|
||||
throw "Not inside a git repository."
|
||||
}
|
||||
|
||||
$repoRoot = [System.IO.Path]::GetFullPath($repoRoot.Trim())
|
||||
$skillPath = Join-Path $repoRoot $SkillFile
|
||||
$unitLogicPath = Join-Path $repoRoot $UnitLogicFile
|
||||
|
||||
if (-not (Test-Path -LiteralPath $skillPath)) {
|
||||
throw "Hakurei Karvi embark skill file not found: $skillPath"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $unitLogicPath)) {
|
||||
throw "UnitLogic file not found: $unitLogicPath"
|
||||
}
|
||||
|
||||
$skillText = Get-Content -LiteralPath $skillPath -Raw -Encoding UTF8
|
||||
$unitLogicText = Get-Content -LiteralPath $unitLogicPath -Raw -Encoding UTF8
|
||||
|
||||
$afterTransformMatch = [regex]::Match(
|
||||
$skillText,
|
||||
'public override void AfterTransformFromBoat\(UnitData self, GridData grid, MapData mapData, MoveType moveType,\s*\r?\n\s*List<Vector2Int> path = null\)\s*\{[\s\S]*?\r?\n \}'
|
||||
)
|
||||
if (-not $afterTransformMatch.Success) {
|
||||
throw "Karvi landing guardrail failed: missing AfterTransformFromBoat override."
|
||||
}
|
||||
|
||||
$afterTransform = $afterTransformMatch.Value
|
||||
foreach ($needle in @(
|
||||
'if (moveType != MoveType.ActiveMove) return;',
|
||||
'if (!self.GetSkill(SkillType.DASH, out _)) return;',
|
||||
'self.AddActionPoint(ActionPointType.Attack);'
|
||||
)) {
|
||||
if (-not $afterTransform.Contains($needle)) {
|
||||
throw "Karvi landing guardrail failed: AfterTransformFromBoat missing '$needle'."
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($forbidden in @(
|
||||
'TryTriggerMoveBonus',
|
||||
'skill is not DashSkill',
|
||||
'Level <= 0',
|
||||
'HasCharge()'
|
||||
)) {
|
||||
if ($afterTransform.Contains($forbidden)) {
|
||||
throw "Karvi landing guardrail failed: landing reward must not depend on Dash charge state ('$forbidden')."
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($needle in @(
|
||||
'BoatToLand(mapData, unitData, moveType, path);',
|
||||
'unitData.AfterTransformFromBoat(mapData, gridData, moveType, path);'
|
||||
)) {
|
||||
if (-not $unitLogicText.Contains($needle)) {
|
||||
throw "Karvi landing guardrail failed: UnitLogic missing '$needle'."
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Hakurei Karvi landing dash guardrail passed."
|
||||
452
Tools/CheckHakureiStatueActionIcons.ps1
Normal file
@ -0,0 +1,452 @@
|
||||
param(
|
||||
[string[]]$ActionDataAssetFiles = @(
|
||||
"Unity/Assets/BundleResources/DataAssets/ActionDataAssets.asset",
|
||||
"Unity/Assets/BundleResources/Export/ActionDataAssets.asset"
|
||||
),
|
||||
[string[]]$GridAndResourceDataAssetFiles = @(
|
||||
"Unity/Assets/BundleResources/DataAssets/GridAndResourceDataAssets.asset",
|
||||
"Unity/Assets/BundleResources/Export/GridAndResourceDataAssets.asset"
|
||||
),
|
||||
[string[]]$CultureCardDataAssetFiles = @(
|
||||
"Unity/Assets/BundleResources/DataAssets/CultureCardDataAssets.asset",
|
||||
"Unity/Assets/BundleResources/Export/CultureCardDataAssets.asset"
|
||||
),
|
||||
[string]$HeroHintActionCircleFile = "Unity/Assets/Scripts/TH1_UI/HintUI/HeroHintPanelCommonCircleMono.cs"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = git rev-parse --show-toplevel 2>$null
|
||||
if (-not $repoRoot) {
|
||||
throw "Not inside a git repository."
|
||||
}
|
||||
$repoRoot = [System.IO.Path]::GetFullPath($repoRoot.Trim())
|
||||
|
||||
$failures = @()
|
||||
|
||||
function Add-Failure {
|
||||
param([string]$Message)
|
||||
$script:failures += $Message
|
||||
}
|
||||
|
||||
function Resolve-RepoPath {
|
||||
param([string]$RelativePath)
|
||||
return [System.IO.Path]::GetFullPath((Join-Path $repoRoot $RelativePath))
|
||||
}
|
||||
|
||||
function Read-RepoText {
|
||||
param([string]$RelativePath)
|
||||
$path = Resolve-RepoPath $RelativePath
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
Add-Failure "Missing file: $RelativePath"
|
||||
return ""
|
||||
}
|
||||
|
||||
return Get-Content -LiteralPath $path -Raw -Encoding UTF8
|
||||
}
|
||||
|
||||
function Get-YamlListBlock {
|
||||
param(
|
||||
[string]$RelativePath,
|
||||
[string]$StartPattern,
|
||||
[string]$NextPattern,
|
||||
[string]$RequiredPattern,
|
||||
[string]$MissingMessage
|
||||
)
|
||||
|
||||
$path = Resolve-RepoPath $RelativePath
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
Add-Failure "Missing file: $RelativePath"
|
||||
return ""
|
||||
}
|
||||
|
||||
$lines = @(Get-Content -LiteralPath $path -Encoding UTF8)
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -notmatch $StartPattern) {
|
||||
continue
|
||||
}
|
||||
|
||||
$start = $i
|
||||
$end = $lines.Count - 1
|
||||
for ($j = $i + 1; $j -lt $lines.Count; $j++) {
|
||||
if ($lines[$j] -match $NextPattern) {
|
||||
$end = $j - 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
$block = $lines[$start..$end] -join "`n"
|
||||
if ($block -match $RequiredPattern) {
|
||||
return $block
|
||||
}
|
||||
}
|
||||
|
||||
Add-Failure "$RelativePath $MissingMessage"
|
||||
return ""
|
||||
}
|
||||
|
||||
function Get-BuildActionBlock {
|
||||
param(
|
||||
[string]$RelativePath,
|
||||
[int]$ResourceType
|
||||
)
|
||||
|
||||
return Get-YamlListBlock `
|
||||
-RelativePath $RelativePath `
|
||||
-StartPattern '^ - ActionId:\s*$' `
|
||||
-NextPattern '^ - ActionId:\s*$' `
|
||||
-RequiredPattern "(?m)^\s+ActionType:\s+1\s*$[\s\S]*?^\s+ResourceType:\s+$ResourceType\s*$" `
|
||||
-MissingMessage "missing Build action ResourceType $ResourceType."
|
||||
}
|
||||
|
||||
function Get-GridResourceBlock {
|
||||
param(
|
||||
[string]$RelativePath,
|
||||
[int]$ResourceType
|
||||
)
|
||||
|
||||
return Get-YamlListBlock `
|
||||
-RelativePath $RelativePath `
|
||||
-StartPattern '^ - Resource:\s+\d+\s*$' `
|
||||
-NextPattern '^ - Resource:\s+\d+\s*$|^ - Wonder:\s+\d+\s*$' `
|
||||
-RequiredPattern "(?m)^ - Resource:\s+$ResourceType\s*$" `
|
||||
-MissingMessage "missing GridAndResource Resource $ResourceType."
|
||||
}
|
||||
|
||||
function Get-CultureCardBlock {
|
||||
param(
|
||||
[string]$RelativePath,
|
||||
[int]$ResourceType
|
||||
)
|
||||
|
||||
return Get-YamlListBlock `
|
||||
-RelativePath $RelativePath `
|
||||
-StartPattern '^ - CardType:\s+\d+\s*$' `
|
||||
-NextPattern '^ - CardType:\s+\d+\s*$|^ - TaskType:\s+\d+\s*$' `
|
||||
-RequiredPattern "(?m)^\s+ResourceType:\s+$ResourceType\s*$" `
|
||||
-MissingMessage "missing CultureCard ResourceType $ResourceType."
|
||||
}
|
||||
|
||||
function Get-VariantEntries {
|
||||
param([string]$Block)
|
||||
|
||||
$entries = @()
|
||||
if ([string]::IsNullOrEmpty($Block)) {
|
||||
return $entries
|
||||
}
|
||||
|
||||
$lines = @($Block -split "`r?`n")
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -notmatch '^ - IgnoreCivId:') {
|
||||
continue
|
||||
}
|
||||
|
||||
$start = $i
|
||||
$end = $lines.Count
|
||||
for ($j = $i + 1; $j -lt $lines.Count; $j++) {
|
||||
if ($lines[$j] -match '^ - IgnoreCivId:') {
|
||||
$end = $j
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
$entryLines = @($lines[$start..($end - 1)])
|
||||
$entryText = $entryLines -join "`n"
|
||||
$civId = $null
|
||||
$forceId = $null
|
||||
$spriteGuid = ""
|
||||
$levelGuids = @()
|
||||
|
||||
if ($entryText -match '(?m)^\s+CivId:\s+(\d+)\s*$') {
|
||||
$civId = [int]$Matches[1]
|
||||
}
|
||||
if ($entryText -match '(?m)^\s+ForceId:\s+(\d+)\s*$') {
|
||||
$forceId = [int]$Matches[1]
|
||||
}
|
||||
if ($entryText -match '(?m)^\s+Sprite:\s+\{fileID:\s+21300000,\s+guid:\s+([0-9a-f]{32}),\s+type:\s+3\}\s*$') {
|
||||
$spriteGuid = $Matches[1]
|
||||
}
|
||||
|
||||
foreach ($line in $entryLines) {
|
||||
if ($line -match '^\s+- \{fileID:\s+21300000,\s+guid:\s+([0-9a-f]{32}),\s+type:\s+3\}\s*$') {
|
||||
$levelGuids += $Matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
$entries += [pscustomobject]@{
|
||||
CivId = $civId
|
||||
ForceId = $forceId
|
||||
SpriteGuid = $spriteGuid
|
||||
LevelGuids = $levelGuids
|
||||
Text = $entryText
|
||||
}
|
||||
|
||||
$i = $end - 1
|
||||
}
|
||||
|
||||
return $entries
|
||||
}
|
||||
|
||||
function Get-VariantEntry {
|
||||
param(
|
||||
[object[]]$Entries,
|
||||
[int]$CivId,
|
||||
[int]$ForceId
|
||||
)
|
||||
|
||||
return @($Entries | Where-Object { $_.CivId -eq $CivId -and $_.ForceId -eq $ForceId } | Select-Object -First 1)
|
||||
}
|
||||
|
||||
function Test-HakureiGuidLeakToWrongEmpire {
|
||||
param(
|
||||
[string]$Context,
|
||||
[object[]]$Entries,
|
||||
[hashtable]$HakureiGuidSet
|
||||
)
|
||||
|
||||
$wrongEntries = @($Entries | Where-Object { $_.CivId -eq 5 -and $_.ForceId -eq 5 })
|
||||
foreach ($entry in $wrongEntries) {
|
||||
if ($HakureiGuidSet.ContainsKey($entry.SpriteGuid)) {
|
||||
Add-Failure "$Context still assigns Hakurei temple guid $($entry.SpriteGuid) to CivId 5 ForceId 5."
|
||||
}
|
||||
|
||||
foreach ($guid in $entry.LevelGuids) {
|
||||
if ($HakureiGuidSet.ContainsKey($guid)) {
|
||||
Add-Failure "$Context still assigns Hakurei temple level guid $guid to CivId 5 ForceId 5."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-ExpectedSprite {
|
||||
param(
|
||||
[string]$Context,
|
||||
[object]$Entry,
|
||||
[string]$ExpectedGuid
|
||||
)
|
||||
|
||||
if (-not $Entry) {
|
||||
Add-Failure "$Context missing Hakurei/Reimu CivId 4 ForceId 4 entry."
|
||||
return
|
||||
}
|
||||
|
||||
if ($Entry.SpriteGuid -ne $ExpectedGuid) {
|
||||
Add-Failure "$Context expected Hakurei/Reimu sprite $ExpectedGuid, got '$($Entry.SpriteGuid)'."
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-ExpectedLevels {
|
||||
param(
|
||||
[string]$Context,
|
||||
[object]$Entry,
|
||||
[string[]]$ExpectedLevelGuids
|
||||
)
|
||||
|
||||
if (-not $Entry) {
|
||||
return
|
||||
}
|
||||
|
||||
$actual = @($Entry.LevelGuids)
|
||||
if ($actual.Count -ne $ExpectedLevelGuids.Count) {
|
||||
Add-Failure "$Context expected $($ExpectedLevelGuids.Count) level sprites, got $($actual.Count)."
|
||||
return
|
||||
}
|
||||
|
||||
for ($i = 0; $i -lt $ExpectedLevelGuids.Count; $i++) {
|
||||
if ($actual[$i] -ne $ExpectedLevelGuids[$i]) {
|
||||
Add-Failure "$Context level sprite $i expected $($ExpectedLevelGuids[$i]), got '$($actual[$i])'."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$obsoleteStatueGuids = @(
|
||||
"b2f4a0f7de7b4d6cadd036f59061465f", # ReimuForces_Norway_Statue_Horse
|
||||
"20071dc3a43a456e8a4c753340625c0b", # ReimuForces_Norway_Statue_Queen
|
||||
"9dddeb2a6c4f4ff6ab1f4714caf8fbb1", # ReimuForces_Norway_Statue_Chariot
|
||||
"9931e374408c4dbfa254eca565417b33", # ReimuForces_Norway_Statue_Minister
|
||||
"0a7f4ac077094d2686205d65132d0ddf" # ReimuForces_Norway_Statue_King
|
||||
)
|
||||
|
||||
$obsoleteStatueAssetFiles = @(
|
||||
"Unity/Assets/BundleResources/ArtResources/TH1Buildings/TH1BuildingsReimuForces/Norway/ReimuForces_Norway_Statue_Horse.png",
|
||||
"Unity/Assets/BundleResources/ArtResources/TH1Buildings/TH1BuildingsReimuForces/Norway/ReimuForces_Norway_Statue_Horse.png.meta",
|
||||
"Unity/Assets/BundleResources/ArtResources/TH1Buildings/TH1BuildingsReimuForces/Norway/ReimuForces_Norway_Statue_Queen.png",
|
||||
"Unity/Assets/BundleResources/ArtResources/TH1Buildings/TH1BuildingsReimuForces/Norway/ReimuForces_Norway_Statue_Queen.png.meta",
|
||||
"Unity/Assets/BundleResources/ArtResources/TH1Buildings/TH1BuildingsReimuForces/Norway/ReimuForces_Norway_Statue_Chariot.png",
|
||||
"Unity/Assets/BundleResources/ArtResources/TH1Buildings/TH1BuildingsReimuForces/Norway/ReimuForces_Norway_Statue_Chariot.png.meta",
|
||||
"Unity/Assets/BundleResources/ArtResources/TH1Buildings/TH1BuildingsReimuForces/Norway/ReimuForces_Norway_Statue_Minister.png",
|
||||
"Unity/Assets/BundleResources/ArtResources/TH1Buildings/TH1BuildingsReimuForces/Norway/ReimuForces_Norway_Statue_Minister.png.meta",
|
||||
"Unity/Assets/BundleResources/ArtResources/TH1Buildings/TH1BuildingsReimuForces/Norway/ReimuForces_Norway_Statue_King.png",
|
||||
"Unity/Assets/BundleResources/ArtResources/TH1Buildings/TH1BuildingsReimuForces/Norway/ReimuForces_Norway_Statue_King.png.meta"
|
||||
)
|
||||
|
||||
$expectedHakureiTempleIcons = [ordered]@{
|
||||
"19" = @{
|
||||
Label = "Horse/Aunn"
|
||||
Icon = "4bf1f23582ff4612bef38837ee07f33d"
|
||||
Levels = @(
|
||||
"4bf1f23582ff4612bef38837ee07f33d",
|
||||
"4bf1f23582ff4612bef38837ee07f33d",
|
||||
"72377380e1c143f09cf29b802d6f4c99",
|
||||
"ae7878f1d737474c8a05d9d19c88c7c9",
|
||||
"559e5120271243e885975fa82dd8ff5e",
|
||||
"5743d7003f464328bde939a3a313445a"
|
||||
)
|
||||
}
|
||||
"20" = @{
|
||||
Label = "Queen/Sumireko"
|
||||
Icon = "f68789b350d04e33bb008283ac4eb212"
|
||||
Levels = @(
|
||||
"f68789b350d04e33bb008283ac4eb212",
|
||||
"f68789b350d04e33bb008283ac4eb212",
|
||||
"0196396f558840e2915771edfc78598b",
|
||||
"07671f70a857421c85c6f3311683b97a",
|
||||
"a6793dfea9ac4138ab60e1da1957b6a3",
|
||||
"f09cb2896a2f4df78e05c10c36c24195"
|
||||
)
|
||||
}
|
||||
"21" = @{
|
||||
Label = "Chariot/Suika"
|
||||
Icon = "070c43baedb847379a2e88e84c0971f1"
|
||||
Levels = @(
|
||||
"070c43baedb847379a2e88e84c0971f1",
|
||||
"070c43baedb847379a2e88e84c0971f1",
|
||||
"0f12cf0287514fb1a4279349b0359a7f",
|
||||
"9943f3bd38eb4037881bcf2c95cbb24d",
|
||||
"e4eb1f1eb43d47e58510e62630ca4c64",
|
||||
"19ff5772bcf443fa8eaaf4692b4b4d5f"
|
||||
)
|
||||
}
|
||||
"22" = @{
|
||||
Label = "Minister/Kasen"
|
||||
Icon = "780d2b186aba4b12a534d31f17b9e543"
|
||||
Levels = @(
|
||||
"780d2b186aba4b12a534d31f17b9e543",
|
||||
"780d2b186aba4b12a534d31f17b9e543",
|
||||
"f93f7d5a7d034699982125ca35ef0d6b",
|
||||
"29bb86743edd4d9d8717889de455e784",
|
||||
"2f930da228be406c8b2d60c25090f7dc",
|
||||
"37cf51b34adf43e990bbd1f54aa09982"
|
||||
)
|
||||
}
|
||||
"28" = @{
|
||||
Label = "King/Reimu"
|
||||
Icon = "1f203689c4d443f99703c68c109b18cb"
|
||||
Levels = @(
|
||||
"1f203689c4d443f99703c68c109b18cb",
|
||||
"1f203689c4d443f99703c68c109b18cb",
|
||||
"4388ce32a375479f9a6a2047592f79d9",
|
||||
"99dd9c7c18ad4214997a91a1523a621f",
|
||||
"de00272eea2b40d2a8cb939b188178d8",
|
||||
"22bec2a8a52b4050aa60a20d6709f514"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
$hakureiTempleGuidSet = @{}
|
||||
foreach ($resourceType in $expectedHakureiTempleIcons.Keys) {
|
||||
$config = $expectedHakureiTempleIcons[$resourceType]
|
||||
$hakureiTempleGuidSet[$config["Icon"]] = $true
|
||||
foreach ($guid in $config["Levels"]) {
|
||||
$hakureiTempleGuidSet[$guid] = $true
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($relativePath in $obsoleteStatueAssetFiles) {
|
||||
$path = Resolve-RepoPath $relativePath
|
||||
if (Test-Path -LiteralPath $path) {
|
||||
Add-Failure "Obsolete Hakurei statue asset must not exist: $relativePath"
|
||||
}
|
||||
}
|
||||
|
||||
$assetTextFiles = @($ActionDataAssetFiles + $GridAndResourceDataAssetFiles + $CultureCardDataAssetFiles)
|
||||
foreach ($assetFile in $assetTextFiles) {
|
||||
$text = Read-RepoText $assetFile
|
||||
foreach ($obsoleteGuid in $obsoleteStatueGuids) {
|
||||
if ($text.Contains($obsoleteGuid)) {
|
||||
Add-Failure "$assetFile still references obsolete Hakurei statue guid $obsoleteGuid."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($assetFile in $ActionDataAssetFiles) {
|
||||
foreach ($resourceType in ($expectedHakureiTempleIcons.Keys | Sort-Object)) {
|
||||
$config = $expectedHakureiTempleIcons[$resourceType]
|
||||
$block = Get-BuildActionBlock -RelativePath $assetFile -ResourceType $resourceType
|
||||
if ([string]::IsNullOrEmpty($block)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ($block -notmatch "(?m)^\s+VarientIcon:\s+1\s*$") {
|
||||
Add-Failure "$assetFile ResourceType $resourceType must keep VarientIcon enabled."
|
||||
}
|
||||
|
||||
$entries = @(Get-VariantEntries $block)
|
||||
$hakureiEntry = Get-VariantEntry -Entries $entries -CivId 4 -ForceId 4
|
||||
$context = "$assetFile Build ResourceType $resourceType $($config["Label"])"
|
||||
Assert-ExpectedSprite -Context $context -Entry $hakureiEntry -ExpectedGuid $config["Icon"]
|
||||
Test-HakureiGuidLeakToWrongEmpire -Context $context -Entries $entries -HakureiGuidSet $hakureiTempleGuidSet
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($assetFile in $GridAndResourceDataAssetFiles) {
|
||||
foreach ($resourceType in ($expectedHakureiTempleIcons.Keys | Sort-Object)) {
|
||||
$config = $expectedHakureiTempleIcons[$resourceType]
|
||||
$block = Get-GridResourceBlock -RelativePath $assetFile -ResourceType $resourceType
|
||||
if ([string]::IsNullOrEmpty($block)) {
|
||||
continue
|
||||
}
|
||||
|
||||
$entries = @(Get-VariantEntries $block)
|
||||
$hakureiEntry = Get-VariantEntry -Entries $entries -CivId 4 -ForceId 4
|
||||
$context = "$assetFile Grid Resource $resourceType $($config["Label"])"
|
||||
Assert-ExpectedSprite -Context $context -Entry $hakureiEntry -ExpectedGuid $config["Icon"]
|
||||
Assert-ExpectedLevels -Context $context -Entry $hakureiEntry -ExpectedLevelGuids $config["Levels"]
|
||||
Test-HakureiGuidLeakToWrongEmpire -Context $context -Entries $entries -HakureiGuidSet $hakureiTempleGuidSet
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($assetFile in $CultureCardDataAssetFiles) {
|
||||
foreach ($resourceType in ($expectedHakureiTempleIcons.Keys | Sort-Object)) {
|
||||
$config = $expectedHakureiTempleIcons[$resourceType]
|
||||
$block = Get-CultureCardBlock -RelativePath $assetFile -ResourceType $resourceType
|
||||
if ([string]::IsNullOrEmpty($block)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ($block -notmatch "(?m)^\s+VarientIcon:\s+1\s*$") {
|
||||
Add-Failure "$assetFile CultureCard ResourceType $resourceType must keep VarientIcon enabled."
|
||||
}
|
||||
|
||||
$entries = @(Get-VariantEntries $block)
|
||||
$hakureiEntry = Get-VariantEntry -Entries $entries -CivId 4 -ForceId 4
|
||||
$context = "$assetFile CultureCard ResourceType $resourceType $($config["Label"])"
|
||||
Assert-ExpectedSprite -Context $context -Entry $hakureiEntry -ExpectedGuid $config["Icon"]
|
||||
Test-HakureiGuidLeakToWrongEmpire -Context $context -Entries $entries -HakureiGuidSet $hakureiTempleGuidSet
|
||||
}
|
||||
}
|
||||
|
||||
$heroHintText = Read-RepoText $HeroHintActionCircleFile
|
||||
if ($heroHintText -match 'SkillIcon\.sprite\s*=\s*actionInfo\.Icon\s*;') {
|
||||
Add-Failure "$HeroHintActionCircleFile action circles must not assign actionInfo.Icon directly."
|
||||
}
|
||||
|
||||
foreach ($needle in @(
|
||||
'private static Sprite ResolveActionIcon(ActionInfo actionInfo)',
|
||||
'actionInfo.GetIcon(selfPlayer.CivEnum, selfPlayer.ForceEnum, GridSpType.None)',
|
||||
'actionInfo.GetIcon(0u, 0u, GridSpType.None) ?? actionInfo.Icon'
|
||||
)) {
|
||||
if (-not $heroHintText.Contains($needle)) {
|
||||
Add-Failure "$HeroHintActionCircleFile missing variant-aware action icon logic: $needle"
|
||||
}
|
||||
}
|
||||
|
||||
if ($failures.Count -gt 0) {
|
||||
Write-Host "Hakurei statue icon check failed:" -ForegroundColor Red
|
||||
foreach ($failure in $failures) {
|
||||
Write-Host " - $failure" -ForegroundColor Red
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Hakurei statue icon checks passed."
|
||||
@ -128,11 +128,14 @@ Assert-True ($gridAsset -match "GridSpType:\s+6") "GridAndResourceDataAssets mus
|
||||
$gridData = Read-RepoText "Unity/Assets/Scripts/TH1_Data/GridData.cs"
|
||||
$mapData = Read-RepoText "Unity/Assets/Scripts/TH1_Data/MapData.cs"
|
||||
$unitLogic = Read-RepoText "Unity/Assets/Scripts/TH1_Logic/Unit/UnitLogic.cs"
|
||||
$hakureiNorwayHeroSkill = Read-RepoText "Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill/HakureiNorwayHeroSkill.cs"
|
||||
$gridObjectDataAssets = Read-RepoText "Unity/Assets/Scripts/TH1_DataAssetsScript/GridObjectDataAssets.cs"
|
||||
$gridRenderer = Read-RepoText "Unity/Assets/Scripts/TH1_Renderer/GridRenderer.cs"
|
||||
Assert-True ($gridData -match "KasenBeastGuide") "GridData must define KasenBeastGuide GridSpType."
|
||||
Assert-True ($mapData -match "NormalizeKasenPermanentBerserkSkill") "MapData must normalize Kasen Lv4 permanent berserk."
|
||||
Assert-True ($unitLogic -match "SkillType\.KasenPermanentBerserk") "UnitLogic must include KasenPermanentBerserk in berserk damage calculation."
|
||||
Assert-True ($hakureiNorwayHeroSkill -match "(?s)public override void OnMove\(UnitData self, GridData grid, MapData mapData, MoveType moveType,.*?self\.AddOrOverrideSkill\(SkillType\.KasenBeastGuideReady, mapData, self\.Id\).*?self\.AddActionPoint\(ActionPointType\.Attack\).*?\}") "Kasen active move must grant ready and attack action point."
|
||||
Assert-True ($hakureiNorwayHeroSkill -notmatch "(?s)public override void OnMove\(UnitData self, GridData grid, MapData mapData, MoveType moveType,.*?FindKasenBeastGuideGrid\(mapData, self\).*?return;.*?\}") "Kasen active move must not skip ready when an old beast guide is still on the map."
|
||||
Assert-True ($gridObjectDataAssets -match "GridSpType\.KasenBeastGuide") "GridObjectDataAssets must resolve the Kasen beast-guide sprite."
|
||||
Assert-True ($gridRenderer -match "GridSpType\.KasenBeastGuide") "GridRenderer must keep the special tile layer visible for Kasen beast guide."
|
||||
|
||||
|
||||
52
Tools/CheckMoveTransformVisualRefresh.ps1
Normal file
@ -0,0 +1,52 @@
|
||||
param(
|
||||
[string]$FragmentMoveFile = "Unity/Assets/Scripts/TH1_Anim/Fragments/FragmentMove.cs",
|
||||
[string]$UnitRendererFile = "Unity/Assets/Scripts/TH1_Renderer/UnitRenderer.cs"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = git rev-parse --show-toplevel 2>$null
|
||||
if (-not $repoRoot) {
|
||||
throw "Not inside a git repository."
|
||||
}
|
||||
|
||||
$repoRoot = [System.IO.Path]::GetFullPath($repoRoot.Trim())
|
||||
$fragmentMovePath = Join-Path $repoRoot $FragmentMoveFile
|
||||
$unitRendererPath = Join-Path $repoRoot $UnitRendererFile
|
||||
|
||||
if (-not (Test-Path -LiteralPath $fragmentMovePath)) {
|
||||
throw "FragmentMove file not found: $fragmentMovePath"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $unitRendererPath)) {
|
||||
throw "UnitRenderer file not found: $unitRendererPath"
|
||||
}
|
||||
|
||||
$fragmentMoveText = Get-Content -LiteralPath $fragmentMovePath -Raw -Encoding UTF8
|
||||
$unitRendererText = Get-Content -LiteralPath $unitRendererPath -Raw -Encoding UTF8
|
||||
|
||||
foreach ($needle in @(
|
||||
'public void InstantUpdateUnitImageOnly()',
|
||||
'RenderUpdateUnitImage();'
|
||||
)) {
|
||||
if (-not $unitRendererText.Contains($needle)) {
|
||||
throw "Move transform visual-refresh guardrail failed: UnitRenderer missing '$needle'."
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($needle in @(
|
||||
'Data.UnitRenderer?.InstantUpdateUnitImageOnly();',
|
||||
'Data.UnitRenderer.AnimManager.EnqueueAnim(UnitAtomAnimType.Move,animData);',
|
||||
'Data.UnitRenderer?.InstantUpdateUnit(true);'
|
||||
)) {
|
||||
if (-not $fragmentMoveText.Contains($needle)) {
|
||||
throw "Move transform visual-refresh guardrail failed: FragmentMove missing '$needle'."
|
||||
}
|
||||
}
|
||||
|
||||
$startRefresh = $fragmentMoveText.IndexOf('Data.UnitRenderer?.InstantUpdateUnitImageOnly();')
|
||||
$moveAnim = $fragmentMoveText.IndexOf('Data.UnitRenderer.AnimManager.EnqueueAnim(UnitAtomAnimType.Move,animData);')
|
||||
if ($startRefresh -lt 0 -or $moveAnim -lt 0 -or $startRefresh -gt $moveAnim) {
|
||||
throw "Move transform visual-refresh guardrail failed: FragmentMove must refresh transformed unit image before starting the move animation."
|
||||
}
|
||||
|
||||
Write-Host "Move transform visual-refresh guardrail passed."
|
||||
108
Tools/CheckPeaceWonderHostileActionSemantics.ps1
Normal file
@ -0,0 +1,108 @@
|
||||
param()
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||
$failures = @()
|
||||
|
||||
function Add-Failure {
|
||||
param([string]$Message)
|
||||
$script:failures += $Message
|
||||
}
|
||||
|
||||
function Read-RepoText {
|
||||
param([string]$RelativePath)
|
||||
$path = Join-Path $repoRoot $RelativePath
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
Add-Failure "Missing file: $RelativePath"
|
||||
return ""
|
||||
}
|
||||
return Get-Content -LiteralPath $path -Raw
|
||||
}
|
||||
|
||||
function Assert-Pattern {
|
||||
param(
|
||||
[string]$Text,
|
||||
[string]$Pattern,
|
||||
[string]$Message
|
||||
)
|
||||
if ($Text -notmatch $Pattern) {
|
||||
Add-Failure $Message
|
||||
}
|
||||
}
|
||||
|
||||
$actionLogic = Read-RepoText "Unity/Assets/Scripts/TH1_Logic/Action/ActionLogic.cs"
|
||||
$unitActionLogic = Read-RepoText "Unity/Assets/Scripts/TH1_Logic/Action/UnitActionLogic.cs"
|
||||
$playerActionLogic = Read-RepoText "Unity/Assets/Scripts/TH1_Logic/Action/PlayerActionLogic.cs"
|
||||
$unitLogic = Read-RepoText "Unity/Assets/Scripts/TH1_Logic/Unit/UnitLogic.cs"
|
||||
$infiltrateSkill = Read-RepoText "Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill/InfiltrateSkill.cs"
|
||||
|
||||
Assert-Pattern $actionLogic `
|
||||
"internal\s+static\s+class\s+PeaceWonderProgressRecorder" `
|
||||
"ActionLogic must expose a central PeaceWonderProgressRecorder."
|
||||
|
||||
Assert-Pattern $actionLogic `
|
||||
"(?s)class\s+PeaceWonderProgressRecorder\b.*?BreakProgress\(MapData mapData, PlayerData player\).*?player\.TurnNoAttack\s*=\s*0;" `
|
||||
"PeaceWonderProgressRecorder.BreakProgress(MapData, PlayerData) must be the central TurnNoAttack reset."
|
||||
|
||||
Assert-Pattern $actionLogic `
|
||||
"(?s)class\s+PeaceWonderProgressRecorder\b.*?BreakProgress\(MapData mapData, UnitData unit\).*?BreakProgress\(mapData, player\);" `
|
||||
"PeaceWonderProgressRecorder.BreakProgress(MapData, UnitData) must resolve the unit owner and delegate to the player overload."
|
||||
|
||||
Assert-Pattern $actionLogic `
|
||||
"(?s)class\s+ActiveAttackActionRecorder\b.*?MarkStarted\(MapData mapData, PlayerData player\)\s*\{\s*PeaceWonderProgressRecorder\.BreakProgress\(mapData, player\);\s*\}" `
|
||||
"ActiveAttackActionRecorder must delegate player resets to PeaceWonderProgressRecorder."
|
||||
|
||||
Assert-Pattern $actionLogic `
|
||||
"(?s)class\s+ActiveAttackActionRecorder\b.*?MarkStarted\(MapData mapData, UnitData unit\)\s*\{\s*PeaceWonderProgressRecorder\.BreakProgress\(mapData, unit\);\s*\}" `
|
||||
"ActiveAttackActionRecorder must delegate unit resets to PeaceWonderProgressRecorder."
|
||||
|
||||
Assert-Pattern $unitLogic `
|
||||
"(?s)if\s*\(\s*type\s+is\s+DamageType\.ActiveAttack.*?PeaceWonderProgressRecorder\.BreakProgress\(mapData,\s*player1\);" `
|
||||
"Damage settlement for active/follow/splash/true attack damage must break Peace progress through PeaceWonderProgressRecorder."
|
||||
|
||||
Assert-Pattern $unitActionLogic `
|
||||
"(?s)class\s+UnitActionRaid\b.*?Main\.CityLogic\.AddCityRaidedAmount\(actionParams\.MapData,\s*city,\s*amount\);\s*player\.AddCoin\(amount,\s*grid\);\s*PeaceWonderProgressRecorder\.BreakProgress\(actionParams\.MapData,\s*player\);" `
|
||||
"UnitActionRaid must break Peace progress after a successful raid payout."
|
||||
|
||||
Assert-Pattern $playerActionLogic `
|
||||
"(?s)if\s*\(_actionId\.PlayerActionType\s*==\s*PlayerActionType\.DanegeldDemand\).*?info\.DanegeldState\s*=\s*DanegeldState\.Demanding;.*?PeaceWonderProgressRecorder\.BreakProgress\(actionParams\.MapData,\s*player\);" `
|
||||
"DanegeldDemand must break Peace progress for the demander when the demand is started."
|
||||
|
||||
$playerActionBreaks = [regex]::Matches($playerActionLogic, "PeaceWonderProgressRecorder\.BreakProgress")
|
||||
if ($playerActionBreaks.Count -ne 1) {
|
||||
Add-Failure "PlayerActionLogic should only break Peace progress for DanegeldDemand, not DanegeldPay or DanegeldReject."
|
||||
}
|
||||
|
||||
Assert-Pattern $infiltrateSkill `
|
||||
"selfPlayer\.AddCoin\(stolenAmount,\s*targetCity\.Grid\(mapData\)\);\s*PeaceWonderProgressRecorder\.BreakProgress\(mapData,\s*selfPlayer\);" `
|
||||
"Infiltrate city theft must break Peace progress when the thief receives stolen city income."
|
||||
|
||||
$allowedDirectResetFiles = @(
|
||||
"Unity/Assets/Scripts/TH1_Logic/Action/ActionLogic.cs",
|
||||
"Unity/Assets/Scripts/TH1_Logic/Player/PlayerLogic.cs"
|
||||
)
|
||||
|
||||
$scriptRoot = Join-Path $repoRoot "Unity/Assets/Scripts/TH1_Logic"
|
||||
$csFiles = Get-ChildItem -LiteralPath $scriptRoot -Recurse -Filter "*.cs"
|
||||
foreach ($file in $csFiles) {
|
||||
$text = Get-Content -LiteralPath $file.FullName -Raw
|
||||
if ($text -notmatch "TurnNoAttack\s*=\s*0") {
|
||||
continue
|
||||
}
|
||||
|
||||
$relative = $file.FullName.Substring($repoRoot.Length).TrimStart([char[]]@("\", "/")) -replace "\\", "/"
|
||||
if ($allowedDirectResetFiles -notcontains $relative) {
|
||||
Add-Failure "$relative resets TurnNoAttack directly; route Peace progress breaks through PeaceWonderProgressRecorder."
|
||||
}
|
||||
}
|
||||
|
||||
if ($failures.Count -gt 0) {
|
||||
Write-Host "Peace wonder hostile-action semantics check failed:" -ForegroundColor Red
|
||||
foreach ($failure in $failures) {
|
||||
Write-Host " - $failure" -ForegroundColor Red
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Peace wonder hostile-action semantics checks passed."
|
||||
46
Tools/CheckSakuyaGuardDamageBearer.ps1
Normal file
@ -0,0 +1,46 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = git rev-parse --show-toplevel 2>$null
|
||||
if (-not $repoRoot) {
|
||||
throw "Not inside a git repository."
|
||||
}
|
||||
$repoRoot = [System.IO.Path]::GetFullPath($repoRoot.Trim())
|
||||
|
||||
$sakuyaPath = Join-Path $repoRoot "Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill/SakuyaGuardSkill.cs"
|
||||
$synergyPath = Join-Path $repoRoot "Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill/KaguyaFrenchSynergySkill.cs"
|
||||
|
||||
$sakuyaText = Get-Content -Raw $sakuyaPath
|
||||
$synergyText = Get-Content -Raw $synergyPath
|
||||
|
||||
if ($sakuyaText -notmatch 'DamageSettlement\s*\(\s*mapData\s*,\s*info\.DamageOrigin\s*,\s*unit\s*,[\s\S]*?DamageType\.Splash') {
|
||||
throw "SakuyaGuard guardrail failed: guard damage should keep the legacy nested DamageSettlement(..., DamageType.Splash) settlement model."
|
||||
}
|
||||
|
||||
foreach ($required in @(
|
||||
'TryGetTransferredDamageOriginalTarget',
|
||||
'_guardTransferContexts.Push',
|
||||
'_guardTransferContexts.Pop',
|
||||
'info.DamageValue = 0'
|
||||
)) {
|
||||
if (-not $sakuyaText.Contains($required)) {
|
||||
throw "SakuyaGuard guardrail failed: missing required snippet '$required'."
|
||||
}
|
||||
}
|
||||
|
||||
if ($sakuyaText.Contains('info.DamageBearer = unit')) {
|
||||
throw "SakuyaGuard guardrail failed: Sakuya guard must not use DamageBearer because it changes legacy settlement side effects."
|
||||
}
|
||||
|
||||
if (-not $synergyText.Contains('SakuyaGuardSkill.TryGetTransferredDamageOriginalTarget')) {
|
||||
throw "KaguyaFrenchSynergy guardrail failed: synergy debuff must redirect Sakuya-guard transferred damage to the original target."
|
||||
}
|
||||
|
||||
if ($synergyText.Contains('DamageType.CounterAttack')) {
|
||||
throw "KaguyaFrenchSynergy guardrail failed: do not globally add counterattack synergy debuffs as part of the Sakuya guard fix."
|
||||
}
|
||||
|
||||
if (-not $synergyText.Contains('debuffTarget.AddSkill_Legacy')) {
|
||||
throw "KaguyaFrenchSynergy guardrail failed: synergy debuff must be applied through the selected debuffTarget."
|
||||
}
|
||||
|
||||
Write-Host "SakuyaGuard transferred-debuff guardrail passed."
|
||||
@ -79,6 +79,7 @@ Assert-Contains $skillText 'GetAroundGridIdList(range, landingGrid, remainCenter
|
||||
Assert-Contains $skillText 'attackInfo.IsSuikaFallingSplash = true;' 'skill attack marker'
|
||||
Assert-Contains $skillText 'attackInfo.SuikaFallingSplashFinalGrid = landingGrid;' 'skill final grid marker'
|
||||
Assert-Contains $skillText 'ExecuteSuikaFallingSplashDamage(map, suika, targetGrid, AnimPhase.AttackImpact + 10)' 'post-impact splash visual phase'
|
||||
Assert-Contains $skillText 'TargetGridHasOtherUnit(map, suika, landingGrid)' 'death-replacement target occupancy guard'
|
||||
|
||||
if ($attackGroundFragmentText -match 'SkillType\.SuikaFallingSplash\s*=>\s*ProjectileType\.Bomb') {
|
||||
throw "Suika falling splash animation guardrail failed: FragmentAttackGround must not map SuikaFallingSplash to Bomb projectile."
|
||||
@ -90,8 +91,8 @@ if (-not $method.Success) {
|
||||
}
|
||||
|
||||
$body = $method.Value
|
||||
if ($body -notmatch 'ExecuteSuikaFallingSplashDamage\(map, suika, targetGrid, AnimPhase\.AttackImpact \+ 10\);[\s\S]*?sightRefreshGrids = CollectSuikaFallingSplashNewSightGrids\(map, suika, attackInfo\.OriginPlayer,[\s\S]*?landingGrid\);[\s\S]*?TryRepositionUnitWithoutMoveSideEffects\(map, suika, landingGrid\)[\s\S]*?attackInfo\.IsSuikaFallingSplash = true;[\s\S]*?attackInfo\.SuikaFallingSplashFinalGrid = landingGrid;[\s\S]*?attackInfo\.SuikaFallingSplashSightRefreshGrids = sightRefreshGrids;[\s\S]*?UpdateSightByPath') {
|
||||
throw "Suika falling splash animation guardrail failed: attack-after-splash must settle damage, reposition data, cache new sight grids, then update sight."
|
||||
if ($body -notmatch 'ExecuteSuikaFallingSplashDamage\(map, suika, targetGrid, AnimPhase\.AttackImpact \+ 10\);[\s\S]*?TargetGridHasOtherUnit\(map, suika, landingGrid\)[\s\S]*?TryFindRandomEmptyAround\(map, suika, targetGrid, out landingGrid\)[\s\S]*?sightRefreshGrids = CollectSuikaFallingSplashNewSightGrids\(map, suika, attackInfo\.OriginPlayer,[\s\S]*?landingGrid\);[\s\S]*?TryRepositionUnitWithoutMoveSideEffects\(map, suika, landingGrid\)[\s\S]*?attackInfo\.IsSuikaFallingSplash = true;[\s\S]*?attackInfo\.SuikaFallingSplashFinalGrid = landingGrid;[\s\S]*?attackInfo\.SuikaFallingSplashSightRefreshGrids = sightRefreshGrids;[\s\S]*?UpdateSightByPath') {
|
||||
throw "Suika falling splash animation guardrail failed: attack-after-splash must settle damage, avoid death-replacement occupants, cache new sight grids, reposition data, then update sight."
|
||||
}
|
||||
|
||||
$groundMethod = [regex]::Match($actionText, '(?ms)public class UnitAttackGroundAction.*?protected override bool Execute\(CommonActionParams actionParams\).*?^\s*public override bool CheckCan')
|
||||
@ -99,7 +100,7 @@ if (-not $groundMethod.Success) {
|
||||
throw "Suika falling splash animation guardrail failed: cannot locate UnitAttackGroundAction execute body."
|
||||
}
|
||||
|
||||
if ($groundMethod.Value -notmatch 'suikaGroundSightRefreshGrids = HakureiNorwayHeroSkillUtil\.CollectSuikaFallingSplashNewSightGrids[\s\S]*?unit1\.AttackGroundExecute\(actionParams\.MapData, targetGrid, out animSkillData\)[\s\S]*?sightRefreshGrids: suikaGroundSightRefreshGrids') {
|
||||
if ($groundMethod.Value -notmatch 'suikaGroundSightRefreshGrids = HakureiNorwayHeroSkillUtil\.CollectSuikaFallingSplashNewSightGrids[\s\S]*?unit1\.AttackGroundExecute\(actionParams\.MapData, targetGrid, out animSkillData,[\s\S]*?out skillAttackGroundCountsAsActiveAttack\)[\s\S]*?sightRefreshGrids: suikaGroundSightRefreshGrids') {
|
||||
throw "Suika falling splash animation guardrail failed: ground self-jump must cache sight grids before AttackGroundExecute updates sight, then pass them into the Suika fragment."
|
||||
}
|
||||
|
||||
|
||||
140
Tools/CheckSumirekoEnglandOrbProtection.ps1
Normal file
@ -0,0 +1,140 @@
|
||||
param(
|
||||
[string]$SkillFile = "Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill/HakureiNorwayHeroSkill.cs"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = git rev-parse --show-toplevel 2>$null
|
||||
if (-not $repoRoot) {
|
||||
throw "Not inside a git repository."
|
||||
}
|
||||
|
||||
$repoRoot = [System.IO.Path]::GetFullPath($repoRoot.Trim())
|
||||
$skillPath = Join-Path $repoRoot $SkillFile
|
||||
|
||||
if (-not (Test-Path -LiteralPath $skillPath)) {
|
||||
throw "Sumireko guardrail failed: skill file not found: $skillPath"
|
||||
}
|
||||
|
||||
$skillText = Get-Content -LiteralPath $skillPath -Raw -Encoding UTF8
|
||||
|
||||
$protectStart = $skillText.IndexOf('public static bool CanSumirekoEnglandOrbProtectDamage(SettlementInfo info)')
|
||||
$protectEnd = $skillText.IndexOf('public static void ConsumeSumirekoEnglandOrbProtection(', $protectStart)
|
||||
if ($protectStart -lt 0 -or $protectEnd -lt 0 -or $protectEnd -le $protectStart) {
|
||||
throw "Sumireko guardrail failed: missing CanSumirekoEnglandOrbProtectDamage."
|
||||
}
|
||||
|
||||
$protectBlock = $skillText.Substring($protectStart, $protectEnd - $protectStart)
|
||||
foreach ($needle in @(
|
||||
'info?.DamageTarget == null',
|
||||
'info.DamageValue <= 0',
|
||||
'info.DamageBearer != null',
|
||||
'IsSumirekoOccultOrb(info.DamageTarget)',
|
||||
'DamageType.ActiveAttack',
|
||||
'DamageType.CounterAttack',
|
||||
'DamageType.FollowAttack',
|
||||
'DamageType.Splash',
|
||||
'DamageType.True',
|
||||
'DamageType.DelayAttack',
|
||||
'DamageType.PushAttack'
|
||||
)) {
|
||||
if (-not $protectBlock.Contains($needle)) {
|
||||
throw "Sumireko guardrail failed: protection gate missing '$needle'."
|
||||
}
|
||||
}
|
||||
|
||||
if ($protectBlock.Contains('DamageType.KillSelf')) {
|
||||
throw "Sumireko guardrail failed: KillSelf must not be a protected damage type."
|
||||
}
|
||||
|
||||
$buffStart = $skillText.IndexOf('public partial class SumirekoEnglandOrbBuffSkill')
|
||||
$buffEnd = $skillText.IndexOf('public partial class SumirekoOrbSwapMaxValueBuffSkill', $buffStart)
|
||||
if ($buffStart -lt 0 -or $buffEnd -lt 0 -or $buffEnd -le $buffStart) {
|
||||
throw "Sumireko guardrail failed: missing SumirekoEnglandOrbBuffSkill.BeforeDamagedSupportStage."
|
||||
}
|
||||
|
||||
$buffBlock = $skillText.Substring($buffStart, $buffEnd - $buffStart)
|
||||
foreach ($needle in @(
|
||||
'CanSumirekoEnglandOrbProtectDamage(info)',
|
||||
'TryFindDamageProxyOrb(mapData, info.DamageTarget, out var orb',
|
||||
'ConsumeSumirekoEnglandOrbProtection(mapData, orb, proxySkill)',
|
||||
'info.DamageValue = 0',
|
||||
'PlaySumirekoEnglandOrbProtectionVisual(mapData, info.DamageTarget, orb'
|
||||
)) {
|
||||
if (-not $buffBlock.Contains($needle)) {
|
||||
throw "Sumireko guardrail failed: England orb buff missing '$needle'."
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($forbidden in @(
|
||||
'info.DamageBearer = orb',
|
||||
'DamageBearer = orb',
|
||||
'InstantUpdateUnit(false)',
|
||||
'InstantUpdateUnit(true)'
|
||||
)) {
|
||||
if ($buffBlock.Contains($forbidden)) {
|
||||
throw "Sumireko guardrail failed: England orb buff contains forbidden '$forbidden'."
|
||||
}
|
||||
}
|
||||
|
||||
$unitLogicPath = Join-Path $repoRoot "Unity/Assets/Scripts/TH1_Logic/Unit/UnitLogic.cs"
|
||||
if (-not (Test-Path -LiteralPath $unitLogicPath)) {
|
||||
throw "Sumireko guardrail failed: UnitLogic file not found: $unitLogicPath"
|
||||
}
|
||||
|
||||
$unitLogicText = Get-Content -LiteralPath $unitLogicPath -Raw -Encoding UTF8
|
||||
foreach ($needle in @(
|
||||
'attackDmg += activeSettlement.DamageValue',
|
||||
'counterDmg += counterSettlement.DamageValue'
|
||||
)) {
|
||||
if (-not $unitLogicText.Contains($needle)) {
|
||||
throw "Sumireko guardrail failed: UnitLogic damage fragment output missing '$needle'."
|
||||
}
|
||||
}
|
||||
|
||||
if ($unitLogicText.Contains('attackDmg += dmg1;') -or $unitLogicText.Contains('counterDmg += dmg2;')) {
|
||||
throw "Sumireko guardrail failed: UnitLogic still reports pre-settlement attack/counter damage."
|
||||
}
|
||||
|
||||
$visualFiles = @(
|
||||
@{
|
||||
Path = "Unity/Assets/Scripts/TH1_Logic/Action/ActionVisualEventCollector.cs"
|
||||
Required = @(
|
||||
'var visualDamage = settlement.DamageValue',
|
||||
'MainTargetPreAttackDamage += visualDamage',
|
||||
'visualDamage,'
|
||||
)
|
||||
},
|
||||
@{
|
||||
Path = "Unity/Assets/Scripts/TH1_Logic/Action/UnitActionLogic.cs"
|
||||
Required = @('settlement.DamageValue')
|
||||
},
|
||||
@{
|
||||
Path = "Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill/SplashSkill.cs"
|
||||
Required = @('settlement.DamageValue')
|
||||
},
|
||||
@{
|
||||
Path = "Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill/StompSkill.cs"
|
||||
Required = @('settlement.DamageValue')
|
||||
},
|
||||
@{
|
||||
Path = "Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill/PathStompSkill.cs"
|
||||
Required = @('settlement.DamageValue')
|
||||
}
|
||||
)
|
||||
|
||||
foreach ($entry in $visualFiles) {
|
||||
$path = Join-Path $repoRoot $entry.Path
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
throw "Sumireko guardrail failed: visual file not found: $path"
|
||||
}
|
||||
|
||||
$text = Get-Content -LiteralPath $path -Raw -Encoding UTF8
|
||||
foreach ($needle in $entry.Required) {
|
||||
if (-not $text.Contains($needle)) {
|
||||
throw "Sumireko guardrail failed: visual file '$($entry.Path)' missing '$needle'."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Sumireko England orb protection guardrail passed."
|
||||
51
Tools/CheckSuwakoAttackGroundAgain.ps1
Normal file
@ -0,0 +1,51 @@
|
||||
param()
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
$failures = @()
|
||||
|
||||
function Add-Failure {
|
||||
param([string]$Message)
|
||||
$script:failures += $Message
|
||||
}
|
||||
|
||||
function Read-RepoText {
|
||||
param([string]$RelativePath)
|
||||
$path = Join-Path $repoRoot $RelativePath
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
Add-Failure "Missing file: $RelativePath"
|
||||
return ""
|
||||
}
|
||||
return Get-Content -LiteralPath $path -Raw
|
||||
}
|
||||
|
||||
$actionLogic = Read-RepoText "Unity/Assets/Scripts/TH1_Logic/Action/ActionLogic.cs"
|
||||
$attackGetAttackPointSkill = Read-RepoText "Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill/AttackGetAttackPointSkill.cs"
|
||||
|
||||
if ($attackGetAttackPointSkill -notmatch "public bool TryConsumeAndGrantAttackPoint\(MapData mapData, UnitData self\)") {
|
||||
Add-Failure "AttackGetAttackPointSkill must expose a reusable consume-and-grant helper for non-damage attack-ground actions."
|
||||
}
|
||||
|
||||
if ($attackGetAttackPointSkill -notmatch "_level--;\s*self\.AddActionPoint\(ActionPointType\.Attack\);") {
|
||||
Add-Failure "AttackGetAttackPointSkill must decrement its layer before granting an attack action point."
|
||||
}
|
||||
|
||||
if ($attackGetAttackPointSkill -notmatch "AfterActiveAttackOther[\s\S]*?TryConsumeAndGrantAttackPoint\(mapData, attackInfo\.DamageOrigin\)") {
|
||||
Add-Failure "Normal active attacks must still use the same consume-and-grant helper."
|
||||
}
|
||||
|
||||
$suwakoSummonPattern = "unit1\.ClearActionPoint\(\)[\s\S]*?actionParams\.MapData\.AddUnitData\(targetGrid\.Id, city1\.Id, fullType, out suwakoUnit, 0\.2f\)[\s\S]*?TryConsumeAndGrantAttackPoint\(actionParams\.MapData, unit1\)"
|
||||
if ($actionLogic -notmatch $suwakoSummonPattern) {
|
||||
Add-Failure "Suwako empty-grid AttackGround summon must consume AttackGetAttackPoint and restore an Attack action point after ClearActionPoint."
|
||||
}
|
||||
|
||||
if ($failures.Count -gt 0) {
|
||||
Write-Host "Suwako AttackGround again check failed:" -ForegroundColor Red
|
||||
foreach ($failure in $failures) {
|
||||
Write-Host " - $failure" -ForegroundColor Red
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Suwako AttackGround again checks passed."
|
||||
@ -2,6 +2,12 @@
|
||||
|
||||
Dashboard 功能变更应在这里记录入口、数据源、接口和持久化位置,便于后续维护与迁移。
|
||||
|
||||
## 2026-06-30
|
||||
|
||||
- 新增 `其他事务` 一级导航入口,当前包含 `垫付条目` 二级栏。
|
||||
- `垫付条目` 读取 `Tools/Dashboard/data/advance_items.json`,展示垫付款项分组、合计、已记录/估算金额和估算依据。
|
||||
- 新增前端模块 `js/misc_affairs.js`,不新增后端 API;当前数据通过静态 JSON 持久化,方便后续逐条追加。
|
||||
|
||||
## 2026-06-26
|
||||
|
||||
- `平衡性分析` 新增二级模块 `英雄升级`,读取当前 `HeroDataAssets` 英雄任务和 `UnitTypeDataAssets` 英雄基础数据,估算 Lv1->Lv2、Lv2->Lv3、Lv3->Lv4 所需回合数。
|
||||
|
||||
BIN
Tools/Dashboard/assets/heroes/NorwayAunn_Juggernaut.png
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
Tools/Dashboard/assets/heroes/NorwayAunn_Juggernaut_Stone.png
Normal file
|
After Width: | Height: | Size: 196 KiB |
BIN
Tools/Dashboard/assets/heroes/NorwayAunn_Stone.png
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
Tools/Dashboard/assets/heroes/NorwayKasen_Oni.png
Normal file
|
After Width: | Height: | Size: 278 KiB |
BIN
Tools/Dashboard/assets/heroes/NorwaySuika_Juggernaut.png
Normal file
|
After Width: | Height: | Size: 255 KiB |
BIN
Tools/Dashboard/assets/heroes/NorwaySuika_Medium.png
Normal file
|
After Width: | Height: | Size: 248 KiB |
@ -452,6 +452,7 @@ body::after {
|
||||
.summary-card .value.orange { color: var(--accent-orange); }
|
||||
.summary-card .value.red { color: var(--accent-red); }
|
||||
.summary-card .value.yellow { color: var(--accent-yellow); }
|
||||
.summary-card .value.cyan { color: var(--accent-cyan); }
|
||||
|
||||
/* ========== Placeholder Panels ========== */
|
||||
.placeholder-panel {
|
||||
@ -720,6 +721,230 @@ body::after {
|
||||
.filter-select { cursor: pointer; }
|
||||
.filter-select option { background: var(--bg-card); color: var(--text-primary); }
|
||||
|
||||
/* ========== Audio Inventory ========== */
|
||||
.audio-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 14px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.audio-meta,
|
||||
.audio-muted {
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.audio-refresh-btn {
|
||||
min-height: 32px;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid rgba(59, 130, 246, 0.28);
|
||||
border-radius: 6px;
|
||||
background: rgba(59, 130, 246, 0.06);
|
||||
color: var(--accent-blue);
|
||||
font-family: inherit;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.audio-refresh-btn:hover {
|
||||
background: rgba(59, 130, 246, 0.12);
|
||||
}
|
||||
|
||||
.audio-refresh-btn.running {
|
||||
opacity: 0.65;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.audio-summary-grid {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.audio-system-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.audio-system-note {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
background: var(--bg-card-hover);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.audio-system-summary {
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.audio-source {
|
||||
color: var(--text-muted);
|
||||
font-family: Consolas, 'SFMono-Regular', monospace;
|
||||
font-size: 11px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.audio-note {
|
||||
margin-top: 10px;
|
||||
padding: 8px 10px;
|
||||
border-left: 3px solid var(--accent-blue);
|
||||
background: rgba(59, 130, 246, 0.06);
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.audio-table td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.audio-table td.wrap {
|
||||
white-space: normal;
|
||||
min-width: 180px;
|
||||
max-width: 420px;
|
||||
}
|
||||
|
||||
.audio-code-cell {
|
||||
font-family: Consolas, 'SFMono-Regular', monospace;
|
||||
font-size: 11px !important;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.audio-tree {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.audio-tree-node {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-card);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.audio-tree-group {
|
||||
background: #fbfdff;
|
||||
}
|
||||
|
||||
.audio-tree-leaf {
|
||||
padding: 10px 12px;
|
||||
border-left-width: 4px;
|
||||
}
|
||||
|
||||
.audio-tree-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.audio-tree-group > .audio-tree-head {
|
||||
padding: 12px 14px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.audio-tree-main {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.audio-tree-title {
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.audio-tree-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 5px;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.audio-tree-progress {
|
||||
flex: 0 0 190px;
|
||||
}
|
||||
|
||||
.audio-coverage {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(80px, 1fr) 42px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.audio-coverage-track {
|
||||
height: 8px;
|
||||
border-radius: 999px;
|
||||
background: #e5eaf1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.audio-coverage-fill {
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: var(--accent-green);
|
||||
}
|
||||
|
||||
.audio-tree-children {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.audio-tree-depth-1 {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.audio-tree-depth-2 {
|
||||
margin-left: 18px;
|
||||
}
|
||||
|
||||
.audio-tree-depth-3,
|
||||
.audio-tree-depth-4 {
|
||||
margin-left: 32px;
|
||||
}
|
||||
|
||||
.audio-tree-evidence {
|
||||
margin-top: 7px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.audio-tree-suggestion {
|
||||
margin-top: 7px;
|
||||
color: var(--text-primary);
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
background: rgba(59, 130, 246, 0.05);
|
||||
border-radius: 6px;
|
||||
padding: 7px 8px;
|
||||
}
|
||||
|
||||
.audio-status-covered { border-left-color: var(--accent-green); }
|
||||
.audio-status-reused { border-left-color: var(--accent-cyan); }
|
||||
.audio-status-partial { border-left-color: var(--accent-orange); }
|
||||
.audio-status-configured-unused { border-left-color: var(--accent-yellow); }
|
||||
.audio-status-asset-only { border-left-color: var(--accent-purple); }
|
||||
.audio-status-missing { border-left-color: var(--accent-red); }
|
||||
|
||||
/* ========== Mechanics Documents ========== */
|
||||
.mechanics-layout {
|
||||
display: grid;
|
||||
@ -5562,6 +5787,268 @@ body::after {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* ========== Questionnaire ========== */
|
||||
.questionnaire-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.questionnaire-topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 18px 20px;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.questionnaire-title {
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.questionnaire-subtitle {
|
||||
margin-top: 4px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.questionnaire-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.questionnaire-status {
|
||||
min-height: 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.questionnaire-status:not(:empty) {
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
background: var(--bg-card);
|
||||
}
|
||||
|
||||
.questionnaire-status.success { color: #047857; border-color: rgba(16, 185, 129, 0.25); }
|
||||
.questionnaire-status.warn { color: #b45309; border-color: rgba(245, 158, 11, 0.3); }
|
||||
.questionnaire-status.error { color: #b91c1c; border-color: rgba(239, 68, 68, 0.3); }
|
||||
|
||||
.questionnaire-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(260px, 360px) minmax(0, 1fr);
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.questionnaire-list-panel,
|
||||
.questionnaire-record-panel {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
min-height: 520px;
|
||||
}
|
||||
|
||||
.questionnaire-panel-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.questionnaire-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.questionnaire-list-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.questionnaire-list-item:hover,
|
||||
.questionnaire-list-item.active {
|
||||
border-color: var(--accent-blue);
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
.questionnaire-list-title {
|
||||
font-weight: 700;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.questionnaire-list-meta,
|
||||
.questionnaire-list-time,
|
||||
.questionnaire-record-subtitle,
|
||||
.questionnaire-detail-meta,
|
||||
.questionnaire-answer-meta {
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.questionnaire-records {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.questionnaire-record-head,
|
||||
.questionnaire-detail-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.questionnaire-record-title,
|
||||
.questionnaire-detail-title {
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.questionnaire-record-table-wrap {
|
||||
overflow-x: auto;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.questionnaire-record-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
min-width: 760px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.questionnaire-record-table th,
|
||||
.questionnaire-record-table td {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.questionnaire-record-table th {
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-secondary);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.questionnaire-record-table tr:last-child td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.questionnaire-mono {
|
||||
font-family: Consolas, 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.questionnaire-detail {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.questionnaire-detail-head {
|
||||
padding: 14px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.questionnaire-detail-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.questionnaire-detail-tags span {
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
background: var(--bg-card);
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.questionnaire-answer-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.questionnaire-answer-card {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
background: var(--bg-card);
|
||||
}
|
||||
|
||||
.questionnaire-answer-question {
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.questionnaire-answer-text {
|
||||
margin-top: 8px;
|
||||
color: var(--text-secondary);
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.questionnaire-empty {
|
||||
color: var(--text-muted);
|
||||
padding: 40px 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.questionnaire-empty.compact {
|
||||
padding: 18px 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.questionnaire-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.questionnaire-list-panel,
|
||||
.questionnaire-record-panel {
|
||||
min-height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.questionnaire-topbar,
|
||||
.questionnaire-record-head,
|
||||
.questionnaire-detail-head {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.questionnaire-actions,
|
||||
.questionnaire-detail-tags {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== Community Monitor ========== */
|
||||
.cm-module .module-body {
|
||||
display: flex;
|
||||
@ -7206,6 +7693,22 @@ body::after {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.audio-tree-head {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.audio-tree-progress {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.audio-tree-depth-2,
|
||||
.audio-tree-depth-3,
|
||||
.audio-tree-depth-4 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
#arch-canvas {
|
||||
height: 380px;
|
||||
}
|
||||
@ -7756,3 +8259,268 @@ body::after {
|
||||
max-height: min(58vh, 520px);
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== Misc Affairs / Advance Items ========== */
|
||||
.misc-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.misc-header-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.misc-header-left {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.misc-header-title {
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.misc-header-count {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.misc-btn {
|
||||
padding: 7px 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
background: var(--bg-card);
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.misc-btn:hover {
|
||||
border-color: rgba(59, 130, 246, 0.35);
|
||||
color: var(--accent-blue);
|
||||
background: rgba(59, 130, 246, 0.05);
|
||||
}
|
||||
|
||||
.advance-summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.advance-summary-card,
|
||||
.advance-group,
|
||||
.advance-source-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.advance-summary-card {
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.advance-summary-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.advance-summary-value {
|
||||
font-size: 22px;
|
||||
font-weight: 850;
|
||||
color: var(--accent-blue);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.advance-summary-value.recorded {
|
||||
color: var(--accent-green);
|
||||
}
|
||||
|
||||
.advance-summary-value.estimated {
|
||||
color: var(--accent-orange);
|
||||
}
|
||||
|
||||
.advance-group-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.advance-group {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.advance-group-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 13px 16px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
background: var(--bg-card-hover);
|
||||
}
|
||||
|
||||
.advance-group-title {
|
||||
font-size: 15px;
|
||||
font-weight: 800;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.advance-group-total {
|
||||
font-size: 16px;
|
||||
font-weight: 850;
|
||||
color: var(--accent-blue);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.advance-table-wrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.advance-table {
|
||||
width: 100%;
|
||||
min-width: 760px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.advance-table th,
|
||||
.advance-table td {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid rgba(226, 232, 240, 0.8);
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.advance-table th {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
background: #fbfdff;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.advance-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.advance-id {
|
||||
width: 84px;
|
||||
color: var(--text-muted);
|
||||
font-family: Consolas, 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.advance-name {
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.advance-amount {
|
||||
width: 110px;
|
||||
font-weight: 850;
|
||||
color: var(--text-primary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.advance-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 54px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.advance-status.recorded {
|
||||
color: #047857;
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
}
|
||||
|
||||
.advance-status.estimated {
|
||||
color: #b45309;
|
||||
background: rgba(245, 158, 11, 0.12);
|
||||
}
|
||||
|
||||
.advance-note {
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.advance-note.muted {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.advance-group-foot {
|
||||
padding: 9px 16px 12px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.advance-source-card {
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.advance-source-title {
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.advance-source-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(180px, 260px) 1fr;
|
||||
gap: 12px;
|
||||
padding: 7px 0;
|
||||
border-top: 1px solid rgba(226, 232, 240, 0.8);
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.advance-source-row a {
|
||||
color: var(--accent-blue);
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.advance-source-row a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.advance-summary-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.advance-source-row {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.misc-header-bar,
|
||||
.misc-header-left {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.advance-summary-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@ -564,7 +564,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "保护区",
|
||||
"desc": "提供金币(浅海)/**<城市发展度>**(森林)或科技点。若4个保护区围成一圈且拥有至少3种不同类型,则收益翻倍。",
|
||||
"desc": "提供**<金币>**(位于浅海时)/**<城市发展度>**(位于森林时)或**<科技点>**(位于深海或山脉时)。若4个保护区围成一圈且拥有至少3种不同类型,则收益翻倍。",
|
||||
"cost": 20,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -873,6 +873,25 @@
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
},
|
||||
{
|
||||
"actionType": 0,
|
||||
"actionTypeName": "None",
|
||||
"unitType": 0,
|
||||
"unitTypeName": "None",
|
||||
"giantType": 0,
|
||||
"giantName": "None",
|
||||
"techType": 0,
|
||||
"skillType": 0,
|
||||
"wonderType": 0,
|
||||
"resourceType": 0,
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "拆毁",
|
||||
"desc": "使该单位自行销毁。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
},
|
||||
{
|
||||
"actionType": 0,
|
||||
"actionTypeName": "None",
|
||||
@ -1006,6 +1025,25 @@
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
},
|
||||
{
|
||||
"actionType": 0,
|
||||
"actionTypeName": "None",
|
||||
"unitType": 0,
|
||||
"unitTypeName": "None",
|
||||
"giantType": 0,
|
||||
"giantName": "None",
|
||||
"techType": 0,
|
||||
"skillType": 0,
|
||||
"wonderType": 0,
|
||||
"resourceType": 0,
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "跳过本回合",
|
||||
"desc": "清空所有**<行动点>**",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
},
|
||||
{
|
||||
"actionType": 0,
|
||||
"actionTypeName": "None",
|
||||
@ -1039,7 +1077,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "战争号角",
|
||||
"desc": "花费**<金币>**,立即获得1点**<攻击行动点>**,并获得**<狂暴>**状态,持续至本回合结束。",
|
||||
"desc": "花费**<金币>**,立即获得1点**<攻击行动点>**,并获得**<狂暴>**状态,持续至本回合结束。每回合仅能使用1次",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -1063,6 +1101,44 @@
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
},
|
||||
{
|
||||
"actionType": 0,
|
||||
"actionTypeName": "None",
|
||||
"unitType": 0,
|
||||
"unitTypeName": "None",
|
||||
"giantType": 0,
|
||||
"giantName": "None",
|
||||
"techType": 0,
|
||||
"skillType": 0,
|
||||
"wonderType": 0,
|
||||
"resourceType": 0,
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "吽形石守",
|
||||
"desc": "消耗任意**<行动点>**,进入**<吽形石守>**状态。若回合结束时剩余任意**<行动点>**,将会自动进入**<吽形石守>**状态。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
},
|
||||
{
|
||||
"actionType": 0,
|
||||
"actionTypeName": "None",
|
||||
"unitType": 0,
|
||||
"unitTypeName": "None",
|
||||
"giantType": 0,
|
||||
"giantName": "None",
|
||||
"techType": 0,
|
||||
"skillType": 0,
|
||||
"wonderType": 0,
|
||||
"resourceType": 0,
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "清除英灵符文",
|
||||
"desc": "消耗**<施法行动点>**,清除当前地块上的**<英灵符文>**。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
},
|
||||
{
|
||||
"actionType": 0,
|
||||
"actionTypeName": "None",
|
||||
@ -1115,7 +1191,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "支付贡金",
|
||||
"desc": "向**<博丽灵梦>**支付**<金币>**,移除自身**<退治目标>**状态。费用为2 * 灵梦等级 * 退治层数。",
|
||||
"desc": "向**<博丽灵梦>**支付**<金币>**,移除自身**<退治目标>**状态。费用为2",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
@ -1172,7 +1248,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "回收兽引",
|
||||
"desc": "回收兽引并获得移动行动点。等级2以上额外恢复生命。",
|
||||
"desc": "不消耗**<行动点>**,回收当前地块上的**<兽引>**,自身获得1点**<移动行动点>**。华扇等级2以上时,自身额外恢复5点生命。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
@ -1190,7 +1266,7 @@
|
||||
"resourceType": 0,
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "鬼形重现",
|
||||
"name": "鬼之腕",
|
||||
"desc": "茨木华扇进入鬼形态并获得狂暴。占位版本不消耗行动点。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
@ -1209,8 +1285,8 @@
|
||||
"resourceType": 0,
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "逃脱/石化",
|
||||
"desc": "高丽野阿吽攻击后选择逃脱或原地石化。未指定目标时执行石化。",
|
||||
"name": "清除兽引",
|
||||
"desc": "消耗**<施法行动点>**,清除当前地块上的**<兽引>**。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
@ -1248,7 +1324,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "攫鬼归身",
|
||||
"desc": "小萃香附着到附近萃香身上,增加小萃香层数。",
|
||||
"desc": "**<小萃香>**附着到附近的**<伊吹萃香>**身上,增加**<萃影>**层数。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
@ -1267,7 +1343,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "踏鞴散鬼",
|
||||
"desc": "将萃香身上的小萃香全部抖出到附近空格。",
|
||||
"desc": "将**<萃影>**全部化作**<小萃香>**,生成在附近空地上。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
@ -1286,7 +1362,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "燐火化鬼",
|
||||
"desc": "萃香消耗5点生命生成一只小萃香。",
|
||||
"desc": "消耗5点生命生成一只**<小萃香>**。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
@ -1305,7 +1381,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "天手力男投",
|
||||
"desc": "萃香将附近单位投掷到指定或随机附近地格。",
|
||||
"desc": "将附近友方单位投掷到指定地块,并造成范围溅射伤害。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
@ -1323,8 +1399,8 @@
|
||||
"resourceType": 0,
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "从天而降",
|
||||
"desc": "萃香降落到目标地格并造成周围溅射伤害。",
|
||||
"name": "大江山悉皆杀",
|
||||
"desc": "降落到目标地块,并造成范围溅射伤害。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
@ -1438,7 +1514,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "建造城墙",
|
||||
"desc": "消耗8金币,在城市中心建造**<城墙>**。",
|
||||
"desc": "消耗8**<金币>**,在**<城市中心>**建造**<城墙>**,能够为拥有**<城防>**的单位提供极高的额外防御。",
|
||||
"cost": 8,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -1457,7 +1533,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "博丽酒宴",
|
||||
"desc": "消耗宴会点,为该城市增加1点发展度。费用为5+该城市已转化次数。",
|
||||
"desc": "消耗**<宴会点>**,为该城市增加1点发展度。费用为5+该城市已发起酒宴次数。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
@ -1856,7 +1932,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "芙兰朵露·斯卡雷特",
|
||||
"desc": "**<[马]职阶英雄>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[马]职阶英雄>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄,每一个**<芙兰朵露·斯卡雷特>**复活时间独立计算。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -1894,7 +1970,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "十六夜咲夜",
|
||||
"desc": "**<[相]职阶英雄>**,全场只能存在一个**<十六夜咲夜>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[相]职阶英雄>**,全场只能存在一个**<十六夜咲夜>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -1913,7 +1989,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "红美铃",
|
||||
"desc": "**<[车]职阶英雄>**,全场只能存在一个**<红美铃>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[车]职阶英雄>**,全场只能存在一个**<红美铃>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -1932,7 +2008,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "帕秋莉诺蕾姬",
|
||||
"desc": "**<[后]职阶英雄>**。全场只能存在一个**<帕秋莉诺蕾姬>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[后]职阶英雄>**。全场只能存在一个**<帕秋莉诺蕾姬>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -2653,9 +2729,9 @@
|
||||
"resourceType": 0,
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "丹麦金",
|
||||
"desc": "向某个帝国发起勒索。若对方不支付,下回合所有单位抢劫收益翻倍。",
|
||||
"cost": 0,
|
||||
"name": "征收丹麦金",
|
||||
"desc": "花费**<我方城市数量>***3点**<金币>**,向对方征收**<丹麦金>**,收取对方下回合收益的20%。若对方拒绝支付,下回合对其所有城市的**<劫掠>**收益翻倍。对同一目标两回合内无法重复发起贡金要求。",
|
||||
"cost": 3,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
},
|
||||
@ -2672,8 +2748,8 @@
|
||||
"resourceType": 0,
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "支付勒索",
|
||||
"desc": "支付勒索后,双方丹麦金关系变回两清。",
|
||||
"name": "支付丹麦金",
|
||||
"desc": "支付本次**<丹麦金>**后,双方丹麦金状态清除。",
|
||||
"cost": 5,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
@ -2691,8 +2767,8 @@
|
||||
"resourceType": 0,
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "拒绝勒索",
|
||||
"desc": "拒绝支付丹麦金。勒索方对你的城市抢劫收益翻倍。",
|
||||
"name": "拒绝丹麦金",
|
||||
"desc": "拒绝支付**<丹麦金>**。对方对你的城市**<劫掠>**收益翻倍。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": true
|
||||
@ -2920,7 +2996,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "灌溉工程",
|
||||
"desc": "建造**<灌溉工程>**。不能与另一个灌溉工程相邻。每3回合在附近生成一片庄稼。附近每片农田额外提供1点**<城市发展度>**(上限2点),为市场提供额外金币。研发**<灌溉>**可建造在与两片水域**<直接相邻>**的平原上。研发**<排涝>**后,可以建造在与两片陆地**<直接相邻>**的水域中。",
|
||||
"desc": "建造**<灌溉工程>**。不能与另一个**<灌溉工程>**相邻。每2回合在附近生成一片**<庄稼>**。附近每片**<农田>**额外提供1点**<城市发展度>**(上限2点),为**<市场>**提供额外**<金币>**,需建在与**<山脉>**相邻,或至少与三块**<浅海>**相邻的**<平原>**地块上。研发**<排涝>**后,还可建造在至少与三块**<平原>**相邻的**<浅海>**地块上。",
|
||||
"cost": 5,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3129,7 +3205,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "蓬莱山辉夜",
|
||||
"desc": "**<[王]职阶英雄>**。全场最多存在一名**<蓬莱山辉夜>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[王]职阶英雄>**。全场最多存在一名**<蓬莱山辉夜>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3148,7 +3224,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "八意永琳",
|
||||
"desc": "**<[后]职阶英雄>**。全场最多存在一名**<八意永琳>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[后]职阶英雄>**。全场最多存在一名**<八意永琳>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3167,7 +3243,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "因幡帝",
|
||||
"desc": "**<[相]职阶英雄>**。全场最多存在一名**<因幡帝>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[相]职阶英雄>**。全场最多存在一名**<因幡帝>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3186,7 +3262,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "铃仙优昙华院因幡",
|
||||
"desc": "**<[马]职阶英雄>**。全场最多存在一名**<铃仙·优昙华院·因幡>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[马]职阶英雄>**。全场最多存在一名**<铃仙·优昙华院·因幡>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3205,7 +3281,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "藤原妹红",
|
||||
"desc": "**<[车]职阶英雄>**。全场最多存在一名**<藤原妹红>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[车]职阶英雄>**。全场最多存在一名**<藤原妹红>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3224,7 +3300,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "八坂神奈子",
|
||||
"desc": "**<[王]职阶英雄>**。全场最多存在一名**<八坂神奈子>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[王]职阶英雄>**。全场最多存在一名**<八坂神奈子>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3243,7 +3319,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "洩矢诹访子",
|
||||
"desc": "**<[后]职阶英雄>**。全场最多存在一名**<洩矢诹访子>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[后]职阶英雄>**。全场最多存在一名**<洩矢诹访子>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3262,7 +3338,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "东风谷早苗",
|
||||
"desc": "**<[相]职阶英雄>**。全场最多存在一名**<东风谷早苗>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[相]职阶英雄>**。全场最多存在一名**<东风谷早苗>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3281,7 +3357,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "射命丸文",
|
||||
"desc": "**<[马]职阶英雄>**。全场最多存在一名**<射命丸文>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[马]职阶英雄>**。全场最多存在一名**<射命丸文>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3300,7 +3376,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "犬走椛",
|
||||
"desc": "**<[车]职阶英雄>**。全场最多存在一名**<犬走椛>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[车]职阶英雄>**。全场最多存在一名**<犬走椛>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3528,7 +3604,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "古明地觉",
|
||||
"desc": "**<[王]职阶英雄>**。全场最多存在一名**<古明地觉>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[王]职阶英雄>**。全场最多存在一名**<古明地觉>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3566,7 +3642,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "灵乌路空",
|
||||
"desc": "**<[马]职阶英雄>**。全场只能存在一个**<灵乌路空>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[马]职阶英雄>**。全场只能存在一个**<灵乌路空>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3585,7 +3661,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "星熊勇仪",
|
||||
"desc": "**<[车]职阶英雄>**。全场只能存在一个**<星熊勇仪>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[车]职阶英雄>**。全场只能存在一个**<星熊勇仪>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3604,7 +3680,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "火焰猫燐",
|
||||
"desc": "**<[相]职阶英雄>**。全场只能存在一个**<火焰猫燐>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[相]职阶英雄>**。全场只能存在一个**<火焰猫燐>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3623,7 +3699,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "博丽灵梦",
|
||||
"desc": "**<[王]职阶英雄>**。全场最多存在一名**<博丽灵梦>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[王]职阶英雄>**。全场最多存在一名**<博丽灵梦>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3642,7 +3718,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "宇佐见堇子",
|
||||
"desc": "**<[后]职阶英雄>**。全场最多存在一名**<宇佐见堇子>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[后]职阶英雄>**。全场最多存在一名**<宇佐见堇子>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3661,7 +3737,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "茨木华扇",
|
||||
"desc": "**<[相]职阶英雄>**。全场最多存在一名**<茨木华扇>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[相]职阶英雄>**。全场最多存在一名**<茨木华扇>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
@ -3680,7 +3756,7 @@
|
||||
"featureType": 0,
|
||||
"cultureCardType": 0,
|
||||
"name": "高丽野阿吽",
|
||||
"desc": "**<[马]职阶英雄>**。全场最多存在一名**<高丽野阿吽>**。阵亡后将存在复活冷却时间,时长随英雄等级增长.",
|
||||
"desc": "**<[马]职阶英雄>**。阵亡后将存在复活冷却时间,时长随英雄等级增长。可消耗**<文化点>**提前复活英雄。",
|
||||
"cost": 0,
|
||||
"cityExp": 0,
|
||||
"noNeedTech": false
|
||||
|
||||
225
Tools/Dashboard/data/advance_items.json
Normal file
@ -0,0 +1,225 @@
|
||||
{
|
||||
"updatedAt": "2026-06-30",
|
||||
"currency": "CNY",
|
||||
"sources": [
|
||||
{
|
||||
"id": "x-premium-web",
|
||||
"label": "X Premium 网页端 Premium 月费",
|
||||
"url": "https://help.x.com/en/using-x/x-premium",
|
||||
"capturedAt": "2026-06-30",
|
||||
"note": "按 X Premium 官方帮助页列出的 Premium 网页端月费 USD 8 估算。"
|
||||
},
|
||||
{
|
||||
"id": "qq-super-vip-annual",
|
||||
"label": "QQ 超级会员年费",
|
||||
"url": "https://vip.qq.com/year_vip/year_region.html",
|
||||
"capturedAt": "2026-06-30",
|
||||
"note": "按腾讯 QQ 年费专区公开口径估算:年费超级会员 240 元/年,年费尊贵特权包含最高 2000 人群。"
|
||||
},
|
||||
{
|
||||
"id": "qq-group-2000-qualification",
|
||||
"label": "QQ群 2000 人群资格说明",
|
||||
"url": "https://vip.qq.com/freedom/freedom_group.html",
|
||||
"capturedAt": "2026-06-30",
|
||||
"note": "腾讯会员群特权页说明超级会员年费用户有机会获得 2000 人群和 3000 人群。"
|
||||
},
|
||||
{
|
||||
"id": "usd-cny-spot",
|
||||
"label": "USD/CNY 即期汇率",
|
||||
"url": "https://www.xe.com/en-us/currencyconverter/convert/?Amount=1&From=USD&To=CNY",
|
||||
"capturedAt": "2026-06-30",
|
||||
"note": "按 2026-06-30 可查询即期汇率约 1 USD = 6.79 CNY 估算。"
|
||||
}
|
||||
],
|
||||
"exchangeRates": [
|
||||
{
|
||||
"pair": "USD_CNY",
|
||||
"rate": 6.79,
|
||||
"capturedAt": "2026-06-30",
|
||||
"note": "用于记录 X Premium 5 个月估算,后续以实际账单金额为准。"
|
||||
}
|
||||
],
|
||||
"items": [
|
||||
{
|
||||
"id": "ADV-0001",
|
||||
"module": "宣发模块",
|
||||
"name": "困困熊 视频 6支+中英",
|
||||
"amount": 3500,
|
||||
"currency": "CNY",
|
||||
"status": "recorded",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"id": "ADV-0002",
|
||||
"module": "宣发模块",
|
||||
"name": "芒朔 插画",
|
||||
"amount": 3000,
|
||||
"currency": "CNY",
|
||||
"status": "recorded",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"id": "ADV-0003",
|
||||
"module": "宣发模块",
|
||||
"name": "疯帽子茶会",
|
||||
"amount": 6300,
|
||||
"currency": "CNY",
|
||||
"status": "recorded",
|
||||
"components": [
|
||||
{ "label": "分项 1", "amount": 3000, "currency": "CNY" },
|
||||
{ "label": "分项 2", "amount": 1500, "currency": "CNY" },
|
||||
{ "label": "分项 3", "amount": 1800, "currency": "CNY" }
|
||||
],
|
||||
"note": "用户原始记录:3000 + 1500 + 1800。"
|
||||
},
|
||||
{
|
||||
"id": "ADV-0004",
|
||||
"module": "宣发模块",
|
||||
"name": "陈皮 插画",
|
||||
"amount": 3000,
|
||||
"currency": "CNY",
|
||||
"status": "recorded",
|
||||
"components": [
|
||||
{ "label": "分项 1", "amount": 1500, "currency": "CNY" },
|
||||
{ "label": "分项 2", "amount": 1500, "currency": "CNY" }
|
||||
],
|
||||
"note": "用户原始记录:1500 + 1500。"
|
||||
},
|
||||
{
|
||||
"id": "ADV-0005",
|
||||
"module": "up主",
|
||||
"name": "白河愁",
|
||||
"amount": 500,
|
||||
"currency": "CNY",
|
||||
"status": "recorded",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"id": "ADV-0006",
|
||||
"module": "up主",
|
||||
"name": "ZGW",
|
||||
"amount": 2000,
|
||||
"currency": "CNY",
|
||||
"status": "recorded",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"id": "ADV-0007",
|
||||
"module": "up主",
|
||||
"name": "挖坑寨主",
|
||||
"amount": 2000,
|
||||
"currency": "CNY",
|
||||
"status": "recorded",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"id": "ADV-0008",
|
||||
"module": "up主",
|
||||
"name": "尘埃",
|
||||
"amount": 1000,
|
||||
"currency": "CNY",
|
||||
"status": "recorded",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"id": "ADV-0009",
|
||||
"module": "up主",
|
||||
"name": "推特高级会员费用 5个月",
|
||||
"amount": 272,
|
||||
"currency": "CNY",
|
||||
"status": "estimated",
|
||||
"estimate": {
|
||||
"sourceId": "x-premium-web",
|
||||
"formula": "USD 8/月 * 5个月 * 6.79 USD_CNY",
|
||||
"originalAmount": 40,
|
||||
"originalCurrency": "USD"
|
||||
},
|
||||
"note": "估算金额,后续以实际账单或订阅档位为准。"
|
||||
},
|
||||
{
|
||||
"id": "ADV-0010",
|
||||
"module": "up主",
|
||||
"name": "腾讯高级VIP(2000人群)1年",
|
||||
"amount": 240,
|
||||
"currency": "CNY",
|
||||
"status": "estimated",
|
||||
"estimate": {
|
||||
"sourceId": "qq-super-vip-annual",
|
||||
"formula": "QQ 超级会员年费 240 元/年"
|
||||
},
|
||||
"note": "按超级会员年费估算;2000 人群资格与当前账号会员等级/年费资格有关,实付和资格以腾讯开通页为准。"
|
||||
},
|
||||
{
|
||||
"id": "ADV-0011",
|
||||
"module": "资产",
|
||||
"name": "UI 阿令",
|
||||
"amount": 13000,
|
||||
"currency": "CNY",
|
||||
"status": "recorded",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"id": "ADV-0012",
|
||||
"module": "阿里云服务",
|
||||
"name": "阿里云服务",
|
||||
"amount": 150,
|
||||
"currency": "CNY",
|
||||
"status": "recorded",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"id": "ADV-0013",
|
||||
"module": "宣发模块",
|
||||
"name": "喵燐老师 PV 2支",
|
||||
"amount": 5000,
|
||||
"currency": "CNY",
|
||||
"status": "recorded",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"id": "ADV-0014",
|
||||
"module": "资产",
|
||||
"name": "人鱼老师 UI设计",
|
||||
"amount": 1500,
|
||||
"currency": "CNY",
|
||||
"status": "recorded",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"id": "ADV-0015",
|
||||
"module": "宣发模块",
|
||||
"name": "上海THO展会",
|
||||
"amount": 400,
|
||||
"currency": "CNY",
|
||||
"status": "recorded",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"id": "ADV-0016",
|
||||
"module": "宣发模块",
|
||||
"name": "杭州THO展会",
|
||||
"amount": 450,
|
||||
"currency": "CNY",
|
||||
"status": "recorded",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"id": "ADV-0017",
|
||||
"module": "宣发模块",
|
||||
"name": "周边印刷",
|
||||
"amount": 1156,
|
||||
"currency": "CNY",
|
||||
"status": "recorded",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"id": "ADV-0018",
|
||||
"module": "资产",
|
||||
"name": "3D小人模型制作",
|
||||
"amount": 1000,
|
||||
"currency": "CNY",
|
||||
"status": "recorded",
|
||||
"note": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -69,7 +69,7 @@
|
||||
},
|
||||
"heroes": {
|
||||
"label": "英雄立绘",
|
||||
"count": 58,
|
||||
"count": 64,
|
||||
"groups": {
|
||||
"_root": [
|
||||
"assets/heroes/BritishByakuren.png",
|
||||
@ -114,9 +114,13 @@
|
||||
"assets/heroes/IndianYuugi_Juggernaut.png",
|
||||
"assets/heroes/NorwayAunn.png",
|
||||
"assets/heroes/NorwayAunn_Gold.png",
|
||||
"assets/heroes/NorwayAunn_Juggernaut.png",
|
||||
"assets/heroes/NorwayAunn_Juggernaut_Stone.png",
|
||||
"assets/heroes/NorwayAunn_Silver.png",
|
||||
"assets/heroes/NorwayAunn_Stone.png",
|
||||
"assets/heroes/NorwayKasen.png",
|
||||
"assets/heroes/NorwayKasen_Gold.png",
|
||||
"assets/heroes/NorwayKasen_Oni.png",
|
||||
"assets/heroes/NorwayKasen_Silver.png",
|
||||
"assets/heroes/NorwayReimu.png",
|
||||
"assets/heroes/NorwayReimu_Gold.png",
|
||||
@ -124,6 +128,8 @@
|
||||
"assets/heroes/NorwaySuika.png",
|
||||
"assets/heroes/NorwaySuika_Gold.png",
|
||||
"assets/heroes/NorwaySuika_Huge.png",
|
||||
"assets/heroes/NorwaySuika_Juggernaut.png",
|
||||
"assets/heroes/NorwaySuika_Medium.png",
|
||||
"assets/heroes/NorwaySuika_Silver.png",
|
||||
"assets/heroes/NorwaySuika_Small.png",
|
||||
"assets/heroes/NorwaySumireko.png",
|
||||
|
||||