using System; using System.Collections.Generic; using System.Text; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif [Serializable] public class TeamShareSlotData { public int heroId; public List skillGroupIds = new List(); } [Serializable] public class TeamShareSnapshot { public const int CurrentVersion = 3; public const int SlotCount = 5; public const string DefaultSchemeName = "\u65b9\u68481"; public int version = CurrentVersion; public int leaderHeroId; public string schemeName = DefaultSchemeName; public TeamShareSlotData[] slots = CreateDefaultSlots(); public static TeamShareSlotData[] CreateDefaultSlots() { TeamShareSlotData[] result = new TeamShareSlotData[SlotCount]; for (int i = 0; i < result.Length; i++) { result[i] = new TeamShareSlotData(); } return result; } } [Serializable] public class TeamShareApplyReport { public string shareCode; public string schemeName; public int appliedHeroCount; public int skippedHeroCount; public int appliedSkillCount; public int skippedSkillCount; public List messages = new List(); } public static class TeamShareCodec { public const string Prefix = "T3"; public const string LegacyPrefixV3 = "TS3"; public const string LegacyPrefixV2 = "BT2"; public const string LegacyPrefixV1 = "BT1"; public const string EmptySlotToken = "0"; private const char OuterSeparator = '-'; private const string HeroSlot01Key = "selected_heroSlot01_heroID"; private const string HeroSlot02Key = "selected_heroSlot02_heroID"; private const string HeroSlot03Key = "selected_heroSlot03_heroID"; private const string HeroSlot04Key = "selected_heroSlot04_heroID"; private const string HeroSlot05Key = "selected_heroSlot05_heroID"; private static AllyHero_SO[] cachedHeroes; private static Dictionary cachedHeroById; public static bool TryExportCurrentTeam(out string shareCode, out string errorMessage) { return TryExportCurrentTeam(TeamShareSnapshot.DefaultSchemeName, out shareCode, out errorMessage); } public static bool TryExportCurrentTeam(string schemeName, out string shareCode, out string errorMessage) { shareCode = string.Empty; errorMessage = string.Empty; TeamShareSnapshot snapshot; if (!TryBuildCurrentSnapshot(schemeName, out snapshot, out errorMessage)) { return false; } shareCode = ExportSnapshot(snapshot); return true; } public static string ExportSnapshot(TeamShareSnapshot snapshot) { TeamShareSnapshot safeSnapshot = snapshot ?? new TeamShareSnapshot(); NormalizeSnapshot(safeSnapshot); byte[] payloadBytes = BuildPayloadBytes(safeSnapshot); string payloadText = ToBase64Url(payloadBytes); return safeSnapshot.schemeName + OuterSeparator + Prefix + payloadText; } public static bool TryParse(string shareCode, out TeamShareSnapshot snapshot, out string errorMessage) { snapshot = null; errorMessage = string.Empty; if (string.IsNullOrWhiteSpace(shareCode)) { errorMessage = "Share code is empty."; return false; } string trimmed = shareCode.Trim(); string visibleSchemeName; string payload; SplitVisibleNameAndPayload(trimmed, out visibleSchemeName, out payload); if (string.IsNullOrWhiteSpace(payload)) { errorMessage = "Invalid share code format."; return false; } if (payload.StartsWith(Prefix, StringComparison.Ordinal)) { return TryParseT3Payload(payload, visibleSchemeName, out snapshot, out errorMessage); } if (payload.StartsWith(LegacyPrefixV3, StringComparison.Ordinal)) { return TryParseTs3Payload(payload, visibleSchemeName, out snapshot, out errorMessage); } if (payload.StartsWith(LegacyPrefixV2, StringComparison.Ordinal) || payload.StartsWith(LegacyPrefixV1, StringComparison.Ordinal)) { return TryParseLegacyPayload(payload, visibleSchemeName, out snapshot, out errorMessage); } errorMessage = "Unsupported share code version."; return false; } public static bool TryValidate(string shareCode, out TeamShareSnapshot snapshot, out string errorMessage) { snapshot = null; errorMessage = string.Empty; TeamShareSnapshot parsed; if (!TryParse(shareCode, out parsed, out errorMessage)) { return false; } if (!TryValidateSnapshot(parsed, out errorMessage)) { return false; } snapshot = parsed; return true; } public static bool TryApplyToCurrentTeam(string shareCode, out string errorMessage) { TeamShareApplyReport report; return TryApplyToCurrentTeam(shareCode, out report, out errorMessage); } public static bool TryApplyToCurrentTeam(string shareCode, out TeamShareApplyReport report, out string errorMessage) { errorMessage = string.Empty; report = new TeamShareApplyReport { shareCode = shareCode ?? string.Empty }; TeamShareSnapshot snapshot; if (!TryParse(shareCode, out snapshot, out errorMessage)) { return false; } if (!TryApplySnapshotPermissive(snapshot, report, out errorMessage)) { return false; } ApplySnapshotToTeamManager(snapshot); ApplySnapshotToSelectionPrefs(snapshot); RefreshSelectionViews(); report.schemeName = snapshot.schemeName; report.shareCode = ExportSnapshot(snapshot); return true; } public static bool TryBuildCurrentSnapshot(out TeamShareSnapshot snapshot, out string errorMessage) { return TryBuildCurrentSnapshot(TeamShareSnapshot.DefaultSchemeName, out snapshot, out errorMessage); } public static bool TryBuildCurrentSnapshot(string schemeName, out TeamShareSnapshot snapshot, out string errorMessage) { snapshot = new TeamShareSnapshot(); snapshot.schemeName = SanitizeSchemeName(schemeName); errorMessage = string.Empty; int[] heroIds = ReadCurrentSelectedHeroIds(); for (int i = 0; i < TeamShareSnapshot.SlotCount; i++) { TeamShareSlotData slot = new TeamShareSlotData(); int heroId = i < heroIds.Length ? heroIds[i] : 0; slot.heroId = heroId; if (heroId > 0) { AllyHero_SO hero = FindHeroById(heroId); if (hero == null) { errorMessage = "Unknown hero id in current team: " + heroId; return false; } hero.LoadEquippedSkillsFromLocal(); slot.skillGroupIds = SanitizeSkillIds(hero.equippedSkillGroupIDs); } snapshot.slots[i] = slot; } snapshot.leaderHeroId = ResolveCurrentLeaderHeroId(snapshot); NormalizeSnapshot(snapshot); return true; } public static bool TryValidateSnapshot(TeamShareSnapshot snapshot, out string errorMessage) { errorMessage = string.Empty; if (snapshot == null) { errorMessage = "Team snapshot is null."; return false; } NormalizeSnapshot(snapshot); EnsureHeroCache(); HashSet uniqueHeroIds = new HashSet(); bool hasAnyHero = false; for (int i = 0; i < TeamShareSnapshot.SlotCount; i++) { TeamShareSlotData slot = snapshot.slots[i]; if (slot == null) { errorMessage = "Slot data is missing."; return false; } if (slot.heroId <= 0) { if (slot.skillGroupIds != null && slot.skillGroupIds.Count > 0) { errorMessage = "Empty slot cannot contain skills."; return false; } continue; } hasAnyHero = true; if (!uniqueHeroIds.Add(slot.heroId)) { errorMessage = "Duplicate hero id detected: " + slot.heroId; return false; } AllyHero_SO hero = FindHeroById(slot.heroId); if (hero == null) { errorMessage = "Hero not found: " + slot.heroId; return false; } hero.LoadEquippedEquipmentFromLocal(); List sanitizedSkillIds = SanitizeSkillIds(slot.skillGroupIds); int maxSkillSlots = Mathf.Max(0, hero.GetEffectiveSkillSlotLimit()); if (sanitizedSkillIds.Count > maxSkillSlots) { errorMessage = "Too many skills equipped for hero: " + hero.ally_heroName; return false; } AllyHero_SO.AllyLevelInfo levelInfo = hero.GetEffectiveLevelForCurrentEXP(); int currentLevelId = levelInfo != null ? levelInfo.levelID : 0; for (int skillIndex = 0; skillIndex < sanitizedSkillIds.Count; skillIndex++) { int skillGroupId = sanitizedSkillIds[skillIndex]; SkillGroup group = hero.GetSkillGroupByID(skillGroupId); if (group == null) { errorMessage = "Skill group id is invalid for hero " + hero.ally_heroName + ": " + skillGroupId; return false; } int requiredLevel = Mathf.Max(0, group.thisSkill_levelLimit); if (currentLevelId < requiredLevel) { errorMessage = "Hero level is too low for skill: " + (group.groupName ?? skillGroupId.ToString()); return false; } } slot.skillGroupIds = sanitizedSkillIds; } if (!hasAnyHero) { errorMessage = "Share code contains no heroes."; return false; } if (snapshot.leaderHeroId > 0 && !uniqueHeroIds.Contains(snapshot.leaderHeroId)) { errorMessage = "Leader hero is not present in team slots."; return false; } return true; } private static byte[] BuildPayloadBytes(TeamShareSnapshot snapshot) { List bytes = new List(128); WriteVarInt(bytes, snapshot.leaderHeroId); for (int i = 0; i < TeamShareSnapshot.SlotCount; i++) { TeamShareSlotData slot = snapshot.slots[i] ?? new TeamShareSlotData(); WriteVarInt(bytes, Mathf.Max(0, slot.heroId)); List skillIds = slot.heroId > 0 ? SanitizeSkillIds(slot.skillGroupIds) : new List(); WriteVarInt(bytes, skillIds.Count); for (int j = 0; j < skillIds.Count; j++) { WriteVarInt(bytes, skillIds[j]); } } byte[] schemeNameBytes = Encoding.UTF8.GetBytes(SanitizeSchemeName(snapshot.schemeName)); bytes.AddRange(schemeNameBytes); ushort checksum = ComputeShortHashUShort(bytes); bytes.Add((byte)(checksum & 0xFF)); bytes.Add((byte)((checksum >> 8) & 0xFF)); return bytes.ToArray(); } private static bool TryParseT3Payload(string payload, string visibleSchemeName, out TeamShareSnapshot snapshot, out string errorMessage) { snapshot = null; errorMessage = string.Empty; string encodedPayload = payload.Substring(Prefix.Length); byte[] rawBytes; if (!TryFromBase64Url(encodedPayload, out rawBytes)) { errorMessage = "Invalid share code payload."; return false; } if (rawBytes == null || rawBytes.Length < 2) { errorMessage = "Invalid share code payload."; return false; } int checksumIndex = rawBytes.Length - 2; ushort expectedChecksum = (ushort)( rawBytes[checksumIndex] | (rawBytes[checksumIndex + 1] << 8)); List payloadBytes = new List(checksumIndex); for (int i = 0; i < checksumIndex; i++) { payloadBytes.Add(rawBytes[i]); } ushort actualChecksum = ComputeShortHashUShort(payloadBytes); if (expectedChecksum != actualChecksum) { errorMessage = "Share code checksum failed."; return false; } TeamShareSnapshot parsed = new TeamShareSnapshot(); int readIndex = 0; if (!TryReadVarInt(payloadBytes, ref readIndex, out parsed.leaderHeroId)) { errorMessage = "Leader hero id is invalid."; return false; } for (int slotIndex = 0; slotIndex < TeamShareSnapshot.SlotCount; slotIndex++) { TeamShareSlotData slot = new TeamShareSlotData(); int heroId; if (!TryReadVarInt(payloadBytes, ref readIndex, out heroId)) { errorMessage = "Invalid slot hero id."; return false; } slot.heroId = heroId; int skillCount; if (!TryReadVarInt(payloadBytes, ref readIndex, out skillCount) || skillCount < 0) { errorMessage = "Invalid skill count."; return false; } for (int skillIndex = 0; skillIndex < skillCount; skillIndex++) { int skillId; if (!TryReadVarInt(payloadBytes, ref readIndex, out skillId)) { errorMessage = "Invalid skill id."; return false; } slot.skillGroupIds.Add(skillId); } parsed.slots[slotIndex] = slot; } int nameByteCount = payloadBytes.Count - readIndex; if (nameByteCount < 0) { errorMessage = "Invalid share code payload."; return false; } string embeddedSchemeName = nameByteCount > 0 ? Encoding.UTF8.GetString(payloadBytes.GetRange(readIndex, nameByteCount).ToArray()) : string.Empty; parsed.schemeName = SanitizeSchemeName(embeddedSchemeName); if (string.IsNullOrWhiteSpace(parsed.schemeName)) { parsed.schemeName = SanitizeSchemeName(visibleSchemeName); } NormalizeSnapshot(parsed); snapshot = parsed; return true; } private static bool TryParseTs3Payload(string payload, string visibleSchemeName, out TeamShareSnapshot snapshot, out string errorMessage) { snapshot = null; errorMessage = string.Empty; string encodedPayload = payload.Substring(Prefix.Length); byte[] rawBytes; if (!TryFromBase64Url(encodedPayload, out rawBytes)) { errorMessage = "Invalid share code payload."; return false; } if (rawBytes == null || rawBytes.Length < 4) { errorMessage = "Invalid share code payload."; return false; } int checksumIndex = rawBytes.Length - 4; uint expectedChecksum = (uint)( rawBytes[checksumIndex] | (rawBytes[checksumIndex + 1] << 8) | (rawBytes[checksumIndex + 2] << 16) | (rawBytes[checksumIndex + 3] << 24)); List payloadBytes = new List(checksumIndex); for (int i = 0; i < checksumIndex; i++) { payloadBytes.Add(rawBytes[i]); } uint actualChecksum = ComputeShortHashUInt(payloadBytes); if (expectedChecksum != actualChecksum) { errorMessage = "Share code checksum failed."; return false; } TeamShareSnapshot parsed = new TeamShareSnapshot(); int readIndex = 0; if (!TryReadVarInt(payloadBytes, ref readIndex, out parsed.leaderHeroId)) { errorMessage = "Leader hero id is invalid."; return false; } for (int slotIndex = 0; slotIndex < TeamShareSnapshot.SlotCount; slotIndex++) { TeamShareSlotData slot = new TeamShareSlotData(); int heroId; if (!TryReadVarInt(payloadBytes, ref readIndex, out heroId)) { errorMessage = "Invalid slot hero id."; return false; } slot.heroId = heroId; int skillCount; if (!TryReadVarInt(payloadBytes, ref readIndex, out skillCount) || skillCount < 0) { errorMessage = "Invalid skill count."; return false; } for (int skillIndex = 0; skillIndex < skillCount; skillIndex++) { int skillId; if (!TryReadVarInt(payloadBytes, ref readIndex, out skillId)) { errorMessage = "Invalid skill id."; return false; } slot.skillGroupIds.Add(skillId); } parsed.slots[slotIndex] = slot; } int schemeNameLength; if (!TryReadVarInt(payloadBytes, ref readIndex, out schemeNameLength) || schemeNameLength < 0) { errorMessage = "Invalid scheme name."; return false; } if (readIndex + schemeNameLength != payloadBytes.Count) { errorMessage = "Invalid share code payload."; return false; } string embeddedSchemeName = Encoding.UTF8.GetString(payloadBytes.GetRange(readIndex, schemeNameLength).ToArray()); parsed.schemeName = SanitizeSchemeName(embeddedSchemeName); if (string.IsNullOrWhiteSpace(parsed.schemeName)) { parsed.schemeName = SanitizeSchemeName(visibleSchemeName); } NormalizeSnapshot(parsed); snapshot = parsed; return true; } private static bool TryParseLegacyPayload(string payload, string visibleSchemeName, out TeamShareSnapshot snapshot, out string errorMessage) { snapshot = null; errorMessage = string.Empty; string[] segments = payload.Split(new[] { "|" }, StringSplitOptions.None); int expectedSegmentCount = 1 + TeamShareSnapshot.SlotCount + 2; if (segments.Length != expectedSegmentCount) { errorMessage = "Invalid legacy share code format."; return false; } string prefix = segments[0]; if (!string.Equals(prefix, LegacyPrefixV1, StringComparison.Ordinal) && !string.Equals(prefix, LegacyPrefixV2, StringComparison.Ordinal)) { errorMessage = "Unsupported legacy share code version."; return false; } string checksumSegment = segments[segments.Length - 1]; string checksumToken = checksumSegment; if (checksumSegment.StartsWith("H=", StringComparison.Ordinal)) { checksumToken = checksumSegment.Substring(2); } string expectedPayload = string.Join("|", segments, 0, segments.Length - 1); string expectedHash = ComputeShortHash(expectedPayload); if (!string.Equals(expectedHash, checksumToken, StringComparison.OrdinalIgnoreCase)) { errorMessage = "Share code checksum failed."; return false; } TeamShareSnapshot parsed = new TeamShareSnapshot(); parsed.schemeName = SanitizeSchemeName(visibleSchemeName); string leaderSegment = segments[segments.Length - 2]; if (!leaderSegment.StartsWith("L=", StringComparison.Ordinal)) { errorMessage = "Leader segment is missing."; return false; } int leaderHeroId; if (!int.TryParse(leaderSegment.Substring(2), out leaderHeroId)) { errorMessage = "Leader hero id is invalid."; return false; } parsed.leaderHeroId = leaderHeroId; for (int i = 0; i < TeamShareSnapshot.SlotCount; i++) { TeamShareSlotData slotData; if (!TryParseLegacySlot(segments[i + 1], out slotData, out errorMessage)) { return false; } parsed.slots[i] = slotData; } NormalizeSnapshot(parsed); snapshot = parsed; return true; } private static bool TryParseLegacySlot(string token, out TeamShareSlotData slotData, out string errorMessage) { slotData = new TeamShareSlotData(); errorMessage = string.Empty; string safeToken = string.IsNullOrWhiteSpace(token) ? EmptySlotToken : token.Trim(); if (string.Equals(safeToken, EmptySlotToken, StringComparison.Ordinal)) { return true; } int splitIndex = safeToken.IndexOf('@'); string heroPart = splitIndex >= 0 ? safeToken.Substring(0, splitIndex) : safeToken; string skillsPart = splitIndex >= 0 && splitIndex + 1 < safeToken.Length ? safeToken.Substring(splitIndex + 1) : string.Empty; int heroId; if (!int.TryParse(heroPart, out heroId) || heroId <= 0) { errorMessage = "Invalid hero id: " + heroPart; return false; } slotData.heroId = heroId; slotData.skillGroupIds = new List(); if (string.IsNullOrWhiteSpace(skillsPart)) { return true; } string[] skillTokens = skillsPart.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < skillTokens.Length; i++) { int skillId; if (!int.TryParse(skillTokens[i], out skillId) || skillId <= 0) { errorMessage = "Invalid skill id: " + skillTokens[i]; return false; } slotData.skillGroupIds.Add(skillId); } slotData.skillGroupIds = SanitizeSkillIds(slotData.skillGroupIds); return true; } private static void NormalizeSnapshot(TeamShareSnapshot snapshot) { if (snapshot == null) { return; } snapshot.version = TeamShareSnapshot.CurrentVersion; snapshot.schemeName = SanitizeSchemeName(snapshot.schemeName); if (snapshot.slots == null || snapshot.slots.Length != TeamShareSnapshot.SlotCount) { TeamShareSlotData[] normalizedSlots = TeamShareSnapshot.CreateDefaultSlots(); if (snapshot.slots != null) { int copyLength = Mathf.Min(snapshot.slots.Length, normalizedSlots.Length); for (int i = 0; i < copyLength; i++) { normalizedSlots[i] = snapshot.slots[i] ?? new TeamShareSlotData(); } } snapshot.slots = normalizedSlots; } for (int i = 0; i < snapshot.slots.Length; i++) { if (snapshot.slots[i] == null) { snapshot.slots[i] = new TeamShareSlotData(); } if (snapshot.slots[i].heroId <= 0) { snapshot.slots[i].heroId = 0; snapshot.slots[i].skillGroupIds = new List(); continue; } snapshot.slots[i].skillGroupIds = SanitizeSkillIds(snapshot.slots[i].skillGroupIds); } } private static List SanitizeSkillIds(IEnumerable source) { List result = new List(); if (source == null) { return result; } HashSet dedupe = new HashSet(); foreach (int skillId in source) { if (skillId <= 0 || !dedupe.Add(skillId)) { continue; } result.Add(skillId); } return result; } private static string SanitizeSchemeName(string schemeName) { string safeName = string.IsNullOrWhiteSpace(schemeName) ? TeamShareSnapshot.DefaultSchemeName : schemeName.Trim(); safeName = safeName.Replace("\r", string.Empty).Replace("\n", string.Empty).Replace(OuterSeparator.ToString(), string.Empty); return safeName.Length > 24 ? safeName.Substring(0, 24) : safeName; } private static int[] ReadCurrentSelectedHeroIds() { int[] ids = new int[TeamShareSnapshot.SlotCount]; newTeamSelector selector = newTeamSelector.Instance; if (selector != null) { List createdSlots = selector.GetCreatedSlots(); if (createdSlots != null && createdSlots.Count > 0) { for (int i = 0; i < ids.Length && i < createdSlots.Count; i++) { ids[i] = createdSlots[i] != null ? Mathf.Max(0, createdSlots[i].heroSlot_heroID) : 0; } return ids; } ids[0] = Mathf.Max(0, selector.selected_heroSlot01_heroID); ids[1] = Mathf.Max(0, selector.selected_heroSlot02_heroID); ids[2] = Mathf.Max(0, selector.selected_heroSlot03_heroID); ids[3] = Mathf.Max(0, selector.selected_heroSlot04_heroID); ids[4] = Mathf.Max(0, selector.selected_heroSlot05_heroID); return ids; } ids[0] = PlayerPrefs.GetInt(HeroSlot01Key, 0); ids[1] = PlayerPrefs.GetInt(HeroSlot02Key, 0); ids[2] = PlayerPrefs.GetInt(HeroSlot03Key, 0); ids[3] = PlayerPrefs.GetInt(HeroSlot04Key, 0); ids[4] = PlayerPrefs.GetInt(HeroSlot05Key, 0); return ids; } private static int ResolveCurrentLeaderHeroId(TeamShareSnapshot snapshot) { TeamManager teamManager = TeamManager.Instance; if (teamManager != null) { TeamSetting selectedTeam = null; try { selectedTeam = teamManager.getCurrentSelectedTeam(); } catch { selectedTeam = null; } if (selectedTeam != null && selectedTeam.LeaderId > 0) { return selectedTeam.LeaderId; } } if (snapshot != null && snapshot.slots != null) { for (int i = 0; i < snapshot.slots.Length; i++) { if (snapshot.slots[i] != null && snapshot.slots[i].heroId > 0) { return snapshot.slots[i].heroId; } } } return 0; } private static bool TryApplySnapshotPermissive(TeamShareSnapshot snapshot, TeamShareApplyReport report, out string errorMessage) { errorMessage = string.Empty; if (snapshot == null || snapshot.slots == null) { errorMessage = "Team snapshot is null."; return false; } HashSet seenHeroIds = new HashSet(); List appliedHeroIds = new List(); for (int i = 0; i < snapshot.slots.Length; i++) { TeamShareSlotData slot = snapshot.slots[i]; if (slot == null) { snapshot.slots[i] = new TeamShareSlotData(); continue; } if (slot.heroId <= 0) { slot.skillGroupIds = new List(); continue; } if (!seenHeroIds.Add(slot.heroId)) { report.skippedHeroCount++; report.messages.Add("\u69fd\u4f4d " + (i + 1) + " \u7684\u91cd\u590d\u89d2\u8272\u5df2\u8df3\u8fc7"); snapshot.slots[i] = new TeamShareSlotData(); continue; } AllyHero_SO hero = FindHeroById(slot.heroId); if (hero == null) { report.skippedHeroCount++; report.messages.Add("\u69fd\u4f4d " + (i + 1) + " \u7684\u89d2\u8272\u4e0d\u5b58\u5728\uff0c\u5df2\u8df3\u8fc7"); snapshot.slots[i] = new TeamShareSlotData(); continue; } if (!hero.isUnlocked) { report.skippedHeroCount++; report.messages.Add("\u69fd\u4f4d " + (i + 1) + " \u7684\u89d2\u8272\u672a\u89e3\u9501\uff0c\u5df2\u8df3\u8fc7"); snapshot.slots[i] = new TeamShareSlotData(); continue; } hero.LoadEquippedEquipmentFromLocal(); List sourceSkillIds = SanitizeSkillIds(slot.skillGroupIds); List equippedSkillIds = new List(); AllyHero_SO.AllyLevelInfo levelInfo = hero.GetEffectiveLevelForCurrentEXP(); int currentLevelId = levelInfo != null ? levelInfo.levelID : 0; int maxSkillSlots = Mathf.Max(0, hero.GetEffectiveSkillSlotLimit()); for (int skillIndex = 0; skillIndex < sourceSkillIds.Count; skillIndex++) { int skillGroupId = sourceSkillIds[skillIndex]; SkillGroup group = hero.GetSkillGroupByID(skillGroupId); if (group == null) { report.skippedSkillCount++; report.messages.Add("\u89d2\u8272 " + hero.ally_heroName + " \u7684\u6280\u80fd\u4e0d\u5b58\u5728\uff0c\u5df2\u8df3\u8fc7"); continue; } if (currentLevelId < Mathf.Max(0, group.thisSkill_levelLimit)) { report.skippedSkillCount++; report.messages.Add("\u89d2\u8272 " + hero.ally_heroName + " \u7684\u6280\u80fd " + (group.groupName ?? skillGroupId.ToString()) + " \u672a\u89e3\u9501\uff0c\u5df2\u8df3\u8fc7"); continue; } if (equippedSkillIds.Count >= maxSkillSlots) { report.skippedSkillCount += sourceSkillIds.Count - skillIndex; report.messages.Add("\u89d2\u8272 " + hero.ally_heroName + " \u5df2\u8fbe\u5230\u6280\u80fd\u69fd\u4e0a\u9650"); break; } equippedSkillIds.Add(skillGroupId); } hero.equippedSkillGroupIDs = equippedSkillIds.ToArray(); hero.SaveEquippedSkillsToLocal(); slot.heroId = hero.ally_heroID; slot.skillGroupIds = equippedSkillIds; appliedHeroIds.Add(hero.ally_heroID); report.appliedHeroCount++; report.appliedSkillCount += equippedSkillIds.Count; #if UNITY_EDITOR if (!Application.isPlaying) { EditorUtility.SetDirty(hero); } #endif } #if UNITY_EDITOR if (!Application.isPlaying) { AssetDatabase.SaveAssets(); } #endif if (appliedHeroIds.Count == 0) { errorMessage = "No unlocked heroes could be applied."; return false; } if (snapshot.leaderHeroId <= 0 || !appliedHeroIds.Contains(snapshot.leaderHeroId)) { snapshot.leaderHeroId = appliedHeroIds[0]; } report.schemeName = snapshot.schemeName; return true; } private static void ApplySnapshotToTeamManager(TeamShareSnapshot snapshot) { TeamManager teamManager = TeamManager.Instance; if (teamManager == null || snapshot == null || snapshot.slots == null) { return; } TeamSetting selectedTeam = null; try { selectedTeam = teamManager.getCurrentSelectedTeam(); } catch { selectedTeam = null; } if (selectedTeam == null) { return; } if (selectedTeam.teamIdList == null) { selectedTeam.teamIdList = new List(); } while (selectedTeam.teamIdList.Count < TeamShareSnapshot.SlotCount) { selectedTeam.teamIdList.Add(new CharacterView(0, null)); } for (int i = 0; i < TeamShareSnapshot.SlotCount; i++) { TeamShareSlotData slot = snapshot.slots[i] ?? new TeamShareSlotData(); selectedTeam.teamIdList[i] = new CharacterView(slot.heroId, null); } selectedTeam.LeaderId = snapshot.leaderHeroId > 0 ? snapshot.leaderHeroId : ResolveCurrentLeaderHeroId(snapshot); teamManager.setCurrentSelectedTeam(selectedTeam, teamManager.currentSelectedTeam); } private static void ApplySnapshotToSelectionPrefs(TeamShareSnapshot snapshot) { if (snapshot == null || snapshot.slots == null) { return; } PlayerPrefs.SetInt(HeroSlot01Key, snapshot.slots[0] != null ? snapshot.slots[0].heroId : 0); PlayerPrefs.SetInt(HeroSlot02Key, snapshot.slots[1] != null ? snapshot.slots[1].heroId : 0); PlayerPrefs.SetInt(HeroSlot03Key, snapshot.slots[2] != null ? snapshot.slots[2].heroId : 0); PlayerPrefs.SetInt(HeroSlot04Key, snapshot.slots[3] != null ? snapshot.slots[3].heroId : 0); PlayerPrefs.SetInt(HeroSlot05Key, snapshot.slots[4] != null ? snapshot.slots[4].heroId : 0); PlayerPrefs.Save(); } private static void RefreshSelectionViews() { newTeamSelector selector = newTeamSelector.Instance; if (selector != null) { selector.LoadSelectedHeroes(); } if (teamSettingPanel.Instance != null) { teamSettingPanel.Instance.updateTeamSettingElements(); teamSettingPanel.Instance.updateSelectableCharacterViewList(); } } private static AllyHero_SO FindHeroById(int heroId) { EnsureHeroCache(); AllyHero_SO hero; return cachedHeroById != null && cachedHeroById.TryGetValue(heroId, out hero) ? hero : null; } private static void EnsureHeroCache() { if (cachedHeroById != null && cachedHeroById.Count > 0) { return; } cachedHeroes = RuntimeResourcesCache.LoadAllAllyHeroes(); cachedHeroById = new Dictionary(); if (cachedHeroes == null) { return; } for (int i = 0; i < cachedHeroes.Length; i++) { AllyHero_SO hero = cachedHeroes[i]; if (hero == null || hero.ally_heroID <= 0) { continue; } cachedHeroById[hero.ally_heroID] = hero; } } private static void SplitVisibleNameAndPayload(string rawShareCode, out string visibleSchemeName, out string payload) { visibleSchemeName = TeamShareSnapshot.DefaultSchemeName; payload = rawShareCode; int t3Index = rawShareCode.IndexOf(OuterSeparator + Prefix, StringComparison.Ordinal); if (t3Index >= 0) { visibleSchemeName = SanitizeSchemeName(rawShareCode.Substring(0, t3Index)); payload = rawShareCode.Substring(t3Index + 1); return; } int ts3Index = rawShareCode.IndexOf(OuterSeparator + LegacyPrefixV3, StringComparison.Ordinal); if (ts3Index >= 0) { visibleSchemeName = SanitizeSchemeName(rawShareCode.Substring(0, ts3Index)); payload = rawShareCode.Substring(ts3Index + 1); return; } int btIndex = rawShareCode.IndexOf(OuterSeparator + LegacyPrefixV2, StringComparison.Ordinal); if (btIndex < 0) { btIndex = rawShareCode.IndexOf(OuterSeparator + LegacyPrefixV1, StringComparison.Ordinal); } if (btIndex >= 0) { visibleSchemeName = SanitizeSchemeName(rawShareCode.Substring(0, btIndex)); payload = rawShareCode.Substring(btIndex + 1); } } private static void WriteVarInt(List bytes, int value) { uint safeValue = value <= 0 ? 0u : (uint)value; while (safeValue >= 0x80u) { bytes.Add((byte)(safeValue | 0x80u)); safeValue >>= 7; } bytes.Add((byte)safeValue); } private static bool TryReadVarInt(List bytes, ref int index, out int value) { value = 0; if (bytes == null || index < 0 || index >= bytes.Count) { return false; } int shift = 0; uint result = 0u; while (index < bytes.Count && shift < 35) { byte b = bytes[index++]; result |= (uint)(b & 0x7F) << shift; if ((b & 0x80) == 0) { if (result > int.MaxValue) { return false; } value = (int)result; return true; } shift += 7; } return false; } private static string ToBase64Url(byte[] bytes) { string text = Convert.ToBase64String(bytes ?? Array.Empty()); return text.Replace('+', '-').Replace('/', '_').TrimEnd('='); } private static bool TryFromBase64Url(string text, out byte[] bytes) { bytes = null; if (string.IsNullOrWhiteSpace(text)) { return false; } string safeText = text.Replace('-', '+').Replace('_', '/'); switch (safeText.Length % 4) { case 2: safeText += "=="; break; case 3: safeText += "="; break; } try { bytes = Convert.FromBase64String(safeText); return true; } catch { return false; } } private static uint ComputeShortHashUInt(List bytes) { const uint fnvOffset = 2166136261u; const uint fnvPrime = 16777619u; uint hash = fnvOffset; if (bytes == null) { return hash; } for (int i = 0; i < bytes.Count; i++) { hash ^= bytes[i]; hash *= fnvPrime; } return hash; } private static ushort ComputeShortHashUShort(List bytes) { uint hash = ComputeShortHashUInt(bytes); return (ushort)((hash ^ (hash >> 16)) & 0xFFFFu); } private static string ComputeShortHash(string payload) { byte[] bytes = Encoding.UTF8.GetBytes(payload ?? string.Empty); return ToBase36(ComputeShortHashUInt(new List(bytes))).ToUpperInvariant(); } private static string ToBase36(uint value) { const string alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; if (value == 0u) { return "0"; } StringBuilder builder = new StringBuilder(); uint current = value; while (current > 0u) { uint remainder = current % 36u; builder.Insert(0, alphabet[(int)remainder]); current /= 36u; } return builder.ToString(); } }