debug skill , 增加renderer的保护,修改房间里civ状态的同步逻辑

This commit is contained in:
wuwenbo 2026-05-19 15:29:26 +08:00
parent 32822df303
commit 9bc559cfc0
11 changed files with 466 additions and 92 deletions

View File

@ -0,0 +1,107 @@
---
name: th1-crashsight-daily
description: TH1 project-specific daily CrashSight triage workflow for using the logged-in Chrome session to scan recent versions, inspect every CrashSight error, decode obfuscated Unity C# stacks, classify only direct exceptions or try/catch captured exceptions as blocking, and write Markdown blocking/debug reports under MD/. Use when the user asks for CrashSight daily reports, 最近一天异常扫描, version-scoped crash/error triage, blocking/debug report generation, or recurring production error review.
---
# TH1 CrashSight Daily
## Core Rule
Do not over-classify business `LogSystem.LogError` telemetry as blocking.
Classify an issue as `blocking` only when one of these is true:
- CrashSight issue type is a real exception, such as `NullReferenceException`, `KeyNotFoundException`, `InvalidOperationException`, `ArgumentNullException`, `MemoryPackSerializationException`, `DllNotFoundException`, etc.
- The visible message or detail page contains a try/catch captured exception object or stack, such as `System.*Exception`, `异常类型`, `异常信息`, `调用堆栈`, `error: System...`, `failed: System...`, `ex: Object reference`, or `at Namespace.Type.Method(...)`.
- A `UnityLogError` is clearly wrapping an exception caught by code, for example `OnMessageReceived 处理失败, error: System.NullReferenceException...`, `Timer任务执行异常: 异常类型: ...`, or `EventManager Publish<...> listener failed: System...`.
Classify as `debug` when the issue is only a plain project log or diagnostic state, even if it sounds serious:
- Player/map/action mismatch logs without an exception object, such as `CompleteExecute Player 不一致`, `Map不一致`, `OnReceivedActionExcute Player 不一致`.
- AI/action diagnostics such as `存在相似action`, `不应该出现在...`, `CheckCan No`, `ActionConfirm send failed`, unless a concrete exception stack is present.
- Networking/environment/send telemetry such as STS/OSS failures, P2P send/connect failures, lobby failures, ForceUpdate/request logs, player-net mapping failure logs, Steam not logged in.
- UI/prefab guard logs such as `CityInfoMono.SetCulture: ... is null` or `FragmentDie: UnitRenderer 为空` when they are plain guard logs, not thrown/caught exceptions.
- Save/file/Workshop/local environment logs when they do not include an exception stack.
When uncertain after quick preview, open the detail page. If the detail still does not contain a concrete exception object/stack, keep it in `debug` and record the code location only.
## Workflow
1. Use the Chrome skill, not the in-app browser, because CrashSight requires the user's logged-in Chrome session.
2. Open the CrashSight errors URL from the user or the previous daily URL.
3. Filter scope:
- status: open/processing, usually `status=0,2`.
- exception category: `ERROR`.
- date: last 1 day.
- versions: use the two user-specified recent versions; if unspecified, inspect the version dropdown and choose the newest two target release versions only. Avoid broad wildcard ranges unless the user explicitly asks.
4. Capture all list pages, increasing `rows` when possible, and dedupe by Issue ID. Store raw captured rows under `Temp/CrashSight/Daily_<yyyy-MM-dd>/`.
5. Inspect issues one by one:
- Use quick preview when it shows full message and stack.
- Open issue detail when preview is truncated, message is `-`, the stack lacks symbols, or classification depends on whether a real exception is present.
6. Decode obfuscated online stacks before code search:
- Use `Tools/DecodeOnlineError.ps1` or `Tools/ObfuscatedExceptionDecoder.ps1`.
- Decode all blocking candidates and any debug rows that need code location from an obfuscated stack.
7. Locate code with `rg` and the decoded symbols. Prefer exact method/class names first, then stable message strings.
8. Generate Markdown under `MD/CrashSight_<yyyy-MM-dd>_<versions>_1day/`.
## Output Layout
Create this structure:
```text
MD/CrashSight_<yyyy-MM-dd>_<versionA>_<versionB>_1day/
├── index.md
├── debug_summary.md
├── report_manifest.json
└── blocking/
├── 001_issue_<id>.md
└── ...
```
Use a filesystem-safe version suffix, for example `0.7.1k_0.7.1j`.
`index.md` must include:
- filter scope and capture time.
- CrashSight total seen and deduped rows.
- blocking issue count/occurrence count.
- debug issue count/occurrence count.
- blocking family table sorted by occurrence count.
- top blocking issues with links to per-issue reports.
`debug_summary.md` must include:
- debug category summary with counts, occurrences, code locations, and example Issue IDs.
- debug detail table for every debug Issue.
- no trigger-cause analysis and no business fix explanation.
Each `blocking/*.md` must include:
- Issue ID, CrashSight URL, type, versions, first/last seen, count.
- raw message and key stack.
- decoded stack or decoded log text.
- code location with file paths and line numbers when possible.
- trigger reason and why it is blocking.
- focused recommendation.
`report_manifest.json` must mirror the final classification and counts so a later run can audit changes.
## Classification Audit
Before finalizing, run a text audit over the generated results:
- Verify every `blocking` issue either has a non-`UnityLogError` exception type or contains a concrete caught exception/stack in the message/detail.
- Search debug rows for `System.*Exception`, `异常类型`, `调用堆栈`, `Object reference`, `KeyNotFoundException`, `ArgumentNullException`; promote only those with real exception context.
- Search blocking rows for plain telemetry strings like `Player 不一致`, `Map不一致`, `存在相似action`, `ForceUpdate 玩家网络映射失败`, `安全写入失败`, `P2P message send failed`; demote them unless they also include a real exception object/stack.
- Confirm `blocking/*.md` count equals `report_manifest.json.blockingReports.length`.
## Reporting Back
In the final response, provide:
- link to `index.md`.
- final blocking/debug counts.
- a short note that plain `LogSystem.LogError` diagnostics were kept in debug unless they wrapped an actual exception.
- any limitations, such as rows that required detail pages but still had no full stack.
At the end of Chrome automation, close/finalize browser tabs according to the Chrome skill instructions.

