车阶与马阶英雄
49
.codex/skills/th1-chronic-issue/SKILL.md
Normal 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
|
||||
```
|
||||
4
.codex/skills/th1-chronic-issue/agents/openai.yaml
Normal 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."
|
||||
@ -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."
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
58
.codex/skills/th1-serious-incident/SKILL.md
Normal 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.
|
||||
4
.codex/skills/th1-serious-incident/agents/openai.yaml
Normal 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."
|
||||
@ -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?
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 57 KiB |
@ -0,0 +1,2 @@
|
||||
# Ameno-Tajikarao Throw Skill Icon Feedback
|
||||
|
||||
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 998 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
@ -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"
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"selected": null,
|
||||
"feedback": ""
|
||||
}
|
||||
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 353 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 320 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 305 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 114 KiB |
@ -0,0 +1,2 @@
|
||||
# Ameno-Tajikarao Throw Option 3 Variation Feedback
|
||||
|
||||
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1018 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1020 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1012 KiB |
@ -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"
|
||||
}
|
||||
|
After Width: | Height: | Size: 16 KiB |
@ -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"}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"selected": null,
|
||||
"feedback": ""
|
||||
}
|
||||
|
After Width: | Height: | Size: 433 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 293 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 266 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 361 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 214 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 952 KiB |
|
After Width: | Height: | Size: 988 KiB |
|
After Width: | Height: | Size: 960 KiB |
|
After Width: | Height: | Size: 988 KiB |
|
After Width: | Height: | Size: 938 KiB |
|
After Width: | Height: | Size: 990 KiB |
|
After Width: | Height: | Size: 1021 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
@ -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"}
|
||||
@ -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` 已通过。
|
||||
11
MD/ChronicIssueList/2026-06-24-sumireko-lv4-existing-orbs.md
Normal 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.
|
||||
17
MD/ChronicIssueList/index.md
Normal 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 癌症项。
|
||||
|
||||
## 处理规则
|
||||
|
||||
- 反复出现两次以上、或用户明确指出“又出这个问题”的缺陷,必须加入本清单。
|
||||
- 加入清单后,默认不接受只修表象;优先改生成器、校验脚本、测试或流程入口。
|
||||
- 每个条目必须写清楚触发入口、复发机制、根治防线和剩余验证缺口。
|
||||
108
MD/CrashSight_2026-06-23_0.7.4c_network_sync_all/index.md
Normal 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.
|
||||
@ -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
|
||||
}
|
||||
@ -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` 配置:当存在全文明兜底项和后续具体项时,提示顺序不会影响运行时,但仍可提示配置可读性问题。
|
||||
@ -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`,不得只在聊天里口头说明。
|
||||
15
MD/SeriousIncidentLog/index.md
Normal 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` 的所有共享回调做一次审计,确认普通单位升级、英雄升级、巨人/衍生物转换、技能转换不会互相污染奖励逻辑。
|
||||
@ -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 {
|
||||
|
||||
289
Tools/Dashboard/data/ai_logic.json
Normal 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 次更新,避免死循环。",
|
||||
"普通路径由行为树产生 MaxAiAction;ENABLE_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.BTNodeId,Editor 下 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 与模型数据。"
|
||||
]
|
||||
}
|
||||
@ -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>
|
||||
|
||||
266
Tools/Dashboard/js/ai_logic.js
Normal 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 => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
}[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);
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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, '"');
|
||||
}
|
||||
|
||||
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()">×</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;
|
||||
|
||||
@ -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)
|
||||
|
||||
|
Before Width: | Height: | Size: 167 KiB |
|
After Width: | Height: | Size: 229 KiB |
@ -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:
|
||||
|
Before Width: | Height: | Size: 136 KiB |
@ -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:
|
||||
|
Before Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 197 KiB |
@ -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:
|
||||
|
After Width: | Height: | Size: 255 KiB |
@ -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:
|
||||
|
Before Width: | Height: | Size: 158 KiB |
@ -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:
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||