Files
bansonic_beta_main/Assets/scripts/Team/TeamShareCodec.cs
T
2026-06-25 11:48:56 +08:00

1222 lines
39 KiB
C#

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<int> skillGroupIds = new List<int>();
}
[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<string> messages = new List<string>();
}
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<int, AllyHero_SO> 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<int> uniqueHeroIds = new HashSet<int>();
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<int> 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<byte> bytes = new List<byte>(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<int> skillIds = slot.heroId > 0 ? SanitizeSkillIds(slot.skillGroupIds) : new List<int>();
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<byte> payloadBytes = new List<byte>(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<byte> payloadBytes = new List<byte>(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<int>();
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<int>();
continue;
}
snapshot.slots[i].skillGroupIds = SanitizeSkillIds(snapshot.slots[i].skillGroupIds);
}
}
private static List<int> SanitizeSkillIds(IEnumerable<int> source)
{
List<int> result = new List<int>();
if (source == null)
{
return result;
}
HashSet<int> dedupe = new HashSet<int>();
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<slots_heroSlots> 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<int> seenHeroIds = new HashSet<int>();
List<int> appliedHeroIds = new List<int>();
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<int>();
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<int> sourceSkillIds = SanitizeSkillIds(slot.skillGroupIds);
List<int> equippedSkillIds = new List<int>();
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<CharacterView>();
}
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 = Resources.LoadAll<AllyHero_SO>(string.Empty);
cachedHeroById = new Dictionary<int, AllyHero_SO>();
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<byte> 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<byte> 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<byte>());
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<byte> 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<byte> 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<byte>(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();
}
}