View File

@ -0,0 +1,4 @@
interface:
display_name: "TH1 CrashSight Daily"
short_description: "Daily CrashSight triage for TH1 reports"
default_prompt: "Use $th1-crashsight-daily to scan recent CrashSight errors and write blocking/debug reports."

View File

@ -114,15 +114,22 @@ namespace RuntimeData
// 旧存档兼容WaterType 字段不存在时默认为 Pangea
if (!System.Enum.IsDefined(typeof(Logic.MapWaterType), WaterType))
WaterType = Logic.MapWaterType.Pangea;
MultiCivs ??= new List<MemberCiv>();
PlayerSettlements ??= new List<PlayerSettlementInfo>();
MatchLimits ??= new List<MatchLimitType>();
RefreshMultiCivsDict();
}
// 根据房间成员信息更新 mapconfig 信息
public void UpdateLobbyMember(Dictionary<ulong, MemberInfo> memberInfos)
public bool UpdateLobbyMember(Dictionary<ulong, MemberInfo> memberInfos)
{
if (memberInfos == null) return false;
MultiCivs ??= new List<MemberCiv>();
var changed = false;
// 先剔除已离开 lobby 的成员,避免 MultiCivs 留下幽灵占位:
// 旧版只 Add 不 Remove会导致大厅 nP 跳号(1P/4P/5P/6P)
// 以及开战时给离线幽灵创 PlayerData、占用 PlayerCount 名额、AI 补位变少。
MultiCivs.RemoveAll(mc => !memberInfos.ContainsKey(mc.MemberId));
changed |= MultiCivs.RemoveAll(mc => mc == null || !memberInfos.ContainsKey(mc.MemberId)) > 0;
RefreshMultiCivsDict();
foreach (var kv in memberInfos)
{
@ -132,22 +139,31 @@ namespace RuntimeData
civ.CivId = 0;
civ.ForceId = 0;
MultiCivs.Add(civ);
changed = true;
}
RefreshMultiCivsDict();
return changed;
}
// 内部刷新
private void RefreshMultiCivsDict()
{
if (_memberCivs.Count == MultiCivs.Count) return;
_memberCivs ??= new Dictionary<ulong, MemberCiv>();
MultiCivs ??= new List<MemberCiv>();
_memberCivs.Clear();
foreach (var memberCiv in MultiCivs) _memberCivs[memberCiv.MemberId] = memberCiv;
foreach (var memberCiv in MultiCivs)
{
if (memberCiv == null) continue;
_memberCivs[memberCiv.MemberId] = memberCiv;
}
}
public MemberCiv GetMemberCiv(ulong memberId)
{
MultiCivs ??= new List<MemberCiv>();
foreach (var memberCiv in MultiCivs)
{
if (memberCiv == null) continue;
if (memberCiv.MemberId == memberId) return memberCiv;
}
@ -155,22 +171,73 @@ namespace RuntimeData
}
// 主从端一致的更新某一个成员信息
public void UpdateMemberCiv(MemberCiv civ)
public bool UpdateMemberCiv(MemberCiv civ)
{
if (civ == null) return false;
if (LobbyManager.Instance.Lobby.IsInLobby() && !LobbyManager.Instance.Lobby.IsLobbyOwner())
{
GameNetSender.Instance.ChangeCiv(civ);
return;
var selfMemberId = LobbyManager.Instance.Lobby.GetSelfMemberId();
if (civ.MemberId != selfMemberId)
{
LogSystem.LogError($"客户端只能修改自己的阵营: self={selfMemberId}, target={civ.MemberId}");
return false;
}
if (!GameNetSender.Instance.ChangeCiv(civ)) return false;
ApplyMemberCivLocal(civ);
return true;
}
if (LobbyManager.Instance.Lobby.IsInLobby() && !LobbyManager.Instance.Lobby.IsMemberInLobby(civ.MemberId))
{
LogSystem.LogError($"不能修改不在房间内的成员阵营: target={civ.MemberId}");
return false;
}
return ApplyMemberCivLocal(civ);
}
private bool ApplyMemberCivLocal(MemberCiv civ)
{
MultiCivs ??= new List<MemberCiv>();
foreach (var memberCiv in MultiCivs)
{
if (memberCiv == null) continue;
if (memberCiv.MemberId != civ.MemberId) continue;
if (memberCiv.CivId == civ.CivId && memberCiv.ForceId == civ.ForceId) return false;
memberCiv.CivId = civ.CivId;
memberCiv.ForceId = civ.ForceId;
return;
memberCiv.PlayerId = civ.PlayerId;
RefreshMultiCivsDict();
return true;
}
MultiCivs.Add(civ);
MultiCivs.Add(new MemberCiv
{
MemberId = civ.MemberId,
CivId = civ.CivId,
ForceId = civ.ForceId,
PlayerId = civ.PlayerId
});
RefreshMultiCivsDict();
return true;
}
public bool HasSameLobbyMembers(Dictionary<ulong, MemberInfo> memberInfos)
{
if (memberInfos == null) return false;
MultiCivs ??= new List<MemberCiv>();
if (MultiCivs.Count != memberInfos.Count) return false;
var seen = new HashSet<ulong>();
foreach (var memberCiv in MultiCivs)
{
if (memberCiv == null) return false;
if (!memberInfos.ContainsKey(memberCiv.MemberId)) return false;
if (!seen.Add(memberCiv.MemberId)) return false;
}
return seen.Count == memberInfos.Count;
}
// 主从端一致的本地数据检测

