车阶与马阶英雄

This commit is contained in:
daixiawu 2026-06-24 20:24:00 +08:00
parent aac3eb9254
commit d2290b50f6
170 changed files with 244571 additions and 14290 deletions

View File

@ -0,0 +1,49 @@
---
name: th1-chronic-issue
description: TH1 recurring-defect workflow for long-term chronic issue tracking and root-cause fixes. Use when the user says a problem happened again, asks to add an issue to the 癌症清单/长期清单/chronic issue list, demands 根治/永久修复, or when Codex identifies a repeated generator/build/serialization/localization/network/save defect that should not be handled as a one-off patch.
---
# TH1 Chronic Issue
## Overview
Use this skill to turn repeated TH1 defects into durable engineering work: record the recurring pattern, fix the root cause, and add a guardrail so the same class of bug is easier to catch next time.
## Workflow
1. Run the full-access preflight first.
2. Use the relevant TH1 domain skill before changing subsystem code, for example `th1-base` for MemoryPack/build/generator work or `th1-network-sync` for multiplayer issues.
3. Preserve the exact repeated symptom with date, command/error text, affected entrypoint, and why it is recurring.
4. Update `MD/ChronicIssueList/index.md` and create or update a focused issue record under `MD/ChronicIssueList/`.
5. Root-cause the recurrence. Prefer fixing generators, validators, tests, editor menu logic, or shared workflow entrypoints over manually patching generated output.
6. Add a guardrail where practical: script, deterministic check, unit/editor test, build validation, or explicit workflow rule.
7. Verify with the narrowest reliable command. If Unity Editor validation is still required, state that gap clearly.
## Chronic Issue Record
Use IDs like `TH1-CI-YYYY-MM-DD-NNN`. Each record should include:
- Status
- First recorded date
- Raw symptom and affected command/menu/path
- Why this is recurring
- Root cause
- Root-cause fix
- Guardrail added
- Verification performed
- Remaining validation gaps
## MemoryPack Skill Generator Guardrail
For repeated `MemoryPackable`/partial/union errors:
- Inspect `Unity/Assets/Scripts/TH1_Logic/Editor/MemoryPackUnionGenerator.cs`.
- Check generated files under `Unity/Assets/Scripts/TH1_Logic/Skill/Generate/`.
- Do not hand-edit generated files as the only fix.
- Run `scripts/check_memorypack_skill_duplicates.ps1` from this skill to detect source classes that already have `[MemoryPackable]` while a generated `{Class}.MemoryPackable.g.cs` also exists.
Example:
```powershell
.codex/skills/th1-chronic-issue/scripts/check_memorypack_skill_duplicates.ps1
```

View File

@ -0,0 +1,4 @@
interface:
display_name: "TH1 Chronic Issues"
short_description: "Track recurring TH1 defects and force root-cause fixes."
default_prompt: "Use $th1-chronic-issue to triage a recurring TH1 problem and update the chronic issue list."

View File

@ -0,0 +1,51 @@
param(
[string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..\..\..")).Path
)
$skillRoot = Join-Path $RepoRoot "Unity\Assets\Scripts\TH1_Logic\Skill"
$generateRoot = Join-Path $skillRoot "Generate"
if (-not (Test-Path $skillRoot)) {
Write-Error "Skill root not found: $skillRoot"
exit 2
}
$sourceMemoryPackable = @{}
$sourceFiles = Get-ChildItem -Path $skillRoot -Recurse -Filter "*.cs" |
Where-Object {
$_.FullName -notlike "*\Generate\*" -and
$_.Name -notlike "*.g.cs"
}
$classPattern = "(?ms)(?<attrs>(?:\s*\[[^\]]+\]\s*)*)\s*public\s+(?:abstract\s+)?(?:partial\s+)?class\s+(?<name>[A-Za-z_][A-Za-z0-9_]*)\s*:\s*SkillBase\b"
foreach ($file in $sourceFiles) {
$text = Get-Content -Raw -LiteralPath $file.FullName
foreach ($match in [regex]::Matches($text, $classPattern)) {
$attrs = $match.Groups["attrs"].Value
if ($attrs -match "\[\s*MemoryPackable(?:Attribute)?\b") {
$sourceMemoryPackable[$match.Groups["name"].Value] = $file.FullName
}
}
}
$duplicates = @()
if (Test-Path $generateRoot) {
Get-ChildItem -Path $generateRoot -Filter "*.MemoryPackable.g.cs" | ForEach-Object {
$className = $_.BaseName -replace "\.MemoryPackable\.g$", ""
if ($sourceMemoryPackable.ContainsKey($className)) {
$duplicates += [pscustomobject]@{
Class = $className
Source = $sourceMemoryPackable[$className]
Generated = $_.FullName
}
}
}
}
if ($duplicates.Count -gt 0) {
Write-Host "Duplicate MemoryPackable skill partials found:" -ForegroundColor Red
$duplicates | Format-Table -AutoSize
exit 1
}
Write-Host "OK: no duplicate MemoryPackable skill partials found."

View File

@ -1,6 +1,6 @@
---
name: th1-content-gating
description: TH1 project-specific content availability and release-channel gating guide for faction/empire and hero visibility across public, Steam beta, and editor-only builds. Use whenever Codex designs or changes TH1 content gating, faction/empire availability, Reimu/Hakurei or other unreleased empire rollout, Steam beta macros, multiplayer player-slot civ selection, singleplayer outside select faction lists, AI/default civ selection, hero deployment selection, PlayerActionType.SelectHero validation, or package-channel separation for public/beta/internal content.
description: TH1 project-specific content availability and release-channel gating guide for faction/empire and hero visibility across public, Steam beta, and editor-only builds. Use whenever Codex designs or changes TH1 content gating, adds or rolls out a new faction/empire, changes faction/empire availability, Reimu/Hakurei or other unreleased empire rollout, Steam beta macros, multiplayer player-slot civ selection, multiplayer host room faction selection permissions, singleplayer outside select faction lists, AI/default civ selection, hero deployment selection, PlayerActionType.SelectHero validation, or package-channel separation for public/beta/internal content.
---
# TH1 Content Gating
@ -44,20 +44,52 @@ Recommended runtime shape:
```csharp
public static class ContentGate
{
public static bool IsSteamBetaBuild
private const string PublicSteamBranchName = "public";
private const int SteamBranchNameBufferSize = 128;
public static bool IsSteamBetaBranch
{
get
{
#if UNITY_EDITOR
return true;
#elif STEAM_BETA
return true;
#else
return false;
return IsRunningOnSteamBetaBranch();
#endif
}
}
private static bool IsRunningOnSteamBetaBranch()
{
#if STEAM_CHANNEL || STEAMWORKS_NET
if (!TryGetSteamBranchName(out var branchName)) return false;
return !string.Equals(branchName, PublicSteamBranchName, StringComparison.OrdinalIgnoreCase);
#else
return false;
#endif
}
#if STEAM_CHANNEL || STEAMWORKS_NET
private static bool TryGetSteamBranchName(out string branchName)
{
branchName = null;
try
{
if (!SteamAPI.IsSteamRunning()) return false;
if (!SteamApps.GetCurrentBetaName(out branchName, SteamBranchNameBufferSize)) return false;
branchName = branchName?.Trim();
return !string.IsNullOrEmpty(branchName);
}
catch (Exception)
{
branchName = null;
return false;
}
}
#endif
public static bool IsEditorBuild
{
get
@ -75,7 +107,7 @@ public static class ContentGate
return availability switch
{
ContentAvailability.Public => true,
ContentAvailability.SteamBeta => IsSteamBetaBuild,
ContentAvailability.SteamBeta => IsSteamBetaBranch,
ContentAvailability.EditorOnly => IsEditorBuild,
_ => false
};
@ -83,7 +115,7 @@ public static class ContentGate
}
```
Prefer a project-specific beta define such as `STEAM_BETA`. Do not infer beta from `STEAM_CHANNEL` or `STEAMWORKS_NET`; those mean Steam integration is compiled, not that the package is beta. Wire `STEAM_BETA` from the build window/command line beside existing Steam defines.
Do not use a project-specific beta compile define for Steam branch availability. Compile defines describe the package that was built, not the Steam branch that is running it. For "same package, different Steam branch behavior", query `SteamApps.GetCurrentBetaName` at runtime and treat Steam's `public` branch as non-beta.
## Data Source
@ -106,12 +138,25 @@ public static bool CanUseHeroForPlayer(PlayerData player, GiantType giantType);
For heroes, resolve their owning `Empire` through `UnitTypeDataAssets.GetUnitTypeInfo(UnitType.Giant, giantType, 1, out info)` and apply both hero-specific and owning-empire gates. If a hero has no explicit hero gate, inherit its empire gate.
## New Empire Addition Checklist
When adding a new playable faction/empire, treat content gating as one required integration module, not as a later UI cleanup.
- Add the empire to the authoritative content identity and availability mapping. Missing entries default to `Public`, so explicitly set experimental empires such as Hakurei/Reimu to `SteamBeta` or `EditorOnly` when needed.
- Verify `ContentGate.CanUseEmpire(Empire)` and `ContentGate.CanUseEmpire(civId, forceId)` accept or reject the new empire in the intended channels.
- Update every player-facing selection source that uses a fixed civ/force list. Do not assume DataAssets-driven lists cover all room widgets.
- For multiplayer rooms, always inspect both `UIOutsideMultiplayView` and `UIOutsideMultiplayMemberRowMono`. The room-level list and the row-widget list must expose the same available empires after `ContentGate` filtering.
- Confirm the lobby owner/host can cycle to and select the new empire for their own human row, host-controlled rows, open rows, and AI rows when product rules allow it.
- Confirm host authority still rejects unavailable new empires from client `ChangeCivMessage` and from stale lobby/save data.
- If a new empire is intentionally hidden in public builds, verify public builds cannot restore it from last-selected preferences, lobby config, random/default picks, or AI slot defaults.
## Required Enforcement Points
Cover all three surfaces from the user's requirement:
1. Multiplayer player-slot civ selection.
- Filter civ/force options shown in `UIOutsideMultiplayView` and row widgets.
- Keep `UIOutsideMultiplayView.RoomForceOptions` / `GetAvailableRoomForceOptions()` and `UIOutsideMultiplayMemberRowMono` row option logic in sync. A new empire added only to the room-level list can still appear unselectable to the host because the row widget may refresh display from its older local list.
- Validate `MapConfig.UpdateSelfMemberCiv`, `UpdateSelfMemberConfig`, `SetPlayerSlotCiv`, and host-side `UpdateMemberCiv`/`ApplyMemberCivLocal` paths before accepting `MemberCiv.CivId/ForceId`.
- Host must reject unavailable `ChangeCivMessage` even if a client shows or sends it.
- If an existing lobby/config/save contains a now-unavailable civ, normalize it to the first available public/beta-legal empire before start, or block start with a clear internal log.
@ -135,9 +180,9 @@ Current code uses Steam-related defines such as `STEAM_CHANNEL`, `STEAMWORKS_NET
- `STEAM_CHANNEL` / `STEAMWORKS_NET`: Steam services compiled.
- `STEAM_TEST`: test/editor feature switch.
- `STEAM_BETA`: required new content-release channel switch for beta-only content.
- Steam beta branch: runtime content-release channel switch from `SteamApps.GetCurrentBetaName`; `public` is non-beta, any configured beta branch is beta unless product policy narrows the accepted branch names.
Add `STEAM_BETA` to the unified build flow when creating Steam beta packages. Public Steam packages must compile without `STEAM_BETA`; Unity Editor should still show all content so designers can test `EditorOnly` and `SteamBeta` content.
Do not add a beta-only compile define to the unified build flow. A single Steam package can be uploaded to public and beta branches, and the game should select branch-specific content by runtime Steam branch name. Unity Editor should still show all content so designers can test `EditorOnly` and `SteamBeta` content.
## Implementation Workflow
@ -146,6 +191,7 @@ Add `STEAM_BETA` to the unified build flow when creating Steam beta packages. Pu
- `Unity/Assets/Scripts/TH1_UI/View/Outside/UIOutsideSelectView.cs`
- `Unity/Assets/Scripts/TH1_UI/View/Outside/UIOutsideSelectCheckPanelMono.cs`
- `Unity/Assets/Scripts/TH1_UI/View/Outside/UIOutsideMultiplayView.cs`
- `Unity/Assets/Scripts/TH1_UI/View/Outside/UIOutsideMultiplayMemberRowMono.cs`
- `Unity/Assets/Scripts/TH1_UI/View/Info/UIInfoHeroView.cs`
- `Unity/Assets/Scripts/TH1_Logic/Action/PlayerActionLogic.cs`
- `Unity/Assets/Scripts/TH1_Logic/Editor/TH1UnifiedBuildWindow.cs`
@ -164,7 +210,7 @@ Add `STEAM_BETA` to the unified build flow when creating Steam beta packages. Pu
5. Enforce in authority paths after UI filtering.
6. Wire `STEAM_BETA` into build defines and ensure public builds clear it.
6. Ensure Steam builds include normal Steam integration defines (`STEAM_CHANNEL` / `STEAMWORKS_NET`) so runtime branch detection can call Steamworks. Do not add a separate beta-only compile define.
7. Add logs for blocked unavailable content in authoritative paths. Keep game-facing text out of code unless the user explicitly requests UI copy.
@ -187,8 +233,10 @@ dotnet build Unity/TH1.Steam.Editor.csproj --no-restore
Manual validation matrix:
- Unity Editor: public, beta, and editor-only empires/heroes are visible and selectable.
- Steam beta build with `STEAM_BETA`: public and beta content are visible/selectable; editor-only content is hidden/rejected.
- Public build without `STEAM_BETA`: only public content is visible/selectable; beta/editor-only content is hidden/rejected.
- Same Steam package launched from a beta branch: public and beta content are visible/selectable; editor-only content is hidden/rejected.
- Same Steam package launched from the public branch: only public content is visible/selectable; beta/editor-only content is hidden/rejected.
- Multiplayer lobby owner/host can cycle to each allowed new empire on their own human row and the display remains on the selected empire after the row refreshes.
- Multiplayer lobby owner/host can set allowed new empires on host-controlled, open, and AI slots when those slot types are enabled.
- Multiplayer host receives a hidden civ from a client: host rejects it and does not mutate `MapConfig`.
- Random/AI/default selection never picks unavailable empires.
- Old saved preference or old lobby slot using hidden content is normalized or blocked deterministically.
@ -196,8 +244,9 @@ Manual validation matrix:
## Pitfalls
- Do not only hide buttons; network/action paths must reject.
- Do not add a new empire to only one multiplayer room option list. `UIOutsideMultiplayView` and `UIOutsideMultiplayMemberRowMono` must stay consistent or the host may be unable to visibly select the empire.
- Do not use `UNITY_EDITOR || STEAM_CHANNEL` for beta content.
- Do not make beta availability depend on Steam login or runtime Steam branch APIs unless explicitly requested; package defines are deterministic and testable.
- Do not make beta availability depend on compile-time beta defines; they cannot support one package behaving differently on Steam public and beta branches.
- Do not put release policy in prefab active flags.
- Do not mutate generated config or export-flow files unless the user explicitly asks for that workflow.
- Do not add player-facing hardcoded strings for blocked content.

View File

@ -145,7 +145,7 @@ Use this hero class standard as authoritative. The letter grade is the class pri
| Bishop / 相 | `A(15)` | `B(2)` | `C(1)` | `C(1)` | `A(2)` | `A(45)` | `B(3)` | `B(3)` | `A(2)` | `B(2)` |
| Queen / 后 | `C(10)` | `S(3)` | `B(2)` | `A(2)` | `B(1.5)` | `C(30)` | `B(3)` | `B(3)` | `S(3)` | `B(2)` |
| King / 王 | `S(20)` | `B(2)` | `B(2)` | `C(1)` | `C(1)` | `B(35)` | `S(4)` | `B(3)` | `C(1)` | `C(1)` |
| Rook / 车 | `S(20)` | `S(3)` | `S(3)` | `C(1)` | `C(1)` | `S(50)` | `S(4)` | `S(5)` | `C(1)` | `C(1)` |
| Rook / 车 | `S(20)` | `S(3)` | `S(2.5)` | `C(1)` | `C(1)` | `S(50)` | `S(4)` | `S(5)` | `C(1)` | `C(1)` |
| Knight / 马 | `C(10)` | `C(1)` | `S(3)` | `C(1)` | `S(2.5)` | `C(30)` | `B(3)` | `A(4)` | `C(1)` | `S(3)` |
Do not use first-four-faction historical values as the standard when they conflict with this table. Existing hero data can explain legacy deviations, but new or audited designs should start from this class standard.

View File

@ -0,0 +1,58 @@
---
name: th1-serious-incident
description: TH1 project workflow for severe incident triage, root-cause attribution, postmortem writing, and permanent incident log maintenance. Use when the user reports a serious gameplay, data, network, save, build, release, or production regression; asks which commit caused a severe problem; asks to record or maintain 事故记录, 严重事故, 事件簿, postmortem, or root-cause notes; or Codex discovers a high-impact defect that needs a durable record under MD/SeriousIncidentLog.
---
# TH1 Serious Incident
## Workflow
1. Run and report the full-access preflight before any file, command, browse, or edit work.
2. Use the relevant TH1 domain skill first for the subsystem involved, such as `th1-action-logic`, `th1-hero-implementation`, `th1-network-sync`, `th1-game-archive`, or `th1-online-debug`.
3. Preserve the raw symptom: who reported it, absolute date, exact observed behavior, expected behavior, affected mode/version if known, and any reproduction notes.
4. Trace the cause with source evidence before assigning blame:
- Use `rg` to find the code path.
- Use `git blame -L <start>,<end> -- <file>` on the faulty lines.
- Use `git log -S "<changed expression>" -- <file>` and, when needed, `git log -G "<regex>" -- <file>`.
- Use `git show --stat --summary --format=fuller <commit>` and a focused `git show --unified=<n> <commit> -- <file>`.
- Distinguish the original causal commit, later commits that changed the symptom, and the current fix.
5. Fix the defect when the user asked for repair or when the fix is clearly in scope. Keep edits narrow and verify with the project build or the most relevant deterministic check.
6. Create or update an incident record under `MD/SeriousIncidentLog/`.
7. Update `MD/SeriousIncidentLog/index.md` every time a new incident is opened, fixed, or materially reclassified.
## Incident Records
Use `TH1-SI-YYYY-MM-DD-NNN` as the incident id. Store each incident as:
```text
MD/SeriousIncidentLog/YYYY-MM-DD-short-english-slug.md
```
Each incident entry must include:
- Incident id
- Severity and status
- First reported date and reporter/source
- Symptom
- Expected behavior
- Impact and affected systems
- Reproduction or detection path
- Root cause
- Introducing commit(s), with dates and commit messages
- Fix summary and changed files
- Verification performed
- Remaining validation gaps
- Follow-up guardrails or tests
## Severity
- `S1`: severe player-facing or production-impacting defect, economy corruption, save/network risk, release blocker, crash loop, or data loss.
- `S2`: serious feature regression with contained impact and a clear workaround.
- `S3`: notable defect worth tracking but not a release blocker.
## Rules
- Do not hide uncertainty. Mark unverified fields as `Pending` instead of presenting guesses as facts.
- Do not assign a single commit as the cause when the evidence shows an original defect plus a later symptom-shaping change.
- Do not edit generated export outputs just to record an incident.
- Keep the incident log concise enough to be useful during future emergency debugging.

View File

@ -0,0 +1,4 @@
interface:
display_name: "TH1 Serious Incident"
short_description: "Record and triage severe TH1 project incidents."
default_prompt: "Use $th1-serious-incident to record a severe TH1 incident with root cause, impact, fix, validation, and follow-up guardrails."

View File

@ -1,6 +1,6 @@
---
name: th1-tech-design
description: TH1 project-specific technology design and configuration guide for TechDataAssets, TechAtom, PlayerDataAssets TechPool/TechStart/display TechAtomList, faction-specific tech replacement, and first-four-faction tech patterns. Use when designing, reviewing, or editing TH1 empire/faction technologies, initial techs, unlock atoms, tech tree positions, or tech-related AI scoring.
description: TH1 project-specific technology design and configuration guide for TechDataAssets, TechAtom, PlayerDataAssets TechPool/TechStart/display TechAtomList, faction-specific tech replacement, and implemented faction tech patterns. Use when designing, reviewing, or editing TH1 empire/faction technologies, initial techs, unlock atoms, tech tree positions, or tech-related AI scoring.
---
# TH1 Tech Design
@ -21,11 +21,12 @@ Use this skill before changing `TechDataAssets.asset`, `TechDataAssets.cs`, or `
- Keep generated/export files untouched unless the user explicitly asks for export/import workflow changes.
- For a new build/train/upgrade action, verify or add the matching `ActionDataAssets.asset` row and runtime action logic.
- When replacing a tech, preserve its `CostLevel`, `TechTreeCircleViewType`, and intended father relationship unless the design explicitly changes the tree.
- When replacing a tech that other technologies depend on, also update the downstream technologies' `FatherTechList`. `FatherTechList` is an OR-list in the current runtime: a tech is learnable if the player has any listed father. If a faction removes base `Ramming` from `TechPool` and uses `HakureiRamming` instead, downstream shared techs such as `Aquatism` must list `HakureiRamming` as an alternate father, or that faction can never learn the shared downstream tech even though the tree visually keeps the slot.
- If a faction tech still needs the base action, include the original base TechAtom plus the new faction TechAtom. Do not replace the atom just for wording.
- If a text says a TechAtom has a gameplay effect, either ensure code consumes that TechAtom or move the text to the feature that actually implements it.
- Update AI scoring/mapping when replacing a tech that affects economy, units, diplomacy, sea access, or other strategic priorities.
## First Four Faction Pattern
## Implemented Faction Patterns
Use these as the local precedent for new faction techs.
@ -33,6 +34,7 @@ Use these as the local precedent for new faction techs.
- Kaguya/French (`ForceId 1`): starts with `KaguyaHunting`; replaces several land branch techs while keeping normal tree circles. Some unique techs are renamed but still carry base actions, such as `KaguyaTrade` retaining `BuildMarket + StartWonderWEALTH`.
- Kanako/Germany (`ForceId 2`): starts with `KanakoClimbing`; replaces many early/mid technologies. It keeps base movement/building atoms where required and layers mountain/industry-specific atoms on top. Display atoms remain 4 faction-defining atoms.
- Komeiji/Indian (`ForceId 3`): starts with `KomeijiIndianMuladhara`; replaces selected military/naval lines and uses an initial tech for global passive mechanics. Some numeric gaps are intentionally preserved. Display atoms show 4 core identity atoms.
- Hakurei/Norway (`ForceId 4`): starts with `HakureiVikingStart` and `HakureiFishing`; replaces `Fishing`, `Ramming`, `Strategy`, `Smithery`, `Diplomacy`, and `Trade` with faction TechTypes while still reusing shared downstream sea techs `Sailing`, `Aquatism`, and `Navigation`. Replacement father audits are mandatory here. Incident note: `Aquatism` originally listed only `Ramming` / `RemiliaRamming` as fathers, while Hakurei removed `Ramming` from `TechPool` and learned `HakureiRamming`; this made `Aquatism`/海洋防御 unlearnable for 博丽帝国 despite the player completing the intended military harbor slot.
The pattern is replacement-by-TechType in `TechPool`, not replacement by stuffing many atoms into `PlayerInfo.TechAtomList`.
@ -53,18 +55,23 @@ Use existing tech names as the style baseline before proposing or editing names.
1. Read current `PlayerInfo` for the target force/civ: `TechPool`, `TechStart`, and display `TechAtomList`.
2. Decode existing `TechInfo` rows for every base tech being replaced, including father techs, circle, cost, and atom list.
3. Append new `TechType`/`TechAtom` enum values. Preserve old numeric IDs.
4. Add or update `TechInfo` rows. Put real unlocks in `TechInfo.TechAtomList`.
5. Add or update `TechAtomInfo` rows. If `EnableAction` is true, verify the exact `CommonActionId`.
6. Update `PlayerInfo.TechPool` for learnable replacement, `TechStart` for true initial ownership, and `TechAtomList` only for up to 4 display atoms.
7. Implement any passive behavior in runtime code if no existing system consumes the new TechAtom.
8. Update AI scoring and unit/action mapping for replaced strategic techs.
9. Run the relevant build, usually `dotnet build Unity/TH1.Hotfix.csproj --no-restore`.
3. Decode all downstream `TechInfo` rows whose `FatherTechList` contains the replaced base tech, even if those downstream techs remain shared/common. Decide whether each downstream tech should accept the faction replacement as an alternate father.
4. Append new `TechType`/`TechAtom` enum values. Preserve old numeric IDs.
5. Add or update `TechInfo` rows. Put real unlocks in `TechInfo.TechAtomList`.
6. For each replaced tech, update both directions of the tree: the replacement tech's own `FatherTechList`, and every downstream shared tech's `FatherTechList` that needs the replacement as an alternate parent.
7. Add or update `TechAtomInfo` rows. If `EnableAction` is true, verify the exact `CommonActionId`.
8. Update `PlayerInfo.TechPool` for learnable replacement, `TechStart` for true initial ownership, and `TechAtomList` only for up to 4 display atoms.
9. Implement any passive behavior in runtime code if no existing system consumes the new TechAtom.
10. Update AI scoring and unit/action mapping for replaced strategic techs.
11. Run the relevant build, usually `dotnet build Unity/TH1.Hotfix.csproj --no-restore`.
## Common Checks
- Does the faction still have the same number of learnable tree slots as the base civ unless intentionally changed?
- Does every replacement tech have reachable father techs within that faction's `TechPool`?
- Does every shared downstream tech in the faction `TechPool` have at least one reachable father after replacement? Pay special attention to mixed lines where early slots are faction-specific but later slots remain common, such as Hakurei `HakureiFishing`/`HakureiRamming` feeding common `Sailing`/`Aquatism`/`Navigation`.
- For every removed base tech, has `TechDataAssets.GetNextTechs(baseTech)` been checked and have intended shared descendants received the faction replacement in their `FatherTechList`?
- Can the target faction learn every tech in its `TechPool` from its `TechStart` by repeatedly applying the runtime condition `TechSet intersects FatherTechList`? This reachability simulation catches invisible or unclickable techs before manual Unity testing.
- Are display atoms only identity highlights, not hidden effect carriers?
- Are obsolete atoms no longer referenced by any `TechInfo.TechAtomList` or `PlayerInfo.TechAtomList`?
- Does the real gameplay code match the TechAtom/tech description?

View File

@ -8,6 +8,8 @@ This repository is a Unity 2022.3 LTS / ET Framework turn-based strategy game. T
- For architecture questions, read the `MD/GameMDFramework/00-*` overview document and the relevant subsystem document before editing.
- For code navigation, read `Unity/graphify-out/GRAPH_REPORT.md` for Unity code and `graphify-out/GRAPH_REPORT.md` for whole-repository context.
- If a task touches action execution, networking, localization, online errors, backend upload, or CrashSight reports, use the matching `.codex/skills/th1-*` skill first.
- For severe incidents, postmortems, root-cause attribution, or requests to maintain an incident log, use `.codex/skills/th1-serious-incident` and update `MD/SeriousIncidentLog/index.md`.
- For repeated defects, problems the user says happened again, or requests to 根治 / add to 癌症清单, use `.codex/skills/th1-chronic-issue` and update `MD/ChronicIssueList/index.md` before treating the issue as fixed.
- For broad TH1 Unity/code/resource/build changes, use `.codex/skills/th1-base` first, then layer the narrower business skill on top.
- Claude-era context remains in `.claude/` and `Unity/Assets/Scripts/CLAUDE.md`; treat it as historical source material, not the active entrypoint.
@ -45,6 +47,7 @@ This repository is a Unity 2022.3 LTS / ET Framework turn-based strategy game. T
- `th1-server-backend`: Aliyun Function Compute, OSS/STS upload, collect data, and backend debugging.
- `th1-online-debug`: production/obfuscated stack decoding and online issue tracing.
- `th1-crashsight-daily`: daily CrashSight triage and report generation.
- `th1-chronic-issue`: recurring defect workflow for the long-term 癌症清单 and root-cause guardrails.
- `.agents/skills` contains migrated Claude-era reference skills, including `graphify`, `th1-architecture`, `th1-ui-patterns`, `th1-config-guide`, `th1-ai-nodes`, and `th1-anim-scope`.
- `.codex/agents` contains migrated specialist agent profiles. Use them as role guidance for large investigations, but keep the final integration decisions in this main workspace.

View File

@ -1,5 +1,5 @@
{
"nextId": 385,
"nextId": 387,
"bugs": [
{
"id": 2,
@ -3931,12 +3931,34 @@
"id": 384,
"title": "bug铃仙击杀后再攻击失效可能是幻像太多的原因 (击杀精微身)",
"description": "",
"status": "open",
"status": "fixed",
"priority": "medium",
"module": "",
"longTerm": false,
"createdAt": 1782100527558,
"updatedAt": 1782100527558
"updatedAt": 1782217588706
},
{
"id": 385,
"title": "征服模式对方失去所有城市不会胜利,我方投降不会失败,并且投降后可以出英雄和小兵",
"description": "",
"status": "open",
"priority": "medium",
"module": "",
"longTerm": false,
"createdAt": 1782217585290,
"updatedAt": 1782217585290
},
{
"id": 386,
"title": "在房间里切阵营有时候切不到博丽帝国的bug",
"description": "",
"status": "open",
"priority": "medium",
"module": "",
"longTerm": false,
"createdAt": 1782217693173,
"updatedAt": 1782217693173
}
]
}

View File

@ -79,6 +79,37 @@
"summary": "Three local white-only transparent PNG silhouette previews for girl oni form: girl head, oni horns, oni face negative space."
}
]
},
{
"id": "batch-suika",
"title": "Suika Skill Icon Drafts",
"scope": "suika",
"status": "reviewing",
"created_at": "2026-06-24T19:51:59+08:00",
"summary": "Draft white-only transparent PNG skill icons for Suika skills and actions.",
"requirements": [
"Final icons are white foreground on transparent background.",
"Use simple low-poly silhouettes matching TH1 Suika skill icon language.",
"Keep drafts under Design/drafts until explicitly approved for Unity asset integration."
],
"rounds": [
{
"id": "preview-suika-ameno-tajikarao-throw-20260624",
"title": "Preview: Suika Ameno-Tajikarao Throw icon draft",
"path": "suika/20260624-195159",
"status": "generated",
"created_at": "2026-06-24T19:51:59+08:00",
"summary": "Three gpt-image-2 white-only transparent PNG silhouette drafts: throw arc, oni lift throw, and landing impact."
},
{
"id": "preview-suika-ameno-tajikarao-throw-option3-variations-20260624",
"title": "Preview: Suika Ameno-Tajikarao Throw option 3 variations",
"path": "suika/20260624-195939",
"status": "generated",
"created_at": "2026-06-24T19:59:39+08:00",
"summary": "Six gpt-image-2 white-only transparent PNG variations based on the selected option 3 direction."
}
]
}
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -0,0 +1,2 @@
# Ameno-Tajikarao Throw Skill Icon Feedback

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 998 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -0,0 +1,33 @@
{
"scope": "suika",
"run_id": "20260624-195159",
"object": "Ameno-Tajikarao Throw",
"model": "gpt-image-2",
"final_format": "256x256 transparent PNG, white-only foreground",
"reference_icons": [
"Unity/Assets/BundleResources/TH1UI/Icon/SkillIcon/Hakurei/Skill_SuikaMiniSpawnAfterMove.png",
"Unity/Assets/BundleResources/TH1UI/Icon/SkillIcon/Hakurei/Skill_SuikaDamageHalveDropMini.png",
"Unity/Assets/BundleResources/TH1UI/Icon/SkillIcon/Hakurei/Skill_SuikaBigForm.png",
"Unity/Assets/BundleResources/TH1UI/Icon/SkillIcon/Hakurei/Skill_SuikaGiantForm.png",
"Unity/Assets/BundleResources/TH1UI/Icon/SkillIcon/Hakurei/Skill_SuikaFallingSplash.png",
"Unity/Assets/BundleResources/TH1UI/Icon/SkillIcon/Hakurei/Skill_SuikaMiniUnitMarker.png"
],
"variants": [
{
"id": 1,
"name": "Throw Arc",
"path": "Design/drafts/skill-icons/suika/20260624-195159/variants/ameno-tajikarao-throw-option1.png"
},
{
"id": 2,
"name": "Oni Lift Throw",
"path": "Design/drafts/skill-icons/suika/20260624-195159/variants/ameno-tajikarao-throw-option2.png"
},
{
"id": 3,
"name": "Landing Impact",
"path": "Design/drafts/skill-icons/suika/20260624-195159/variants/ameno-tajikarao-throw-option3.png"
}
],
"preview": "Design/drafts/skill-icons/suika/20260624-195159/ameno-tajikarao-throw-preview.png"
}