View File

@ -377,12 +377,18 @@ namespace Logic
{
if (LobbyManager.Instance.Lobby.IsLobbyOwner())
{
Main.Instance.MapConfig.UpdateLobbyMember(LobbyManager.Instance.Lobby.GetAllMemberInfo());
if (Main.Instance.MapConfig.UpdateLobbyMember(LobbyManager.Instance.Lobby.GetAllMemberInfo()))
Main.Instance.MapConfig.CheckMapConfigChanged();
}
else
{
if (LobbyManager.Instance.Lobby.GetAllMemberInfo().Count != Main.Instance.MapConfig.MultiCivs.Count)
var memberInfos = LobbyManager.Instance.Lobby.GetAllMemberInfo();
if (GameNetSender.Instance.NeedsLobbyDataFromHost()
|| !Main.Instance.MapConfig.HasSameLobbyMembers(memberInfos))
{
GameNetSender.Instance.MarkLobbyDataSyncRequired();
GameNetSender.Instance.RequestLobbyData();
}
}
if (_recordTime > 1f)

View File

@ -350,8 +350,8 @@ namespace TH1_Logic.Steam
}
if (!LobbyManager.Instance.Lobby.IsLobbyOwner()) return;
if (message.Civ == null) return;
Main.Instance.MapConfig.UpdateMemberCiv(message.Civ);
Main.Instance.MapConfig.CheckMapConfigChanged();
if (Main.Instance.MapConfig.UpdateMemberCiv(message.Civ))
Main.Instance.MapConfig.CheckMapConfigChanged();
}
// 只有玩家会收到
@ -365,6 +365,15 @@ namespace TH1_Logic.Steam
if (LobbyManager.Instance.Lobby.IsLobbyOwner()) return;
if (message.Config == null) return;
Main.Instance.MapConfig = message.Config;
if (Main.Instance.MapConfig.HasSameLobbyMembers(LobbyManager.Instance.Lobby.GetAllMemberInfo()))
{
GameNetSender.Instance.MarkLobbyDataSyncedFromHost();
}
else
{
GameNetSender.Instance.MarkLobbyDataSyncRequired();
GameNetSender.Instance.RequestLobbyData();
}
EventManager.Publish(new UpdateUIOutsideMultiplayRoomSetting());
}
@ -377,6 +386,8 @@ namespace TH1_Logic.Steam
return;
}
if (!LobbyManager.Instance.Lobby.IsLobbyOwner()) return;
if (Main.Instance.MapConfig.UpdateLobbyMember(LobbyManager.Instance.Lobby.GetAllMemberInfo()))
Main.Instance.MapConfig.CheckMapConfigChanged();
GameNetSender.Instance.SendLobbyData(Main.Instance.MapConfig, message.MemberId);
}

View File

@ -11,6 +11,7 @@ using Logic.Action;
using Logic.AI;
using Logic.CrashSight;
using RuntimeData;
using Steamworks;
using TH1_Logic.Chat;
using TH1_Logic.Core;
using TH1_Logic.Net;
@ -26,6 +27,7 @@ namespace TH1_Logic.Steam
private const float RequestForceUpdateCooldown = 5f;
private float _lastRequestLobbyDataTime = -RequestLobbyDataCooldown;
private float _lastRequestForceUpdateTime = -RequestForceUpdateCooldown;
private bool _needLobbyDataFromHost;
// 发送消息给房主
public bool SendMessage(BaseMessage message)
@ -194,17 +196,19 @@ namespace TH1_Logic.Steam
}
// 修改阵营 (成员 => 房主)
public void ChangeCiv(MemberCiv memberCiv)
public bool ChangeCiv(MemberCiv memberCiv)
{
if (memberCiv == null)
{
LogSystem.LogError($"Get Self MemberCiv Error ");
return;
return false;
}
var data = new ChangeCivMessage();
data.Civ = memberCiv;
SendMessage(data);
if (!SendMessage(data)) return false;
MarkLobbyDataSyncRequired();
return true;
}
// 更新房间配置 (房主 => 所有成员)
@ -217,15 +221,45 @@ namespace TH1_Logic.Steam
}
// 请求更新房间配置 (单成员 => 房主)
public void RequestLobbyData()
public bool RequestLobbyData(bool force = false)
{
if (LobbyManager.Instance.Lobby.IsLobbyOwner()) return false;
if (!LobbyManager.Instance.Lobby.IsInLobby()) return false;
var hostId = LobbyManager.Instance.Lobby.GetLobbyOwnerId();
if (hostId == 0 || !SimpleP2P.Instance.IsConnectedTo(new CSteamID(hostId))) return false;
var now = UnityEngine.Time.time;
if (now - _lastRequestLobbyDataTime < RequestLobbyDataCooldown) return;
if (!force && now - _lastRequestLobbyDataTime < RequestLobbyDataCooldown) return false;
_lastRequestLobbyDataTime = now;
var data = new RequestLobbyDataMessage();
data.MemberId = LobbyManager.Instance.Lobby.GetSelfMemberId();
SendMessage(data);
if (SendMessage(data)) return true;
_lastRequestLobbyDataTime = now - RequestLobbyDataCooldown + 0.2f;
return false;
}
public void MarkLobbyDataSyncRequired()
{
_needLobbyDataFromHost = true;
_lastRequestLobbyDataTime = -RequestLobbyDataCooldown;
}
public void MarkLobbyDataSyncedFromHost()
{
_needLobbyDataFromHost = false;
}
public void ClearLobbyDataSyncState()
{
_needLobbyDataFromHost = false;
_lastRequestLobbyDataTime = -RequestLobbyDataCooldown;
}
public bool NeedsLobbyDataFromHost()
{
return _needLobbyDataFromHost;
}
// 更新房间配置 (房主 => 单成员)

View File

@ -510,6 +510,7 @@ namespace TH1_Logic.Steam
// 断开所有P2P连接
SimpleP2P.Instance.DisconnectAll();
SteamMatchmaking.LeaveLobby(CurrentLobby);
GameNetSender.Instance.ClearLobbyDataSyncState();
ResetLobbyState();
OnLobbyLeftEvent?.Invoke(null);
}
@ -1019,6 +1020,15 @@ namespace TH1_Logic.Steam
CheckIfKicked();
OnLobbyReadyInternal();
OnLobbyMembersChangedInternal();
if (IsLobbyOwner())
{
GameNetSender.Instance.ClearLobbyDataSyncState();
}
else
{
GameNetSender.Instance.MarkLobbyDataSyncRequired();
GameNetSender.Instance.RequestLobbyData(true);
}
// 触发加入成功事件
OnLobbyEnteredEvent?.Invoke(CurrentLobby);
@ -1147,7 +1157,18 @@ namespace TH1_Logic.Steam
// P2P事件处理
private void OnP2PPeerConnected(CSteamID steamID)
{
if (IsLobbyOwner()) Main.Instance.GameLogic.OnConnectToOtherPlayer(steamID.m_SteamID);
if (IsLobbyOwner())
{
Main.Instance.GameLogic.OnConnectToOtherPlayer(steamID.m_SteamID);
if (Main.Instance.GameLogic.GetCurState() == GameState.Menu && Main.Instance.MapConfig != null)
{
if (Main.Instance.MapConfig.UpdateLobbyMember(GetAllMemberInfo()))
Main.Instance.MapConfig.CheckMapConfigChanged();
GameNetSender.Instance.SendLobbyData(Main.Instance.MapConfig, steamID.m_SteamID);
}
}
else if (steamID.m_SteamID == GetLobbyOwnerId())
GameNetSender.Instance.RequestLobbyData(true);
LogSystem.LogInfo($"P2P connection established with: {steamID}");
}

View File

@ -435,13 +435,20 @@ namespace TH1Renderer
public void RenderUpdateUnitMap()
{
foreach (var unitData in Main.MapData.UnitMap.UnitList)
{
if (unitData == null)
continue;
if(!ROUnitMap.ContainsKey(unitData.Id))
{
//生成单位图像
ROUnitMap[unitData.Id] = new UnitRenderer(_unitPrefab,_unitRenderMap,unitData.Id);
var unitRenderer = new UnitRenderer(_unitPrefab,_unitRenderMap,unitData.Id);
if (!unitRenderer.IsValid)
continue;
ROUnitMap[unitData.Id] = unitRenderer;
//立刻更新每个unit的视觉
ROUnitMap[unitData.Id].InstantUpdateUnit(true);
unitRenderer.InstantUpdateUnit(true);
}
}
}
//当projectileMap出现新的对象时新建对象
@ -486,9 +493,13 @@ namespace TH1Renderer
// 2) 补齐缺失:数据层有但 ROUnitMap 没有的
foreach (var unitData in mapData.UnitMap.UnitList)
{
if (unitData == null)
continue;
if (!ROUnitMap.ContainsKey(unitData.Id))
{
ROUnitMap[unitData.Id] = new UnitRenderer(_unitPrefab, _unitRenderMap, unitData.Id);
var unitRenderer = new UnitRenderer(_unitPrefab, _unitRenderMap, unitData.Id);
if (unitRenderer.IsValid)
ROUnitMap[unitData.Id] = unitRenderer;
}
}

View File