View File

@ -0,0 +1,4 @@
{
"selected": null,
"feedback": ""
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

@ -0,0 +1,2 @@
# Ameno-Tajikarao Throw Option 3 Variation Feedback

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1018 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1020 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1012 KiB

View File

@ -0,0 +1,47 @@
{
"scope": "suika",
"run_id": "20260624-195939",
"object": "Ameno-Tajikarao Throw option 3 variations",
"model": "gpt-image-2",
"source_reference": "Design/drafts/skill-icons/suika/20260624-195159/variants/ameno-tajikarao-throw-option3.png",
"final_format": "256x256 transparent PNG, white-only foreground",
"variants": [
{
"id": "v01",
"name": "Heavy Smash",
"summary": "Strong downward smash and big impact burst",
"path": "Design/drafts/skill-icons/suika/20260624-195939/variants/ameno-tajikarao-throw-v01.png"
},
{
"id": "v02",
"name": "Compact UI",
"summary": "Simplified arc, target, and ground crack",
"path": "Design/drafts/skill-icons/suika/20260624-195939/variants/ameno-tajikarao-throw-v02.png"
},
{
"id": "v03",
"name": "Grab Hand",
"summary": "Large grabbing hand cue with thrown target",
"path": "Design/drafts/skill-icons/suika/20260624-195939/variants/ameno-tajikarao-throw-v03.png"
},
{
"id": "v04",
"name": "Landing Impact",
"summary": "Damage impact emphasized",
"path": "Design/drafts/skill-icons/suika/20260624-195939/variants/ameno-tajikarao-throw-v04.png"
},
{
"id": "v05",
"name": "Crescent Arc",
"summary": "Large throw arc wrapping the icon",
"path": "Design/drafts/skill-icons/suika/20260624-195939/variants/ameno-tajikarao-throw-v05.png"
},
{
"id": "v06",
"name": "Target Clear",
"summary": "Thrown target silhouette reads clearly",
"path": "Design/drafts/skill-icons/suika/20260624-195939/variants/ameno-tajikarao-throw-v06.png"
}
],
"preview": "Design/drafts/skill-icons/suika/20260624-195939/ameno-tajikarao-throw-option3-variations-preview.png"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,6 @@
{"prompt":"Use case: logo-brand\nAsset type: TH1 Unity skill icon draft, 256x256 final transparent PNG after chroma-key removal\nPrimary request: Create variation A based on the previous best option 3 for Suika Ibuki skill icon Ameno-Tajikarao Throw. Keep the same core read: a thrown unit silhouette hurled diagonally downward into a blocky cracked impact mark. Make the downward smash feel heavier and more decisive, with a broad curved motion band and a large angular impact burst. Match TH1 Suika skill icons: white-only silhouette, low-poly, compact action symbol.\nStyle/medium: ultra-simple low-poly geometric game ability icon, white-only flat silhouette, thick uniform strokes, 2-5 large blocky masses, large negative-space cutouts.\nComposition/framing: centered, generous padding, diagonal throw motion from upper right to lower left, impact at bottom center; no frame, card, border.\nConstraints: pure white subject only; perfectly flat solid #00ff00 chroma-key background for removal; no color in subject; no gray shading; no text, numbers, letters, watermark; no shadow, glow, gradient, bevel, 3D render, frame, border, or background scenery; no tiny particles, dots, hairline strokes, ornate detail, portrait face, or full character illustration.","model":"gpt-image-2","quality":"medium","size":"1024x1024","out":"ameno-tajikarao-throw-v01-source.png"}
{"prompt":"Use case: logo-brand\nAsset type: TH1 Unity skill icon draft, 256x256 final transparent PNG after chroma-key removal\nPrimary request: Create variation B based on the previous best option 3 for Suika Ibuki skill icon Ameno-Tajikarao Throw. Make it more compact and UI-symbol-like: one clean curved throw band, one small grabbed unit silhouette, one simple angular ground crack. Reduce detail and keep the read clear at tiny button size. Match TH1 Suika skill icons: white-only silhouette, low-poly, compact action symbol.\nStyle/medium: ultra-simple low-poly geometric game ability icon, white-only flat silhouette, thick uniform strokes, 2-4 large blocky masses, large negative-space cutouts.\nComposition/framing: centered, generous padding, diagonal motion, impact at lower center; no frame, card, border.\nConstraints: pure white subject only; perfectly flat solid #00ff00 chroma-key background for removal; no color in subject; no gray shading; no text, numbers, letters, watermark; no shadow, glow, gradient, bevel, 3D render, frame, border, or background scenery; no tiny particles, dots, hairline strokes, ornate detail, portrait face, or full character illustration.","model":"gpt-image-2","quality":"medium","size":"1024x1024","out":"ameno-tajikarao-throw-v02-source.png"}
{"prompt":"Use case: logo-brand\nAsset type: TH1 Unity skill icon draft, 256x256 final transparent PNG after chroma-key removal\nPrimary request: Create variation C based on the previous best option 3 for Suika Ibuki skill icon Ameno-Tajikarao Throw. Emphasize the grabbing hand: a large blocky oni hand/claw at upper right has just released a small unit into a downward throw arc, ending in a cracked impact shape. Keep it simple, not a character portrait. Match TH1 Suika skill icons: white-only silhouette, low-poly, compact action symbol.\nStyle/medium: ultra-simple low-poly geometric game ability icon, white-only flat silhouette, thick uniform strokes, 2-5 large blocky masses, large negative-space cutouts.\nComposition/framing: centered, generous padding, hand and unit at upper right, impact at lower left/center; no frame, card, border.\nConstraints: pure white subject only; perfectly flat solid #00ff00 chroma-key background for removal; no color in subject; no gray shading; no text, numbers, letters, watermark; no shadow, glow, gradient, bevel, 3D render, frame, border, or background scenery; no tiny particles, dots, hairline strokes, ornate detail, portrait face, or full character illustration.","model":"gpt-image-2","quality":"medium","size":"1024x1024","out":"ameno-tajikarao-throw-v03-source.png"}
{"prompt":"Use case: logo-brand\nAsset type: TH1 Unity skill icon draft, 256x256 final transparent PNG after chroma-key removal\nPrimary request: Create variation D based on the previous best option 3 for Suika Ibuki skill icon Ameno-Tajikarao Throw. Emphasize the landing damage: a small thrown unit is about to hit a large cracked ground symbol, with a short bold throw arc above it. The icon should read as throw plus impact splash. Match TH1 Suika skill icons: white-only silhouette, low-poly, compact action symbol.\nStyle/medium: ultra-simple low-poly geometric game ability icon, white-only flat silhouette, thick uniform strokes, 2-5 large blocky masses, large negative-space cutouts.\nComposition/framing: centered, generous padding, big impact mark at lower center, small falling unit above, one curved band; no frame, card, border.\nConstraints: pure white subject only; perfectly flat solid #00ff00 chroma-key background for removal; no color in subject; no gray shading; no text, numbers, letters, watermark; no shadow, glow, gradient, bevel, 3D render, frame, border, or background scenery; no tiny particles, dots, hairline strokes, ornate detail, portrait face, or full character illustration.","model":"gpt-image-2","quality":"medium","size":"1024x1024","out":"ameno-tajikarao-throw-v04-source.png"}
{"prompt":"Use case: logo-brand\nAsset type: TH1 Unity skill icon draft, 256x256 final transparent PNG after chroma-key removal\nPrimary request: Create variation E based on the previous best option 3 for Suika Ibuki skill icon Ameno-Tajikarao Throw. Emphasize a huge crescent-shaped throw arc wrapping around the icon, with a compact tumbling unit inside the arc and a sharp blocky impact burst at the end. Match TH1 Suika skill icons: white-only silhouette, low-poly, compact action symbol.\nStyle/medium: ultra-simple low-poly geometric game ability icon, white-only flat silhouette, thick uniform strokes, 2-5 large blocky masses, large negative-space cutouts.\nComposition/framing: centered, generous padding, large crescent arc from upper left to lower right, impact at lower right/center; no frame, card, border.\nConstraints: pure white subject only; perfectly flat solid #00ff00 chroma-key background for removal; no color in subject; no gray shading; no text, numbers, letters, watermark; no shadow, glow, gradient, bevel, 3D render, frame, border, or background scenery; no tiny particles, dots, hairline strokes, ornate detail, portrait face, or full character illustration.","model":"gpt-image-2","quality":"medium","size":"1024x1024","out":"ameno-tajikarao-throw-v05-source.png"}
{"prompt":"Use case: logo-brand\nAsset type: TH1 Unity skill icon draft, 256x256 final transparent PNG after chroma-key removal\nPrimary request: Create variation F based on the previous best option 3 for Suika Ibuki skill icon Ameno-Tajikarao Throw. Make the thrown target unit more readable: a compact blocky humanoid silhouette tumbling through a diagonal arc, with a simple angular impact crack below. Keep the target unit abstract and not portrait-like. Match TH1 Suika skill icons: white-only silhouette, low-poly, compact action symbol.\nStyle/medium: ultra-simple low-poly geometric game ability icon, white-only flat silhouette, thick uniform strokes, 2-5 large blocky masses, large negative-space cutouts.\nComposition/framing: centered, generous padding, target unit near upper right/middle, impact at lower center, curved motion band connecting them; no frame, card, border.\nConstraints: pure white subject only; perfectly flat solid #00ff00 chroma-key background for removal; no color in subject; no gray shading; no text, numbers, letters, watermark; no shadow, glow, gradient, bevel, 3D render, frame, border, or background scenery; no tiny particles, dots, hairline strokes, ornate detail, portrait face, or full character illustration.","model":"gpt-image-2","quality":"medium","size":"1024x1024","out":"ameno-tajikarao-throw-v06-source.png"}

View File

@ -0,0 +1,4 @@
{
"selected": null,
"feedback": ""
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 960 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,8 @@
{"prompt":"Use case: logo-brand\nAsset type: TH1 Unity skill icon draft, 256x256 final transparent PNG after chroma-key removal\nPrimary request: Minimal variation 01 for Suika Ibuki skill icon Ameno-Tajikarao Throw. Use only three bold shapes: one thick curved throw arc, one tiny blocky target unit, one simple V-shaped ground impact. Very sparse, iconic, readable at tiny button size.\nStyle/medium: ultra-simple low-poly geometric game ability icon, white-only flat silhouette, thick uniform strokes, 2-3 large blocky masses, large negative-space cutouts.\nComposition/framing: centered, generous padding, diagonal throw from upper right into lower center impact; no frame, card, border.\nConstraints: pure white subject only; perfectly flat solid #00ff00 chroma-key background for removal; no color in subject; no gray shading; no text, numbers, letters, watermark; no shadow, glow, gradient, bevel, 3D render, frame, border, or background scenery; no tiny particles, dots, hairline strokes, ornate detail, portrait face, full character illustration, complex cracks, or many debris pieces.","model":"gpt-image-2","quality":"medium","size":"1024x1024","out":"ameno-tajikarao-throw-min-v01-source.png"}
{"prompt":"Use case: logo-brand\nAsset type: TH1 Unity skill icon draft, 256x256 final transparent PNG after chroma-key removal\nPrimary request: Minimal variation 02 for Suika Ibuki skill icon Ameno-Tajikarao Throw. Use only a large crescent arc and a small falling target above a single blocky impact wedge. No extra crack lines. Clear action glyph, not an illustration.\nStyle/medium: ultra-simple low-poly geometric game ability icon, white-only flat silhouette, thick uniform strokes, 2-3 large blocky masses, large negative-space cutouts.\nComposition/framing: centered, generous padding, crescent arc dominates, target and impact remain compact; no frame, card, border.\nConstraints: pure white subject only; perfectly flat solid #00ff00 chroma-key background for removal; no color in subject; no gray shading; no text, numbers, letters, watermark; no shadow, glow, gradient, bevel, 3D render, frame, border, or background scenery; no tiny particles, dots, hairline strokes, ornate detail, portrait face, full character illustration, complex cracks, or many debris pieces.","model":"gpt-image-2","quality":"medium","size":"1024x1024","out":"ameno-tajikarao-throw-min-v02-source.png"}
{"prompt":"Use case: logo-brand\nAsset type: TH1 Unity skill icon draft, 256x256 final transparent PNG after chroma-key removal\nPrimary request: Minimal variation 03 for Suika Ibuki skill icon Ameno-Tajikarao Throw. Make the throw path a thick boomerang-like white band, with a very small angular target silhouette near the tip and a tiny ground chevron. Extremely clean, only essential shapes.\nStyle/medium: ultra-simple low-poly geometric game ability icon, white-only flat silhouette, thick uniform strokes, 2-3 large blocky masses, large negative-space cutouts.\nComposition/framing: centered, generous padding, boomerang arc from upper left to lower right; no frame, card, border.\nConstraints: pure white subject only; perfectly flat solid #00ff00 chroma-key background for removal; no color in subject; no gray shading; no text, numbers, letters, watermark; no shadow, glow, gradient, bevel, 3D render, frame, border, or background scenery; no tiny particles, dots, hairline strokes, ornate detail, portrait face, full character illustration, complex cracks, or many debris pieces.","model":"gpt-image-2","quality":"medium","size":"1024x1024","out":"ameno-tajikarao-throw-min-v03-source.png"}
{"prompt":"Use case: logo-brand\nAsset type: TH1 Unity skill icon draft, 256x256 final transparent PNG after chroma-key removal\nPrimary request: Minimal variation 04 for Suika Ibuki skill icon Ameno-Tajikarao Throw. Use a simple diagonal arrow-like throw band, a small square target, and one angular splash triangle at the landing point. The icon should feel like a command/action symbol.\nStyle/medium: ultra-simple low-poly geometric game ability icon, white-only flat silhouette, thick uniform strokes, 2-4 large blocky masses, large negative-space cutouts.\nComposition/framing: centered, generous padding, clear diagonal from top right to bottom left; no frame, card, border.\nConstraints: pure white subject only; perfectly flat solid #00ff00 chroma-key background for removal; no color in subject; no gray shading; no text, numbers, letters, watermark; no shadow, glow, gradient, bevel, 3D render, frame, border, or background scenery; no tiny particles, dots, hairline strokes, ornate detail, portrait face, full character illustration, complex cracks, or many debris pieces.","model":"gpt-image-2","quality":"medium","size":"1024x1024","out":"ameno-tajikarao-throw-min-v04-source.png"}
{"prompt":"Use case: logo-brand\nAsset type: TH1 Unity skill icon draft, 256x256 final transparent PNG after chroma-key removal\nPrimary request: Minimal variation 05 for Suika Ibuki skill icon Ameno-Tajikarao Throw. Show only a tumbling target silhouette and a bold curved drop stroke ending in a single cracked diamond impact. Keep target abstract and tiny, no face, no hand, no character details.\nStyle/medium: ultra-simple low-poly geometric game ability icon, white-only flat silhouette, thick uniform strokes, 2-4 large blocky masses, large negative-space cutouts.\nComposition/framing: centered, generous padding, falling target in upper half, impact diamond in lower half; no frame, card, border.\nConstraints: pure white subject only; perfectly flat solid #00ff00 chroma-key background for removal; no color in subject; no gray shading; no text, numbers, letters, watermark; no shadow, glow, gradient, bevel, 3D render, frame, border, or background scenery; no tiny particles, dots, hairline strokes, ornate detail, portrait face, full character illustration, complex cracks, or many debris pieces.","model":"gpt-image-2","quality":"medium","size":"1024x1024","out":"ameno-tajikarao-throw-min-v05-source.png"}
{"prompt":"Use case: logo-brand\nAsset type: TH1 Unity skill icon draft, 256x256 final transparent PNG after chroma-key removal\nPrimary request: Minimal variation 06 for Suika Ibuki skill icon Ameno-Tajikarao Throw. Make it a near-symbol: one thick hook-shaped throw stroke, one small target block at the end, and one short ground mark. No impact burst, just a concise throw-to-ground icon.\nStyle/medium: ultra-simple low-poly geometric game ability icon, white-only flat silhouette, thick uniform strokes, 2-3 large blocky masses, large negative-space cutouts.\nComposition/framing: centered, generous padding, hook shape wraps around target; no frame, card, border.\nConstraints: pure white subject only; perfectly flat solid #00ff00 chroma-key background for removal; no color in subject; no gray shading; no text, numbers, letters, watermark; no shadow, glow, gradient, bevel, 3D render, frame, border, or background scenery; no tiny particles, dots, hairline strokes, ornate detail, portrait face, full character illustration, complex cracks, or many debris pieces.","model":"gpt-image-2","quality":"medium","size":"1024x1024","out":"ameno-tajikarao-throw-min-v06-source.png"}
{"prompt":"Use case: logo-brand\nAsset type: TH1 Unity skill icon draft, 256x256 final transparent PNG after chroma-key removal\nPrimary request: Minimal variation 07 for Suika Ibuki skill icon Ameno-Tajikarao Throw. Use one simple falling-body block and one oversized clean impact chevron, connected by a short curved stroke. Strong small-icon readability, almost like a pictogram.\nStyle/medium: ultra-simple low-poly geometric game ability icon, white-only flat silhouette, thick uniform strokes, 2-4 large blocky masses, large negative-space cutouts.\nComposition/framing: centered, generous padding, body above, impact below, no extra debris; no frame, card, border.\nConstraints: pure white subject only; perfectly flat solid #00ff00 chroma-key background for removal; no color in subject; no gray shading; no text, numbers, letters, watermark; no shadow, glow, gradient, bevel, 3D render, frame, border, or background scenery; no tiny particles, dots, hairline strokes, ornate detail, portrait face, full character illustration, complex cracks, or many debris pieces.","model":"gpt-image-2","quality":"medium","size":"1024x1024","out":"ameno-tajikarao-throw-min-v07-source.png"}
{"prompt":"Use case: logo-brand\nAsset type: TH1 Unity skill icon draft, 256x256 final transparent PNG after chroma-key removal\nPrimary request: Minimal variation 08 for Suika Ibuki skill icon Ameno-Tajikarao Throw. Use a compact downward swoosh and one geometric target silhouette landing on a simple triangular impact mark. Preserve the feel of option 3 but remove all unnecessary detail.\nStyle/medium: ultra-simple low-poly geometric game ability icon, white-only flat silhouette, thick uniform strokes, 2-4 large blocky masses, large negative-space cutouts.\nComposition/framing: centered, generous padding, compact diagonal motion, impact lower center; no frame, card, border.\nConstraints: pure white subject only; perfectly flat solid #00ff00 chroma-key background for removal; no color in subject; no gray shading; no text, numbers, letters, watermark; no shadow, glow, gradient, bevel, 3D render, frame, border, or background scenery; no tiny particles, dots, hairline strokes, ornate detail, portrait face, full character illustration, complex cracks, or many debris pieces.","model":"gpt-image-2","quality":"medium","size":"1024x1024","out":"ameno-tajikarao-throw-min-v08-source.png"}

View File

@ -0,0 +1,27 @@
# TH1-CI-2026-06-23-001 MemoryPack 技能 partial 重复特性
- 状态:已解决,已从 active 癌症清单移出
- 首次记录2026-06-23
- 来源:用户反馈 `BerserkSkill.MemoryPackable.g.cs(9,6): error CS0579: Duplicate 'MemoryPackable' attribute`
- 影响Unity 编译失败,阻断热更代码构建;同一生成菜单可重复制造问题
## 症状
`BerserkSkill` 源类已经声明 `[MemoryPackable]`,生成器又在 `Unity/Assets/Scripts/TH1_Logic/Skill/Generate/BerserkSkill.MemoryPackable.g.cs` 生成了带 `[MemoryPackable]` 的 partial 类C# 合并 partial 类型属性后报 CS0579。
## 根因
`Unity/Assets/Scripts/TH1_Logic/Editor/MemoryPackUnionGenerator.cs``GenerateSkillSubClassesPartialClasses` 无条件为每个 `SkillBase` 子类生成 `{ClassName}.MemoryPackable.g.cs`。它没有扫描源码类声明是否已经有 `[MemoryPackable]`,因此一旦业务源码手动加了该特性,就会与生成 partial 重复。
## 根治防线
- 生成器在生成前扫描 `SkillBase` 目录下非 `Generate`、非 `.g.cs` 的源码类声明。
- 如果源码类声明已经有 `[MemoryPackable]`,跳过对应 `{ClassName}.MemoryPackable.g.cs`
- 删除当前触发编译错误的 `BerserkSkill.MemoryPackable.g.cs`
- 新增 `.codex/skills/th1-chronic-issue/scripts/check_memorypack_skill_duplicates.ps1`,用于扫描“源码类 + 生成 partial”双重 `[MemoryPackable]`
## 剩余验证
- `check_memorypack_skill_duplicates.ps1` 已通过。
- `dotnet build Unity/TH1.Hotfix.csproj --no-restore` 已通过,确认 CS0579 消失。
- `dotnet build Unity/TH1.Editor.csproj --no-restore` 已通过。

View File

@ -0,0 +1,11 @@
# TH1-CI-2026-06-24-001 Sumireko Lv4 Existing Occult Orbs
- Status: fixed in code, pending Unity gameplay validation
- First recorded date: 2026-06-24
- Raw symptom and affected entrypoint: player reported that after Sumireko reaches Lv4, Occult Orbs placed before the upgrade still do not become Lv4 effects. Affected entrypoint is `UnitActionHeroUpgrade.Execute`.
- Why this is recurring: the earlier fix added full-field orb aura refresh and made newly created Lv4 orbs carry `SumirekoOrbSwapMaxValue`, but hero upgrade did not resynchronize already existing orb units.
- Root cause: `AttackGroundExecute` adds `SumirekoOrbSwapMaxValue` only when a new orb is created by an already-Lv4 Sumireko. Existing orbs never receive that source skill during the Lv3 -> Lv4 upgrade action, so full-field buff refresh still reads them as non-Lv4 sources.
- Root-cause fix: added a post-upgrade Sumireko sync that traverses the whole map, applies `SumirekoOrbSwapMaxValue` to all alive same-player Sumireko Occult Orbs, then refreshes all Sumireko orb buffs.
- Guardrail added: chronic issue record documents that source skills on persistent derivative units must be upgraded at the hero-upgrade entrypoint, not only during future unit creation.
- Verification performed: pending `dotnet build Unity/TH1.Hotfix.csproj --no-restore`.
- Remaining validation gaps: Unity Editor gameplay should verify Lv4 upgrade with Norway, Denmark, and England orbs already on the field, including multiplayer/replay if this hero is enabled there.

View File

@ -0,0 +1,17 @@
# TH1 长期癌症清单
本清单记录反复出现、靠临时补丁容易复发、必须优先根治的问题。以后遇到同类反复问题,先使用 `.codex/skills/th1-chronic-issue`,补充本清单,再做根因修复。
## 列表
| ID | 日期 | 状态 | 严重度 | 问题 | 根治方向 | 记录 |
| --- | --- | --- | --- | --- | --- | --- |
| TH1-CI-2026-06-24-001 | 2026-06-24 | Fixed in code | Medium | Sumireko Lv4 did not upgrade existing Occult Orbs | Sync persistent orb source skills from hero upgrade action and refresh all orb buffs | [record](2026-06-24-sumireko-lv4-existing-orbs.md) |
当前无 active 癌症项。
## 处理规则
- 反复出现两次以上、或用户明确指出“又出这个问题”的缺陷,必须加入本清单。
- 加入清单后,默认不接受只修表象;优先改生成器、校验脚本、测试或流程入口。
- 每个条目必须写清楚触发入口、复发机制、根治防线和剩余验证缺口。

View File

@ -0,0 +1,108 @@
# CrashSight 0.7.4c Network Sync Scan
- Generated at: 2026-06-23 20:51:15
- CrashSight filter: `version=0.7.4c`, `date=all`, ERROR, status `0,2`, all returned pages sorted by upload time desc.
- Raw capture dir: `Temp/CrashSight/Scan_2026-06-23_0.7.4c_all`
- All deduped ERROR issues: 53; blocking 21 issues / 85 occurrences; logerror 32 issues / 324 occurrences.
- Direct network/sync diagnostics: 14 issues / 24 occurrences.
- Blocking exceptions triggered on network serialization/send paths: 17 issues / 78 occurrences; root cause is concentrated in missing MemoryPackUnion registrations.
## Conclusion
All direct network desync records in 0.7.4c are classified as `logerror`: project-authored diagnostics without a concrete exception object. The clearest sync symptoms are `OnReceivedActionExcute MapHash mismatch`, `OnReceivedActionConfirm Version mismatch`, `message.Index > Main.MapData.Net.Actions.Count`, `CompleteExecute Player mismatch`, and one `MapDataDebug` full-map diff.
A separate blocking group affects multiplayer sync indirectly: `GameNetSender` serializes `MapData` during ActionExecute/ForceUpdate/MapData send paths, then `MemoryPackSerializationException` is thrown because `SumirekoNorwayOrbBuffSkill`, `SumirekoDenmarkOrbBuffSkill`, or `SumirekoEnglandOrbBuffSkill` is not registered in `Logic.Skill.SkillBase` MemoryPackUnion. These are not state-machine desyncs, but they can stop multiplayer messages or ForceUpdate from being sent.
## Category Summary
| Category | Issues | Occurrences | Devices | Classification |
|---|---:|---:|---:|---|
| P2P/lobby connection failure | 2 | 7 | 7 | direct network/sync diagnostic, logerror |
| Network send/broadcast failure | 4 | 7 | 6 | direct network/sync diagnostic, logerror |
| Reconnect/ForceUpdate request | 3 | 5 | 5 | direct network/sync diagnostic, logerror |
| Action sync Version/Index/MapHash mismatch | 3 | 3 | 3 | direct network/sync diagnostic, logerror |
| MapDataDebug diff diagnostic | 1 | 1 | 1 | direct network/sync diagnostic, logerror |
| CompleteExecute player mismatch | 1 | 1 | 1 | direct network/sync diagnostic, logerror |
| Network serialization path MemoryPack blocking | 17 | 78 | 35 | blocking, but root cause is missing skill serialization config |
## Direct Network/Sync Issues
### P2P/lobby connection failure
| Issue | Count | Devices | First | Last | Message | Code location |
|---|---:|---:|---|---|---|---|
| [5dcdf980496a9d479b490224701c7291](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/5dcdf980496a9d479b490224701c7291?pid=10) | 6 | 6 | 2026-06-23 18:29:26 | 2026-06-23 20:39:25 | Connection failed - Reason: 1000 | `SimpleP2P` / `SteamLobbyManager` connection failure reporting |
| [dc8552b108b508843917e43c7b9795b7](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/dc8552b108b508843917e43c7b9795b7?pid=10) | 1 | 1 | 2026-06-23 19:06:53 | 2026-06-23 19:06:53 | 应用层拒绝连接 - 错误码: 1000可能原因1.对方未创建监听套接字 2.对方主动拒绝 3.对方游戏未运行 | |
### Network send/broadcast failure
| Issue | Count | Devices | First | Last | Message | Code location |
|---|---:|---:|---|---|---|---|
| [f149bdbbe0b337b88b15aa54862a7d31](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/f149bdbbe0b337b88b15aa54862a7d31?pid=10) | 3 | 2 | 2026-06-23 18:44:50 | 2026-06-23 19:06:56 | MemberStateSyncMessage: 房主广播失败 | `GameNetSender.BroadcastMessage` -> `SteamLobbyManager.BroadcastMessage` |
| [43bfc0c946cf4a882cd1bb2551c4794d](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/43bfc0c946cf4a882cd1bb2551c4794d?pid=10) | 2 | 2 | 2026-06-23 18:44:50 | 2026-06-23 19:04:20 | P2P broadcast preflight failed: target=76561199030260664, reason=Target is not a lobby peer: 76561199030260664 | `Unity/Assets/Scripts/TH1_Logic/Steam/SteamLobbyManager.cs:1866` |
| [62d0f12ad62e1be26ddb8a902a29f6ca](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/62d0f12ad62e1be26ddb8a902a29f6ca?pid=10) | 1 | 1 | 2026-06-23 18:44:50 | 2026-06-23 18:44:50 | MemberStateSyncMessage: 房主广播失败 | `GameNetSender.BroadcastMessage` -> `SteamLobbyManager.BroadcastMessage` |
| [815d47ac4b63b73a89169b04d5dc2261](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/815d47ac4b63b73a89169b04d5dc2261?pid=10) | 1 | 1 | 2026-06-23 18:44:49 | 2026-06-23 18:44:49 | P2P broadcast preflight failed: target=76561199472113200, reason=No connection to 76561199472113200 | `Unity/Assets/Scripts/TH1_Logic/Steam/SteamLobbyManager.cs:1866` |
### Reconnect/ForceUpdate request
| Issue | Count | Devices | First | Last | Message | Code location |
|---|---:|---:|---|---|---|---|
| [9cb68d6848abbc5d035a5447de77a2c7](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/9cb68d6848abbc5d035a5447de77a2c7?pid=10) | 2 | 2 | 2026-06-23 18:46:57 | 2026-06-23 18:55:00 | 触发断线重连, 触发原因: Disconnected | |
| [8785e3d80995ff7ec71023b1d9d71e96](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/8785e3d80995ff7ec71023b1d9d71e96?pid=10) | 2 | 2 | 2026-06-23 18:46:57 | 2026-06-23 18:55:00 | 客户端请求重连: SendRequestForceUpdate | `GameNetReceiver` / `GameLogic` ForceUpdate request path |
| [01b9873df5e63cde60f4947769ab7a57](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/01b9873df5e63cde60f4947769ab7a57?pid=10) | 1 | 1 | 2026-06-23 18:50:37 | 2026-06-23 18:50:37 | 客户端请求重连: SendRequestForceUpdate | `GameNetReceiver` / `GameLogic` ForceUpdate request path |
### Action sync Version/Index/MapHash mismatch
| Issue | Count | Devices | First | Last | Message | Code location |
|---|---:|---:|---|---|---|---|
| [0e5dee835ed29162a30240fe43a8a25b](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/0e5dee835ed29162a30240fe43a8a25b?pid=10) | 1 | 1 | 2026-06-23 19:03:31 | 2026-06-23 19:03:31 | 成员端: message.Index > Main.MapData.Net.Actions.Count | `GameNetReceiver.OnReceivedMapConfirm` |
| [0ffe6e3c49f1822c8c339bf3149e3ff3](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/0ffe6e3c49f1822c8c339bf3149e3ff3?pid=10) | 1 | 1 | 2026-06-23 18:59:56 | 2026-06-23 18:59:56 | OnReceivedActionConfirm Version 不一致 | `Unity/Assets/Scripts/TH1_Logic/Steam/GameNetReceiver.cs:145` |
| [31d181d8ead2be8a50256bc006b3f454](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/31d181d8ead2be8a50256bc006b3f454?pid=10) | 1 | 1 | 2026-06-23 18:49:07 | 2026-06-23 18:49:07 | OnReceivedActionExcute MapHash 不一致,拒绝执行 | `Unity/Assets/Scripts/TH1_Logic/Steam/GameNetReceiver.cs:207` |
### MapDataDebug diff diagnostic
| Issue | Count | Devices | First | Last | Message | Code location |
|---|---:|---:|---|---|---|---|
| [fca177a3a8710c55bdaefbb75a1b31bf](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/fca177a3a8710c55bdaefbb75a1b31bf?pid=10) | 1 | 1 | 2026-06-23 18:55:00 | 2026-06-23 18:55:00 | GridMap differs (serialized data mismatch) PlayerMap differs (serialized data mismatch) PlayerMap.PlayerDataList differs (serialized data mismatch) UnitMap differs (serialized dat... | `GameNetReceiver.OnReceivedMapDataDebug` / `MapData.FindDifferences` |
### CompleteExecute player mismatch
| Issue | Count | Devices | First | Last | Message | Code location |
|---|---:|---:|---|---|---|---|
| [1161efadf785a874b148a8b513bd4182](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/1161efadf785a874b148a8b513bd4182?pid=10) | 1 | 1 | 2026-06-23 19:01:43 | 2026-06-23 19:01:43 | CompleteExecute Player 不一致 Action : TurnEnd Wonder : None Resource : None Feature : None Terrain : None Unit : None Giant : None Vegetation : None UnitAction : None CityLevelUpAct... | `Unity/Assets/Scripts/TH1_Logic/Action/ActionLogic.cs:1340` |
## Blocking Exceptions On Network Paths
These issue stacks include `GameNetSender`, `RuntimeData.NetData`, or `ActionLogicBase.CompleteExecute`, so they happened while serializing/sending multiplayer Action or MapData payloads. The exception type is `MemoryPackSerializationException`, and the root cause is missing derived skill types in `Logic.Skill.SkillBase` MemoryPackUnion.
| Issue | Count | Devices | Message | Key stack/path |
|---|---:|---:|---|---|
| [f9cf55fc1109065c560a9194fcbd0b2d](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/f9cf55fc1109065c560a9194fcbd0b2d?pid=10) | 13 | 1 | Type Logic.Skill.SumirekoNorwayOrbBuffSkill is not annotated in Logic.Skill.SkillBase MemoryPackUnion. | Logic.Skill.SkillBase+SkillBaseFormatter.Serialize (MemoryPack.MemoryPackWriter& writer, Logic.Skill.SkillBase& value) (at <000000000000000... |
| [ade38a9e9d371aecf16d87cb26bbda40](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/ade38a9e9d371aecf16d87cb26bbda40?pid=10) | 12 | 2 | Type Logic.Skill.SumirekoNorwayOrbBuffSkill is not annotated in Logic.Skill.SkillBase MemoryPackUnion. | Logic.Skill.SkillBase+SkillBaseFormatter.Serialize (MemoryPack.MemoryPackWriter& writer, Logic.Skill.SkillBase& value) (at <000000000000000... |
| [cbb1ba7c6a5bf150d055f1b47b26ba60](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/cbb1ba7c6a5bf150d055f1b47b26ba60?pid=10) | 12 | 1 | Type Logic.Skill.SumirekoNorwayOrbBuffSkill is not annotated in Logic.Skill.SkillBase MemoryPackUnion. | Logic.Skill.SkillBase+SkillBaseFormatter.Serialize (MemoryPack.MemoryPackWriter& writer, Logic.Skill.SkillBase& value) (at <000000000000000... |
| [3c40baefd3129d2f4c27a2ce73ab2d14](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/3c40baefd3129d2f4c27a2ce73ab2d14?pid=10) | 8 | 1 | Type Logic.Skill.SumirekoNorwayOrbBuffSkill is not annotated in Logic.Skill.SkillBase MemoryPackUnion. | Logic.Skill.SkillBase+SkillBaseFormatter.Serialize (MemoryPack.MemoryPackWriter& writer, Logic.Skill.SkillBase& value) (at <000000000000000... |
| [dd14f8aba57a344f5041d8967a28950b](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/dd14f8aba57a344f5041d8967a28950b?pid=10) | 8 | 8 | Type Logic.Skill.SumirekoNorwayOrbBuffSkill is not annotated in Logic.Skill.SkillBase MemoryPackUnion. | Logic.Skill.SkillBase+SkillBaseFormatter.Serialize (MemoryPack.MemoryPackWriter& writer, Logic.Skill.SkillBase& value) (at <000000000000000... |
| [0f608159a84a9393ea0e5ec88c13ad0b](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/0f608159a84a9393ea0e5ec88c13ad0b?pid=10) | 4 | 4 | Type Logic.Skill.SumirekoNorwayOrbBuffSkill is not annotated in Logic.Skill.SkillBase MemoryPackUnion. | Logic.Skill.SkillBase+SkillBaseFormatter.Serialize (MemoryPack.MemoryPackWriter& writer, Logic.Skill.SkillBase& value) (at <000000000000000... |
| [55bce2dadf3575c6b5de6aef7fcdd334](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/55bce2dadf3575c6b5de6aef7fcdd334?pid=10) | 4 | 2 | Type Logic.Skill.SumirekoNorwayOrbBuffSkill is not annotated in Logic.Skill.SkillBase MemoryPackUnion. | Logic.Skill.SkillBase+SkillBaseFormatter.Serialize (MemoryPack.MemoryPackWriter& writer, Logic.Skill.SkillBase& value) (at <000000000000000... |
| [ff205241a49d7d833289e7cb08da66fb](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/ff205241a49d7d833289e7cb08da66fb?pid=10) | 4 | 4 | Type Logic.Skill.SumirekoNorwayOrbBuffSkill is not annotated in Logic.Skill.SkillBase MemoryPackUnion. | Logic.Skill.SkillBase+SkillBaseFormatter.Serialize (MemoryPack.MemoryPackWriter& writer, Logic.Skill.SkillBase& value) (at <000000000000000... |
| [64a27708e79550a86f244d113056ce8b](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/64a27708e79550a86f244d113056ce8b?pid=10) | 2 | 1 | Type Logic.Skill.SumirekoEnglandOrbBuffSkill is not annotated in Logic.Skill.SkillBase MemoryPackUnion. | Logic.Skill.SkillBase+SkillBaseFormatter.Serialize (MemoryPack.MemoryPackWriter& writer, Logic.Skill.SkillBase& value) (at <000000000000000... |
| [75c2f712a5704a98eed5fbe368bf53a5](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/75c2f712a5704a98eed5fbe368bf53a5?pid=10) | 2 | 2 | Type Logic.Skill.SumirekoDenmarkOrbBuffSkill is not annotated in Logic.Skill.SkillBase MemoryPackUnion. | Logic.Skill.SkillBase+SkillBaseFormatter.Serialize (MemoryPack.MemoryPackWriter& writer, Logic.Skill.SkillBase& value) (at <000000000000000... |
| [b39945670d01bcadb312b72b5efb382c](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/b39945670d01bcadb312b72b5efb382c?pid=10) | 2 | 2 | Type Logic.Skill.SumirekoNorwayOrbBuffSkill is not annotated in Logic.Skill.SkillBase MemoryPackUnion. | Logic.Skill.SkillBase+SkillBaseFormatter.Serialize (MemoryPack.MemoryPackWriter& writer, Logic.Skill.SkillBase& value) (at <000000000000000... |
| [d452d481df091bd492673d5290420adf](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/d452d481df091bd492673d5290420adf?pid=10) | 2 | 2 | Type Logic.Skill.SumirekoNorwayOrbBuffSkill is not annotated in Logic.Skill.SkillBase MemoryPackUnion. | Logic.Skill.SkillBase+SkillBaseFormatter.Serialize (MemoryPack.MemoryPackWriter& writer, Logic.Skill.SkillBase& value) (at <000000000000000... |
| [2d90c9accee1ce654f443ae70159fa2e](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/2d90c9accee1ce654f443ae70159fa2e?pid=10) | 1 | 1 | EventManager Publish<ExecuteUIInfoGridInfo> listener failed: MemoryPack.MemoryPackSerializationException: Type Logic.Skill.SumirekoNorwayOrbBuffSkill... | UnityEngine.Debug.LogError(Object) |
| [494ca474e37321954228f2a7f76a7972](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/494ca474e37321954228f2a7f76a7972?pid=10) | 1 | 1 | Type Logic.Skill.SumirekoNorwayOrbBuffSkill is not annotated in Logic.Skill.SkillBase MemoryPackUnion. | Logic.Skill.SkillBase+SkillBaseFormatter.Serialize (MemoryPack.MemoryPackWriter& writer, Logic.Skill.SkillBase& value) (at <000000000000000... |
| [813d13572f07783f073d20628cc93cf6](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/813d13572f07783f073d20628cc93cf6?pid=10) | 1 | 1 | Type Logic.Skill.SumirekoDenmarkOrbBuffSkill is not annotated in Logic.Skill.SkillBase MemoryPackUnion. | Logic.Skill.SkillBase+SkillBaseFormatter.Serialize (MemoryPack.MemoryPackWriter& writer, Logic.Skill.SkillBase& value) (at <000000000000000... |
| [d85c624fbfd83eaab263094cfb96f447](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/d85c624fbfd83eaab263094cfb96f447?pid=10) | 1 | 1 | EventManager Publish<ExecuteUIBottomBottomBarNextButtonClick> listener failed: MemoryPack.MemoryPackSerializationException: Type Logic.Skill.Sumireko... | UnityEngine.Debug.LogError(Object) |
| [fdbddc05dfe36b809c2a794cbd627e1d](https://crashsight.qq.com/crash-reporting/errors/01076c49ce/fdbddc05dfe36b809c2a794cbd627e1d?pid=10) | 1 | 1 | Type Logic.Skill.SumirekoNorwayOrbBuffSkill is not annotated in Logic.Skill.SkillBase MemoryPackUnion. | Logic.Skill.SkillBase+SkillBaseFormatter.Serialize (MemoryPack.MemoryPackWriter& writer, Logic.Skill.SkillBase& value) (at <000000000000000... |
## Priority Notes
1. Fix the missing MemoryPackUnion registrations first. This dominates the blocking issues on multiplayer send/serialization paths, and it can break ActionExecute, ForceUpdate, saves, and any other path that serializes `MapData`. This touches MemoryPack, so it needs explicit confirmation before code changes under the project rules.
2. For the three direct desync diagnostics (`OnReceivedActionExcute MapHash mismatch`, `OnReceivedActionConfirm Version mismatch`, and `message.Index > Actions.Count`), capture both peers with `MapDataDebug` before ForceUpdate repairs the map. Current CrashSight rows mostly have `hasLogFile=false`, so they show the final guard line but not the first divergent action.
3. The P2P preflight rows (`Target is not a lobby peer`, `No connection`) line up with `MemberStateSyncMessage: host broadcast failed`. Check cleanup after a member leaves/disconnects and ensure host broadcasts only target current lobby peers.
## Limitations
- This scan used the CrashSight API. Most rows have `hasLogFile=false`, so the report has issue rows, sample stacks, and same-device ERROR sequences, but not full Unity logs.
- `date=all` was accepted by the CrashSight API and returned 53 deduped 0.7.4c ERROR issues. The scan covers status `0,2`; archived/ignored statuses are outside this capture.
- No code was changed in this scan.

View File

@ -0,0 +1,49 @@
{
"version": "0.7.4c",
"date": "all",
"runDir": "Temp\\CrashSight\\Scan_2026-06-23_0.7.4c_all",
"totalIssues": 53,
"blockingIssues": 21,
"blockingOccurrences": 85,
"logerrorIssues": 32,
"logerrorOccurrences": 324,
"networkIssueIds": [
"5dcdf980496a9d479b490224701c7291",
"dc8552b108b508843917e43c7b9795b7",
"f149bdbbe0b337b88b15aa54862a7d31",
"43bfc0c946cf4a882cd1bb2551c4794d",
"62d0f12ad62e1be26ddb8a902a29f6ca",
"815d47ac4b63b73a89169b04d5dc2261",
"9cb68d6848abbc5d035a5447de77a2c7",
"8785e3d80995ff7ec71023b1d9d71e96",
"01b9873df5e63cde60f4947769ab7a57",
"0e5dee835ed29162a30240fe43a8a25b",
"0ffe6e3c49f1822c8c339bf3149e3ff3",
"31d181d8ead2be8a50256bc006b3f454",
"fca177a3a8710c55bdaefbb75a1b31bf",
"1161efadf785a874b148a8b513bd4182"
],
"networkIssues": 14,
"networkOccurrences": 24,
"blockingNetworkPathIssueIds": [
"f9cf55fc1109065c560a9194fcbd0b2d",
"ade38a9e9d371aecf16d87cb26bbda40",
"cbb1ba7c6a5bf150d055f1b47b26ba60",
"3c40baefd3129d2f4c27a2ce73ab2d14",
"dd14f8aba57a344f5041d8967a28950b",
"0f608159a84a9393ea0e5ec88c13ad0b",
"55bce2dadf3575c6b5de6aef7fcdd334",
"ff205241a49d7d833289e7cb08da66fb",
"64a27708e79550a86f244d113056ce8b",
"75c2f712a5704a98eed5fbe368bf53a5",
"b39945670d01bcadb312b72b5efb382c",
"d452d481df091bd492673d5290420adf",
"2d90c9accee1ce654f443ae70159fa2e",
"494ca474e37321954228f2a7f76a7972",
"813d13572f07783f073d20628cc93cf6",
"d85c624fbfd83eaab263094cfb96f447",
"fdbddc05dfe36b809c2a794cbd627e1d"
],
"blockingNetworkPathIssues": 17,
"blockingNetworkPathOccurrences": 78
}

View File

@ -0,0 +1,88 @@
# TH1-SI-2026-06-23-002 博丽奇观按钮显示埃及图标
## 基本信息
- 严重度S2
- 状态:已在工作区修复,待 Unity 实机回归
- 首次记录日期2026-06-23
- 报告来源:用户手动游玩发现
- 相关文件:`Unity/Assets/Scripts/TH1_DataAssetsScript/ActionDataAssets.cs``Unity/Assets/BundleResources/DataAssets/ActionDataAssets.asset`
## 现象
博丽帝国建造奇观的七个行动按钮全部显示成埃及奇观图标。
## 期望
博丽帝国 `CivId: 4 / ForceId: 4` 的建造奇观按钮应显示 `ActionDataAssets.asset` 中配置的博丽/维京奇观图标,而不是埃及默认图标。
## 影响
- 影响博丽帝国局内建造奇观按钮的图标识别。
- 实际奇观建造逻辑和 `GridAndResourceDataAssets.WonderInfoList` 映射未发现错误;博丽奇观实体映射为 `Wonder 28..34`,且 `CivId/ForceId = 4/4`
- 主要影响 UI 表现和玩家判断,未发现会把建造结果写成埃及奇观。
## 根因
`ActionInfo.GetIcon``ActionInfo.GetActionName` 原本按 `IconList` 顺序返回第一个匹配项:
- `IgnoreCivId: 1 / IgnoreForceId: 1` 的兜底项会匹配所有文明。
- 该兜底项的 Sprite 是埃及默认奇观图标。
- 博丽 `CivId: 4 / ForceId: 4` 的具体图标项被追加在兜底项后面。
因此博丽帝国查找建造奇观按钮图标时,会先命中兜底项,永远不会走到后面的博丽专属项。
## 引入提交
### 前置条件提交
- 提交:`233029d10c`
- 日期2026-06-12 16:02:05 +0800
- 作者daixiawu
- 提交信息:`V0.7.3f`
该提交在奇观按钮 `IconList` 中加入了 `IgnoreCivId: 1 / IgnoreForceId: 1` 的兜底项,并使用埃及图标作为兜底图标。这个提交本身主要为后续缺省显示提供 fallback但它让列表顺序开始具有遮挡风险。
### 直接触发提交
- 提交:`40b5d0d1703757ca08a196e2ab5e2b8b036a407a`
- 日期2026-06-20 16:53:20 +0800
- 作者daixiawu
- 提交信息:`博丽帝国开发2`
该提交为博丽帝国追加了七个建造奇观按钮图标项:
- `CivId: 4`
- `ForceId: 4`
- 七个 Sprite 分别对应和平、智慧、贸易、财富、权力、文化、探索奇观
但这些具体项被追加在 2026-06-12 的全文明兜底项之后。由于 `GetIcon` 采用“第一个匹配立即返回”,博丽项虽然存在,但运行时无法命中。
## 修复
已修改 `ActionInfo` 的图标/名称变体选择规则:
- 对文明/势力匹配计算具体度分数。
- 精确匹配 `CivId + ForceId` 优先于只匹配一项。
- 任意具体匹配优先于 `IgnoreCivId + IgnoreForceId` 的兜底项。
- 同等具体度仍保留列表中的先后顺序。
当前工作区修复文件:
- `Unity/Assets/Scripts/TH1_DataAssetsScript/ActionDataAssets.cs`
## 验证
- 已执行:`dotnet build Unity/TH1.Hotfix.csproj --no-restore`
- 结果通过0 errors保留既有 warnings。
## 未完成验证
- 尚需在 Unity 内使用博丽帝国开局,打开城市建造奇观按钮,确认七个按钮均显示博丽/维京奇观图标。
- 尚需快速确认其他文明的奇观按钮仍显示各自专属图标;无专属图标的文明应继续使用兜底图标。
## 防线
- `IconList` 这类带 `IgnoreCivId/IgnoreForceId` 的配置不能依赖 Unity 列表顺序来保证具体项优先。
- 以后新增文明专属图标时,代码选择规则应始终按“具体匹配优先,兜底最后”处理。
- 若后续新增编辑器校验,可扫描 `VarientIcon` 配置:当存在全文明兜底项和后续具体项时,提示顺序不会影响运行时,但仍可提示配置可读性问题。

View File

@ -0,0 +1,93 @@
# TH1-SI-2026-06-23-001 英雄升级错误返还文化值
## 基本信息
- 严重度S1
- 状态:已在工作区修复,待 Unity 实机回归
- 首次记录日期2026-06-23
- 报告来源:用户手动游玩发现
- 相关文件:`Unity/Assets/Scripts/TH1_Data/PlayerData.cs``Unity/Assets/Scripts/TH1_Logic/Unit/UnitLogic.cs`
## 现象
英雄升级不应该返还文化值,但实际表现为:
- 1 升 2未增加文化值
- 2 升 3增加 15 文化值
- 3 升 4增加 50 文化值
## 影响
- 影响所有走 `HeroUpgrade -> UnitTypeTransform` 的英雄升级。
- 会污染文化经济,导致玩家提前或额外购买文化卡。
- 逻辑位于共享行动/单位转换路径,单人、多人、回放和存档后的后续局势都会继承这个错误结果。
## 根因
`PlayerCultureInfo.OnUnitTransform` 把文化奖励直接挂在单位等级转换上,只按 `origin.UnitLevel``target.UnitLevel` 判断,没有排除英雄、巨人、英雄衍生单位,也没有限定为普通单位升级。
英雄升级路径会调用 `Main.UnitLogic.HeroUpgrade`,随后进入 `UnitLogic.UnitTypeTransform`。该共享转换函数会触发玩家的 `OnUnitTransform` 回调,因此英雄升级被误判为可获得文化奖励的单位升级。
## 引入提交
### 根因提交
- 提交:`0b4b5434bc6b83dc47323693488497faeac3880e`
- 日期2026-03-17 15:21:38 +0800
- 作者wuwenbo `<wuwenbo@bilibili.com>`
- 提交信息:`文化值系统`
该提交新增 `PlayerCultureInfo.OnUnitTransform`,并在单位转换链路中引入按等级转换返还文化的逻辑。原始逻辑同样没有区分普通单位和英雄转换,因此这是事故根因。
### 症状成形提交
- 提交:`599e8824c53eabac2e87c25b7cba4b2d5965e5f3`
- 日期2026-04-10 13:29:50 +0800
- 作者kawagiri `<kawagiri@126.com>`
- 提交信息:`英雄等级批量调整`
该提交把返还条件从原来的 `1->2 +15``2->3 +50` 调整为当前观测到的 `2->3 +15``3->4 +50`。它不是最初引入“英雄升级也返文化”的根因,但让事故表现变成当前数值。
### 暴露提交
- 提交:`8bce7d89dcfcc750c19b8d27f45905fe17849c8c`
- 日期2026-06-22 20:03:09 +0800
- 作者wuwenbo `<wuwenbo@bilibili.com>`
- 提交信息:`增加一批问题保护`
该提交修正了 `UnitTypeTransform` 中传给回调的转换前后类型记录:在单位被实际改成目标类型之前保存 `originFullTypeBeforeTransform``targetFullTypeBeforeTransform`,并把这两个值传给 `OnUnitTransform`、收集系统等回调。
这次修改本身方向是正确的,但它让 2026-03-17 埋下的文化系统缺陷第一次被正确触发。提交之前,旧代码在 `unitData.UnitFullType` 已经被改成目标等级后才构造:
- `originFullType = new UnitFullType(unitData.UnitType, unitData.GiantType, unitData.UnitLevel)`
- `targetFullType = new UnitFullType(targetType, giantType, unitLevel)`
因此旧代码传给文化回调的 `origin` 实际上已经是转换后的等级,英雄 2->3 时回调看到的是 `3->3`,英雄 3->4 时看到的是 `4->4`,不会命中 `2->3``3->4` 的文化奖励条件。也就是说2026-06-22 前没有暴露,是因为另一个 origin/target 记录错误把文化奖励条件“遮住了”,不是因为文化逻辑本身正确。
## 修复
已在 `PlayerCultureInfo.OnUnitTransform` 增加保护:
- 空单位直接返回。
- 英雄、巨人、英雄衍生单位转换直接返回。
- 只有 `UnitType``GiantType` 都不变的普通单位等级提升才允许触发文化奖励。
当前工作区修复文件:
- `Unity/Assets/Scripts/TH1_Data/PlayerData.cs`
## 验证
- 已执行:`dotnet build Unity/TH1.Hotfix.csproj --no-restore`
- 结果通过0 errors保留既有 warnings。
## 未完成验证
- 尚需在 Unity 内进行实机回归:任选普通英雄执行 1->2、2->3、3->4确认文化值不增加。
- 尚需确认普通单位符合设计的升级返文化行为仍然保留。
## 防线
- 今后修改 `UnitTypeTransform``PlayerCultureInfo.OnUnitTransform`、英雄升级或单位升级奖励时,必须同时使用 `.codex/skills/th1-action-logic``.codex/skills/th1-hero-implementation``.codex/skills/th1-serious-incident`
- 所有挂在共享转换回调上的奖励逻辑,必须显式写清作用对象:普通单位、英雄、巨人、衍生单位、技能临时转换分别如何处理。
- 对严重事故必须更新 `MD/SeriousIncidentLog/index.md`,不得只在聊天里口头说明。

View File

@ -0,0 +1,15 @@
# TH1 严重事故事件簿
本目录用于记录 TH1 项目中的严重事故、根因提交、修复、验证和后续防线。以后遇到严重玩法、数值、存档、网络、构建、线上或发布事故,必须使用 `.codex/skills/th1-serious-incident`,并维护本索引。
## 事件列表
| ID | 日期 | 严重度 | 状态 | 事件 | 根因提交 |
| --- | --- | --- | --- | --- | --- |
| TH1-SI-2026-06-23-002 | 2026-06-23 | S2 | 已在工作区修复,待 Unity 实机回归 | 博丽奇观按钮显示埃及图标 | 前置 `233029d10c`;触发 `40b5d0d1703757ca08a196e2ab5e2b8b036a407a` |
| TH1-SI-2026-06-23-001 | 2026-06-23 | S1 | 已在工作区修复,待 Unity 实机回归 | 英雄升级错误返还文化值 | 根因 `0b4b5434bc6b83dc47323693488497faeac3880e`;暴露 `8bce7d89dcfcc750c19b8d27f45905fe17849c8c` |
## 待跟进
- 为英雄升级链路补一条确定性回归检查:任意英雄 1->2、2->3、3->4 升级后,玩家文化值不得增加。
- 对 `UnitTypeTransform` 的所有共享回调做一次审计,确认普通单位升级、英雄升级、巨人/衍生物转换、技能转换不会互相污染奖励逻辑。

View File

@ -769,6 +769,321 @@ body::after {
}
}
/* ========== AI Logic Module ========== */
.ai-page {
display: flex;
flex-direction: column;
gap: 16px;
}
.ai-hero {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 10px;
padding: 20px;
}
.ai-hero-top {
display: flex;
justify-content: space-between;
gap: 16px;
align-items: flex-start;
}
.ai-title {
font-size: 24px;
font-weight: 850;
margin-bottom: 6px;
}
.ai-summary {
max-width: 920px;
color: var(--text-secondary);
line-height: 1.65;
font-size: 13px;
}
.ai-source {
font-size: 11px;
color: var(--text-muted);
white-space: nowrap;
padding-top: 5px;
}
.ai-stats {
display: grid;
grid-template-columns: repeat(5, minmax(120px, 1fr));
gap: 10px;
margin-top: 18px;
}
.ai-stat {
border: 1px solid var(--border-color);
border-radius: 8px;
background: var(--bg-card-hover);
padding: 12px;
}
.ai-stat-value {
font-size: 24px;
font-weight: 850;
color: var(--accent-blue);
line-height: 1;
}
.ai-stat-label {
margin-top: 6px;
font-size: 11px;
color: var(--text-muted);
}
.ai-section-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
.ai-card {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 10px;
overflow: hidden;
}
.ai-card-head {
padding: 14px 16px;
display: flex;
justify-content: space-between;
gap: 12px;
border-bottom: 1px solid var(--border-color);
align-items: center;
}
.ai-card-title {
font-size: 15px;
font-weight: 800;
}
.ai-card-badge,
.ai-chip {
display: inline-flex;
align-items: center;
min-height: 22px;
padding: 3px 8px;
border-radius: 5px;
background: rgba(59,130,246,0.09);
color: var(--accent-blue);
font-size: 11px;
font-weight: 700;
white-space: nowrap;
}
.ai-card-body {
padding: 16px;
}
.ai-flow {
display: grid;
grid-template-columns: repeat(6, minmax(130px, 1fr));
gap: 10px;
overflow-x: auto;
padding-bottom: 4px;
}
.ai-flow-step {
position: relative;
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 12px;
background: var(--bg-card-hover);
min-height: 128px;
}
.ai-flow-step::after {
content: '';
position: absolute;
top: 30px;
right: -8px;
width: 14px;
height: 1px;
background: var(--border-color);
}
.ai-flow-step:last-child::after {
display: none;
}
.ai-flow-index {
width: 24px;
height: 24px;
border-radius: 50%;
background: var(--accent-blue);
color: white;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 800;
margin-bottom: 8px;
}
.ai-flow-name {
font-size: 13px;
font-weight: 800;
margin-bottom: 6px;
}
.ai-flow-desc {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.5;
}
.ai-module-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.ai-module {
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 12px;
background: var(--bg-card-hover);
}
.ai-module-name {
font-size: 13px;
font-weight: 800;
margin-bottom: 6px;
}
.ai-module-text {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.5;
}
.ai-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.ai-list-item {
display: flex;
justify-content: space-between;
gap: 12px;
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 10px 12px;
background: var(--bg-card-hover);
}
.ai-list-main {
min-width: 0;
}
.ai-list-title {
font-size: 13px;
font-weight: 800;
}
.ai-list-desc {
margin-top: 3px;
font-size: 12px;
color: var(--text-secondary);
line-height: 1.45;
}
.ai-table-wrap {
overflow-x: auto;
}
.ai-table {
width: 100%;
border-collapse: collapse;
min-width: 760px;
}
.ai-table th,
.ai-table td {
border-bottom: 1px solid var(--border-color);
padding: 9px 10px;
text-align: left;
vertical-align: top;
font-size: 12px;
}
.ai-table th {
color: var(--text-muted);
font-size: 11px;
font-weight: 800;
background: var(--bg-card-hover);
}
.ai-code {
font-family: Consolas, 'Courier New', monospace;
color: var(--accent-green);
font-size: 12px;
}
.ai-node-toolbar {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 12px;
}
.ai-node-toolbar .search-input {
flex: 1;
min-width: 220px;
}
.ai-node-cats {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 14px;
}
.ai-node-cat {
border: 1px solid var(--border-color);
background: var(--bg-card);
color: var(--text-secondary);
border-radius: 6px;
padding: 6px 10px;
font-family: inherit;
font-size: 12px;
cursor: pointer;
}
.ai-node-cat.active {
color: var(--accent-blue);
border-color: rgba(59,130,246,0.35);
background: rgba(59,130,246,0.08);
}
@media (max-width: 1100px) {
.ai-stats,
.ai-module-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.ai-section-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 760px) {
.ai-hero-top {
flex-direction: column;
}
.ai-stats,
.ai-module-grid {
grid-template-columns: 1fr;
}
}
/* ========== Story Module ========== */
.story-hero-card {
background: var(--bg-card); border: 1px solid var(--border-color);
@ -4083,6 +4398,21 @@ body::after {
width: 220px;
}
#fh-wiki-list-content .fh-collection-table th:nth-child(1),
#fh-wiki-list-content .fh-collection-table td:nth-child(1) {
width: 72px;
}
#fh-wiki-list-content .fh-collection-table th:nth-child(3),
#fh-wiki-list-content .fh-collection-table td:nth-child(3) {
width: 240px;
}
#fh-wiki-list-content .fh-collection-table th:nth-child(5),
#fh-wiki-list-content .fh-collection-table td:nth-child(5) {
width: 420px;
}
.fh-summary-cell {
color: var(--text-muted);
font-size: 11px;
@ -4113,10 +4443,23 @@ body::after {
padding: 5px 8px;
}
.fh-line-textarea {
min-height: 38px;
max-height: 180px;
padding: 7px 8px;
line-height: 1.45;
resize: vertical;
white-space: pre-wrap;
overflow: auto;
}
.fh-line-desc {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-family: inherit;
}
.fh-skill-table td,
.fh-collection-table td {
vertical-align: top;
}
.fh-readonly-name {

View File

@ -0,0 +1,289 @@
{
"generatedAt": "2026-06-24",
"title": "AI逻辑",
"subtitle": "TH1_Logic/AI + TH1_Logic/AITrain + BTNodeCanvas",
"summary": "当前 AI 由 AILogic 回合状态机驱动BehaviourTreeOwner 运行 NodeCanvas 行为树AICalculatorData 作为黑板数据承载国家、城市、军团、单位和目标缓存。行为树节点负责生成、筛选、评分和准备 MaxAiAction最终执行仍回到 ActionLogicBase.CompleteExecute与玩家、网络、回放共用同一 Action 权威层。",
"stats": [
{ "label": "AI核心文件", "value": "5" },
{ "label": "训练/模型文件", "value": "3" },
{ "label": "行为树节点条目", "value": "102" },
{ "label": "节点分类", "value": "8" },
{ "label": "ML状态维度", "value": "802" }
],
"flow": [
{ "name": "StartAILogic", "desc": "绑定 MapData/PlayerData读取 Export/AIConfig刷新 AICalculatorData并重启 AIBT 的 BehaviourTreeOwner。" },
{ "name": "Refresh黑板", "desc": "计算国家策略、城市策略、军团策略、自由单位策略、科技资源/克制缓存和单位目标城市。" },
{ "name": "行为树Update", "desc": "在 Playing 状态循环调用 _btOwner.UpdateBehaviour节点每轮清空临时 Action 缓存并推动黑板状态。" },
{ "name": "生成候选Action", "desc": "AIActionGenerator 从单位、地块、城市、科技、外交和英雄选择入口构造合法 AIActionBase。" },
{ "name": "评分/筛选", "desc": "AICalculateAction 调用 AIActionScoreCalculator以 CalculateType 组合对候选执行模拟评分,选出 MaxAiAction。" },
{ "name": "权威执行", "desc": "AILogic 对 MaxAiAction 调用 CompleteExecute进入真实 Action 层;随后按可见性等待动画并回到 Playing。" }
],
"modules": [
{
"name": "AILogic",
"path": "Unity/Assets/Scripts/TH1_Logic/AI/AILogic.cs",
"role": "AI回合状态机",
"details": [
"状态包含 Prepare、Playing、PrePlay、Pausing、Finished。",
"Playing 内最多执行 200 个 Action行为树单轮最多 150 次更新,避免死循环。",
"普通路径由行为树产生 MaxAiActionENABLE_AIMODEL 路径使用 ONNX 预测 Action 编码。",
"最终执行调用 MaxAiAction.ActionLogic.CompleteExecute(MaxAiAction.Param)。"
]
},
{
"name": "AICalculatorData",
"path": "Unity/Assets/Scripts/TH1_Logic/AI/AIActionBase.cs",
"role": "黑板数据与战略缓存",
"details": [
"保存 Map、Player、CountryStrategy、AttackPlayerSet、城市/军团/自由单位策略。",
"缓存 MilitaryScore、DevelopmentScore、ThreatScore、MilitaryGapScore、地缘距离和城市危险值。",
"维护 AIActions、MaxAiAction、TargetParam、TargetList、Marks、ForeachUnit/Legion/City。",
"Refresh 会重算国家、城市、军团、合并、科技、单位目标等策略。"
]
},
{
"name": "AIActionGenerator",
"path": "Unity/Assets/Scripts/TH1_Logic/AI/AIActionGenerator.cs",
"role": "候选Action生成器",
"details": [
"增量生成路径按 Unit、Grid、City、Tech 轮转,避免一帧穷举过多。",
"静态穷举路径覆盖 LearnTech、StartWonder、PlayerAction、Gain、Build、BuildWonder、GridMisc、TrainUnit、CityLevelUpAction、UnitAction、UnitSkill、UnitMove、UnitAttack、AIParamControl、UnitAttackAlly、UnitAttackGround、BuyCultureCard。",
"生成后通过 ActionLogic.CheckCan 过滤,并复制 CommonActionParams 保持 ID/引用一致。",
"ForUse 版本会排除 FinishHeroTask、Disband、ForceDisband、Demolish、Destroy 等训练/模型不应主动选择的动作。"
]
},
{
"name": "AIActionScoreCalculator",
"path": "Unity/Assets/Scripts/TH1_Logic/AI/AIActionScoreCalculator.cs",
"role": "评分与模拟执行",
"details": [
"CalculateResult 同时保存 AICalculatorType 总评分和 CalculateType 细分评分。",
"GetCalMap 会深拷贝真实地图到 CalMap在模拟图上 RefreshParams 后调用同一套 Action 完成收益评估。",
"评分维度覆盖经济、单位、城市、城防、攻击、防御、探索、科技、军团移动/击杀/攻城等。",
"内置 PathFinder 与可达性组件缓存,用于城市距离、军团目标和移动策略计算。"
]
},
{
"name": "AIConfigAsset / AITechScoreCalculator",
"path": "Unity/Assets/Scripts/TH1_Logic/AI/AIConfigAsset.cs",
"role": "权重配置与科技评分",
"details": [
"AIConfigAsset 保存 MoneyScore、UnitScore、CityScore、探索、科技、远期折扣和难度修正。",
"AIDiffInfo 按 EASY/NORMAL/HARD/LUNATIC 调整自由单位、军团、防守/进攻/发展和城内单位倾向。",
"AITechScoreCalculator 从科技解锁的训练、建设、奇观和特殊效果推导科技收益。"
]
},
{
"name": "AITrain / ModelInference",
"path": "Unity/Assets/Scripts/TH1_Logic/AITrain",
"role": "训练数据与ONNX推理",
"details": [
"TrainingState.GetMapState 生成 802 维状态2 个己方/回合字段、40 维玩家、400 维单位、160 维城市、200 维可见资源/特殊地块。",
"AIActionPacker 将 CommonActionId 与 Player/Unit/City/Grid/Target 索引打包成 8 维 Action。",
"ModelInference 读取 AIModel/cql_model预测 8 维 Action 向量,再从合法动作中选择欧氏距离最近项。",
"TrainingDataRecorder 将 state、validActions、selectedAction、reward、done 写为 jsonl。"
]
},
{
"name": "BTNodeCanvas",
"path": "Unity/Assets/Scripts/BTNodeCanvas",
"role": "NodeCanvas行为树节点库",
"details": [
"大多数判断节点继承 BaseActionTask通过 EndAction(true/false) 把判断结果返回给行为树。",
"BaseActionTask.OnExecute 写入 MainEditor.Instance.BTNodeIdEditor 下 EndAction 会记录 AIRecord/AIStateRecord。",
"节点通过 blackboard.GetVariable<AICalculatorData>(\"Data\") 访问黑板,读写 AIActions、TargetParam、TargetList、Marks、Foreach 集合。",
"核心执行节点包括 AIGeneratorAction、AICalculateAction、AIExecuteAction、AIFinishAction、AIForeachStart/End/Create。"
]
}
],
"sourceFiles": [
{ "group": "AI核心", "file": "AILogic.cs", "path": "Unity/Assets/Scripts/TH1_Logic/AI/AILogic.cs", "purpose": "回合状态机、行为树驱动、真实执行、训练记录、ONNX分支" },
{ "group": "AI核心", "file": "AIActionBase.cs", "path": "Unity/Assets/Scripts/TH1_Logic/AI/AIActionBase.cs", "purpose": "AICalculatorData、策略枚举、AIActionBase、ActionNetData、战略/距离/威胁计算" },
{ "group": "AI核心", "file": "AIActionGenerator.cs", "path": "Unity/Assets/Scripts/TH1_Logic/AI/AIActionGenerator.cs", "purpose": "从 ActionLogicFactory 枚举合法候选 Action" },
{ "group": "AI核心", "file": "AIActionScoreCalculator.cs", "path": "Unity/Assets/Scripts/TH1_Logic/AI/AIActionScoreCalculator.cs", "purpose": "评分、CalMap模拟、CalculateType评分函数、PathFinder缓存" },
{ "group": "AI核心", "file": "AIConfigAsset.cs", "path": "Unity/Assets/Scripts/TH1_Logic/AI/AIConfigAsset.cs", "purpose": "AI权重、科技权重、难度修正配置" },
{ "group": "AI核心", "file": "AITechScoreCalculator.cs", "path": "Unity/Assets/Scripts/TH1_Logic/AI/AITechScoreCalculator.cs", "purpose": "科技军事/建设/奇观/特殊收益评分" },
{ "group": "训练模型", "file": "TrainingState.cs", "path": "Unity/Assets/Scripts/TH1_Logic/AITrain/TrainingState.cs", "purpose": "802维状态、8维Action编码/反编码、合法Action列表、reward地图评分" },
{ "group": "训练模型", "file": "ModelInference.cs", "path": "Unity/Assets/Scripts/TH1_Logic/AITrain/ModelInference.cs", "purpose": "ONNX模型加载和合法动作最近邻选择" },
{ "group": "训练模型", "file": "TrainingDataRecorder.cs", "path": "Unity/Assets/Scripts/TH1_Logic/AITrain/TrainingDataRecorder.cs", "purpose": "训练episode步骤记录和jsonl输出" },
{ "group": "编辑器工具", "file": "AITrainEditorWindow.cs", "path": "Unity/Assets/Scripts/TH1_Logic/Editor/AITrainEditorWindow.cs", "purpose": "AI训练相关编辑器窗口" },
{ "group": "编辑器工具", "file": "AIConfigEditorWindow.cs", "path": "Unity/Assets/Scripts/TH1_Logic/Editor/AIConfigEditorWindow.cs", "purpose": "AI配置编辑器窗口" },
{ "group": "生成配置", "file": "AIConfig.cs / AIConfigPartial.cs", "path": "Unity/Assets/Scripts/TH1_Config/GenerateCS/AIConfig.cs", "purpose": "Excel生成的AI配置类型和Partial扩展" },
{ "group": "外部入口", "file": "UIOutsideSelectAISettingRowMono.cs", "path": "Unity/Assets/Scripts/TH1_UI/View/Outside/UIOutsideSelectAISettingRowMono.cs", "purpose": "局外AI设置UI行" }
],
"strategies": [
{ "name": "Strategy", "values": "Attack, Defend, Development, Military, EmergencyDefend, Retreat, Common, DefendAttack, None", "purpose": "国家、城市、军团、单位共用的高层倾向。" },
{ "name": "CountryAttackType", "values": "CounterattackInvasions, BullyWeak, ForcedExpansion, None", "purpose": "国家级攻击目标选择倾向。" },
{ "name": "LegionStrategyType", "values": "SelfDefend, SelfAttack, CountryDefendMatch, CountryDefendMax, CountryAttackMatch, CountryAttackMax, None", "purpose": "军团防守/攻击目标匹配与最大收益选择。" },
{ "name": "FreeUnitStrategyType", "values": "FreeDevelopment1, Retreat, FreeDevelopment2, None", "purpose": "未归入军团的单位发展或撤退策略。" },
{ "name": "CalculateType", "values": "PlayerTechDefend, PlayerTechAttack, PlayerTechScore, CityLevelUpDefend, CityTrainDefend, CityTrainAttack, CityDevelopment, UnitCollect, UnitUpgrade, UnitRecovery, UnitAttackCityCenter, UnitExplore, UnitRetreat, UnitMoveToTargetGrid, UnitAttack, LegionDefendKill, LegionDefendMove, LegionDefendAttack, LegionAttackMoveInCity, LegionAttackMoveToCity, LegionAttackCityUnit, LegionAttackUnit, LegionDevelopmentMoveToCityTerritory, LegionDevelopmentKill, LegionDevelopmentMoveToCity, LegionDevelopmentAttackUnit, LegionDevelopmentMoveToOtherCity, UnitKill, CityLevelUp, TechGap, TechResource, DiplomacyTech, GridMiscCreateMountain, GridMiscGrowTree 等", "purpose": "行为树评分节点传入的细分收益函数集合。" },
{ "name": "AICalculatorType", "values": "Sight, UnitCanMove, MoneyScore, UnitScore, CityScore, CityDefendScore, UnitAttack, UnitDefend, UnitExplore, UnitExploreCityCenter, UnitExploreTreasure, UnitExploreStarfish, TechScore", "purpose": "旧/基础评分维度与 CalculateResult.ScoreDict。" }
],
"nodeCategories": [
{
"id": "core",
"name": "执行/流程",
"desc": "驱动候选生成、评分、执行、结束和循环控制。",
"nodes": [
{ "class": "BaseActionTask", "title": "ActionTask基类", "file": "BaseActionTask.cs" },
{ "class": "BaseCondition", "title": "ConditionTask基类", "file": "BaseCondition.cs" },
{ "class": "AIGeneratorAction", "title": "构建行为", "file": "AIGeneratorAction.cs" },
{ "class": "AIGeneratorActionReturnTrue", "title": "构建行为(返回True)", "file": "AIGeneratorActionReturnTrue.cs" },
{ "class": "AICalculateAction", "title": "评分策略", "file": "AICalculateAction.cs" },
{ "class": "AIExecuteAction", "title": "执行", "file": "AIExcuteAction.cs" },
{ "class": "AIFinishAction", "title": "执行结束", "file": "AIFinishAction.cs" },
{ "class": "AIForeachCreate", "title": "迭代器", "file": "AIForeachCreate.cs" },
{ "class": "AIForeachStart", "title": "迭代一次开始", "file": "AIForeachStart.cs" },
{ "class": "AIForeachEnd", "title": "迭代一次结束", "file": "AIForeachEnd.cs" },
{ "class": "MarkAction", "title": "标记读写", "file": "MarkAction.cs" }
]
},
{
"id": "action-filter",
"name": "Action筛选",
"desc": "按 Action 类型、数量、随机、费用、科技、训练、地块杂项等过滤候选。",
"nodes": [
{ "class": "AIParamActionCount", "title": "判断 Action 数量", "file": "AIParamActionCount.cs" },
{ "class": "AIParamActionRandom", "title": "Action 随机", "file": "AIParamActionRandom.cs" },
{ "class": "AIParamAIParamActionType", "title": "AI Param Action 筛选", "file": "AIParamAIParamActionType.cs" },
{ "class": "AIParamRandomAction", "title": "随机一个 Action", "file": "AIParamRandomAction.cs" },
{ "class": "AIParamPlayerActionType", "title": "筛选玩家行为", "file": "AIParamPlayerActionType.cs" },
{ "class": "AIParamUnitActionType", "title": "Action Type 筛选", "file": "AIParamUnitActionType.cs" },
{ "class": "AIParamUnitActionTypes", "title": "多 Action Type 筛选", "file": "AIParamUnitActionTypes.cs" },
{ "class": "AIParamGridMiscType", "title": "GridMisc 限制", "file": "AIParamGridMiscType.cs" },
{ "class": "AIParamTrainUnitType", "title": "造小兵限制", "file": "AIParamTrainUnitType.cs" },
{ "class": "AIParamTechType", "title": "学科技限制", "file": "AIParamTechType.cs" },
{ "class": "AIParamTechTypes", "title": "Tech 多筛选", "file": "AIParamTechTypes.cs" },
{ "class": "AIParamCultureCardType", "title": "Culture Card 筛选", "file": "AIParamCultureCardType.cs" },
{ "class": "AIParamUnitShipAction", "title": "限制船升级操作", "file": "AIParamUnitShipAction.cs" }
]
},
{
"id": "unit",
"name": "单位/英雄状态",
"desc": "判断自身或目标单位的血量、等级、类型、阵营、技能和行动点。",
"nodes": [
{ "class": "AIParamHealth", "title": "判断自身血量", "file": "AIParamHealth.cs" },
{ "class": "AIParamLevel", "title": "等级判断", "file": "AIParamLevel.cs" },
{ "class": "AIParamUnitCiv", "title": "筛选阵营", "file": "AIParamUnitCiv.cs" },
{ "class": "AIParamUnitFullType", "title": "小兵英雄类型筛选", "file": "AIParamUnitFullType.cs" },
{ "class": "AIParamSelfSkill", "title": "判断自己是否拥有技能", "file": "AIParamSelfSkill.cs" },
{ "class": "AIParamSelfSkillLevel", "title": "判断技能层数", "file": "AIParamSelfSkillLevel.cs" },
{ "class": "AIUnitCanMoveAndAttack", "title": "小兵能移动能攻击", "file": "AIUnitCanMoveAndAttack.cs" },
{ "class": "AIParamGridCollect", "title": "判断自身处于村庄或者敌方城市", "file": "AIParamGridCollect.cs" },
{ "class": "AIParamHeroStrategy", "title": "单位策略为", "file": "AIParamHeroStrategy.cs" },
{ "class": "AIParamHeroStrategyList", "title": "单位策略为(列表)", "file": "AIParamHeroStrategyList.cs" },
{ "class": "AIParamMustGarrison", "title": "必须驻守城市", "file": "AIParamMustGarrison.cs" },
{ "class": "AIParanIsUnitInSelfCity", "title": "小兵在我方城市上", "file": "AIParanIsUnitInSelfCity.cs" }
]
},
{
"id": "target",
"name": "目标单位/目标格",
"desc": "筛选目标血量、目标技能、目标阵营、目标格距离、可达性和目标城市。",
"nodes": [
{ "class": "AIParamTargetHealth", "title": "目标血量", "file": "AIParamTargetHealth.cs" },
{ "class": "AIParamTargetHealthRatio", "title": "目标血量百分比", "file": "AIParamTargetHealthRatio.cs" },
{ "class": "AIParamTargetSkill", "title": "判断目标是否拥有技能", "file": "AIParamTargetSkill.cs" },
{ "class": "AIParamTargetUnitFullType", "title": "筛选目标单位类型", "file": "AIParamTargetUnitFullType.cs" },
{ "class": "AIParamTargetUnitLeague", "title": "筛选目标是友军", "file": "AIParamTargetUnitLeague.cs" },
{ "class": "AIParamIsTargetAttacker", "title": "目标是敌军?", "file": "AIParamIsTargetAttacker.cs" },
{ "class": "AIParamIsTargetHeroAttacker", "title": "目标英雄是敌军?", "file": "AIParamIsTargetHeroAttacker.cs" },
{ "class": "AIParamIsTargetSelfUnit", "title": "筛选目标是己方单位", "file": "AIParamIsTargetSelfUnit.cs" },
{ "class": "AIParamIsTargetSkillAttacker", "title": "目标有指定技能", "file": "AIParamIsTargetSkillAttacker.cs" },
{ "class": "AIParamIsTargetHasSkill", "title": "目标是否有技能", "file": "AIParamIsTargetHasSkill.cs" },
{ "class": "AIParamIsTargetUnitHealthGap", "title": "筛选目标兵血量缺口", "file": "AIParamIsTargetUnitHealthGap.cs" },
{ "class": "AIParamIsTargetUnitHealthInCondi", "title": "筛选目标兵血量 <= Condi?", "file": "AIParamIsTargetUnitHealthInCondi.cs" },
{ "class": "AIParamIsUnitAround", "title": "目标兵是否在周围", "file": "AIParamIsUnitAround.cs" },
{ "class": "AIParamIsUnitInOtherCity", "title": "目标兵是否占据了敌方城市?", "file": "AIParamIsUnitInOtherCity.cs" },
{ "class": "AIParamTargetGridDistance", "title": "判断移动距离", "file": "AIParamTargetGridDistance.cs" },
{ "class": "AIParamTargetGridTerritory", "title": "判断目标点领土", "file": "AIParamTargetGridTerritory.cs" },
{ "class": "AIParamTargetGridIsMyCity", "title": "判断目标是我的城市", "file": "AIParamTargetGridIsMyCity.cs" },
{ "class": "AIParamTargetGridIsOtherCity", "title": "判断目标是非我城市", "file": "AIParamTargetGridIsOtherCity.cs" },
{ "class": "AIParamTargetGridIsAttackCity", "title": "判断目标格子是敌军城市", "file": "AIParamTargetGridIsAttackCity.cs" },
{ "class": "AIParamCheckTargetGridCanArrive", "title": "判断目标点是否可达", "file": "AIParamCheckTargetGridCanArrive.cs" },
{ "class": "AIParamTargetCityHaveUnit", "title": "目标城市上有单位?", "file": "AIParamTargetCityHaveUnit.cs" },
{ "class": "AIParamTargetCityInAttackRange", "title": "目标城市在射程内?", "file": "AIParamTargetCityInAttackRange.cs" },
{ "class": "AIParamEnemyCityTarget", "title": "将可达最近的敌方城市中心设为目标点", "file": "AIParamEnemyCityTarget.cs" },
{ "class": "AIParamSetMoveTargetGrid", "title": "设置移动目标点", "file": "AIParamSetMoveTargetGrid.cs" },
{ "class": "AIParamRecentlySelfTerritoryTarget", "title": "找最近的我方领土目标", "file": "AIParamRecentlySelfTerritoryTarget.cs" }
]
},
{
"id": "space",
"name": "周边/空间分析",
"desc": "统计周围单位、城市、特殊地块、英雄和攻击威胁。",
"nodes": [
{ "class": "AIParamAroundLeagueThanOtherUnit", "title": "周围友军大于或小于敌军", "file": "AIParamAroundLeagueThanOtherUnit.cs" },
{ "class": "AIParamAroundNoUnitCity", "title": "周围存在无人驻守的我方城市", "file": "AIParamAroundNoUnitCity.cs" },
{ "class": "AIParamAroundOtherCity", "title": "(移动力+射程)周围有敌方城市", "file": "AIParamAroundOtherCity.cs" },
{ "class": "AIParamAroundOtherUnitCount", "title": "周围敌军数量", "file": "AIParamAroundOtherUnitCount.cs" },
{ "class": "AIParamAroundSelfLeagueUnitCount", "title": "周围友军数量", "file": "AIParamAroundSelfLeagueUnitCount.cs" },
{ "class": "AIParamAroundSelfUnitCount", "title": "周围友方英雄", "file": "AIParamAroundSelfUnitCount.cs" },
{ "class": "AIParamAroundSpTypeGrid", "title": "周围地块数量", "file": "AIParamAroundSpTypeGrid.cs" },
{ "class": "AIParamAroundTargetSkillUnit", "title": "目标附近存在不满血的拥有Skill的友方单位", "file": "AIParamAroundTargetSkillUnit.cs" },
{ "class": "AIParamIsAroundUnitHealthInCondi", "title": "周围存在己方单位血量 <= Condi?", "file": "AIParamIsAroundUnitHealthInCondi.cs" },
{ "class": "AIParamIsAroundUnitInOtherCity", "title": "周围己方单位是否占据了敌方城市?", "file": "AIParamIsAroundUnitInOtherCity.cs" },
{ "class": "AIParamIsTargetGridAroundSkillAttacker", "title": "目标格子周围有指定技能敌军", "file": "AIParamIsTargetGridAroundSkillAttacker.cs" },
{ "class": "AIParamIsTargetGridHasAttacker", "title": "目标格子周围有敌军", "file": "AIParamIsTargetGridHasAttacker.cs" },
{ "class": "AIParamIsTargetGridHasHeroAndAttacker", "title": "目标格子周围有英雄且有敌军", "file": "AIParamIsTargetGridHasHeroAndAttacker.cs" },
{ "class": "AIParamIsTargetGridHasHeroAndHealth", "title": "目标格子周围有英雄且血量判断", "file": "AIParamIsTargetGridHasHeroAndHealth.cs" }
]
},
{
"id": "city-country",
"name": "国家/城市/领土",
"desc": "判断国家战略、回合、财富、城市人数、危险值、领土资源和常备军生产。",
"nodes": [
{ "class": "CountryStrategyCondition", "title": "国家战略为?", "file": "CountryStrategyCondition.cs" },
{ "class": "AIParamPlayerTurn", "title": "回合数限制", "file": "AIParamPlayerTurn.cs" },
{ "class": "AIParamPlayerTurnMatch", "title": "回合数限制(带取余)", "file": "AIParamPlayerTurnMatch.cs" },
{ "class": "AIParamPlayerWealth", "title": "玩家钱", "file": "AIParamPlayerWealth.cs" },
{ "class": "AIParamRandom", "title": "随机概率", "file": "AIParamRandom.cs" },
{ "class": "AIParamCityLevelMoreThanX", "title": "场上大于某等级的国家有多少", "file": "AIParamCityLevelMoreThanX.cs" },
{ "class": "AIParamCityUnitCountLimit", "title": "城市人数", "file": "AIParamCityUnitCountLimit.cs" },
{ "class": "AIParamCityUnitRatio", "title": "城市人数比例", "file": "AIParamCityUnitRatio.cs" },
{ "class": "AIParamSelfCityCount", "title": "我方城市数量", "file": "AIParamSelfCityCount.cs" },
{ "class": "AIParamSelfDanger", "title": "判断我的危险值", "file": "AIParamSelfDanger.cs" },
{ "class": "AIParamSelfTerritory", "title": "在我方领土?", "file": "AIParamSelfTerritory.cs" },
{ "class": "AIParamSelfTerritoryResource", "title": "我方领土存在符合条件格子", "file": "AIParamSelfTerritoryResource.cs" },
{ "class": "AIParamDefendTrainUnit", "title": "空城紧急造兵", "file": "AIParamDefendTrainUnit.cs" },
{ "class": "AIParamStandingTrainUnit", "title": "常备军训练单位", "file": "AIParamStandingTrainUnit.cs" },
{ "class": "AIParamExplore", "title": "周围可抵达的探索目标", "file": "AIParamExplore.cs" },
{ "class": "AIActionRecoveryAndNoMove", "title": "回血并且原地不动", "file": "AIActionRecoveryAndNoMove.cs" },
{ "class": "AIParamIsCalm", "title": "设置冷静期 / 不处于冷静期", "file": "AIParamIsCalm.cs" }
]
},
{
"id": "choose",
"name": "择优选择",
"desc": "从候选集合中选最高收益、最远、最低血量、最低等级等目标。",
"nodes": [
{ "class": "AIParamChooseFarthestTarget", "title": "筛选最远目标", "file": "AIParamChooseFarthestTarget.cs" },
{ "class": "AIParamChooseMaxAttacker", "title": "筛选目标单位攻击力", "file": "AIParamChooseMaxAttacker.cs" },
{ "class": "AIParamChooseMaxKillScore", "title": "筛选最高值目标格子(>0)", "file": "AIParamChooseMaxKillScore.cs" },
{ "class": "AIParamChooseMinHealthSelfGiant", "title": "筛选最低血量目标单位(EirinCommon专用)", "file": "AIParamChooseMinHealthSelfGiant.cs" },
{ "class": "AIParamChooseMinLevelHeroAction", "title": "选取最低等级的英雄 Action", "file": "AIParamChooseMinLevelHeroAction.cs" },
{ "class": "AIParamChooseTargetHealth", "title": "筛选血量低于阈值的目标单位", "file": "AIParamChooseTargetHealth.cs" }
]
},
{
"id": "diplomacy",
"name": "外交",
"desc": "围绕好感、结盟请求、使馆建设和外交科技评分。",
"nodes": [
{ "class": "AIParamAnyPlayerFeelValue", "title": "存在好感度 >= 的国家", "file": "AIParamAnyPlayerFeelValue.cs" },
{ "class": "AIParamFeelingValue", "title": "筛选好感度", "file": "AIParamFeelingValue.cs" },
{ "class": "AIParamHandleLeagueRequest", "title": "筛选联盟请求", "file": "AIParamHandleLeagueRequest.cs" },
{ "class": "AIParamBuildEmbassy", "title": "余额/好感/最高分建使馆", "file": "AIParamBuildEmbassy.cs" }
]
}
],
"risks": [
"AI真实执行必须保持在 ActionLogicBase.CompleteExecute不要在行为树节点、UI或网络接收处直接改 MapData。",
"AIActionScoreCalculator 的 CalMap 是模拟图Action.Execute 中不应默认依赖 Main.MapData、真实 UI、相机、动画或非同步随机。",
"CommonActionParams 在替换引用后必须 OnParamChanged跨图/网络/训练反编码后必须设置 MapData 并 RefreshParams。",
"行为树 BaseActionTask 派生节点必须调用 base.OnExecute并且所有路径必须 EndAction否则行为树会卡住。",
"训练/模型路径依赖 802 维状态和 8 维 Action 编码;新增 Action 维度或索引规则时要同步 TrainingState 与模型数据。"
]
}

View File

@ -80,6 +80,10 @@
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16v16H4z"/><path d="M9 4v16M4 9h16"/><path d="M14 14h3v3h-3z"/></svg>
游戏机制
</button>
<button class="sidebar-tab" data-tab="ai-logic">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a7 7 0 00-7 7v4a4 4 0 004 4h1v3h4v-3h1a4 4 0 004-4V9a7 7 0 00-7-7z"/><path d="M9 9h.01M15 9h.01M9 13h6"/></svg>
AI逻辑
</button>
<button class="sidebar-tab" data-tab="story">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>
文案剧情
@ -278,6 +282,7 @@
<button class="sub-tab" data-sub="fh-resources">ResourceInfoList</button>
<button class="sub-tab" data-sub="fh-actions">ActionList</button>
<button class="sub-tab" data-sub="fh-texts">TextDataAssets</button>
<button class="sub-tab" data-sub="fh-wiki-list">WikiList</button>
</div>
<div id="sub-fh-skills" class="sub-panel active">
@ -419,6 +424,27 @@
</div>
</div>
</div>
<div id="sub-fh-wiki-list" class="sub-panel">
<div class="module-card fh-module">
<div class="module-header">
<span class="module-title">WikiDataAssets / Items</span>
<span class="module-badge" id="fh-wiki-list-badge">未加载</span>
</div>
<div class="module-body">
<div class="fh-toolbar">
<input type="text" id="fh-wiki-list-search" class="search-input" placeholder="搜索 Wiki Id / 名称 / 类型 / 描述...">
<button class="gb-btn-sm" id="fh-wiki-list-sort" type="button">ID 倒序</button>
<button class="gb-btn-sm gb-btn-primary" id="fh-wiki-list-add" type="button">新增 WikiItem</button>
<button class="gb-btn-sm" id="fh-wiki-list-refresh">刷新</button>
</div>
<div id="fh-wiki-list-source" class="fh-source"></div>
<div id="fh-wiki-list-content" class="fh-collection-content">
<div class="loading-inline">点击 WikiList 后加载 WikiDataAssets...</div>
</div>
</div>
</div>
</div>
</div>
<div id="panel-mechanics" class="tab-panel">
@ -438,6 +464,12 @@
</div>
</div>
<div id="panel-ai-logic" class="tab-panel">
<div id="ai-logic-content" class="ai-page">
<div class="loading-inline">正在加载 AI 逻辑索引...</div>
</div>
</div>
<div id="panel-story" class="tab-panel">
<!-- Sub-tabs -->
<div class="sub-tabs" id="story-sub-tabs">
@ -1175,6 +1207,7 @@
<script src="js/app.js"></script>
<script src="js/balance.js"></script>
<script src="js/mechanics.js?v=20260526-1"></script>
<script src="js/ai_logic.js"></script>
<script src="js/story.js"></script>
<script src="js/art.js"></script>
<script src="js/sentiment.js"></script>

View File

@ -0,0 +1,266 @@
/**
* TH1 Dashboard - AI Logic Overview
*/
let aiLogicLoaded = false;
let aiLogicData = null;
let aiNodeCategory = 'all';
function aiEscHtml(str) {
return String(str ?? '').replace(/[&<>"']/g, ch => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
}[ch]));
}
async function aiLogicLoad() {
if (aiLogicLoaded) return;
aiLogicLoaded = true;
const root = document.getElementById('ai-logic-content');
if (!root) return;
try {
const resp = await fetch('data/ai_logic.json?t=' + Date.now());
if (!resp.ok) throw new Error(resp.status);
aiLogicData = await resp.json();
aiLogicRender();
} catch (e) {
root.innerHTML = '<div class="loading-inline">AI逻辑索引加载失败</div>';
console.warn('aiLogicLoad failed:', e);
}
}
function aiLogicLoadIfVisible() {
const panel = document.getElementById('panel-ai-logic');
if (panel && panel.classList.contains('active')) {
aiLogicLoad();
}
}
function aiLogicRender() {
const root = document.getElementById('ai-logic-content');
if (!root || !aiLogicData) return;
const allNodes = aiGetAllNodes();
const catCounts = aiLogicData.nodeCategories.map(cat => ({
id: cat.id,
name: cat.name,
count: cat.nodes.length
}));
root.innerHTML = `
<section class="ai-hero">
<div class="ai-hero-top">
<div>
<div class="ai-title">${aiEscHtml(aiLogicData.title)}</div>
<div class="ai-summary">${aiEscHtml(aiLogicData.summary)}</div>
</div>
<div class="ai-source">${aiEscHtml(aiLogicData.subtitle)}<br>${aiEscHtml(aiLogicData.generatedAt)}</div>
</div>
<div class="ai-stats">
${aiLogicData.stats.map(s => `
<div class="ai-stat">
<div class="ai-stat-value">${aiEscHtml(s.value)}</div>
<div class="ai-stat-label">${aiEscHtml(s.label)}</div>
</div>
`).join('')}
</div>
</section>
<section class="ai-card">
<div class="ai-card-head">
<div class="ai-card-title">AI回合主流程</div>
<span class="ai-card-badge">Action权威层</span>
</div>
<div class="ai-card-body">
<div class="ai-flow">
${aiLogicData.flow.map((step, index) => `
<div class="ai-flow-step">
<div class="ai-flow-index">${index + 1}</div>
<div class="ai-flow-name">${aiEscHtml(step.name)}</div>
<div class="ai-flow-desc">${aiEscHtml(step.desc)}</div>
</div>
`).join('')}
</div>
</div>
</section>
<section class="ai-card">
<div class="ai-card-head">
<div class="ai-card-title">代码模块分层</div>
<span class="ai-card-badge">${aiLogicData.modules.length}</span>
</div>
<div class="ai-card-body">
<div class="ai-module-grid">
${aiLogicData.modules.map(mod => `
<article class="ai-module">
<div class="ai-module-name">${aiEscHtml(mod.name)}</div>
<div class="ai-module-text">${aiEscHtml(mod.role)}</div>
<div class="ai-code" title="${aiEscHtml(mod.path)}">${aiEscHtml(mod.path)}</div>
<div class="ai-list" style="margin-top:10px;">
${mod.details.map(d => `<div class="ai-module-text">• ${aiEscHtml(d)}</div>`).join('')}
</div>
</article>
`).join('')}
</div>
</div>
</section>
<div class="ai-section-grid">
<section class="ai-card">
<div class="ai-card-head">
<div class="ai-card-title">策略与评分枚举</div>
<span class="ai-card-badge">${aiLogicData.strategies.length}</span>
</div>
<div class="ai-card-body">
<div class="ai-list">
${aiLogicData.strategies.map(item => `
<div class="ai-list-item">
<div class="ai-list-main">
<div class="ai-list-title">${aiEscHtml(item.name)}</div>
<div class="ai-list-desc">${aiEscHtml(item.purpose)}</div>
<div class="ai-list-desc">${aiEscHtml(item.values)}</div>
</div>
</div>
`).join('')}
</div>
</div>
</section>
<section class="ai-card">
<div class="ai-card-head">
<div class="ai-card-title">源码入口</div>
<span class="ai-card-badge">${aiLogicData.sourceFiles.length}个文件</span>
</div>
<div class="ai-card-body">
<div class="ai-table-wrap">
<table class="ai-table">
<thead><tr><th>分组</th><th></th><th></th></tr></thead>
<tbody>
${aiLogicData.sourceFiles.map(file => `
<tr>
<td>${aiEscHtml(file.group)}</td>
<td><span class="ai-code" title="${aiEscHtml(file.path)}">${aiEscHtml(file.file)}</span></td>
<td>${aiEscHtml(file.purpose)}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
</section>
</div>
<section class="ai-card">
<div class="ai-card-head">
<div class="ai-card-title">BTNodeCanvas节点索引</div>
<span class="ai-card-badge">${allNodes.length}个节点类</span>
</div>
<div class="ai-card-body">
<div class="ai-node-toolbar">
<input type="text" id="ai-node-search" class="search-input" placeholder="搜索节点类名、中文名或文件..." oninput="aiRenderNodeTable()">
</div>
<div class="ai-node-cats" id="ai-node-cats">
<button class="ai-node-cat active" data-cat="all">全部 ${allNodes.length}</button>
${catCounts.map(cat => `<button class="ai-node-cat" data-cat="${aiEscHtml(cat.id)}">${aiEscHtml(cat.name)} ${cat.count}</button>`).join('')}
</div>
<div id="ai-node-category-desc" class="ai-summary" style="margin-bottom:12px;"></div>
<div class="ai-table-wrap">
<table class="ai-table">
<thead><tr><th>分类</th><th></th><th>/</th><th></th></tr></thead>
<tbody id="ai-node-table-body"></tbody>
</table>
</div>
</div>
</section>
<section class="ai-card">
<div class="ai-card-head">
<div class="ai-card-title">维护风险点</div>
<span class="ai-card-badge">必须保持</span>
</div>
<div class="ai-card-body">
<div class="ai-list">
${aiLogicData.risks.map(risk => `
<div class="ai-list-item">
<div class="ai-list-main">
<div class="ai-list-desc">${aiEscHtml(risk)}</div>
</div>
</div>
`).join('')}
</div>
</div>
</section>
`;
const catRoot = document.getElementById('ai-node-cats');
catRoot?.querySelectorAll('.ai-node-cat').forEach(btn => {
btn.addEventListener('click', () => {
aiNodeCategory = btn.dataset.cat || 'all';
catRoot.querySelectorAll('.ai-node-cat').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
aiRenderNodeTable();
});
});
aiRenderNodeTable();
}
function aiGetAllNodes() {
if (!aiLogicData) return [];
return aiLogicData.nodeCategories.flatMap(cat =>
cat.nodes.map(node => ({
...node,
categoryId: cat.id,
category: cat.name,
categoryDesc: cat.desc
}))
);
}
function aiRenderNodeTable() {
const body = document.getElementById('ai-node-table-body');
if (!body || !aiLogicData) return;
const query = (document.getElementById('ai-node-search')?.value || '').trim().toLowerCase();
const selectedCat = aiLogicData.nodeCategories.find(cat => cat.id === aiNodeCategory);
const desc = document.getElementById('ai-node-category-desc');
if (desc) {
desc.textContent = selectedCat ? selectedCat.desc : '行为树节点通过黑板 Data 读写候选 Action、目标参数、目标列表、标记和迭代上下文。';
}
let rows = aiGetAllNodes();
if (aiNodeCategory !== 'all') rows = rows.filter(node => node.categoryId === aiNodeCategory);
if (query) {
rows = rows.filter(node => `${node.category} ${node.class} ${node.title} ${node.file}`.toLowerCase().includes(query));
}
if (!rows.length) {
body.innerHTML = '<tr><td colspan="4">没有匹配节点</td></tr>';
return;
}
body.innerHTML = rows.map(node => `
<tr>
<td>${aiEscHtml(node.category)}</td>
<td><span class="ai-code">${aiEscHtml(node.class)}</span></td>
<td>${aiEscHtml(node.title)}</td>
<td><span class="ai-code">BTNodeCanvas/${aiEscHtml(node.file)}</span></td>
</tr>
`).join('');
}
window.aiLogicLoad = aiLogicLoad;
window.aiRenderNodeTable = aiRenderNodeTable;
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.sidebar-tab[data-tab="ai-logic"]').forEach(tab => {
tab.addEventListener('click', () => window.setTimeout(aiLogicLoad, 0));
});
aiLogicLoadIfVisible();
});
window.addEventListener('hashchange', aiLogicLoadIfVisible);

View File

@ -848,6 +848,9 @@ function initTabs() {
if (tab.dataset.tab === 'mechanics' && typeof mechanicsLoad === 'function') {
mechanicsLoad();
}
if (tab.dataset.tab === 'ai-logic' && typeof aiLogicLoad === 'function') {
aiLogicLoad();
}
if (tab.dataset.tab === 'form-helper' && typeof fhLoad === 'function') {
fhLoad();
}

View File

@ -82,6 +82,19 @@ const fhCollections = {
sortDirection: 'desc',
nameReadonly: true,
},
'wiki-list': {
title: 'WikiDataAssets / Items',
itemLabel: 'Id',
nameField: 'Name',
descField: 'Desc',
rows: [],
fields: [],
readonlyFields: [],
loaded: false,
loading: false,
sortDirection: 'desc',
canAdd: true,
},
};
const FH_VIEW_TYPES = [
@ -107,6 +120,18 @@ function fhAttr(value) {
return fhEsc(value).replace(/"/g, '&quot;');
}
function fhBindLineTextareas(root) {
if (!root) return;
root.querySelectorAll('textarea.fh-line-textarea').forEach(textarea => {
const resize = () => {
textarea.style.height = 'auto';
textarea.style.height = `${textarea.scrollHeight + 2}px`;
};
textarea.addEventListener('input', resize);
resize();
});
}
function fhLoad() {
fhInit();
if (fhActiveCollection !== 'skills') {
@ -270,7 +295,7 @@ function fhRenderSkills() {
<input class="fh-line-input" data-field="name" value="${fhAttr(skill.name)}">
</td>
<td>
<input class="fh-line-input fh-line-desc" data-field="desc" value="${fhAttr(skill.desc)}">
<textarea class="fh-line-textarea fh-line-desc" data-field="desc">${fhEsc(skill.desc)}</textarea>
</td>
<td><span class="badge ${fhBadgeFor(skill.skillViewTypeLabel)}">${fhEsc(skill.skillViewTypeLabel)}</span></td>
<td>${skill.notShow ? '<span class="badge badge-red">隐藏</span>' : '<span class="badge badge-green">可见</span>'}</td>
@ -301,6 +326,7 @@ function fhRenderSkills() {
</table>
</div>
`;
fhBindLineTextareas(content);
}
function fhUpdateSkillSortButton() {
@ -613,6 +639,12 @@ function fhInitCollectionControls(key) {
});
}
fhUpdateCollectionSortButton(key);
const add = document.getElementById(`fh-${key}-add`);
if (add && !add.dataset.bound) {
add.dataset.bound = '1';
add.addEventListener('click', () => fhOpenCollectionAdd(key));
}
}
async function fhLoadCollection(key, force = false) {
@ -635,6 +667,7 @@ async function fhLoadCollection(key, force = false) {
config.title = data.title || config.title;
config.fields = data.fields || [];
config.readonlyFields = data.readonlyFields || [];
config.canAdd = !!data.canAdd;
config.rows = (data.rows || []).map(row => fhNormalizeCollectionRow(row));
config.loaded = true;
@ -715,7 +748,7 @@ function fhRenderCollection(key) {
${config.nameReadonly ? `<span class="fh-readonly-name">${fhEsc(row.name)}</span>` : `<input class="fh-line-input" data-field="name" value="${fhAttr(row.name)}">`}
</td>
<td>
<input class="fh-line-input fh-line-desc" data-field="desc" value="${fhAttr(row.desc)}">
<textarea class="fh-line-textarea fh-line-desc" data-field="desc">${fhEsc(row.desc)}</textarea>
</td>
<td class="fh-summary-cell">${fhEsc(fhCollectionSummary(key, row))}</td>
<td class="fh-action-cell">
@ -744,6 +777,7 @@ function fhRenderCollection(key) {
</table>
</div>
`;
fhBindLineTextareas(content);
}
function fhCollectionSummary(key, row) {
@ -866,6 +900,110 @@ async function fhSaveCollection(key, row, values, options = {}) {
}
}
async function fhOpenCollectionAdd(key) {
const config = fhCollections[key];
if (!config || !config.canAdd) return;
if (!config.loaded) {
await fhLoadCollection(key, true);
}
if (!config.fields?.length) {
fhToast('字段配置尚未加载', true);
return;
}
const maxId = Math.max(0, ...(config.rows || []).map(row => Number(row.itemId || 0)));
const row = {
assetIndex: -1,
itemId: maxId + 1,
name: '',
desc: '',
values: {},
fields: (config.fields || []).map(field => ({ ...field })),
readonly: {},
};
(config.fields || []).forEach(field => {
if (field.key === config.itemLabel || field.key === 'Id') {
row.values[field.key] = maxId + 1;
} else if (field.key === config.nameField) {
row.values[field.key] = '';
} else if (field.key === config.descField) {
row.values[field.key] = '';
} else if (field.kind === 'bool') {
row.values[field.key] = false;
} else if (field.kind === 'intList') {
row.values[field.key] = [];
} else if (field.kind === 'int' || field.kind === 'float') {
row.values[field.key] = 0;
} else {
row.values[field.key] = '';
}
});
document.getElementById('fh-collection-add-modal')?.remove();
const overlay = document.createElement('div');
overlay.id = 'fh-collection-add-modal';
overlay.className = 'gb-modal-overlay';
overlay.innerHTML = `
<div class="gb-modal fh-modal">
<div class="gb-modal-header">
<span class="gb-modal-title">新增 ${fhEsc(config.title)}</span>
<button class="gb-modal-close" onclick="fhCloseCollectionAdd()">&times;</button>
</div>
<div class="gb-modal-body">
${fhRenderCollectionDetailForm(key, row)}
</div>
<div class="fh-modal-footer">
<button class="gb-btn-sm gb-btn-primary" onclick="fhSaveCollectionAdd('${key}')">新增</button>
<button class="gb-btn-sm" onclick="fhCloseCollectionAdd()">关闭</button>
</div>
</div>
`;
overlay.addEventListener('click', event => {
if (event.target === overlay) fhCloseCollectionAdd();
});
document.body.appendChild(overlay);
}
function fhCloseCollectionAdd() {
document.getElementById('fh-collection-add-modal')?.remove();
}
async function fhSaveCollectionAdd(key) {
const config = fhCollections[key];
const modal = document.getElementById('fh-collection-add-modal');
if (!config || !modal) return;
const values = {};
(config.fields || []).forEach(field => {
values[field.key] = fhReadCollectionField(modal, field.key, field.kind || 'string');
});
try {
const resp = await fetch(`/api/form-helper/collection/${key}/add`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ values }),
});
const data = await resp.json();
if (!resp.ok || !data.success) {
throw new Error(data.error || `HTTP ${resp.status}`);
}
if (data.row) {
config.rows.push(fhNormalizeCollectionRow(data.row));
} else {
config.loaded = false;
await fhLoadCollection(key, true);
}
fhCloseCollectionAdd();
fhRenderCollection(key);
fhToast(`已新增 ${config.title}`);
if (data.row) fhOpenCollectionDetail(key, data.row.assetIndex);
} catch (err) {
fhToast(`新增失败:${err.message || err}`, true);
throw err;
}
}
function fhOpenCollectionDetail(key, assetIndex) {
const liveRow = fhApplyCollectionLineInputs(key, assetIndex);
const row = liveRow ? fhCloneCollectionRow(liveRow) : null;
@ -999,3 +1137,6 @@ window.fhOpenCollectionDetail = fhOpenCollectionDetail;
window.fhRestoreCollectionDetail = fhRestoreCollectionDetail;
window.fhSaveCollectionDetail = fhSaveCollectionDetail;
window.fhCloseCollectionDetail = fhCloseCollectionDetail;
window.fhOpenCollectionAdd = fhOpenCollectionAdd;
window.fhSaveCollectionAdd = fhSaveCollectionAdd;
window.fhCloseCollectionAdd = fhCloseCollectionAdd;

View File

@ -101,6 +101,7 @@ UNIT_TYPE_DATA_ASSET = os.path.join(PROJECT_ROOT, 'Unity', 'Assets', 'BundleReso
GRID_RESOURCE_DATA_ASSET = os.path.join(PROJECT_ROOT, 'Unity', 'Assets', 'BundleResources', 'DataAssets', 'GridAndResourceDataAssets.asset')
ACTION_DATA_ASSET = os.path.join(PROJECT_ROOT, 'Unity', 'Assets', 'BundleResources', 'DataAssets', 'ActionDataAssets.asset')
TEXT_DATA_ASSET = os.path.join(PROJECT_ROOT, 'Unity', 'Assets', 'BundleResources', 'DataAssets', 'TextDataAssets.asset')
WIKI_DATA_ASSET = os.path.join(PROJECT_ROOT, 'Unity', 'Assets', 'BundleResources', 'DataAssets', 'WikiDataAssets.asset')
DESIGN_MECHANICS_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..', 'Design', 'final', 'mechanics'))
DESIGN_NARRATIVE_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..', 'Design', 'final', 'narrative'))
DESIGN_SHARED_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..', 'Design', 'shared'))
@ -684,6 +685,8 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
self.handle_devplan_subtask_delete()
elif self.path.startswith('/api/form-helper/collection/') and self.path.endswith('/save'):
self._handle_form_helper_collection_save()
elif self.path.startswith('/api/form-helper/collection/') and self.path.endswith('/add'):
self._handle_form_helper_collection_add()
elif self.path == '/api/form-helper/skills/save':
self._handle_form_helper_skill_save()
elif self.path == '/api/art-dev/icon-reviews/save':
@ -1176,6 +1179,26 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
],
'readonly_fields': ['FieldName'],
},
'wiki-list': {
'title': 'WikiDataAssets / Items',
'asset_path': WIKI_DATA_ASSET,
'list_key': 'Items',
'item_key': 'Id',
'name_field': 'Name',
'desc_field': 'Desc',
'can_add': True,
'fields': [
{'key': 'Id', 'kind': 'int', 'label': 'Id', 'primary': True},
{'key': 'Name', 'kind': 'string', 'label': 'Name', 'quick': True},
{'key': 'Types', 'kind': 'intList', 'label': 'Types'},
{'key': 'DescType', 'kind': 'int', 'label': 'DescType'},
{'key': 'Desc', 'kind': 'string', 'label': 'Desc', 'quick': True},
{'key': 'UseHint', 'kind': 'bool', 'label': 'UseHint'},
{'key': 'IconSizeType', 'kind': 'int', 'label': 'IconSizeType'},
{'key': 'HasIcon', 'kind': 'bool', 'label': 'HasIcon'},
],
'readonly_fields': ['Icon', 'HintProvider'],
},
}
def _handle_form_helper_skills(self):
@ -1228,6 +1251,7 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
'count': len(rows),
'fields': config['fields'],
'readonlyFields': config.get('readonly_fields', []),
'canAdd': bool(config.get('can_add')),
'rows': rows,
})
except Exception as e:
@ -1260,13 +1284,35 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
except Exception as e:
self._send_json({'success': False, 'error': str(e)}, 500)
def _form_helper_collection_key_from_path(self, save):
def _handle_form_helper_collection_add(self):
try:
collection_key = self._form_helper_collection_key_from_path(save=True, action='add')
config = self.FORM_HELPER_COLLECTIONS.get(collection_key)
if not config:
self._send_json({'success': False, 'error': 'Unknown form helper collection'}, 404)
return
if not config.get('can_add'):
self._send_json({'success': False, 'error': 'Collection does not support add'}, 400)
return
payload = self._read_json_body()
values = payload.get('values') or {}
if not isinstance(values, dict):
self._send_json({'success': False, 'error': 'values must be an object'}, 400)
return
result = self._add_form_helper_collection_row(collection_key, values)
self._send_json(result)
except Exception as e:
self._send_json({'success': False, 'error': str(e)}, 500)
def _form_helper_collection_key_from_path(self, save, action='save'):
path = urlparse(self.path).path.strip('/')
parts = path.split('/')
# api/form-helper/collection/{key}[/save]
# api/form-helper/collection/{key}[/save|add]
if len(parts) < 4 or parts[0] != 'api' or parts[1] != 'form-helper' or parts[2] != 'collection':
raise ValueError('Invalid form helper collection path')
if save and (len(parts) < 5 or parts[4] != 'save'):
if save and (len(parts) < 5 or parts[4] != action):
raise ValueError('Invalid form helper save path')
return parts[3]
@ -1367,7 +1413,7 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
for field in config['fields']:
key = field['key']
kind = field.get('kind', 'string')
raw_value = item.get(key) if isinstance(item, dict) else None
raw_value = self._get_form_helper_item_field(config, item, key) if isinstance(item, dict) else None
value = self._current_form_helper_value(raw_value, kind)
values[key] = value
field_payload = dict(field)
@ -1401,6 +1447,38 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
'searchText': ' '.join(str(x) for x in searchable if x is not None),
}
def _get_form_helper_item_field(self, config, item, key):
if config.get('list_key') == 'Items':
desc_items = item.get('DescItems')
first_desc = desc_items[0] if isinstance(desc_items, list) and desc_items else None
if first_desc is None and isinstance(desc_items, str):
first_desc = self._parse_form_helper_wiki_first_desc(desc_items)
if not isinstance(first_desc, dict):
first_desc = {}
if key == 'DescType':
return first_desc.get('DescType')
if key == 'Desc':
return first_desc.get('Desc')
if key == 'UseHint':
return first_desc.get('UseHint')
return item.get(key)
def _parse_form_helper_wiki_first_desc(self, desc_text):
result = {}
if not desc_text:
return result
for raw_line in str(desc_text).splitlines():
line = raw_line.strip()
if line.startswith('- DescType:'):
result['DescType'] = self._parse_form_helper_scalar(line[len('- DescType:'):].strip())
elif line.startswith('Desc:'):
result['Desc'] = self._parse_form_helper_scalar(line[len('Desc:'):].strip())
elif line.startswith('UseHint:'):
result['UseHint'] = self._parse_form_helper_scalar(line[len('UseHint:'):].strip())
if all(key in result for key in ('DescType', 'Desc', 'UseHint')):
break
return result
def _current_form_helper_value(self, raw_value, kind):
if kind == 'string':
return self._decode_form_helper_string(raw_value or '')
@ -1594,6 +1672,19 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
if expected_item_id is not None and current_item_id != expected_item_id:
raise ValueError(f'item id mismatch: expected {expected_item_id}, current {current_item_id}')
if config.get('list_key') == 'Items' and 'Id' in values:
new_id = self._coerce_form_helper_value(values.get('Id'), 'int')
if new_id <= 0:
raise ValueError('Id must be positive')
if new_id > 0x00ffffff:
raise ValueError('Id exceeds manual WikiItem range 0x00FFFFFF')
for other_index, other_block in enumerate(blocks):
if other_index == asset_index:
continue
other_item = self._parse_form_helper_block(lines, other_block)
if self._safe_form_helper_int(other_item.get('Id')) == new_id:
raise ValueError(f'WikiItem Id already exists: {new_id}')
changed = False
for key, value in values.items():
field = field_config.get(key)
@ -1601,16 +1692,24 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
continue
kind = field.get('kind', 'string')
new_value = self._coerce_form_helper_value(value, kind)
current_value = self._current_form_helper_value(current_item.get(key), kind)
current_value = self._current_form_helper_value(self._get_form_helper_item_field(config, current_item, key), kind)
if new_value == current_value:
continue
self._replace_form_helper_top_field(
lines,
block,
key,
self._format_form_helper_yaml_value(new_value, kind),
key == config['item_key'],
)
if config.get('list_key') == 'Items' and key in ('DescType', 'Desc', 'UseHint'):
self._replace_form_helper_wiki_desc_field(
lines,
block,
key,
self._format_form_helper_yaml_value(new_value, kind),
)
else:
self._replace_form_helper_top_field(
lines,
block,
key,
self._format_form_helper_yaml_value(new_value, kind),
key == config['item_key'],
)
changed = True
if changed:
@ -1628,6 +1727,61 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
'source': self._project_relpath(asset_path),
}
def _add_form_helper_collection_row(self, collection_key, values):
if collection_key != 'wiki-list':
raise ValueError('Add is only implemented for WikiDataAssets / Items')
config = self.FORM_HELPER_COLLECTIONS[collection_key]
asset_path = config['asset_path']
if not os.path.exists(asset_path):
raise FileNotFoundError(asset_path)
with open(asset_path, 'r', encoding='utf-8', newline='') as f:
lines = f.readlines()
blocks = self._scan_form_helper_list_blocks(lines, config['list_key'], config['item_key'])
existing_ids = []
for block in blocks:
item = self._parse_form_helper_block(lines, block)
existing_ids.append(self._safe_form_helper_int(item.get('Id')))
requested_id = self._coerce_form_helper_value(values.get('Id'), 'int') if values.get('Id') not in (None, '') else 0
next_id = requested_id or ((max(existing_ids) + 1) if existing_ids else 1)
if next_id <= 0:
raise ValueError('Id must be positive')
if next_id > 0x00ffffff:
raise ValueError('Id exceeds manual WikiItem range 0x00FFFFFF')
if next_id in existing_ids:
raise ValueError(f'WikiItem Id already exists: {next_id}')
field_config = {field['key']: field for field in config['fields']}
coerced = {}
for key, field in field_config.items():
if key == 'Id':
coerced[key] = next_id
continue
coerced[key] = self._coerce_form_helper_value(values.get(key), field.get('kind', 'string'))
insert_at = blocks[-1]['end'] if blocks else self._find_form_helper_empty_list_insert(lines, config['list_key'])
ending = self._line_ending(lines[insert_at - 1]) if insert_at > 0 else '\n'
block_lines = self._render_wiki_item_block(coerced, ending)
lines[insert_at:insert_at] = block_lines
temp_path = asset_path + '.tmp'
with open(temp_path, 'w', encoding='utf-8', newline='') as f:
f.writelines(lines)
os.replace(temp_path, asset_path)
rows = self._load_form_helper_collection_rows(collection_key)
row = next((item for item in rows if item.get('itemId') == next_id), rows[-1] if rows else None)
return {
'success': True,
'changed': True,
'collection': collection_key,
'row': row,
'source': self._project_relpath(asset_path),
}
def _save_form_helper_top_text_field(self, collection_key, asset_index, values):
config = self.FORM_HELPER_COLLECTIONS[collection_key]
asset_path = config['asset_path']
@ -1709,6 +1863,110 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
return
raise ValueError(f'Field not found in row block: {key}')
def _replace_form_helper_wiki_desc_field(self, lines, block, key, rendered_value):
desc_start = None
for idx in range(block['start'] + 1, block['end']):
if re.match(r'^ - DescType:\s*', lines[idx]):
desc_start = idx
break
if desc_start is None:
raise ValueError('WikiItem has no DescItems entry to edit')
if key == 'DescType':
lines[desc_start] = f' - DescType: {rendered_value}{self._line_ending(lines[desc_start])}'
return
indent = ' '
pattern = re.compile(rf'^{indent}{re.escape(key)}:\s*')
for idx in range(desc_start, block['end']):
if idx > desc_start and re.match(r'^ - DescType:\s*', lines[idx]):
break
if re.match(r'^ (Icon|IconSizeType|HasIcon|Types|Name|Id):\s*', lines[idx]):
break
if pattern.match(lines[idx]):
lines[idx] = f'{indent}{key}: {rendered_value}{self._line_ending(lines[idx])}'
return
raise ValueError(f'Field not found in Wiki DescItems entry: {key}')
def _find_form_helper_empty_list_insert(self, lines, list_key):
list_header = f' {list_key}:'
for idx, line in enumerate(lines):
if line.strip() == f'{list_key}:':
return idx + 1
if line.rstrip('\r\n') == list_header:
return idx + 1
raise ValueError(f'List not found in asset: {list_key}')
def _render_wiki_item_block(self, values, ending):
def line(text):
return text + ending
desc = values.get('Desc') or ''
return [
line(f" - Id: {self._format_form_helper_yaml_value(values.get('Id', 0), 'int')}"),
line(f" Name: {self._format_form_helper_yaml_value(values.get('Name', ''), 'string')}"),
line(f" Types: {self._format_form_helper_yaml_value(values.get('Types') or [], 'intList')}"),
line(" DescItems:"),
line(f" - DescType: {self._format_form_helper_yaml_value(values.get('DescType', 0), 'int')}"),
line(f" Desc: {self._format_form_helper_yaml_value(desc, 'string')}"),
line(f" UseHint: {self._format_form_helper_yaml_value(values.get('UseHint', False), 'bool')}"),
line(" HintProvider:"),
line(" HintDataType: 0"),
line(' Text: "\\u9ED8\\u8BA4\\u9759\\u6001\\u6587\\u672C..."'),
line(" TechTypeData: 0"),
line(" SkillTypeData: 0"),
line(" ActionIdData:"),
line(" ActionType: 0"),
line(" WonderType: 0"),
line(" ResourceType: 0"),
line(" FeatureType: 0"),
line(" TerrainType: 0"),
line(" UnitType: 0"),
line(" GiantType: 0"),
line(" UnitLevel: 0"),
line(" Vegetation: 0"),
line(" UnitActionType: 0"),
line(" CityLevelUpActionType: 0"),
line(" CityActionType: 0"),
line(" GridMiscActionType: 0"),
line(" SkillType: 0"),
line(" TechType: 0"),
line(" PlayerActionType: 0"),
line(" AIParamType: 0"),
line(" CultureCardType: 0"),
line(" locked: 0"),
line(" TextData:"),
line(" ActionType: 0"),
line(" WonderType: 0"),
line(" ResourceType: 0"),
line(" FeatureType: 0"),
line(" TerrainType: 0"),
line(" UnitType: 0"),
line(" GiantType: 0"),
line(" UnitLevel: 0"),
line(" Vegetation: 0"),
line(" UnitActionType: 0"),
line(" CityLevelUpActionType: 0"),
line(" CityActionType: 0"),
line(" GridMiscActionType: 0"),
line(" SkillType: 0"),
line(" TechType: 0"),
line(" PlayerActionType: 0"),
line(" AIParamType: 0"),
line(" CultureCardType: 0"),
line(" TechAtom: 0"),
line(" GeoIdList: "),
line(" UnitFullType:"),
line(" UnitType: 0"),
line(" GiantType: 0"),
line(" UnitLevel: 0"),
line(" PlayerTaskType: 0"),
line(" WikiId: 0"),
line(" Icon: {fileID: 0}"),
line(f" IconSizeType: {self._format_form_helper_yaml_value(values.get('IconSizeType', 0), 'int')}"),
line(f" HasIcon: {self._format_form_helper_yaml_value(values.get('HasIcon', False), 'bool')}"),
]
def _load_form_helper_skill_rows(self):
if not os.path.exists(SKILL_DATA_ASSET):
raise FileNotFoundError(SKILL_DATA_ASSET)

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 37c7714814e843858159d79c96cf0659
guid: 5cb4a243e40a42bf9cea3c8ef07bd4aa
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
@ -46,8 +46,8 @@ TextureImporter:
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 9
spritePivot: {x: 0.5, y: 0.4}
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
@ -112,7 +112,7 @@ TextureImporter:
outline: []
physicsShape: []
bones: []
spriteID: f11d2dd255ba45dfabc50b908f6491d8
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

View File

@ -1,127 +0,0 @@
fileFormatVersion: 2
guid: 6207dafe0fb14b80b7314df35e57474d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 9
spritePivot: {x: 0.5, y: 0.4}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 2
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 1024
resizeAlgorithm: 0
textureFormat: 25
textureCompression: 1
compressionQuality: 80
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 1
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 4fac3da32b91494cad9d3c52fc6c1e0c
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

View File

@ -1,127 +0,0 @@
fileFormatVersion: 2
guid: 135aac84a57f4cfe8b8836e70b904174
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 9
spritePivot: {x: 0.5, y: 0.4}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 2
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 1024
resizeAlgorithm: 0
textureFormat: 25
textureCompression: 1
compressionQuality: 80
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 1
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 8a92d713684a44a8a374a3a9fcb2d741
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 6656992a691048c9a234ed15116a70ca
fileFormatVersion: 2
guid: 9a0becae1016466d8e87f635f0f086af
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
@ -46,8 +46,8 @@ TextureImporter:
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 9
spritePivot: {x: 0.5, y: 0.4}
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
@ -112,7 +112,7 @@ TextureImporter:
outline: []
physicsShape: []
bones: []
spriteID: 9bfa8a3d54b148bfb00f91e0fe259ee2
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

View File

@ -1,127 +0,0 @@
fileFormatVersion: 2
guid: e49c4a7a68ee4bf78d3f64c9691a9793
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 9
spritePivot: {x: 0.5, y: 0.4}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 2
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 1024
resizeAlgorithm: 0
textureFormat: 25
textureCompression: 1
compressionQuality: 80
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 1
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5093aef0bda042c3adcd8ce192508131
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -5163,6 +5163,40 @@ MonoBehaviour:
NoNeedTech: 1
SpriteSize: {x: 90, y: 90}
SpritePos: {x: 0, y: 15}
- ActionId:
ActionType: 6
WonderType: 0
ResourceType: 0
FeatureType: 0
TerrainType: 0
UnitType: 0
GiantType: 0
UnitLevel: 0
Vegetation: 0
UnitActionType: 42
CityLevelUpActionType: 0
CityActionType: 0
GridMiscActionType: 0
SkillType: 0
TechType: 0
PlayerActionType: 0
AIParamType: 0
CultureCardType: 0
ActionName: "\u6D88\u6563"
Desc: "\u4F7F\u8BE5\u5355\u4F4D\u6D88\u6563\u3002"
NeedTechDesc: 0
TechDesc:
NeedLockDesc: 0
LockDesc:
Icon: {fileID: 21300000, guid: 15241810dc2b8bd4d9fab1bf4ae0ca18, type: 3}
IconViewSizeType: 2
VarientIcon: 0
IconList: []
Cost: 0
CityExp: 0
NoNeedTech: 1
SpriteSize: {x: 90, y: 90}
SpritePos: {x: 0, y: 15}
- ActionId:
ActionType: 6
WonderType: 0
@ -10248,10 +10282,10 @@ MonoBehaviour:
PlayerActionType: 16
AIParamType: 0
CultureCardType: 0
ActionName: "\u8981\u6C42\u8D21\u91D1"
Desc: "\u5411\u5BF9\u65B9\u53D1\u8D77\u8D21\u91D1\u8981\u6C42\uFF0820%\u7684\u56DE\u5408\u6536\u5165\uFF09\u3002\u82E5\u5BF9\u65B9\u62D2\u7EDD\u652F\u4ED8\uFF0C\u4E0B\u56DE\u5408\u5BF9\u5176**<\u52AB\u63A0>**\u6536\u76CA\u7FFB\u500D\u3002\u53D1\u8D77\u8D39\u7528\u4E3A\u6211\u65B9\u57CE\u5E02\u6570\u91CF*3**<\u91D1\u5E01>**\u3002"
NeedTechDesc: 1
TechDesc: "\u5411\u67D0\u4E2A\u5E1D\u56FD\u53D1\u8D77\u52D2\u7D22\u3002\u82E5\u5BF9\u65B9\u4E0D\u652F\u4ED8\uFF0C\u4E0B\u56DE\u5408\u6240\u6709\u5355\u4F4D\u62A2\u52AB\u6536\u76CA\u7FFB\u500D\u3002"
ActionName: "征收丹麦金"
Desc: "花费**<我方城市数量>***3点**<金币>**,向对方征收**<丹麦金>**收取对方下回合收益的20%。若对方拒绝支付,下回合对其所有城市的**<劫掠>**收益翻倍。对同一目标两回合内无法重复发起贡金要求。"
NeedTechDesc: 0
TechDesc:
NeedLockDesc: 0
LockDesc:
Icon: {fileID: 21300000, guid: 21a02f8622114810a34e03c82b7b0638, type: 3}
@ -10283,7 +10317,7 @@ MonoBehaviour:
AIParamType: 0
CultureCardType: 0
ActionName: "\u652F\u4ED8\u4E39\u9EA6\u91D1"
Desc: "\u652F\u4ED8\u52D2\u7D22\u540E\uFF0C\u53CC\u65B9\u4E39\u9EA6\u91D1\u5173\u7CFB\u53D8\u56DE\u4E24\u6E05\u3002"
Desc: "支付本次**<丹麦金>**后,双方丹麦金状态清除。"
NeedTechDesc: 0
TechDesc:
NeedLockDesc: 0
@ -10316,8 +10350,8 @@ MonoBehaviour:
PlayerActionType: 18
AIParamType: 0
CultureCardType: 0
ActionName: "\u62D2\u7EDD\u4E39\u9EA6\u91D1"
Desc: "\u62D2\u7EDD\u652F\u4ED8\u4E39\u9EA6\u91D1\u3002\u52D2\u7D22\u65B9\u5BF9\u4F60\u7684\u57CE\u5E02\u62A2\u52AB\u6536\u76CA\u7FFB\u500D\u3002"
ActionName: "拒绝丹麦金"
Desc: "拒绝支付**<丹麦金>**。对方对你的城市**<劫掠>**收益翻倍。"
NeedTechDesc: 0
TechDesc:
NeedLockDesc: 0

View File

@ -2187,7 +2187,7 @@ MonoBehaviour:
ResourceSubType: 0
Sprite: {fileID: 21300000, guid: c065970504610f04da8781baac3ee5f8, type: 3}
ResourceName: "\u704C\u6E89\u5DE5\u7A0B"
ResourceDesc: "\u6BCF3\u56DE\u5408\u5728\u9644\u8FD1\u7A7A\u5730\u4E0A\u751F\u6210**<\u5E84\u7A3C>**\uFF0C\u5468\u56F4\u6BCF\u7247\u519C\u7530\u989D\u5916\u63D0\u4F9B1\u70B9**<\u57CE\u5E02\u53D1\u5C55\u5EA6>**(\u4E0A\u96502\u70B9)\uFF0C\u4E3A**<\u5E02\u573A>**\u63D0\u4F9B\u989D\u5916\u91D1\u5E01\u3002"
ResourceDesc: "每2回合在附近空地上生成**<庄稼>**周围每片农田额外提供1点**<城市发展度>**(上限2点),为**<市场>**提供额外金币。"
Exp: 0
ChessType: 0
CivIdForceIdNotFromPlayer: 0

View File

@ -659,7 +659,7 @@ MonoBehaviour:
TargetBuff:
- GiantType: 21
HeroIllustration: {fileID: 21300000, guid: 1b95776d20b35034682cec20dd8fd2e2, type: 3}
HeroAvatar: {fileID: 21300000, guid: c662e6880212d104dbdbeda8015d1ef0, type: 3}
HeroAvatar: {fileID: 21300000, guid: 410ea7960b94c344b9e977cc6ce48506, type: 3}
TaskList:
- taskContentType: 13
Param: 25
@ -690,7 +690,7 @@ MonoBehaviour:
TargetBuff:
- GiantType: 22
HeroIllustration: {fileID: 21300000, guid: 98ea1a5d946041e0a46bc7f04a73947f, type: 3}
HeroAvatar: {fileID: 21300000, guid: 81884ebe222d46afb047331033c15379, type: 3}
HeroAvatar: {fileID: 21300000, guid: 6450206d3cf594e4e96c701fe0a66cf7, type: 3}
TaskList:
- taskContentType: 5
Param: 40
@ -721,7 +721,7 @@ MonoBehaviour:
TargetBuff:
- GiantType: 23
HeroIllustration: {fileID: 21300000, guid: d6686cb8912440a2ab25883148f1c342, type: 3}
HeroAvatar: {fileID: 21300000, guid: c74d72e70b64494ca571bfdf1ff71b33, type: 3}
HeroAvatar: {fileID: 21300000, guid: 6850108192547424e8a59c03329e7913, type: 3}
TaskList:
- taskContentType: 23
Param: 25
@ -752,7 +752,7 @@ MonoBehaviour:
TargetBuff:
- GiantType: 24
HeroIllustration: {fileID: 21300000, guid: bb4f6df6cddc46fb803f1188e2491c10, type: 3}
HeroAvatar: {fileID: 21300000, guid: e379c50cca534046b2719f9ef55ff1b8, type: 3}
HeroAvatar: {fileID: 21300000, guid: 9d5b25ac7c2bb264ba58a759a55fdaec, type: 3}
TaskList:
- taskContentType: 23
Param: 25
@ -783,7 +783,7 @@ MonoBehaviour:
TargetBuff:
- GiantType: 25
HeroIllustration: {fileID: 21300000, guid: 477ceacc14644cffb068239634480c82, type: 3}
HeroAvatar: {fileID: 21300000, guid: 2a03491d89974e64ad385166fa959357, type: 3}
HeroAvatar: {fileID: 21300000, guid: 1a199499013fe7e4d8603cd7da8b1dd8, type: 3}
TaskList:
- taskContentType: 23
Param: 25

View File

@ -810,10 +810,26 @@ MonoBehaviour:
- civ: 5
LandformParamList:
- LandformType: 4
ParamPackDesc: "\u6781\u6781\u5C11\u6C34\u679C"
ParamPackDesc: "\u591A\u6C34\u679C"
TerrainType: 0
ResourceType: 3
Rate: 2
ResourceType: 5
Rate: 1.5
InnerOuterCity: 0
IsCountControl: 0
Count: 0
- LandformType: 4
ParamPackDesc: "\u591A\u6E14\u4E1A"
TerrainType: 0
ResourceType: 1
Rate: 1.5
InnerOuterCity: 0
IsCountControl: 0
Count: 0
- LandformType: 4
ParamPackDesc: "\u65E0\u7530"
TerrainType: 0
ResourceType: 6
Rate: 0
InnerOuterCity: 0
IsCountControl: 0
Count: 0
@ -825,6 +841,38 @@ MonoBehaviour:
InnerOuterCity: 0
IsCountControl: 0
Count: 0
- LandformType: 4
ParamPackDesc: "\u4FDD\u5E952\u6E14\u4E1A"
TerrainType: 0
ResourceType: 1
Rate: 0
InnerOuterCity: 1
IsCountControl: 1
Count: 2
- LandformType: 0
ParamPackDesc: "\u4FDD\u5E952\u9646\u5730"
TerrainType: 1
ResourceType: 0
Rate: 0
InnerOuterCity: 1
IsCountControl: 1
Count: 2
- LandformType: 0
ParamPackDesc: "\u4FDD\u5E952\u6D45\u6D77"
TerrainType: 2
ResourceType: 0
Rate: 0
InnerOuterCity: 1
IsCountControl: 1
Count: 2
- LandformType: 4
ParamPackDesc: "\u4FDD\u5E952\u679C"
TerrainType: 0
ResourceType: 5
Rate: 0
InnerOuterCity: 1
IsCountControl: 1
Count: 2
- civ: 6
LandformParamList:
- LandformType: 4

View File

@ -1364,6 +1364,15 @@ MonoBehaviour:
Icon: {fileID: 0}
SkillDesc: "\u4E0D\u5360\u7528\u57CE\u5E02\u4EBA\u53E3"
SkillName: "\u6797\u751F"
- UnitFullType:
UnitType: 47
GiantType: 0
UnitLevel: 0
IgnoreUnitGiantType: 0
IgnoreUnitLevel: 0
Icon: {fileID: 0}
SkillDesc: "\u4E0D\u5360\u7528\u57CE\u5E02\u4EBA\u53E3"
SkillName: "\u81EA\u7ED9\u81EA\u8DB3"
skillPriority: 1
ReserveOnCarry: 0
ReserveLeaveCarry: 0
@ -3583,7 +3592,7 @@ MonoBehaviour:
- SkillType: 276
SkillViewType: 2
SkillName: "\u68A6\u60F3\u5999\u73E0"
SkillDesc: "\u653B\u51FB\u654C\u65B9\u65F6\u9644\u52A0**<\u9000\u6CBB\u76EE\u6807>**\uFF1B\u653B\u51FB\u5E26\u6709**<\u9000\u6CBB\u76EE\u6807>**\u7684\u5355\u4F4D\u65F6\u9020\u6210\u989D\u5916\u4F24\u5BB3\u3002"
SkillDesc: "**<\u4E3B\u52A8\u653B\u51FB>**\u654C\u65B9\u6216**<\u53CD\u51FB>**\u65F6\u9644\u52A0**<\u9000\u6CBB\u76EE\u6807>**\uFF1B\u653B\u51FB\u5E26\u6709**<\u9000\u6CBB\u76EE\u6807>**\u7684\u5355\u4F4D\u65F6\u9020\u6210\u989D\u5916\u4F24\u5BB3\u3002"
NotShow: 0
ShowOnUnitMono: 0
SkillIcon: {fileID: 21300000, guid: 823c7edfd1d14f6a93fc7c27837a93af, type: 3}
@ -3597,8 +3606,7 @@ MonoBehaviour:
- SkillType: 277
SkillViewType: 3
SkillName: "\u9000\u6CBB\u76EE\u6807"
SkillDesc: "\u88AB**<\u535A\u4E3D\u7075\u68A6>**\u653B\u51FB\u65F6\uFF0C\u6BCF\u5C42\u4F7F\u5F97\u81EA\u8EAB\u53D7\u52302\u70B9\u989D\u5916\u4F24\u5BB3\u3002\u53EF\u5411**<\u535A\u4E3D\u5E1D\u56FD>**\u652F\u4ED8**<\u91D1\u5E01>**\u79FB\u9664\u8BE5\u72B6\u6001\uFF0C\u6BCF\u5C42\u8D39\u7528\u4E3A3
* \u7075\u68A6\u7B49\u7EA7\u3002"
SkillDesc: "\u88AB**<\u535A\u4E3D\u7075\u68A6>**\u653B\u51FB\u65F6\uFF0C\u6BCF\u5C42\u4F7F\u5F97\u81EA\u8EAB\u53D7\u52302\u70B9\u989D\u5916\u4F24\u5BB3\u3002\u53EF\u5411**<\u535A\u4E3D\u5E1D\u56FD>**\u652F\u4ED8**<\u91D1\u5E01>**\u79FB\u9664\u8BE5\u72B6\u6001\uFF0C\u6BCF\u5C42\u8D39\u7528\u4E3A2***<\u535A\u4E3D\u7075\u68A6>**\u7B49\u7EA7\u3002\u5C42\u6570\u4E0A\u9650\u4E0E**<\u535A\u4E3D\u7075\u68A6>**\u7B49\u7EA7\u76F8\u540C\u3002"
NotShow: 0
ShowOnUnitMono: 1
SkillIcon: {fileID: 21300000, guid: 88dc3bcf1e32434cac45b64e0f14e23e, type: 3}
@ -3612,8 +3620,7 @@ MonoBehaviour:
- SkillType: 278
SkillViewType: 2
SkillName: "\u68A6\u60F3\u5C01\u5370"
SkillDesc: "**<\u4E3B\u52A8\u79FB\u52A8>**\u6216**<\u5411\u53CB\u65B9\u65BD\u6CD5>**\u540E\uFF0C\u5BF9\u5C04\u7A0B\u5185\u5E26\u6709**<\u9000\u6CBB\u76EE\u6807>**\u7684**<\u654C\u65B9\u82F1\u96C4>**\u53CA**<\u82F1\u96C4\u884D\u751F\u7269>**\u9020\u6210\u989D\u5916\u4F24\u5BB3
* 2\u70B9\u4F24\u5BB3\uFF1B2\u683C\u5185\u53CB\u65B9\u82F1\u96C4\u53CA\u82F1\u96C4\u884D\u751F\u7269\u88AB\u653B\u51FB\u65F6\uFF0C\u653B\u51FB\u8005\u83B7\u5F97\u9000\u6CBB\u3002"
SkillDesc: "**<\u4E3B\u52A8\u79FB\u52A8>**\u6216**<\u5411\u53CB\u65B9\u65BD\u6CD5>**\u540E\uFF0C\u5BF9\u5C04\u7A0B\u5185\u5E26\u6709**<\u9000\u6CBB\u76EE\u6807>**\u7684**<\u654C\u65B9\u82F1\u96C4>**\u53CA**<\u82F1\u96C4\u884D\u751F\u7269>**\u9020\u6210\u989D\u5916\u4F24\u5BB3\uFF1B2\u683C\u5185**<\u53CB\u65B9\u82F1\u96C4>**\u53CA**<\u53CB\u65B9\u82F1\u96C4\u884D\u751F\u7269>**\u88AB\u653B\u51FB\u65F6\uFF0C\u5BF9**<\u653B\u51FB\u8005>**\u9644\u52A01\u5C42**<\u9000\u6CBB\u76EE\u6807>**\u3002"
NotShow: 0
ShowOnUnitMono: 0
SkillIcon: {fileID: 21300000, guid: fce6e2535718443fa69ee97b47dbf64c, type: 3}
@ -3752,7 +3759,7 @@ MonoBehaviour:
ReserveCommonTransform: 1
- SkillType: 288
SkillViewType: 2
SkillName: "深秘投射"
SkillName: "\u6DF1\u79D8\u6295\u5C04"
SkillDesc: "\u79FB\u52A8\u540E\u5411\u4E0D\u540C\u5730\u5F62\u53D1\u5C04**<\u7075\u5F02\u73E0>**\uFF0C\u968F\u7740\u7B49\u7EA7\u589E\u957F\u53EF\u4EE5\u53D1\u5C04**<\u632A\u5A01\u4E4B\u73E0>**\uFF0C**<\u4E39\u9EA6\u4E4B\u73E0>**\u4EE5\u53CA**<\u82F1\u683C\u5170\u4E4B\u73E0>**\u3002\u53EF\u5728**<\u7075\u5F02\u73E0>**\u9644\u8FD1\u4F20\u9001\u3002"
NotShow: 0
ShowOnUnitMono: 0
@ -3767,7 +3774,7 @@ MonoBehaviour:
- SkillType: 289
SkillViewType: 1
SkillName: "\u632A\u5A01\u4E4B\u73E0"
SkillDesc: "可设置在**<深海>**与**<山脉>**中周围1格内的友方单位**<移动力>**与**<攻击力>**互换。"
SkillDesc: "\u53EF\u8BBE\u7F6E\u5728**<\u6DF1\u6D77>**\u4E0E**<\u5C71\u8109>**\u4E2D\uFF0C\u5468\u56F41\u683C\u5185\u7684\u53CB\u65B9\u5355\u4F4D\uFF0C**<\u79FB\u52A8\u529B>**\u4E0E**<\u653B\u51FB\u529B>**\u4E92\u6362\u3002"
NotShow: 0
ShowOnUnitMono: 0
SkillIcon: {fileID: 21300000, guid: a95cdede1f104514a3b05070c9c5d855, type: 3}
@ -3781,7 +3788,7 @@ MonoBehaviour:
- SkillType: 290
SkillViewType: 1
SkillName: "\u4E39\u9EA6\u4E4B\u73E0"
SkillDesc: "可设置在**<浅海>**与**<森林>**中周围1格内的友方单位**<防御力>**与**<攻击力>**互换。"
SkillDesc: "\u53EF\u8BBE\u7F6E\u5728**<\u6D45\u6D77>**\u4E0E**<\u68EE\u6797>**\u4E2D\uFF0C\u5468\u56F41\u683C\u5185\u7684\u53CB\u65B9\u5355\u4F4D\uFF0C**<\u9632\u5FA1\u529B>**\u4E0E**<\u653B\u51FB\u529B>**\u4E92\u6362\u3002"
NotShow: 0
ShowOnUnitMono: 0
SkillIcon: {fileID: 21300000, guid: 0f6b9133d5ef4a3a9916e5d9311914d3, type: 3}
@ -3795,7 +3802,7 @@ MonoBehaviour:
- SkillType: 291
SkillViewType: 1
SkillName: "\u82F1\u683C\u5170\u4E4B\u73E0"
SkillDesc: "\u80FD\u591F\u4E3A\u5468\u56F41\u683C\u5185\u7684\u53CB\u65B9\u5355\u4F4D\u62B5\u63213\u6B21\u4F24\u5BB3\u3002"
SkillDesc: "\u53EF\u8BBE\u7F6E\u5728**<\u5E73\u539F>**\u4E0A\uFF0C\u80FD\u591F\u4E3A\u5468\u56F41\u683C\u5185\u7684\u53CB\u65B9\u5355\u4F4D\u62B5\u63213\u6B21\u4F24\u5BB3\u3002"
NotShow: 0
ShowOnUnitMono: 0
SkillIcon: {fileID: 21300000, guid: 7ddf3fe13fd54b028371b5619380ba44, type: 3}
@ -3809,7 +3816,7 @@ MonoBehaviour:
- SkillType: 292
SkillViewType: 2
SkillName: "\u5F02\u4E16\u754C\u7684\u75AF\u72C2"
SkillDesc: "灵异珠参与属性数值互换时,每一项属性均取原数值与换算值的最大值。"
SkillDesc: "\u7075\u5F02\u73E0\u53C2\u4E0E\u5C5E\u6027\u6570\u503C\u4E92\u6362\u65F6\uFF0C\u6BCF\u4E00\u9879\u5C5E\u6027\u5747\u53D6\u539F\u6570\u503C\u4E0E\u6362\u7B97\u503C\u7684\u6700\u5927\u503C\u3002"
NotShow: 0
ShowOnUnitMono: 0
SkillIcon: {fileID: 21300000, guid: acf11aaa15ab4c5bae3654fcc6c3dd7c, type: 3}
@ -4061,7 +4068,7 @@ MonoBehaviour:
- SkillType: 310
SkillViewType: 0
SkillName: "\u74E6\u5C14\u54C8\u62C9\u8A93\u7EA6"
SkillDesc: "\u6B7B\u4EA1\u540E\u5728\u539F\u5730\u7559\u4E0B**<\u7B26\u6587>**\u3002"
SkillDesc: "死亡后在原地留下**<英灵符文>**。位于**<英灵符文>**地块之上的维京文明**<普通单位>**在受到致死伤害时能够立回恢复6点生命值。"
NotShow: 0
ShowOnUnitMono: 0
SkillIcon: {fileID: 21300000, guid: c1262ff24c1847a3b0695c0471bfbdcf, type: 3}
@ -4172,7 +4179,7 @@ MonoBehaviour:
ReserveCommonTransform: 0
- SkillType: 320
SkillViewType: 1
SkillName: "极光行歌"
SkillName: "\u6781\u5149\u884C\u6B4C"
SkillDesc: "\u79FB\u52A8\u65F6\u4E3A\u5468\u56F41\u683C\u5185\u53CB\u65B9\u5355\u4F4D\u6062\u590D2\u70B9\u751F\u547D\uFF1B\u5468\u56F41\u683C\u5185\u5355\u4F4D\u6B7B\u4EA1\u65F6\uFF0C\u81EA\u8EAB\u6062\u590D5\u70B9\u751F\u547D\u3002"
NotShow: 0
ShowOnUnitMono: 0
@ -4186,8 +4193,8 @@ MonoBehaviour:
ReserveCommonTransform: 0
- SkillType: 321
SkillViewType: 1
SkillName: "霜翼誓印"
SkillDesc: "可向1格范围内友方单位施法赋予**<女武神的庇佑>**持续1回合。"
SkillName: "\u971C\u7FFC\u8A93\u5370"
SkillDesc: "\u53EF\u54111\u683C\u8303\u56F4\u5185\u53CB\u65B9\u5355\u4F4D\u65BD\u6CD5\uFF0C\u8D4B\u4E88**<\u5973\u6B66\u795E\u7684\u5E87\u4F51>**\uFF0C\u6301\u7EED1\u56DE\u5408\u3002"
NotShow: 0
ShowOnUnitMono: 0
SkillIcon: {fileID: 21300000, guid: 98954bee70a64c4ca2dc422a91383ae8, type: 3}
@ -4200,8 +4207,8 @@ MonoBehaviour:
ReserveCommonTransform: 0
- SkillType: 322
SkillViewType: 4
SkillName: "女武神的庇佑"
SkillDesc: "若赐予庇护的**<女武神>**位于1格范围内所受伤害全部转移至**<女武神>**。"
SkillName: "\u5973\u6B66\u795E\u7684\u5E87\u4F51"
SkillDesc: "\u82E5\u8D50\u4E88\u5E87\u62A4\u7684**<\u5973\u6B66\u795E>**\u4F4D\u4E8E1\u683C\u8303\u56F4\u5185\uFF0C\u6240\u53D7\u4F24\u5BB3\u5168\u90E8\u8F6C\u79FB\u81F3**<\u5973\u6B66\u795E>**\u3002"
NotShow: 0
ShowOnUnitMono: 1
SkillIcon: {fileID: 21300000, guid: 98954bee70a64c4ca2dc422a91383ae8, type: 3}

View File

@ -210,7 +210,7 @@ MonoBehaviour:
Description: "\u53EF\u4EE5\u5EFA\u9020**<\u519B\u6E2F>**\u3002\u6240\u6709\u5355\u4F4D\u83B7\u5F97\u6D77\u6D0B\u9632\u5FA1"
icon: {fileID: 0}
CostLevel: 3
FatherTechList: 160000002a000000
FatherTechList: 160000002a00000044000000
TechAtomList: 3a0000003b000000
TechTreeCircleViewType: 23
- TechType: 25
@ -455,7 +455,7 @@ MonoBehaviour:
TechTreeCircleViewType: 19
- TechType: 65
TechName: "\u74E6\u5C14\u54C8\u62C9\u8A93\u7EA6"
Description: "\u7EF4\u4EAC\u521D\u59CB\u79D1\u6280\u3002\u6240\u6709\u9646\u5730\u5355\u4F4D\u53EF\u4EE5\u76F4\u63A5\u8FDB\u5165\u6D45\u6D77\u5E76\u8F6C\u6362\u4E3A**<\u5361\u7EF4\u8239>**\uFF1B\u57CE\u5E02\u5347\u7EA7\u53EF\u9009\u62E9\u7279\u8272\u5DE8\u4EBA\u5355\u4F4D**<\u5973\u6B66\u795E>**\uFF1B**<\u74E6\u5C14\u54C8\u62C9\u8A93\u7EA6>**\u4F53\u7CFB\u56CA\u62EC\u62A2\u52AB\u3001\u5BB4\u4F1A\u70B9\u4E0E\u7B26\u6587\u3002"
Description: "**<博丽帝国>**初始科技。所有**<通行能力>**符合**<陆地或港口>**的单位可以直接进入**<浅海>**并转换为**<卡维船>**;城市升级可选择特色巨人单位**<女武神>****<瓦尔哈拉誓约>**则囊括了**<劫掠>**、**<宴会点>**与**<英灵符文>**等特色体系。"
icon: {fileID: 0}
CostLevel: 0
FatherTechList: 00000000
@ -463,7 +463,7 @@ MonoBehaviour:
TechTreeCircleViewType: 25
- TechType: 66
TechName: "\u5317\u6D77\u6E14\u573A"
Description: "\u7EF4\u4EAC\u7279\u8272\u6355\u9C7C\u79D1\u6280\u3002\u53EF\u4EE5\u8FDB\u884C\u6355\u9C7C\uFF0C\u53EF\u4EE5\u5EFA\u9020\u7279\u8272**<\u957F\u8239\u6E2F>**\uFF0C\u53EF\u4EE5\u8FDB\u5165\u6D45\u6D77\u533A\u57DF\u3002"
Description: "**<博丽帝国>**特色捕鱼科技。可以建造特色建筑**<长船港>**,可以进入浅海区域。"
icon: {fileID: 0}
CostLevel: 1
FatherTechList: 00000000
@ -471,7 +471,7 @@ MonoBehaviour:
TechTreeCircleViewType: 4
- TechType: 67
TechName: "\u5706\u76FE\u5175"
Description: "\u7EF4\u4EAC\u7279\u8272\u9632\u5FA1\u79D1\u6280\u3002\u53EF\u4EE5\u8BAD\u7EC3**<\u5706\u76FE\u5175>**\uFF0C\u4FDD\u7559\u5927\u4F7F\u9986\u540C\u76DF\u4E0E\u8981\u585E\u80FD\u529B\u3002"
Description: "**<博丽帝国>**特色防御科技。可以训练特色单位**<圆盾兵>**。"
icon: {fileID: 0}
CostLevel: 2
FatherTechList: 06000000
@ -479,7 +479,7 @@ MonoBehaviour:
TechTreeCircleViewType: 7
- TechType: 68
TechName: "\u9F99\u9996\u8239\u575E"
Description: "\u7EF4\u4EAC\u7279\u8272\u519B\u6E2F\u79D1\u6280\u3002\u53EF\u4EE5\u5EFA\u9020**<\u9F99\u9996\u8239\u575E>**\uFF0C\u5E76\u5141\u8BB8\u8239\u53EA\u5347\u7EA7\u4E3A**<\u9F99\u8239>**\u3002"
Description: "**<博丽帝国>**特色军港科技。可以建造**<军港>**,并允许船只升级为**<龙船>**。"
icon: {fileID: 0}
CostLevel: 2
FatherTechList: 42000000
@ -487,23 +487,23 @@ MonoBehaviour:
TechTreeCircleViewType: 13
- TechType: 69
TechName: "\u72C2\u6218\u51B6\u70BC"
Description: "\u7EF4\u4EAC\u7279\u8272\u51B6\u70BC\u79D1\u6280\u3002\u53EF\u4EE5\u5EFA\u9020**<\u51B6\u70BC\u5382>**\uFF0C\u53EF\u4EE5\u8BAD\u7EC3**<\u5251\u58EB>**\uFF1B\u5141\u8BB8\u6B65\u5175\u548C\u5251\u58EB\u83B7\u5F97**<\u72C2\u66B4\u6B66\u58EB>**\u80FD\u529B\u3002"
Description: "**<博丽帝国>**特色冶炼科技。可以建造**<冶炼厂>**,可以训练**<剑士>**;允许**<步兵>**和**<剑士>**获得**<战争号角>**特色行动。"
icon: {fileID: 0}
CostLevel: 3
FatherTechList: 03000000
TechAtomList: 150000001600000074000000
TechTreeCircleViewType: 16
- TechType: 70
TechName: "\u7EF4\u4EAC\u5916\u4EA4"
Description: "\u7EF4\u4EAC\u7279\u8272\u5916\u4EA4\u79D1\u6280\u3002\u53EF\u4EE5\u5EFA\u9020**<\u5927\u4F7F\u9986>**\u5E76\u8BAD\u7EC3**<\u95F4\u8C0D>**\u3002"
TechName: "丹麦金外交"
Description: "**<博丽帝国>**特色外交科技。可以建造**<大使馆>**、训练**<间谍>**,并向目标帝国索取**<丹麦金>**。"
icon: {fileID: 0}
CostLevel: 3
FatherTechList: 43000000
TechAtomList: 1b0000001d000000
TechAtomList: 1b0000001d00000076000000
TechTreeCircleViewType: 17
- TechType: 71
TechName: "\u7EF4\u4EAC\u8D38\u6613"
Description: "\u7EF4\u4EAC\u7279\u8272\u8D38\u6613\u79D1\u6280\u3002\u53EF\u4EE5\u5EFA\u9020**<\u5317\u6D77\u5E02\u573A>**\uFF1B\u5317\u6D77\u5E02\u573A\u6BCF\u56DE\u5408\u63D0\u4F9B\u91D1\u5E01\uFF0C\u6570\u989D\u4E3A\u5468\u56F4**<\u952F\u6728\u5382>**\u3001**<\u8C37\u4ED3>**\u3001**<\u51B6\u70BC\u5382>**\u3001**<\u4FDD\u62A4\u533A>**\u7B49\u7EA7\u603B\u548C\uFF1B\u76F8\u90BB**<\u957F\u8239\u6E2F>**\u989D\u5916\u89C6\u4F5C+1\u7B49\u7EA7\uFF0C\u4E0A\u9650\u4E3A8\u3002"
TechName: "北海贸易"
Description: "**<博丽帝国>**特色贸易科技。可以建造**<北海市场>**;北海市场每回合提供金币,数额为周围**<锯木厂>**、**<谷仓>**、**<冶炼厂>**、**<保护区>**等级总和;相邻**<长船港>**额外视作+1等级上限为8。"
icon: {fileID: 0}
CostLevel: 3
FatherTechList: 0e000000
@ -6136,8 +6136,8 @@ MonoBehaviour:
IconList: []
iconViewSizeType: 0
- TechAtom: 70
TechAtomName: "\u704C\u6E89\u5DE5\u7A0B"
Desc: "\u5E1D\u56FD\u7279\u8272\u5EFA\u7B51\u3002\u53EF\u5EFA\u9020\u5728\u4E0E\u81F3\u5C11\u4E24\u7247\u6C34\u57DF**<\u76F4\u63A5\u76F8\u90BB>**\u7684\u5E73\u539F\u4E0A(\u9700\u7814\u53D1**<\u704C\u6E89>**\u79D1\u6280)\uFF0C\u53EF\u5EFA\u9020\u5728\u4E0E\u81F3\u5C11\u4E24\u7247\u9646\u5730**<\u76F4\u63A5\u76F8\u90BB>**\u7684\u6C34\u57DF\u4E2D(\u9700\u7814\u53D1**<\u6392\u6D9D>**\u79D1\u6280)\u3002\u4E0D\u80FD\u4E0E\u53E6\u4E00\u4E2A**<\u704C\u6E89\u5DE5\u7A0B>**\u76F8\u90BB\u3002\u6BCF2\u56DE\u5408\u5728\u9644\u8FD1\u7A7A\u5730\u4E0A\u751F\u6210**<\u5E84\u7A3C>**\uFF0C\u5468\u56F4\u6BCF\u7247\u519C\u7530\u989D\u5916\u63D0\u4F9B1\u70B9**<\u57CE\u5E02\u53D1\u5C55\u5EA6>**(\u4E0A\u96502\u70B9)\uFF0C\u4E3A**<\u5E02\u573A>**\u63D0\u4F9B\u989D\u5916\u91D1\u5E01\u3002"
TechAtomName: "陆田灌渠"
Desc: "可在**<山脉>**附近或与至少三片水域相邻的**<平原>**地块上建造**<灌溉工程>**。每2回合在附近空地上生成**<庄稼>**周围每片农田额外提供1点**<城市发展度>**(上限2点),为**<市场>**提供额外金币。"
IsAddSkill: 0
AddSkillCondition: []
AddSkillType: 0
@ -6214,8 +6214,8 @@ MonoBehaviour:
IconList: []
iconViewSizeType: 2
- TechAtom: 73
TechAtomName: "\u704C\u6E89\u5DE5\u7A0B"
Desc: "\u9635\u8425\u7279\u8272\u5EFA\u7B51\u3002\u53EF\u5EFA\u9020\u5728\u4E0E\u81F3\u5C11\u4E24\u7247\u6C34\u57DF**<\u76F4\u63A5\u76F8\u90BB>**\u7684\u5E73\u539F\u4E0A(\u9700\u7814\u53D1**<\u704C\u6E89>**\u79D1\u6280)\uFF0C\u53EF\u5EFA\u9020\u5728\u4E0E\u81F3\u5C11\u4E24\u7247\u9646\u5730**<\u76F4\u63A5\u76F8\u90BB>**\u7684\u6C34\u57DF\u4E2D(\u9700\u7814\u53D1**<\u6392\u6D9D>**\u79D1\u6280)\u3002\u4E0D\u80FD\u4E0E\u53E6\u4E00\u4E2A**<\u704C\u6E89\u5DE5\u7A0B>**\u76F8\u90BB\u3002\u6BCF2\u56DE\u5408\u5728\u9644\u8FD1\u7A7A\u5730\u4E0A\u751F\u6210**<\u5E84\u7A3C>**\uFF0C\u5468\u56F4\u6BCF\u7247\u519C\u7530\u989D\u5916\u63D0\u4F9B1\u70B9**<\u57CE\u5E02\u53D1\u5C55\u5EA6>**(\u4E0A\u96502\u70B9)\uFF0C\u4E3A**<\u5E02\u573A>**\u63D0\u4F9B\u989D\u5916\u91D1\u5E01\u3002"
TechAtomName: "潮汐灌渠"
Desc: "可在**<山脉>**附近或与至少三片陆地相邻的**<浅海>**地块上建造**<灌溉工程>**。每2回合在附近空地上生成**<庄稼>**周围每片农田额外提供1点**<城市发展度>**(上限2点),为**<市场>**提供额外金币。"
IsAddSkill: 0
AddSkillCondition: []
AddSkillType: 0
@ -7248,7 +7248,7 @@ MonoBehaviour:
iconViewSizeType: 1
- TechAtom: 103
TechAtomName: "\u74E6\u5C14\u54C8\u62C9\u8A93\u7EA6"
Desc: "\u56CA\u62EC**<\u52AB\u63A0>**\u3001\u5BB4\u4F1A\u70B9\u4E0E\u7B26\u6587\u4F53\u7CFB\uFF1A\u6211\u65B9\u5355\u4F4D\u5728\u654C\u65B9\u9886\u571F\u4E0A\u53EF\u52AB\u63A0\u91D1\u5E01\uFF1B\u6218\u543C\u3001\u52AB\u63A0\u3001\u4EA7\u751F\u7B26\u6587\u53EF\u83B7\u5F97\u5BB4\u4F1A\u70B9\uFF1B\u6211\u65B9\u5355\u4F4D\u6B7B\u4EA1\u540E\u7559\u4E0B\u7B26\u6587\u5730\u5757\uFF0C\u7B26\u6587\u5730\u5757\u4E0A\u7684\u6211\u65B9\u5355\u4F4D\u6B7B\u4EA1\u65F6\u7ACB\u5373\u6062\u590D6\u70B9\u751F\u547D\uFF1B\u519B\u8425\u4E2D\u53EF\u6309**<5:1>**\u6D88\u8017\u5BB4\u4F1A\u70B9\u8F6C\u5316\u4E3A\u57CE\u5E02\u53D1\u5C55\u5EA6\u3002"
Desc: "囊括了**<劫掠>**、**<宴会点>**与**<英灵符文>**体系。我方**<普通单位>**在敌方领土时可以通过**<劫掠>**行动夺取敌方**<金币>**。我方**<普通单位>**死亡或发起**<劫掠>**及**<战争号角>**行动时,均能获得**<宴会点>**。消耗**<宴会点>**在城市中心举办**<博丽酒宴>**,可提供额外**<城市发展度>**。我方**<普通单位>**死亡时,会在原地留下**<英灵符文>**,为所有维京文明的**<普通单位>**提供增益。"
IsAddSkill: 1
AddSkillCondition:
- UnitType: 1
@ -7381,7 +7381,7 @@ MonoBehaviour:
iconViewSizeType: 4
- TechAtom: 105
TechAtomName: "\u5973\u6B66\u795E"
Desc: "\u57CE\u5E02\u5347\u7EA7\u81F3Lv.6\u53CA\u4EE5\u4E0A\u65F6\uFF0C\u53EF\u9009\u62E9\u8BAD\u7EC3\u7EF4\u4EAC\u7279\u8272\u5DE8\u4EBA\u5355\u4F4D**<\u5973\u6B66\u795E>**\u3002\u5973\u6B66\u795E0\u653B\u51FB\u30012\u79FB\u52A8\u3001\u98DE\u884C\u300140HP\uFF0C\u53EF\u4E3A\u5468\u56F4\u53CB\u65B9\u6062\u590D\u751F\u547D\u5E76\u627F\u62C5\u4F24\u5BB3\u3002"
Desc: "城市升级至Lv.6及以上时,可选择训练维京特色巨人单位**<女武神>**。女武神可为周围友方恢复生命并承担伤害。"
IsAddSkill: 0
AddSkillCondition: []
AddSkillType: 0
@ -7635,7 +7635,7 @@ MonoBehaviour:
iconViewSizeType: 1
- TechAtom: 118
TechAtomName: "\u4E39\u9EA6\u91D1"
Desc: "\u5411\u5BF9\u65B9\u53D1\u8D77\u8D21\u91D1\u8981\u6C42\u3002\u82E5\u5BF9\u65B9\u62D2\u7EDD\u652F\u4ED8\uFF0C\u4E0B\u56DE\u5408\u5BF9\u5176**<\u52AB\u63A0>**\u6536\u76CA\u7FFB\u500D\u3002"
Desc: "向目标帝国索取**<丹麦金>**。若对方拒绝支付,你对其城市的**<抢劫>**收益翻倍,直到其下次回合结束。"
IsAddSkill: 0
AddSkillCondition: []
AddSkillType: 0

View File

@ -96,14 +96,19 @@ MonoBehaviour:
NotifyUIExamineCultureHint: "\u83B7\u5F974\u70B9\u6587\u5316\u70B9"
NotifyUITurnHint: "\u7B2C{param}\u56DE\u5408"
NotifyUIInfiltrateStealCoin: "\u6210\u529F\u7A83\u53D6{param}\u91D1\u5E01!"
NotifyUIHakureiDanegeldDemandStarted: "\u5DF2\u53D1\u8D77\u5F81\u6536"
PresentationUIDiplomacyYouText: "\u60A8"
PresentationUIDiplomacyThinkYouText: "\u5BF9\u65B9\u8BA4\u4E3A\u60A8:"
PresentationUIHakureiClearExterminationPaymentTitle: "\u6536\u5230\u8D21\u91D1"
PresentationUIHakureiClearExterminationPaymentContent: "{param}\u7684\u6210\u5458\u5411\u60A8\u732E\u4E0A\u8D21\u91D1\uFF0C\u5171\u8BA1{param}\u91D1\u5E01\u3002\u8BF7\u6C42\u60A8\u5C06\u5176\u4ECE**<\u9000\u6CBB\u76EE\u6807>**\u540D\u518C\u4E2D\u53BB\u9664\u3002(\u79FB\u9664{param}\u5C42**<\u9000\u6CBB\u76EE\u6807>**)"
PresentationUIHakureiDanegeldPaymentTitle: "\u6536\u5230\u8D21\u91D1"
PresentationUIHakureiDanegeldPaymentContent: "{param}\u5411\u60A8\u732E\u4E0A\u8D21\u91D1\uFF0C\u5171\u8BA1{param}\u91D1\u5E01\u3002\u8BF7\u6C42\u60A8\u62A4\u5176\u5E73\u5B89\uFF0C\u5728**<\u52AB\u63A0>**\u65F6\u9AD8\u62AC\u8D35\u624B\u3002(**<\u52AB\u63A0>**\u7FFB\u500D\u6548\u679C\u5C06\u4E0D\u751F\u6548)"
PresentationUIHakureiDanegeldRejectedTitle: "\u8D21\u91D1\u4EE4\u88AB\u62D2\u7EDD"
PresentationUIHakureiDanegeldRejectedContent: "{param}\u62D2\u7EDD\u652F\u4ED8**<\u4E39\u9EA6\u91D1>**\uFF0C\u5171\u8BA1{param}\u91D1\u5E01\u3002{param}\u5C06\u4EE5\u6218\u65A7\u8BA1\u606F\uFF0C\u5BF9\u5176**<\u52AB\u63A0>**\u6536\u76CA\u7FFB\u500D\u3002"
PresentationUIHakureiDanegeldPaymentTitle: "\u6536\u5230\u4E39\u9EA6\u91D1"
PresentationUIHakureiDanegeldPaymentContent: "{param}\u652F\u4ED8\u4E86{param}\u91D1\u5E01**<\u4E39\u9EA6\u91D1>**\uFF0C\u4E5E\u6C42\u60A8\u9AD8\u62AC\u8D35\u624B\u62A4\u5176\u5E73\u5B89\u3002"
PresentationUIHakureiDanegeldRejectedTitle: "\u4E39\u9EA6\u91D1\u88AB\u62D2\u4ED8"
PresentationUIHakureiDanegeldRejectedContent: "{param}\u62D2\u7EDD\u652F\u4ED8{param}\u91D1\u5E01**<\u4E39\u9EA6\u91D1>**\u3002{param}\u5BF9\u5176\u57CE\u5E02\u7684**<\u52AB\u63A0>**\u6536\u76CA\u7FFB\u500D\uFF0C\u76F4\u81F3\u4E0B\u4E00\u56DE\u5408\u7ED3\u675F\u3002"
PresentationUIHakureiDanegeldDemandTitle: "\u4E39\u9EA6\u91D1\u8981\u6C42"
PresentationUIHakureiDanegeldDemandContent: "{param}\u5411\u60A8\u7D22\u53D6**<\u4E39\u9EA6\u91D1>**\u3002\u652F\u4ED8{param}\u91D1\u5E01\u8D2D\u4E70\u5E73\u5B89\uFF0C\u6216\u8005\u62D2\u7EDD\u652F\u4ED8\uFF0C\u627F\u62C5\u53CC\u500D\u7684**<\u52AB\u63A0>**\u635F\u5931\uFF0C\u6301\u7EED\u81F3\u4E0B\u4E00\u56DE\u5408\u7ED3\u675F\u3002"
PresentationUIHakureiDanegeldPayButton: "\u652F\u4ED8{param}\u91D1\u5E01"
PresentationUIHakureiDanegeldRejectButton: "\u62D2\u7EDD\u652F\u4ED8"
UnitOfficerName: "\u519B\u5B98"
ForestPreserveName: "\u68EE\u6797\u4FDD\u62A4\u533A"
OceanPreserveName: "\u6D77\u6D0B\u4FDD\u62A4\u533A"

View File

@ -4518,6 +4518,32 @@ MonoBehaviour:
Desc:
HasLevel: 0
LevelSprite: []
- IgnoreCivId: 0
IgnoreForceId: 0
IsGridSpType: 0
GridSpType: 0
CivId: 5
ForceId: 5
OnlyCarryGiant: 1
CarryGiantType: 24
Sprite: {fileID: 21300000, guid: 5cb4a243e40a42bf9cea3c8ef07bd4aa, type: 3}
Name:
Desc:
HasLevel: 0
LevelSprite: []
- IgnoreCivId: 0
IgnoreForceId: 0
IsGridSpType: 0
GridSpType: 0
CivId: 5
ForceId: 5
OnlyCarryGiant: 1
CarryGiantType: 25
Sprite: {fileID: 21300000, guid: 9a0becae1016466d8e87f635f0f086af, type: 3}
Name:
Desc:
HasLevel: 0
LevelSprite: []
ProjectileType: 1
ForceMelee: 0
SameUnitCountLimit: 0
@ -4714,6 +4740,32 @@ MonoBehaviour:
Desc:
HasLevel: 0
LevelSprite: []
- IgnoreCivId: 0
IgnoreForceId: 0
IsGridSpType: 0
GridSpType: 0
CivId: 5
ForceId: 5
OnlyCarryGiant: 1
CarryGiantType: 24
Sprite: {fileID: 21300000, guid: 5cb4a243e40a42bf9cea3c8ef07bd4aa, type: 3}
Name:
Desc:
HasLevel: 0
LevelSprite: []
- IgnoreCivId: 0
IgnoreForceId: 0
IsGridSpType: 0
GridSpType: 0
CivId: 5
ForceId: 5
OnlyCarryGiant: 1
CarryGiantType: 25
Sprite: {fileID: 21300000, guid: 9a0becae1016466d8e87f635f0f086af, type: 3}
Name:
Desc:
HasLevel: 0
LevelSprite: []
ProjectileType: 1
ForceMelee: 0
SameUnitCountLimit: 0
@ -4910,6 +4962,32 @@ MonoBehaviour:
Desc:
HasLevel: 0
LevelSprite: []
- IgnoreCivId: 0
IgnoreForceId: 0
IsGridSpType: 0
GridSpType: 0
CivId: 5
ForceId: 5
OnlyCarryGiant: 1
CarryGiantType: 24
Sprite: {fileID: 21300000, guid: 5cb4a243e40a42bf9cea3c8ef07bd4aa, type: 3}
Name:
Desc:
HasLevel: 0
LevelSprite: []
- IgnoreCivId: 0
IgnoreForceId: 0
IsGridSpType: 0
GridSpType: 0
CivId: 5
ForceId: 5
OnlyCarryGiant: 1
CarryGiantType: 25
Sprite: {fileID: 21300000, guid: 9a0becae1016466d8e87f635f0f086af, type: 3}
Name:
Desc:
HasLevel: 0
LevelSprite: []
ProjectileType: 1
ForceMelee: 0
SameUnitCountLimit: 0
@ -5106,6 +5184,32 @@ MonoBehaviour:
Desc:
HasLevel: 0
LevelSprite: []
- IgnoreCivId: 0
IgnoreForceId: 0
IsGridSpType: 0
GridSpType: 0
CivId: 5
ForceId: 5
OnlyCarryGiant: 1
CarryGiantType: 24
Sprite: {fileID: 21300000, guid: 5cb4a243e40a42bf9cea3c8ef07bd4aa, type: 3}
Name:
Desc:
HasLevel: 0
LevelSprite: []
- IgnoreCivId: 0
IgnoreForceId: 0
IsGridSpType: 0
GridSpType: 0
CivId: 5
ForceId: 5
OnlyCarryGiant: 1
CarryGiantType: 25
Sprite: {fileID: 21300000, guid: 9a0becae1016466d8e87f635f0f086af, type: 3}
Name:
Desc:
HasLevel: 0
LevelSprite: []
ProjectileType: 1
ForceMelee: 0
SameUnitCountLimit: 0
@ -8320,7 +8424,7 @@ MonoBehaviour:
AttackRange: 1
SightRange: 1
Cost: 0
Skills: 020000000300000029010000
Skills: 020000000300000028010000
Sprite: {fileID: 21300000, guid: e379c50cca534046b2719f9ef55ff1b8, type: 3}
IsSpriteVarient: 0
SpriteList: []
@ -8347,7 +8451,7 @@ MonoBehaviour:
AttackRange: 1
SightRange: 1
Cost: 0
Skills: 0200000003000000290100002a010000
Skills: 020000000300000028010000290100002a010000
Sprite: {fileID: 21300000, guid: e379c50cca534046b2719f9ef55ff1b8, type: 3}
IsSpriteVarient: 0
SpriteList: []
@ -8374,7 +8478,7 @@ MonoBehaviour:
AttackRange: 1
SightRange: 1
Cost: 0
Skills: 0200000003000000290100002a010000
Skills: 020000000300000028010000290100002a010000
Sprite: {fileID: 21300000, guid: e379c50cca534046b2719f9ef55ff1b8, type: 3}
IsSpriteVarient: 0
SpriteList: []
@ -8401,7 +8505,7 @@ MonoBehaviour:
AttackRange: 1
SightRange: 1
Cost: 0
Skills: 0200000003000000290100002a0100002b010000
Skills: 020000000300000028010000290100002a0100002b010000
Sprite: {fileID: 21300000, guid: e379c50cca534046b2719f9ef55ff1b8, type: 3}
IsSpriteVarient: 0
SpriteList: []
@ -8419,16 +8523,16 @@ MonoBehaviour:
Force: 5
Name: "\u4F0A\u5439\u8403\u9999 Lv.1"
Desc: "\u79FB\u52A8\u540E\u5728\u9644\u8FD1\u751F\u6210\u5C0F\u8403\u9999\uFF0C\u5C0F\u8403\u9999\u53EF\u9644\u7740\u5230\u8403\u9999\u8EAB\u4E0A\u53E0\u5C42\u3002"
LandType: 5
LandType: 1
NoMaxHealth: 0
MaxHealth: 20
Attack: 3
Defense: 3
MaxHealth: 15
Attack: 2.5
Defense: 2
MoveRange: 1
AttackRange: 1
SightRange: 1
Cost: 0
Skills: 0200000003000000130000000b0100002c010000
Skills: 02000000130000000b0100002c010000
Sprite: {fileID: 21300000, guid: 2a03491d89974e64ad385166fa959357, type: 3}
IsSpriteVarient: 0
SpriteList: []
@ -8446,16 +8550,16 @@ MonoBehaviour:
Force: 5
Name: "\u4F0A\u5439\u8403\u9999 Lv.2"
Desc: "\u53D7\u5230\u4F24\u5BB3\u65F6\uFF0C\u82E5\u62E5\u6709\u5C0F\u8403\u9999\u5C42\u6570\uFF0C\u5219\u51CF\u534A\u4F24\u5BB3\u5E76\u6296\u51FA\u4E00\u53EA\u5C0F\u8403\u9999\u3002"
LandType: 5
LandType: 1
NoMaxHealth: 0
MaxHealth: 30
Attack: 3.5
Defense: 3
MaxHealth: 25
Attack: 3
Defense: 2
MoveRange: 1
AttackRange: 1
SightRange: 1
Cost: 0
Skills: 0200000003000000130000000b0100002c0100002e010000
Skills: 02000000130000000b0100002c0100002e010000
Sprite: {fileID: 21300000, guid: 2a03491d89974e64ad385166fa959357, type: 3}
IsSpriteVarient: 0
SpriteList: []
@ -8473,24 +8577,42 @@ MonoBehaviour:
Force: 5
Name: "\u4F0A\u5439\u8403\u9999 Lv.3"
Desc: "\u53EF\u6D88\u80175\u70B9\u751F\u547D\u4E3B\u52A8\u751F\u6210\u5C0F\u8403\u9999\u3002\u5C0F\u8403\u9999\u5C42\u6570\u8FBE\u52303\u65F6\u8FDB\u5165\u5927\u8403\u9999\u72B6\u6001\uFF0C\u53EF\u6295\u63B7\u9644\u8FD1\u5355\u4F4D\u3002"
LandType: 5
LandType: 1
NoMaxHealth: 0
MaxHealth: 40
Attack: 4
MaxHealth: 35
Attack: 3.5
Defense: 3
MoveRange: 1
AttackRange: 1
SightRange: 1
Cost: 0
Skills: 0200000003000000130000000b0100002c0100002e010000
Skills: 02000000130000000b0100002c0100002e010000
Sprite: {fileID: 21300000, guid: 2a03491d89974e64ad385166fa959357, type: 3}
IsSpriteVarient: 0
SpriteList: []
ProjectileType: 1
ForceMelee: 0
SameUnitCountLimit: 1
EnableAction: 0
EnableActions: []
EnableAction: 1
EnableActions:
- ActionType: 6
WonderType: 0
ResourceType: 0
FeatureType: 0
TerrainType: 0
UnitType: 0
GiantType: 0
UnitLevel: 0
Vegetation: 0
UnitActionType: 39
CityLevelUpActionType: 0
CityActionType: 0
GridMiscActionType: 0
SkillType: 0
TechType: 0
PlayerActionType: 0
AIParamType: 0
CultureCardType: 0
- UnitType: 14
GiantType: 25
UnitLevel: 4
@ -8500,24 +8622,42 @@ MonoBehaviour:
Force: 5
Name: "\u4F0A\u5439\u8403\u9999 Lv.4"
Desc: "\u5C0F\u8403\u9999\u5C42\u6570\u8FBE\u52304\u65F6\u8FDB\u5165\u5DE8\u5927\u8403\u9999\u72B6\u6001\uFF0C\u53EF\u4ECE\u5929\u800C\u964D\u9020\u6210\u5468\u56F4\u6E85\u5C04\u4F24\u5BB3\u3002"
LandType: 5
LandType: 1
NoMaxHealth: 0
MaxHealth: 50
Attack: 5
Defense: 4
MaxHealth: 45
Attack: 4.5
Defense: 3
MoveRange: 1
AttackRange: 1
SightRange: 1
Cost: 0
Skills: 0200000003000000130000000b0100002c0100002e01000031010000
Skills: 02000000130000000b0100002c0100002e01000031010000
Sprite: {fileID: 21300000, guid: 2a03491d89974e64ad385166fa959357, type: 3}
IsSpriteVarient: 0
SpriteList: []
ProjectileType: 12
ForceMelee: 0
SameUnitCountLimit: 1
EnableAction: 0
EnableActions: []
EnableAction: 1
EnableActions:
- ActionType: 6
WonderType: 0
ResourceType: 0
FeatureType: 0
TerrainType: 0
UnitType: 0
GiantType: 0
UnitLevel: 0
Vegetation: 0
UnitActionType: 39
CityLevelUpActionType: 0
CityActionType: 0
GridMiscActionType: 0
SkillType: 0
TechType: 0
PlayerActionType: 0
AIParamType: 0
CultureCardType: 0
- UnitType: 14
GiantType: 26
UnitLevel: 1
@ -9491,15 +9631,33 @@ MonoBehaviour:
AttackRange: 1
SightRange: 1
Cost: 0
Skills: 020000000300000032010000
Skills: 02000000030000005300000032010000
Sprite: {fileID: 21300000, guid: 48bd27e4d8c44678bfe75a2dd83d3918, type: 3}
IsSpriteVarient: 0
SpriteList: []
ProjectileType: 1
ForceMelee: 0
SameUnitCountLimit: 0
EnableAction: 0
EnableActions: []
EnableAction: 1
EnableActions:
- ActionType: 6
WonderType: 0
ResourceType: 0
FeatureType: 0
TerrainType: 0
UnitType: 0
GiantType: 0
UnitLevel: 0
Vegetation: 0
UnitActionType: 42
CityLevelUpActionType: 0
CityActionType: 0
GridMiscActionType: 0
SkillType: 0
TechType: 0
PlayerActionType: 0
AIParamType: 0
CultureCardType: 0
- UnitType: 48
GiantType: 0
UnitLevel: 0

View File

@ -12,8 +12,23 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: c659b850b20e460f866ed3f696be406b, type: 3}
m_Name: VersionConfig
m_EditorClassIdentifier:
CurVersionId: 70401
CurVersionId: 70403
Versions:
- MajorVersion: 0
MinorVersion: 7
PatchVersion: 4
Description:
FourthVersion: 4
- MajorVersion: 0
MinorVersion: 7
PatchVersion: 4
Description: "[\u7248\u672C V0.7.4d]\n\u53D1\u5E03\u65E5\u671F:26.6.23\n\n---------bug\u4FEE\u590D\u4E0E\u4F18\u5316-------------\n1.\u4F18\u5316\u4E86\u535A\u4E3D\u5E1D\u56FD\u5F00\u5C40\u8D44\u6E90\u914D\u7F6E\n2.\u4FEE\u590D\u535A\u4E3D\u5E1D\u56FD\u5934\u50CF\u9519\u8BEF\n3.\u4FEE\u590D\u535A\u4E3D\u7075\u68A6\u7684\u80FD\u529B\u56FE\u6807\u9519\u8BEF\n4.\u4F18\u5316\u4E86AI\u9635\u8425\u9009\u62E9\uFF0C\u4FEE\u590D\u4E86\u65AF\u5361\u96F7\u7279\u9635\u8425\u4E0D\u5728AI\u4E2D\u51FA\u73B0\u7684bug\n5.\u4FEE\u590D\u4E86\u82E5\u5E72\u56FE\u6807\u4E22\u5931\u7684bug\n6.\u4FEE\u590D\u4E86\u72AC\u8D70\u691B\u9644\u8FD1\u5F02\u7AEF\u76EE\u6807\u6B7B\u4EA1\u540E\uFF0C\u79FB\u52A8\u8303\u56F4\u53EF\u80FD\u663E\u793A\u51FA\u9519\u7684bug\n7.\u4FEE\u590D\u4E86\u8054\u673A\u5BF9\u6218\u65F6\u5807\u5B50\u6280\u80FD\u5BF9\u79F0\u8054\u673A\u4E0D\u540C\u6B65\u7684bug\n8.\u4FEE\u590D\u4E86\u652F\u4ED8\u4E39\u9EA6\u91D1\u65F6\u53EF\u80FD\u51FA\u73B0\u6570\u989D\u9519\u8BEF\u7684bug"
FourthVersion: 3
- MajorVersion: 0
MinorVersion: 7
PatchVersion: 4
Description: "[\u7248\u672C V0.7.4c]\n\u53D1\u5E03\u65E5\u671F:26.6.23\n\n---------\u65B0\u5185\u5BB9-------------\n1.\u65B0\u589E\u5916\u4EA4\u884C\u52A8\"\u4E39\u9EA6\u91D1\"\n\n---------bug\u4FEE\u590D\u4E0E\u4F18\u5316-------------\n1.\u4FEE\u590D\u4E86\u5384\u9664\u624E\u7684\u6548\u679C\u5728\u654C\u4EBA\u53CD\u51FB\u65F6\u4E5F\u4F1A\u89E6\u53D1\u7684bug\uFF0C\u5B8C\u5584\u4E86\u63CF\u8FF0\u6587\u6848\n2.\u4FEE\u590D\u4E86\u94C3\u4ED9\u5728\u5E7B\u60F3\u8F83\u591A\u7684\u60C5\u51B5\u4E0B\u53D1\u8D77\u653B\u51FB\u65F6\"\u518D\u653B\"buff\u5931\u6548\u7684bug\n3.\u4FEE\u590D\u4E86\u6D77\u6D0B\u9632\u5FA1\u79D1\u6280\u65E0\u6CD5\u7814\u53D1\u7684bug\n4.\u4FEE\u590D\u4E86\u5FA1\u5C04\u5BAB\u53F8\u5927\u4EBA\u4E0D\u88AB\u89C6\u4F5C\u82F1\u96C4\u884D\u751F\u7269\u7684bug\n5.\u4FEE\u590D\u4E86\u5807\u5B50Lv4\u4E4B\u540E\u4E0D\u4F1A\u5C06\u5F02\u4E16\u754C\u7684\u75AF\u72C2\u6548\u679C\u8986\u76D6\u6240\u6709\u7075\u5F02\u73E0\u7684bug\n6.\u4FEE\u590D\u4E86\u8D21\u7A0E\u672D\u5728\u52AB\u63A0\u540E\u4E0D\u4F1A\u6D88\u5931\u7684bug\n7.\u4FEE\u590D\u4E86\u82F1\u96C4\u5347\u7EA7\u65F6\u9519\u8BEF\u8FD4\u8FD8\u6587\u5316\u503C\u7684bug\n8.\u4FEE\u590D\u4E86\u82F1\u683C\u5170\u4E4B\u73E0\u66FF\u4EE3\u627F\u4F24\u65F6\u53EF\u80FD\u88AB\u4E00\u6B21\u9AD8\u4F24\u5BB3\u653B\u51FB\u76F4\u63A5\u51FB\u6BC1\u7684bug\n9.\u4FEE\u590D\u4E86\u535A\u4E3D\u5E1D\u56FD\u57FA\u7840\u8D44\u6E90\u914D\u7F6E\u5931\u6548\u7684bug\n10.\u4FEE\u590D\u4E86\u5362\u6069\u7B26\u6587\u5730\u5757\u7279\u6B8A\u6548\u679C\u672A\u751F\u6548\u7684bug\u5E76\u5B8C\u5584\u4E86\u63CF\u8FF0\n11.\u4FEE\u590D\u4E86\u5973\u6B66\u795E\u7684\u5E87\u4F51\u72B6\u6001\u53EF\u4EE5\u53E0\u5C42\u7684bug\n12.\u4FEE\u590D\u4E86\u82E5\u5E72\u79D1\u6280/\u6280\u80FD/\u5355\u4F4D\u63CF\u8FF0\u9519\u8BEF\u95EE\u9898\n"
FourthVersion: 2
- MajorVersion: 0
MinorVersion: 7
PatchVersion: 4

View File

@ -10247,10 +10247,10 @@ MonoBehaviour:
PlayerActionType: 16
AIParamType: 0
CultureCardType: 0
ActionName: 21585
Desc: 21635
NeedTechDesc: 1
TechDesc: 21438
ActionName: 21713
Desc: 21715
NeedTechDesc: 0
TechDesc:
NeedLockDesc: 0
LockDesc:
Icon: {fileID: 21300000, guid: 21a02f8622114810a34e03c82b7b0638, type: 3}
@ -10282,7 +10282,7 @@ MonoBehaviour:
AIParamType: 0
CultureCardType: 0
ActionName: 21587
Desc: 21524
Desc: 21669
NeedTechDesc: 0
TechDesc:
NeedLockDesc: 0
@ -10316,7 +10316,7 @@ MonoBehaviour:
AIParamType: 0
CultureCardType: 0
ActionName: 21588
Desc: 21526
Desc: 21694
NeedTechDesc: 0
TechDesc:
NeedLockDesc: 0

View File

@ -2187,7 +2187,7 @@ MonoBehaviour:
ResourceSubType: 0
Sprite: {fileID: 21300000, guid: c065970504610f04da8781baac3ee5f8, type: 3}
ResourceName: 2237
ResourceDesc: 21175
ResourceDesc: 21723
Exp: 0
ChessType: 0
CivIdForceIdNotFromPlayer: 0

Some files were not shown because too many files have changed in this diff Show More