@ -62,6 +62,7 @@ public class UnitMono : MonoBehaviour
public void UpdateUnitDefense(float defenseBonus)
{
if (Defense == null || SuperDefense == null || InfoGroup == null) return;
bool defense = Defense.activeSelf;
bool superdefense = SuperDefense.activeSelf;
bool noDefense = !defense && !superdefense;

View File

@ -23,6 +23,42 @@ namespace TH1_Renderer
private GameObject _ROUnit;
private UnitMono _unitMono;
public bool IsValid => _ROUnit != null && _unitMono != null && _unitMono.SpriteRenderer != null && _unitData != null;
private bool TryRefreshUnitRefs(bool requirePlayer = false, bool requireGrid = false)
{
var mapData = Main.MapData;
if (mapData == null || mapData.UnitMap == null)
{
_unitData = null;
_playerData = null;
_gridData = null;
return false;
}
if (!mapData.UnitMap.GetUnitDataByUnitId(_unitId, out _unitData) || _unitData == null)
{
_unitData = null;
_playerData = null;
_gridData = null;
return false;
}
if (requirePlayer && (!mapData.GetPlayerDataByUnitId(_unitId, out _playerData) || _playerData == null))
{
_playerData = null;
return false;
}
if (requireGrid && (!mapData.GetGridDataByUnitId(_unitId, out _gridData) || _gridData == null))
{
_gridData = null;
return false;
}
return true;
}
//------- 表现层RenderData ---------//
public bool IsAttackHighlight = false;
public bool IsSelectHighlight = false;
@ -90,15 +126,32 @@ namespace TH1_Renderer
public UnitRenderer(GameObject prefab,Transform father, uint uid)
{
_unitId = uid;
Main.MapData.UnitMap.GetUnitDataByUnitId(uid,out _unitData);
Main.MapData.GetPlayerDataByUnitId(uid, out _playerData);
Main.MapData.GetGridDataByUnitId(uid,out _gridData);
AnimManager = new UnitAnimManager();
Vector3 tpos = Table.Instance.GridToWorld(_gridData,"isUnit");
var table = Table.Instance;
if (!TryRefreshUnitRefs(requirePlayer: true, requireGrid: true) || prefab == null || father == null || table == null)
{
_unitData = null;
_playerData = null;
_gridData = null;
return;
}
Vector3 tpos = table.GridToWorld(_gridData,"isUnit");
_ROUnit = GameObject.Instantiate(prefab, tpos, Quaternion.identity, father);
_unitMono = _ROUnit?.GetComponent<UnitMono>();
_unitMono = _ROUnit != null ? _ROUnit.GetComponent<UnitMono>() : null;
if (_unitMono == null || _unitMono.SpriteRenderer == null)
{
if (_ROUnit != null)
GameObject.Destroy(_ROUnit);
_ROUnit = null;
_unitMono = null;
_unitData = null;
_playerData = null;
_gridData = null;
return;
}
// 初始化 StatusArea
if (_unitMono?.StatusAreaContainer != null && _unitMono?.StatusIconPrefab != null)
@ -121,11 +174,17 @@ namespace TH1_Renderer
// 清理状态区域
_statusArea?.ClearAllStatus();
GameObject.Destroy(_ROUnit.gameObject);
if (_ROUnit != null)
GameObject.Destroy(_ROUnit.gameObject);
if(MapRenderer.Instance.ROUnitMap.TryGetValue(_unitId,out var _))
MapRenderer.Instance.ROUnitMap.Remove(_unitId);
var mapRenderer = MapRenderer.Instance;
if (mapRenderer?.ROUnitMap != null)
mapRenderer.ROUnitMap.Remove(_unitId);
_unitData = null;
_playerData = null;
_gridData = null;
_unitMono = null;
_statusArea = null;
}
#region [-------------------- Status Area Management --------------------]
@ -190,17 +249,20 @@ namespace TH1_Renderer
public void InstantDisappear()
{
if (_ROUnit == null) return;
_ROUnit.SetActive(false);
}
public void InstantShow()
{
if (_ROUnit == null) return;
_ROUnit.SetActive(true);
}
public void Update()
{
AnimManager?.Update(_unitMono);
if (_unitMono != null)
AnimManager?.Update(_unitMono);
UpdateShenlanTint();
}
@ -208,7 +270,7 @@ namespace TH1_Renderer
// 只改 RGB保留 alphaHideState 用 alpha 控制半透明)。
private void UpdateShenlanTint()
{
if (_unitMono?.SpriteRenderer == null) return;
if (_unitMono == null || _unitMono.SpriteRenderer == null) return;
if (_unitData == null || !_unitData.IsAlive())
{
if (_shenlanTinted)
@ -240,13 +302,14 @@ namespace TH1_Renderer
public void RenderUpdateUnitDefense()
{
if (_unitData == null || !_unitData.IsAlive()) return;
_unitMono.UpdateUnitDefense(_unitData.GetDefenseMultiplicationParamOnlyForDefenseShow(Main.MapData));
var mapData = Main.MapData;
if (_unitMono == null || mapData == null || !TryRefreshUnitRefs() || !_unitData.IsAlive()) return;
_unitMono.UpdateUnitDefense(_unitData.GetDefenseMultiplicationParamOnlyForDefenseShow(mapData));
}
public void RenderUpdateUnitImage()
{
if (_unitData == null ) return;
if (_unitMono == null || Main.MapData == null || !TryRefreshUnitRefs()) return;
RenderUpdateUnitInfo();
@ -269,6 +332,7 @@ namespace TH1_Renderer
//如果unit死了不能直接die要等动画那边主动凋起才可以die
public bool InstantUpdateUnit(bool showoff)
{
if (_unitMono == null || !TryRefreshUnitRefs()) return false;
//如果要做显隐更新先判断显隐显的情况下再更新image
//如果不做显隐更新直接更新image
if ((showoff && RenderUpdateUnitShowOff())
@ -281,7 +345,7 @@ namespace TH1_Renderer
//瞬间更新unit的 die的情况
public bool InstantUpdateTryDie()
{
if (_unitData == null || !_unitData.IsAlive())
if (!TryRefreshUnitRefs() || !_unitData.IsAlive())
{
Die();
return true;
@ -298,6 +362,7 @@ namespace TH1_Renderer
public void RenderUpdateUnitInfo()
{
if (_unitMono == null || !TryRefreshUnitRefs() || Table.Instance?.UnitTypeDataAssets == null) return;
if (!_unitMono?.HealthText || !_unitMono?.UnitInfoBG) return;
_unitMono.HealthText.text = _unitData.Health.ToString();
//处理血量的颜色。如果血量<一半且<5那么赋予红色否则白色
@ -306,30 +371,33 @@ namespace TH1_Renderer
else
_unitMono.HealthText.color = Color.white;
Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(_unitData.UnitFullType, out var unitInfo);
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(_unitData.UnitFullType, out var unitInfo))
return;
var chessType = unitInfo.ChessType;
if (chessType == ChessType.None)
{
if(Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(_unitData.CarryUnitFullType, out var carryUnitInfo))
chessType = carryUnitInfo.ChessType;
}
if (chessType != ChessType.None)
if (chessType != ChessType.None && _unitMono.ChessImg != null)
{
Table.Instance.UnitTypeDataAssets.GetChessTypeInfo(chessType ,out var chessInfo);
_unitMono.ChessImg.sprite = chessInfo.ChessSprite;
if (Table.Instance.UnitTypeDataAssets.GetChessTypeInfo(chessType ,out var chessInfo))
_unitMono.ChessImg.sprite = chessInfo.ChessSprite;
}
//根据敌我情况更新infoBG的颜色
//_unitInfoBGImg.sprite = (Main.MapData.SameUnion(_playerData.Id , Main.MapData.PlayerMap.SelfPlayerId)) ? ResourceCache.Instance.SpriteCache.UnitInfoSelf :
if (Main.MapData == null) return;
if (Main.MapData.PlayerMap == null) return;
if (!Main.MapData.GetPlayerDataByUnitId(_unitData.Id, out var playerData)) return;
var col = _unitMono.UnitBGBlue;
if (playerData.Id != Main.MapData.PlayerMap.SelfPlayerId)
col =(Main.MapData.SameUnion(playerData.Id, Main.MapData.PlayerMap.SelfPlayerId))
? _unitMono.UnitBGGreen
: _unitMono.UnitBGRed;
_unitMono.ChessBG.color = col;
if (_unitMono.ChessBG != null)
_unitMono.ChessBG.color = col;
_unitMono.UnitInfoBG.color = col;
@ -358,7 +426,9 @@ namespace TH1_Renderer
}
//更改兵种显示文字
if(Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(_unitData.UnitType,_unitData.GiantType,_unitData.UnitLevel,out var info))
if(_unitMono.UnitInfoName != null
&& MultilingualManager.Instance != null
&& Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(_unitData.UnitType,_unitData.GiantType,_unitData.UnitLevel,out var info))
MultilingualManager.Instance.SetUIText(_unitMono.UnitInfoName,info.Name);
SyncStatusWithUnitSkills();
@ -366,6 +436,7 @@ namespace TH1_Renderer
public void RenderUpdateDebug()
{
if (_unitMono == null || !TryRefreshUnitRefs(requirePlayer: true) || _unitMono.RODebugText == null || _unitMono.DebugText == null || Main.MapData?.PlayerMap?.SelfPlayerData == null) return;
if (DebugCenter.Instance.DebugMode)
{
if(!_unitMono.RODebugText.activeSelf)
@ -383,20 +454,24 @@ namespace TH1_Renderer
//如果不是我方单位显示军团及unit的战略
_unitMono.DebugText.text += $"Lid={_unitData.LegionId}\n";
if(!Main.MapData.CheckUnitIdBelongPlayerId(_unitData.Id,Main.MapData.PlayerMap.SelfPlayerData.Id))
if (MainEditor.Instance.Data != null)
{
var mainEditor = MainEditor.Instance;
if (mainEditor?.Data != null)
{
MainEditor.Instance.GetUnitStrategy(_unitId, _unitData.LegionId, _playerData.Id, out var st,
mainEditor.GetUnitStrategy(_unitId, _unitData.LegionId, _playerData.Id, out var st,
out var tar, out var type);
_unitMono.DebugText.text += $"ST:{st} TAR:{tar} TYPE:{type}";
}
}
}
public bool RenderUpdateUnitShowOff()
{
if (_unitData == null || _unitMono == null)
var mapData = Main.MapData;
if (_unitMono == null || mapData == null || mapData.PlayerMap == null || mapData.PlayerMap.SelfPlayerData == null || !TryRefreshUnitRefs())
return false;
bool ret = _unitData.InMainSight();
//如果在视野内但是敌方隐身单位,对当前玩家不可见
if (ret && _unitData.IsHideAndCantSee(Main.MapData, Main.MapData.PlayerMap.SelfPlayerData))
if (ret && _unitData.IsHideAndCantSee(mapData, mapData.PlayerMap.SelfPlayerData))
ret = false;
//由隐转显时,先把 transform 同步到当前 grid避免显示在过期位置
//(敌方隐身单位全程 SetActive(false)RenderUpdateUnitPosition 没机会跑)
@ -407,10 +482,14 @@ namespace TH1_Renderer
}
public void RenderUpdateUnitGlow()
{
var player = _unitData.Player(Main.MapData);
var mapData = Main.MapData;
var table = Table.Instance;
if (_unitMono == null || _unitMono.SpriteRenderer == null || mapData == null || table == null || table.UnitTypeDataAssets == null || ResourceCache.Instance?.MatCache == null || !TryRefreshUnitRefs())
return;
var player = _unitData.Player(mapData);
if (player == null) return;
Sprite sprite;
if (!Table.Instance.UnitTypeDataAssets.GetUnitSprite(Main.MapData, _unitData, out sprite))
if (!table.UnitTypeDataAssets.GetUnitSprite(mapData, _unitData, out sprite))
return;
_unitMono.SpriteRenderer.sprite = sprite;
_unitMono.SpriteRenderer.material = ResourceCache.Instance.MatCache.TH1URPShaders_Default;
@ -419,10 +498,11 @@ namespace TH1_Renderer
//首先处理玩家(判断是否置灰或者高亮)
if (player.IsSelfPlayer())
{
var mapRenderer = MapRenderer.Instance;
//如果MP>0 或者周围有可以攻击的目标,或者可以移动的目标,或者说可以占领城市
if (_unitData.GetActionPoint(ActionPointType.Move) > 0
|| MapRenderer.Instance.CheckUnitHasMoveAttackTarget(_unitId)
|| MapRenderer.Instance.CheckUnitHasSpecialUnitActionTarget(_unitId))
|| (mapRenderer != null && mapRenderer.CheckUnitHasMoveAttackTarget(_unitId))
|| (mapRenderer != null && mapRenderer.CheckUnitHasSpecialUnitActionTarget(_unitId)))
{
_unitMono.SpriteRenderer.material = ResourceCache.Instance.MatCache.TH1URPShaders_Sprite_Glow;
_isGlow = true;
@ -435,14 +515,14 @@ namespace TH1_Renderer
else
{
//如果是正在行动的AI
if (Main.MapData.CurPlayer == player)
if (mapData.CurPlayer == player)
{
if(_unitData.GetActionPoint(ActionPointType.Move) == 0 && _unitData.GetActionPoint(ActionPointType.Capture) == 0 && _unitData.GetActionPoint(ActionPointType.Attack) == 0 && _unitData.GetActionPoint(ActionPointType.Move) == 0)
_unitMono.SpriteRenderer.material = ResourceCache.Instance.MatCache.TH1URPShaders_Sprite_WhiteOverlay;
}
else
//如果是还没行动的AI
if(Main.MapData.GetPlayerHasActedInBigTurn(player))
if(mapData.GetPlayerHasActedInBigTurn(player))
{
//啥都不做
}
@ -459,8 +539,12 @@ namespace TH1_Renderer
public void RenderUpdateHideState()
{
bool hideState = _unitData.IsHideState(Main.MapData);
bool isSelfOrAlly = Main.MapData.SameUnion(_playerData.Id, Main.MapData.PlayerMap.SelfPlayerId);
var mapData = Main.MapData;
if (_unitMono == null || _unitMono.SpriteRenderer == null || mapData == null || mapData.PlayerMap == null || !TryRefreshUnitRefs(requirePlayer: true))
return;
bool hideState = _unitData.IsHideState(mapData);
bool isSelfOrAlly = mapData.SameUnion(_playerData.Id, mapData.PlayerMap.SelfPlayerId);
// 状态未变时的处理:确保显示状态正确
if (hideState == _isHideState)
@ -516,22 +600,23 @@ namespace TH1_Renderer
public void RenderUpdateHideAround()
{
if (_unitMono.HideAround == null) return;
var mapData = Main.MapData;
if (_unitMono == null || _unitMono.HideAround == null || mapData?.GridMap == null || !TryRefreshUnitRefs(requirePlayer: true)) return;
bool show = false;
//只对当前玩家自己的单位显示
var curGrid = _unitData.Grid(Main.MapData);
var curGrid = _unitData.Grid(mapData);
if (_playerData != null && _playerData.IsSelfPlayer() && curGrid != null)
{
_aroundBuf ??= new List<GridData>();
_aroundBuf.Clear();
Main.MapData.GridMap.GetAroundGridData(1, 1, curGrid, _aroundBuf);
mapData.GridMap.GetAroundGridData(1, 1, curGrid, _aroundBuf);
foreach (var around in _aroundBuf)
{
if (around == curGrid) continue;
if (!around.RealUnit(Main.MapData, out var nearUnit)) continue;
if (!nearUnit.IsHideState(Main.MapData)) continue;
if (!around.RealUnit(mapData, out var nearUnit)) continue;
if (!nearUnit.IsHideState(mapData)) continue;
//排除自己的单位和同盟单位
if (Main.MapData.SameUnionByUnitId(_unitData.Id, nearUnit.Id)) continue;
if (mapData.SameUnionByUnitId(_unitData.Id, nearUnit.Id)) continue;
show = true;
break;
}
@ -559,9 +644,13 @@ namespace TH1_Renderer
public void RenderUpdataHighlight()
{
_unitMono.AttackHighlight.SetActive(IsAttackHighlight);
_unitMono.SelectHighlight.SetActive(IsSelectHighlight);
_unitMono.AllyHighlight.SetActive(IsAllyHighlight);
if (_unitMono == null) return;
if (_unitMono.AttackHighlight != null)
_unitMono.AttackHighlight.SetActive(IsAttackHighlight);
if (_unitMono.SelectHighlight != null)
_unitMono.SelectHighlight.SetActive(IsSelectHighlight);
if (_unitMono.AllyHighlight != null)
_unitMono.AllyHighlight.SetActive(IsAllyHighlight);
}
public void SetSelectHighlight(bool v)
@ -569,8 +658,10 @@ namespace TH1_Renderer
if (IsSelectHighlight == v)
return;
IsSelectHighlight = v;
MapRenderer.Instance.HighlightUnitIdSet.Add(_unitId);
MapRenderer.Instance.HighlightUnitIdSetRenderMark = true;
var mapRenderer = MapRenderer.Instance;
if (mapRenderer?.HighlightUnitIdSet == null) return;
mapRenderer.HighlightUnitIdSet.Add(_unitId);
mapRenderer.HighlightUnitIdSetRenderMark = true;
}
public void SetAttackHighlight(bool v)
@ -578,8 +669,10 @@ namespace TH1_Renderer
if (IsAttackHighlight == v)
return;
IsAttackHighlight = v;
MapRenderer.Instance.HighlightUnitIdSet.Add(_unitId);
MapRenderer.Instance.HighlightUnitIdSetRenderMark = true;
var mapRenderer = MapRenderer.Instance;
if (mapRenderer?.HighlightUnitIdSet == null) return;
mapRenderer.HighlightUnitIdSet.Add(_unitId);
mapRenderer.HighlightUnitIdSetRenderMark = true;
}
public void SetAllyHighlight(bool v)
@ -587,13 +680,20 @@ namespace TH1_Renderer
if (IsAllyHighlight == v)
return;
IsAllyHighlight = v;
MapRenderer.Instance.HighlightUnitIdSet.Add(_unitId);
MapRenderer.Instance.HighlightUnitIdSetRenderMark = true;
var mapRenderer = MapRenderer.Instance;
if (mapRenderer?.HighlightUnitIdSet == null) return;
mapRenderer.HighlightUnitIdSet.Add(_unitId);
mapRenderer.HighlightUnitIdSetRenderMark = true;
}
public void RenderUpdateUnitSprite()
{
if (!Table.Instance.UnitTypeDataAssets.GetUnitSprite(Main.MapData, _unitData, out var sprite))
var mapData = Main.MapData;
var table = Table.Instance;
if (_unitMono == null || _unitMono.SpriteRenderer == null || mapData == null || table == null || table.UnitTypeDataAssets == null || !TryRefreshUnitRefs())
return;
if (!table.UnitTypeDataAssets.GetUnitSprite(mapData, _unitData, out var sprite) || sprite == null)
return;
_unitMono.SpriteRenderer.sprite = sprite;
//RenderUpdateUnitSpecialSprite();
@ -603,14 +703,19 @@ namespace TH1_Renderer
public void RenderUpdateUnitPosition()
{
var t = _unitData.Grid(Main.MapData)?.Pos;
var mapData = Main.MapData;
var table = Table.Instance;
if (_unitMono == null || mapData == null || table == null || !TryRefreshUnitRefs(requireGrid: true))
return;
var t = _unitData.Grid(mapData)?.Pos;
if (t == null) return;
_unitMono.transform.position = Table.Instance.GridPosToWorld(new Vector2Int(t.X,t.Y),"isUnit");
_unitMono.transform.position = table.GridPosToWorld(new Vector2Int(t.X,t.Y),"isUnit");
}
public Vector3 GetPosition()
{
return _ROUnit.transform.position;
return _ROUnit != null ? _ROUnit.transform.position : Vector3.zero;
}
public bool isGlow()
@ -619,4 +724,4 @@ namespace TH1_Renderer
}
}
}
}

View File

@ -1,6 +1,7 @@
using System.Collections;
using System.Collections.Generic;
using Logic.Multilingual;
using RuntimeData;
using TH1_Logic.Core;
using TH1_Logic.Net;
using TH1_Logic.Steam;
@ -113,16 +114,22 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
}
public void UpdatePlayerInfoData(CivEnum civ,ForceEnum force)
public bool UpdatePlayerInfoData(CivEnum civ,ForceEnum force)
{
//修改mapConfig
var t = Main.Instance.MapConfig.GetMemberCiv(_lobby.GetSelfMemberId());
if(t != null){
t.CivId = Table.Instance.TransCivEnumToCivId(civ);
t.ForceId = Table.Instance.TransForceEnumToForceId(force);
Main.Instance.MapConfig.UpdateMemberCiv(t);
Main.Instance.MapConfig.CheckMapConfigChanged();
}
var selfMemberId = _lobby.GetSelfMemberId();
var t = Main.Instance.MapConfig.GetMemberCiv(selfMemberId);
if (t == null) return false;
var next = new MemberCiv
{
MemberId = selfMemberId,
CivId = Table.Instance.TransCivEnumToCivId(civ),
ForceId = Table.Instance.TransForceEnumToForceId(force)
};
var accepted = Main.Instance.MapConfig.UpdateMemberCiv(next);
if (accepted) Main.Instance.MapConfig.CheckMapConfigChanged();
return accepted;
}
public void OnClickForces()
@ -132,23 +139,23 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
_forceNameOverride = null;
if (_civ == CivEnum.Egyptian)
{
UpdatePlayerInfoData(CivEnum.French,ForceEnum.Kaguya);
UpdatePlayerInfoView(CivEnum.French,ForceEnum.Kaguya);
if (UpdatePlayerInfoData(CivEnum.French,ForceEnum.Kaguya))
UpdatePlayerInfoView(CivEnum.French,ForceEnum.Kaguya);
}
else if (_civ == CivEnum.French)
{
UpdatePlayerInfoData(CivEnum.Germany,ForceEnum.Kanako);
UpdatePlayerInfoView(CivEnum.Germany,ForceEnum.Kanako);
if (UpdatePlayerInfoData(CivEnum.Germany,ForceEnum.Kanako))
UpdatePlayerInfoView(CivEnum.Germany,ForceEnum.Kanako);
}
else if (_civ == CivEnum.Germany)
{
UpdatePlayerInfoData(CivEnum.Indian,ForceEnum.Satori);
UpdatePlayerInfoView(CivEnum.Indian,ForceEnum.Satori);
if (UpdatePlayerInfoData(CivEnum.Indian,ForceEnum.Satori))
UpdatePlayerInfoView(CivEnum.Indian,ForceEnum.Satori);
}
else if (_civ == CivEnum.Indian)
{
UpdatePlayerInfoData(CivEnum.Egyptian,ForceEnum.Remilia);
UpdatePlayerInfoView(CivEnum.Egyptian,ForceEnum.Remilia);
if (UpdatePlayerInfoData(CivEnum.Egyptian,ForceEnum.Remilia))
UpdatePlayerInfoView(CivEnum.Egyptian,ForceEnum.Remilia);
}
}
void Start()