TH1/Unity/Assets/Scripts/TH1_Logic/Editor/OssEditorWindow.cs
2026-04-18 22:52:09 +08:00

1299 lines
58 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* @Author: 白哉
* @Description: OSS 编辑器(文件下载 · 数据统计 · 上传测试)
* @Date: 2025年04月22日 星期二 15:04:14
* @Modify: 2026年04月17日 合并 OssStatisticEditorWindow
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Logic.CrashSight;
using MemoryPack;
using RuntimeData;
using Steamworks;
using TH1_Logic.Collect;
using TH1_Logic.Core;
using TH1_Logic.Net;
using TH1_Logic.Oss;
using UnityEditor;
using UnityEngine;
namespace Logic.Editor
{
// ──────────────────────────────────────
// 统计用数据类
// ──────────────────────────────────────
public enum OssStatisticType { Unit, Tech, Empire }
public class UnitStatisticLimit
{
public bool LimitUnitType;
public UnitType UnitType;
public bool LimitGiantType;
public GiantType GiantType;
public bool LimitLevel;
public int Level;
public bool LimitCarryUnitType;
public UnitType CarryUnitType;
public bool LimitCarryGiantType;
public GiantType CarryGiantType;
public bool LimitCarryLevel;
public int CarryLevel;
}
public class UnitStatisticResult
{
public float MatchCount;
public float TurnCount;
public float AppearCount;
public float EarliestAppearTurn;
public float DeathCount;
public float KillCount;
public float TotalDamageDealt;
public float TotalDamageTaken;
public float AverageDamageDealt;
public float AverageDamageTaken;
public float TransformFromCount;
public float TransformToCount;
public void Clear()
{
MatchCount = TurnCount = AppearCount = EarliestAppearTurn = 0;
DeathCount = KillCount = 0;
TotalDamageDealt = TotalDamageTaken = AverageDamageDealt = AverageDamageTaken = 0;
TransformFromCount = TransformToCount = 0;
}
}
public class TechStatisticLimit
{
public bool LimitEmpire;
public Empire Empire;
public bool LimitTechType;
public TechType TechType;
}
public class TechStatisticResult
{
public float LearnMatchCount;
public float LearnCount;
public float LearnTurn;
public void Clear() { LearnMatchCount = LearnCount = LearnTurn = 0; }
}
public class EmpireStatisticLimit
{
public bool LimitEmpire;
public Empire Empire;
}
public class EmpireStatisticResult
{
public int MatchCount;
public int TurnCount;
public float CoinPerTurn;
public float TechPointPerTurn;
public float CityCount;
public float MaxCityLevel;
public float CityLevel;
public float TechCount;
public void Clear()
{
MatchCount = TurnCount = 0;
CoinPerTurn = TechPointPerTurn = CityCount = MaxCityLevel = CityLevel = TechCount = 0;
}
}
// ──────────────────────────────────────
// 编辑器窗口
// ──────────────────────────────────────
public class OssEditorWindow : EditorWindow
{
// ── 通用 ──
private Vector2 _barPosition;
private GUIStyle _redBoxStyle;
private GUIStyle _whiteBoxStyle;
private GUIStyle _sectionHeaderStyle;
private int _selectedTab;
private static readonly string[] TabLabels = { "📥 文件下载", "📊 数据统计", "🔧 上传测试" };
// ── 上传测试 ──
private StsTokenService _stsService;
private OssUploadService _uploadService;
private StsCredentials _cachedCredentials;
private StsCredentials _cachedCollectCredentials;
private const string FunctionUrl = "https://get-sts-token-qltjykaafr.cn-shanghai.fcapp.run";
private bool _mapTestsFoldout;
private bool _collectTestsFoldout;
// ── 文件下载 ──
private const string PrefKeyAccessKeyId = "OssDownload_AccessKeyId";
private const string PrefKeyAccessKeySecret = "OssDownload_AccessKeySecret";
private const string PrefKeyEndpoint = "OssDownload_Endpoint";
private const string PrefKeyBucket = "OssDownload_Bucket";
private string _accessKeyId = "";
private string _accessKeySecret = "";
private string _ossEndpoint = "oss-cn-shanghai.aliyuncs.com";
private string _ossBucket = "th1-oss";
private string _downloadStatus = "";
private bool _isDownloading;
// ── 数据统计 ──
private string _statisticPath = "";
private bool _statisticPathInitialized;
private OssStatisticType _statisticType;
private UnitStatisticLimit _unitLimit;
private UnitStatisticResult _unitResult;
private TechStatisticLimit _techLimit;
private TechStatisticResult _techResult;
private EmpireStatisticLimit _empireLimit;
private EmpireStatisticResult _empireResult;
private int _totalFileCount;
private int _validFileCount;
private string[] _versionFolders = Array.Empty<string>();
private bool[] _versionSelected = Array.Empty<bool>();
private bool _versionFoldout = true;
/// <summary>Collect 数据的本地存储路径: Tools/OSS/Data</summary>
public static string DefaultDataPath =>
Path.GetFullPath(Path.Combine(Application.dataPath, "../../Tools/OSS/Data"));
[MenuItem("Tools/Oss 编辑器")]
private static void ShowWindow()
{
var window = GetWindow<OssEditorWindow>();
window.titleContent = new GUIContent("OSS 编辑器");
window.minSize = new Vector2(480, 360);
window.Show();
}
private void OnEnable()
{
_stsService = new StsTokenService(FunctionUrl);
_uploadService = new OssUploadService();
_accessKeyId = EditorPrefs.GetString(PrefKeyAccessKeyId, "");
_accessKeySecret = EditorPrefs.GetString(PrefKeyAccessKeySecret, "");
_ossEndpoint = EditorPrefs.GetString(PrefKeyEndpoint, "oss-cn-shanghai.aliyuncs.com");
_ossBucket = EditorPrefs.GetString(PrefKeyBucket, "th1-oss");
}
// ─────────────────────────────────
// OnGUI
// ─────────────────────────────────
private void OnGUI()
{
InitStyles();
// 顶部 Tab 栏
EditorGUILayout.Space(4);
_selectedTab = GUILayout.Toolbar(_selectedTab, TabLabels, GUILayout.Height(28));
EditorGUILayout.Space(6);
_barPosition = EditorGUILayout.BeginScrollView(_barPosition);
switch (_selectedTab)
{
case 0: DrawDownloadTab(); break;
case 1: DrawStatisticTab(); break;
case 2: DrawUploadTestTab(); break;
}
EditorGUILayout.EndScrollView();
}
private void InitStyles()
{
if (_redBoxStyle == null)
{
_redBoxStyle = InspectorUtils.GetHelpBoxStyle();
InspectorUtils.AddBorder(_redBoxStyle, new Color(0.5f, 0.4f, 0.4f, 0.6f));
}
if (_whiteBoxStyle == null)
{
_whiteBoxStyle = InspectorUtils.GetHelpBoxStyle();
InspectorUtils.AddBorder(_whiteBoxStyle, new Color(1f, 1f, 1f, 0.2f));
}
if (_sectionHeaderStyle == null)
{
_sectionHeaderStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 13,
padding = new RectOffset(2, 0, 4, 4)
};
}
GUI.skin.button.wordWrap = true;
}
// ═══════════════════════════════════
// Tab 0 — 文件下载
// ═══════════════════════════════════
private void DrawDownloadTab()
{
EditorGUILayout.LabelField("OSS 凭证配置", _sectionHeaderStyle);
EditorGUILayout.BeginVertical(_whiteBoxStyle);
DrawPrefField("AccessKey ID", ref _accessKeyId, PrefKeyAccessKeyId);
DrawPrefPasswordField("AccessKey Secret", ref _accessKeySecret, PrefKeyAccessKeySecret);
DrawPrefField("Endpoint", ref _ossEndpoint, PrefKeyEndpoint);
DrawPrefField("Bucket", ref _ossBucket, PrefKeyBucket);
EditorGUILayout.EndVertical();
EditorGUILayout.Space(6);
EditorGUILayout.LabelField("下载操作", _sectionHeaderStyle);
EditorGUILayout.BeginVertical(_whiteBoxStyle);
EditorGUILayout.LabelField("下载目录", DefaultDataPath, EditorStyles.miniLabel);
EditorGUILayout.Space(4);
EditorGUI.BeginDisabledGroup(_isDownloading);
if (GUILayout.Button("更新 Collect 文件", GUILayout.Height(30)))
DownloadCollectFiles();
EditorGUI.EndDisabledGroup();
if (!string.IsNullOrEmpty(_downloadStatus))
EditorGUILayout.HelpBox(_downloadStatus, MessageType.Info);
EditorGUILayout.EndVertical();
}
// ═══════════════════════════════════
// Tab 1 — 数据统计
// ═══════════════════════════════════
private void DrawStatisticTab()
{
if (!_statisticPathInitialized)
{
_statisticPathInitialized = true;
if (string.IsNullOrEmpty(_statisticPath))
_statisticPath = DefaultDataPath;
RefreshVersionFolders();
}
_unitLimit ??= new UnitStatisticLimit();
_unitResult ??= new UnitStatisticResult();
_techLimit ??= new TechStatisticLimit();
_techResult ??= new TechStatisticResult();
_empireLimit ??= new EmpireStatisticLimit();
_empireResult ??= new EmpireStatisticResult();
// 分析目录
EditorGUILayout.LabelField("分析目录", _sectionHeaderStyle);
EditorGUILayout.BeginHorizontal();
var newPath = EditorGUILayout.TextField(_statisticPath);
if (newPath != _statisticPath) { _statisticPath = newPath; RefreshVersionFolders(); }
if (GUILayout.Button("浏览...", GUILayout.Width(60)))
{
var selected = EditorUtility.OpenFolderPanel("选择分析文件夹", "", "");
if (!string.IsNullOrEmpty(selected)) { _statisticPath = selected; RefreshVersionFolders(); }
}
if (GUILayout.Button("刷新", GUILayout.Width(50)))
RefreshVersionFolders();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(4);
// 版本文件夹选择
if (_versionFolders.Length > 0)
{
int selCount = 0;
for (int i = 0; i < _versionSelected.Length; i++) if (_versionSelected[i]) selCount++;
_versionFoldout = EditorGUILayout.BeginFoldoutHeaderGroup(_versionFoldout,
$"版本选择 ({selCount}/{_versionFolders.Length})");
if (_versionFoldout)
{
EditorGUILayout.BeginVertical(_whiteBoxStyle);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("全选", GUILayout.Width(50)))
for (int i = 0; i < _versionSelected.Length; i++) _versionSelected[i] = true;
if (GUILayout.Button("全不选", GUILayout.Width(60)))
for (int i = 0; i < _versionSelected.Length; i++) _versionSelected[i] = false;
EditorGUILayout.EndHorizontal();
for (int i = 0; i < _versionFolders.Length; i++)
_versionSelected[i] = EditorGUILayout.ToggleLeft(_versionFolders[i], _versionSelected[i]);
EditorGUILayout.EndVertical();
}
EditorGUILayout.EndFoldoutHeaderGroup();
}
else
{
EditorGUILayout.HelpBox("未找到版本文件夹,请确认路径正确或点击刷新", MessageType.Warning);
}
EditorGUILayout.Space(6);
// 筛选类型
EditorGUILayout.BeginHorizontal();
InspectorUtils.InspectorTextWidthRich("<b>筛选类型:</b>");
_statisticType = (OssStatisticType)EditorGUILayout.EnumPopup(_statisticType, GUILayout.Width(200));
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(4);
switch (_statisticType)
{
case OssStatisticType.Unit: DrawUnitStatisticGUI(); break;
case OssStatisticType.Tech: DrawTechStatisticGUI(); break;
case OssStatisticType.Empire: DrawEmpireStatisticGUI(); break;
}
}
// ── Unit 统计 ──
private void DrawUnitStatisticGUI()
{
EditorGUILayout.LabelField("筛选条件", _sectionHeaderStyle);
EditorGUILayout.BeginVertical(_whiteBoxStyle);
DrawFilterRow("UnitType", ref _unitLimit.LimitUnitType,
() => _unitLimit.UnitType = (UnitType)EditorGUILayout.EnumPopup(_unitLimit.UnitType, GUILayout.Width(150)));
DrawFilterRow("GiantType", ref _unitLimit.LimitGiantType,
() => _unitLimit.GiantType = (GiantType)EditorGUILayout.EnumPopup(_unitLimit.GiantType, GUILayout.Width(150)));
DrawFilterRow("Level", ref _unitLimit.LimitLevel,
() => _unitLimit.Level = EditorGUILayout.IntField(_unitLimit.Level, GUILayout.Width(150)));
DrawFilterRow("CarryUnitType", ref _unitLimit.LimitCarryUnitType,
() => _unitLimit.CarryUnitType = (UnitType)EditorGUILayout.EnumPopup(_unitLimit.CarryUnitType, GUILayout.Width(150)));
DrawFilterRow("CarryGiantType", ref _unitLimit.LimitCarryGiantType,
() => _unitLimit.CarryGiantType = (GiantType)EditorGUILayout.EnumPopup(_unitLimit.CarryGiantType, GUILayout.Width(150)));
DrawFilterRow("CarryLevel", ref _unitLimit.LimitCarryLevel,
() => _unitLimit.CarryLevel = EditorGUILayout.IntField(_unitLimit.CarryLevel, GUILayout.Width(150)));
EditorGUILayout.Space(4);
if (GUILayout.Button("计算", GUILayout.Height(26)))
CalculateUnitStatistic();
EditorGUILayout.EndVertical();
EditorGUILayout.Space(6);
// 结果
if (_unitResult != null && _unitResult.MatchCount > 0)
{
EditorGUILayout.LabelField("统计结果", _sectionHeaderStyle);
EditorGUILayout.BeginVertical(_redBoxStyle);
InspectorUtils.InspectorTextWidthRich($"<b>扫描文件:</b> {_totalFileCount} <b>有效文件:</b> {_validFileCount} <b>有效局数:</b> {_unitResult.MatchCount}");
InspectorUtils.InspectorTextWidthRich($"<b>单局出战:</b> {_unitResult.AppearCount:F2} <b>最早出战回合:</b> Turn {_unitResult.EarliestAppearTurn:F1}");
InspectorUtils.InspectorTextWidthRich($"<b>单局击杀:</b> {_unitResult.KillCount:F2} <b>单局死亡:</b> {_unitResult.DeathCount:F2}");
InspectorUtils.InspectorTextWidthRich($"<b>单局总造伤:</b> {_unitResult.TotalDamageDealt:F1} <b>单局总承伤:</b> {_unitResult.TotalDamageTaken:F1}");
InspectorUtils.InspectorTextWidthRich($"<b>单位均造伤:</b> {_unitResult.AverageDamageDealt:F1} <b>单位均承伤:</b> {_unitResult.AverageDamageTaken:F1}");
EditorGUILayout.EndVertical();
}
}
// ── Tech 统计 ──
private void DrawTechStatisticGUI()
{
EditorGUILayout.LabelField("筛选条件", _sectionHeaderStyle);
EditorGUILayout.BeginVertical(_whiteBoxStyle);
DrawFilterRow("Empire", ref _techLimit.LimitEmpire, () =>
{
_techLimit.Empire.Civ = (CivEnum)EditorGUILayout.EnumPopup(_techLimit.Empire.Civ, GUILayout.Width(150));
_techLimit.Empire.Force = (ForceEnum)EditorGUILayout.EnumPopup(_techLimit.Empire.Force, GUILayout.Width(150));
});
DrawFilterRow("TechType", ref _techLimit.LimitTechType,
() => _techLimit.TechType = (TechType)EditorGUILayout.EnumPopup(_techLimit.TechType, GUILayout.Width(150)));
EditorGUILayout.Space(4);
if (GUILayout.Button("计算", GUILayout.Height(26)))
CalculateTechStatistic();
EditorGUILayout.EndVertical();
EditorGUILayout.Space(6);
if (_techResult != null && _techResult.LearnMatchCount > 0)
{
EditorGUILayout.LabelField("统计结果", _sectionHeaderStyle);
EditorGUILayout.BeginVertical(_redBoxStyle);
InspectorUtils.InspectorTextWidthRich($"<b>扫描文件:</b> {_totalFileCount} <b>有效文件:</b> {_validFileCount} <b>学习局数:</b> {_techResult.LearnMatchCount}");
InspectorUtils.InspectorTextWidthRich($"<b>平均学习次数(所有局):</b> {_techResult.LearnCount:F2}");
InspectorUtils.InspectorTextWidthRich($"<b>平均首次学习回合(学习局):</b> Turn {_techResult.LearnTurn:F1}");
EditorGUILayout.EndVertical();
}
}
// ── Empire 统计 ──
private void DrawEmpireStatisticGUI()
{
EditorGUILayout.LabelField("筛选条件", _sectionHeaderStyle);
EditorGUILayout.BeginVertical(_whiteBoxStyle);
DrawFilterRow("Empire", ref _empireLimit.LimitEmpire, () =>
{
_empireLimit.Empire.Civ = (CivEnum)EditorGUILayout.EnumPopup(_empireLimit.Empire.Civ, GUILayout.Width(150));
_empireLimit.Empire.Force = (ForceEnum)EditorGUILayout.EnumPopup(_empireLimit.Empire.Force, GUILayout.Width(150));
});
EditorGUILayout.Space(4);
if (GUILayout.Button("计算", GUILayout.Height(26)))
CalculateEmpireStatistic();
EditorGUILayout.EndVertical();
EditorGUILayout.Space(6);
if (_empireResult != null && _empireResult.MatchCount > 0)
{
EditorGUILayout.LabelField("统计结果", _sectionHeaderStyle);
EditorGUILayout.BeginVertical(_redBoxStyle);
InspectorUtils.InspectorTextWidthRich($"<b>扫描文件:</b> {_totalFileCount} <b>有效文件:</b> {_validFileCount} <b>有效局数:</b> {_empireResult.MatchCount}");
InspectorUtils.InspectorTextWidthRich($"<b>单局回合金币:</b> {_empireResult.CoinPerTurn:F1} <b>单局回合科技点:</b> {_empireResult.TechPointPerTurn:F1}");
InspectorUtils.InspectorTextWidthRich($"<b>单局城市数量:</b> {_empireResult.CityCount:F1} <b>最高城市等级:</b> {_empireResult.MaxCityLevel:F1}");
InspectorUtils.InspectorTextWidthRich($"<b>单局城市等级:</b> {_empireResult.CityLevel:F1} <b>单局科技数量:</b> {_empireResult.TechCount:F1}");
EditorGUILayout.EndVertical();
}
}
// ═══════════════════════════════════
// Tab 2 — 上传测试
// ═══════════════════════════════════
private void DrawUploadTestTab()
{
// Map 测试组
EditorGUILayout.LabelField("Map 上传测试", _sectionHeaderStyle);
_mapTestsFoldout = EditorGUILayout.BeginFoldoutHeaderGroup(_mapTestsFoldout, "Map 测试项");
if (_mapTestsFoldout)
{
EditorGUILayout.BeginVertical(_whiteBoxStyle);
DrawTestButton("请求 STS 令牌", TestMapRequestStsToken);
DrawTestButton("上传文件", TestMapUploadFile);
DrawTestButton("完整流程 (令牌+上传)", TestMapFullFlow);
DrawTestButton("上传(错误路径)", TestMapUploadErrorNameFile);
DrawTestButton("上传(大文件)", TestMapUploadBigFile);
EditorGUILayout.EndVertical();
}
EditorGUILayout.EndFoldoutHeaderGroup();
EditorGUILayout.Space(6);
// Collect 测试组
EditorGUILayout.LabelField("Collect 上传测试", _sectionHeaderStyle);
_collectTestsFoldout = EditorGUILayout.BeginFoldoutHeaderGroup(_collectTestsFoldout, "Collect 测试项");
if (_collectTestsFoldout)
{
EditorGUILayout.BeginVertical(_whiteBoxStyle);
DrawTestButton("请求 STS 令牌", TestCollectRequestStsToken);
DrawTestButton("上传文件", TestCollectUploadFile);
DrawTestButton("完整流程 (令牌+上传)", TestCollectFullFlow);
DrawTestButton("上传(错误路径)", TestCollectUploadErrorNameFile);
DrawTestButton("上传(大文件)", TestCollectUploadBigFile);
EditorGUILayout.EndVertical();
}
EditorGUILayout.EndFoldoutHeaderGroup();
EditorGUILayout.Space(6);
// 反序列化测试
EditorGUILayout.LabelField("存档验证", _sectionHeaderStyle);
EditorGUILayout.BeginVertical(_whiteBoxStyle);
DrawTestButton("测试存档反序列化", TestDeserializeArchive);
EditorGUILayout.EndVertical();
}
// ─────────────────────────────────
// GUI 辅助方法
// ─────────────────────────────────
private static void DrawPrefField(string label, ref string value, string prefKey)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(label, GUILayout.Width(120));
var newVal = EditorGUILayout.TextField(value);
if (newVal != value) { value = newVal; EditorPrefs.SetString(prefKey, newVal); }
EditorGUILayout.EndHorizontal();
}
private static void DrawPrefPasswordField(string label, ref string value, string prefKey)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(label, GUILayout.Width(120));
var newVal = EditorGUILayout.PasswordField(value);
if (newVal != value) { value = newVal; EditorPrefs.SetString(prefKey, newVal); }
EditorGUILayout.EndHorizontal();
}
private static void DrawFilterRow(string label, ref bool enabled, System.Action drawControl)
{
EditorGUILayout.BeginHorizontal();
InspectorUtils.InspectorTextWidthRich($"<b> 限制 {label} : </b> ");
enabled = EditorGUILayout.Toggle(enabled, GUILayout.Width(20));
if (enabled) drawControl();
EditorGUILayout.EndHorizontal();
}
private static void DrawTestButton(string label, System.Action action)
{
if (GUILayout.Button(label, GUILayout.Height(24)))
action();
}
// ═══════════════════════════════════
// 上传测试逻辑
// ═══════════════════════════════════
public async void TestMapRequestStsToken()
{
var id = LobbyManager.Instance.Lobby.GetSelfMemberId();
Debug.Log("=== 开始测试 STS 令牌请求 ===");
Debug.Log($"SteamID: {id}");
try
{
var ticket = new byte[1024];
var identity = new SteamNetworkingIdentity();
SteamUser.GetAuthSessionTicket(ticket, 1024, out var ticketSize, ref identity);
var authTicket = BitConverter.ToString(ticket, 0, (int)ticketSize).Replace("-", "").ToLower();
_cachedCredentials = await _stsService.RequestStsTokenAsync(id.ToString(), authTicket);
Debug.Log("<color=green>STS 令牌获取成功!</color>");
Debug.Log($"AccessKeyId: {_cachedCredentials.accessKeyId.Substring(0, 10)}...");
Debug.Log($"Endpoint: {_cachedCredentials.endpoint}");
Debug.Log($"Bucket: {_cachedCredentials.bucket}");
Debug.Log($"ObjectKey: {_cachedCredentials.objectKey}");
Debug.Log($"ExpiresIn: {_cachedCredentials.expiresIn} 秒");
}
catch (Exception ex)
{
Debug.LogError($"<color=red>STS 令牌获取失败:</color> {ex.Message}");
}
}
public async void TestCollectRequestStsToken()
{
var id = LobbyManager.Instance.Lobby.GetSelfMemberId();
Debug.Log("=== 开始测试 Collect STS 令牌请求 ===");
Debug.Log($"SteamID: {id}");
try
{
var ticket = new byte[1024];
var identity = new SteamNetworkingIdentity();
SteamUser.GetAuthSessionTicket(ticket, 1024, out var ticketSize, ref identity);
var authTicket = BitConverter.ToString(ticket, 0, (int)ticketSize).Replace("-", "").ToLower();
_cachedCollectCredentials = await _stsService.RequestStsTokenAsync(id.ToString(), authTicket, "collectdata");
Debug.Log("<color=green>Collect STS 令牌获取成功!</color>");
Debug.Log($"AccessKeyId: {_cachedCollectCredentials.accessKeyId.Substring(0, 10)}...");
Debug.Log($"Endpoint: {_cachedCollectCredentials.endpoint}");
Debug.Log($"Bucket: {_cachedCollectCredentials.bucket}");
Debug.Log($"ObjectKey: {_cachedCollectCredentials.objectKey}");
Debug.Log($"ExpiresIn: {_cachedCollectCredentials.expiresIn} 秒");
}
catch (Exception ex)
{
Debug.LogError($"<color=red>STS 令牌获取失败:</color> {ex.Message}");
}
}
public async void TestMapUploadFile()
{
if (_cachedCredentials == null) { Debug.LogError("请先执行 请求STS令牌"); return; }
Debug.Log("=== 开始测试 OSS 上传 ===");
try
{
var testData = Encoding.UTF8.GetBytes("{\"test\":true,\"timestamp\":" + DateTimeOffset.UtcNow.ToUnixTimeSeconds() + "}");
Debug.Log($"上传数据大小: {testData.Length} bytes");
var result = await _uploadService.UploadFileAsync(_cachedCredentials, testData);
Debug.Log(result
? $"<color=green>文件上传成功!</color> 路径: {_cachedCredentials.bucket}/{_cachedCredentials.objectKey}"
: "<color=red>文件上传失败</color>");
}
catch (Exception ex) { Debug.LogError($"<color=red>上传异常:</color> {ex.Message}"); }
}
public async void TestCollectUploadFile()
{
if (_cachedCollectCredentials == null) { Debug.LogError("请先执行 请求STS令牌"); return; }
Debug.Log("=== 开始测试 Collect 上传 ===");
try
{
var testData = Encoding.UTF8.GetBytes("{\"test\":true,\"timestamp\":" + DateTimeOffset.UtcNow.ToUnixTimeSeconds() + "}");
Debug.Log($"上传数据大小: {testData.Length} bytes");
var result = await _uploadService.UploadFileAsync(_cachedCollectCredentials, testData);
Debug.Log(result
? $"<color=green>Collect 文件上传成功!</color> 路径: {_cachedCollectCredentials.bucket}/{_cachedCollectCredentials.objectKey}"
: "<color=red>Collect 文件上传失败</color>");
}
catch (Exception ex) { Debug.LogError($"<color=red>上传异常:</color> {ex.Message}"); }
}
public async void TestMapUploadErrorNameFile()
{
if (_cachedCredentials == null) { Debug.LogError("请先执行 请求STS令牌"); return; }
Debug.Log("=== 开始测试 OSS 上传(错误路径) ===");
try
{
var testData = Encoding.UTF8.GetBytes("{\"test\":true,\"timestamp\":" + DateTimeOffset.UtcNow.ToUnixTimeSeconds() + "}");
await _uploadService.TestInvalidObjectKeyAsync(_cachedCredentials, testData);
}
catch (Exception ex) { Debug.LogError($"<color=red>上传异常:</color> {ex.Message}"); }
}
public async void TestCollectUploadErrorNameFile()
{
if (_cachedCollectCredentials == null) { Debug.LogError("请先执行 请求STS令牌"); return; }
Debug.Log("=== 开始测试 Collect 上传(错误路径) ===");
try
{
var testData = Encoding.UTF8.GetBytes("{\"test\":true,\"timestamp\":" + DateTimeOffset.UtcNow.ToUnixTimeSeconds() + "}");
await _uploadService.TestInvalidObjectKeyAsync(_cachedCollectCredentials, testData);
}
catch (Exception ex) { Debug.LogError($"<color=red>Collect 上传异常:</color> {ex.Message}"); }
}
public async void TestMapUploadBigFile()
{
if (_cachedCredentials == null) { Debug.LogError("请先执行 请求STS令牌"); return; }
Debug.Log("=== 开始测试 OSS 上传(大文件) ===");
try { await _uploadService.TestOversizedFileAsync(_cachedCredentials); }
catch (Exception ex) { Debug.LogError($"<color=red>上传异常:</color> {ex.Message}"); }
}
public async void TestCollectUploadBigFile()
{
if (_cachedCollectCredentials == null) { Debug.LogError("请先执行 请求STS令牌"); return; }
Debug.Log("=== 开始测试 Collect 上传(大文件) ===");
try { await _uploadService.TestOversizedFileAsync(_cachedCollectCredentials); }
catch (Exception ex) { Debug.LogError($"<color=red>Collect 上传异常:</color> {ex.Message}"); }
}
public async void TestMapFullFlow()
{
Debug.Log("=== 开始完整流程测试 ===");
var manager = new OssManager();
var testData = Encoding.UTF8.GetBytes("{\"match\":\"test\",\"score\":100}");
var id = LobbyManager.Instance.Lobby.GetSelfMemberId();
var result = await manager.UploadGameDataAsync(id.ToString(), testData);
Debug.Log(result ? "<color=green>完整流程测试成功!</color>" : "<color=red>完整流程测试失败</color>");
}
public async void TestCollectFullFlow()
{
Debug.Log("=== 开始 Collect 完整流程测试 ===");
var manager = new OssManager();
var id = LobbyManager.Instance.Lobby.GetSelfMemberId();
var result = await manager.UploadCollectDataAsync(id.ToString(), new CollectData());
Debug.Log(result ? "<color=green>完整流程测试成功!</color>" : "<color=red>完整流程测试失败</color>");
}
public void TestDeserializeArchive()
{
string path = DefaultDataPath;
if (!Directory.Exists(path)) { Debug.LogError($"路径不存在: {path}"); return; }
var datFiles = Directory.GetFiles(path, "*.dat");
if (datFiles.Length == 0) { Debug.LogWarning($"路径下没有找到 .dat 文件: {path}"); return; }
Debug.Log($"=== 开始测试存档反序列化,共找到 {datFiles.Length} 个文件 ===");
int successCount = 0, failCount = 0;
foreach (var filePath in datFiles)
{
string fileName = Path.GetFileName(filePath);
try
{
byte[] data = File.ReadAllBytes(filePath);
var ossData = MemoryPackSerializer.Deserialize<OssData>(data);
if (ossData != null) { successCount++; Debug.Log($"<color=green>[成功] {fileName}</color>"); }
else { failCount++; Debug.LogError($"<color=red>[失败] {fileName} 反序列化结果为 null</color>"); }
}
catch (Exception ex) { failCount++; Debug.LogError($"<color=red>[异常] {fileName}: {ex.Message}</color>"); }
}
Debug.Log($"=== 反序列化完成: 成功 {successCount} / 失败 {failCount} / 共 {datFiles.Length} ===");
}
// ═══════════════════════════════════
// 文件下载逻辑
// ═══════════════════════════════════
private async void DownloadCollectFiles()
{
if (string.IsNullOrEmpty(_accessKeyId) || string.IsNullOrEmpty(_accessKeySecret))
{
_downloadStatus = "错误: 请先填写 AccessKey ID 和 AccessKey Secret";
Debug.LogError(_downloadStatus);
return;
}
_isDownloading = true;
_downloadStatus = "正在获取文件列表...";
Repaint();
try
{
var service = new OssDownloadService(_accessKeyId, _accessKeySecret, _ossEndpoint, _ossBucket);
var dataPath = DefaultDataPath;
if (!Directory.Exists(dataPath)) Directory.CreateDirectory(dataPath);
var allKeys = await service.ListObjectsAsync("collect/");
Debug.Log($"OSS collect/ 目录共发现 {allKeys.Count} 个文件");
if (allKeys.Count == 0)
{
_downloadStatus = "OSS collect/ 目录下没有文件";
_isDownloading = false;
Repaint();
return;
}
int skipCount = 0, downloadCount = 0, failCount = 0;
for (int i = 0; i < allKeys.Count; i++)
{
var key = allKeys[i];
var relativePath = key.Substring("collect/".Length);
var localPath = Path.Combine(dataPath, relativePath.Replace('/', Path.DirectorySeparatorChar));
_downloadStatus = $"处理中 ({i + 1}/{allKeys.Count}): {relativePath}";
EditorUtility.DisplayProgressBar("下载 Collect 文件", _downloadStatus, (float)i / allKeys.Count);
if (File.Exists(localPath)) { skipCount++; continue; }
var success = await service.DownloadObjectAsync(key, localPath);
if (success) downloadCount++; else failCount++;
}
EditorUtility.ClearProgressBar();
_downloadStatus = $"完成! 下载: {downloadCount}, 跳过: {skipCount}, 失败: {failCount}, 共: {allKeys.Count}";
Debug.Log($"<color=green>Collect 文件更新完成: 下载 {downloadCount}, 跳过 {skipCount}, 失败 {failCount}</color>");
}
catch (Exception ex)
{
EditorUtility.ClearProgressBar();
_downloadStatus = $"错误: {ex.Message}";
Debug.LogError($"<color=red>Collect 文件下载失败: {ex.Message}</color>");
}
finally
{
_isDownloading = false;
Repaint();
}
}
// ═══════════════════════════════════
// 版本文件夹辅助
// ═══════════════════════════════════
private void RefreshVersionFolders()
{
if (string.IsNullOrEmpty(_statisticPath) || !Directory.Exists(_statisticPath))
{
_versionFolders = Array.Empty<string>();
_versionSelected = Array.Empty<bool>();
return;
}
var dirs = Directory.GetDirectories(_statisticPath);
Array.Sort(dirs);
var names = new string[dirs.Length];
for (int i = 0; i < dirs.Length; i++)
names[i] = Path.GetFileName(dirs[i]);
// 保留已有选中状态
var oldFolders = _versionFolders;
var oldSelected = _versionSelected;
_versionFolders = names;
_versionSelected = new bool[names.Length];
for (int i = 0; i < names.Length; i++)
{
int oldIdx = Array.IndexOf(oldFolders, names[i]);
_versionSelected[i] = oldIdx >= 0 ? oldSelected[oldIdx] : true;
}
}
private string[] GetSelectedDatFiles()
{
var files = new List<string>();
for (int i = 0; i < _versionFolders.Length; i++)
{
if (!_versionSelected[i]) continue;
var versionPath = Path.Combine(_statisticPath, _versionFolders[i]);
if (Directory.Exists(versionPath))
files.AddRange(Directory.GetFiles(versionPath, "*.dat", SearchOption.AllDirectories));
}
return files.ToArray();
}
// ═══════════════════════════════════
// 数据统计逻辑
// ═══════════════════════════════════
private void CalculateUnitStatistic()
{
_unitResult = new UnitStatisticResult();
_totalFileCount = 0;
_validFileCount = 0;
if (string.IsNullOrEmpty(_statisticPath) || !Directory.Exists(_statisticPath))
{
LogSystem.LogError($"路径不存在: {_statisticPath}");
return;
}
var files = GetSelectedDatFiles();
if (files.Length == 0)
{
LogSystem.LogError("没有选中任何版本文件夹,或选中的文件夹中没有 .dat 文件");
return;
}
_totalFileCount = files.Length;
foreach (var file in files)
{
try
{
var bytes = File.ReadAllBytes(file);
var collectData = MemoryPackSerializer.Deserialize<CollectData>(bytes);
if (collectData == null) continue;
_validFileCount++;
ProcessCollectData(collectData, _unitResult);
}
catch (Exception e) { LogSystem.LogError($"反序列化失败: {file}\n{e.Message}"); }
}
if (_unitResult.MatchCount > 0)
{
_unitResult.AppearCount /= _unitResult.MatchCount;
_unitResult.DeathCount /= _unitResult.MatchCount;
_unitResult.KillCount /= _unitResult.MatchCount;
_unitResult.TotalDamageDealt /= _unitResult.MatchCount;
_unitResult.TotalDamageTaken /= _unitResult.MatchCount;
_unitResult.AverageDamageDealt /= _unitResult.MatchCount;
_unitResult.AverageDamageTaken /= _unitResult.MatchCount;
_unitResult.EarliestAppearTurn /= _unitResult.MatchCount;
}
LogSystem.LogInfo($"统计完成: 共扫描 {_totalFileCount} 个文件,有效 {_validFileCount} 个");
}
private void CalculateTechStatistic()
{
_techResult = new TechStatisticResult();
_totalFileCount = 0;
_validFileCount = 0;
if (string.IsNullOrEmpty(_statisticPath) || !Directory.Exists(_statisticPath))
{
LogSystem.LogError($"路径不存在: {_statisticPath}");
return;
}
var files = GetSelectedDatFiles();
if (files.Length == 0)
{
LogSystem.LogError("没有选中任何版本文件夹,或选中的文件夹中没有 .dat 文件");
return;
}
_totalFileCount = files.Length;
foreach (var file in files)
{
try
{
var bytes = File.ReadAllBytes(file);
var collectData = MemoryPackSerializer.Deserialize<CollectData>(bytes);
if (collectData == null) continue;
_validFileCount++;
ProcessCollectData(collectData, _techResult);
}
catch (Exception e) { LogSystem.LogError($"反序列化失败: {file}\n{e.Message}"); }
}
if (_techResult.LearnMatchCount > 0)
{
_techResult.LearnCount /= _techResult.LearnMatchCount;
_techResult.LearnTurn /= _techResult.LearnMatchCount;
}
LogSystem.LogInfo($"统计完成: 共扫描 {_totalFileCount} 个文件,有效 {_validFileCount} 个");
}
private void CalculateEmpireStatistic()
{
_empireResult = new EmpireStatisticResult();
_totalFileCount = 0;
_validFileCount = 0;
if (string.IsNullOrEmpty(_statisticPath) || !Directory.Exists(_statisticPath))
{
LogSystem.LogError($"路径不存在: {_statisticPath}");
return;
}
var files = GetSelectedDatFiles();
if (files.Length == 0)
{
LogSystem.LogError("没有选中任何版本文件夹,或选中的文件夹中没有 .dat 文件");
return;
}
_totalFileCount = files.Length;
foreach (var file in files)
{
try
{
var bytes = File.ReadAllBytes(file);
var collectData = MemoryPackSerializer.Deserialize<CollectData>(bytes);
if (collectData == null) continue;
_validFileCount++;
ProcessCollectData(collectData, _empireResult);
}
catch (Exception e) { LogSystem.LogError($"反序列化失败: {file}\n{e.Message}"); }
}
if (_empireResult.MatchCount > 0)
{
_empireResult.CoinPerTurn /= _empireResult.MatchCount;
_empireResult.TechPointPerTurn /= _empireResult.MatchCount;
_empireResult.CityCount /= _empireResult.MatchCount;
_empireResult.MaxCityLevel /= _empireResult.MatchCount;
_empireResult.CityLevel /= _empireResult.MatchCount;
_empireResult.TechCount /= _empireResult.MatchCount;
}
LogSystem.LogInfo($"统计完成: 共扫描 {_totalFileCount} 个文件,有效 {_validFileCount} 个");
}
// ─────────────────────────────────
// 数据处理
// ─────────────────────────────────
private void ProcessCollectData(CollectData collectData, UnitStatisticResult result)
{
var turn = -1;
var appearCount = 0;
foreach (var addUnit in collectData.AddUnits)
{
if (!MatchUnitFullType(addUnit.UnitType)) continue;
appearCount++;
if (turn < 0 || addUnit.Turn < turn) turn = (int)addUnit.Turn;
}
if (appearCount == 0) return;
result.MatchCount++;
result.EarliestAppearTurn += turn;
result.AppearCount += appearCount;
foreach (var dmg in collectData.Damages)
{
if (MatchUnitFullType(dmg.OriginUnitType))
{
result.TotalDamageDealt += dmg.OriginDamage;
result.AverageDamageDealt += dmg.OriginDamage / (float)appearCount;
if (dmg.IsKill) result.KillCount++;
}
if (MatchUnitFullType(dmg.TargetUnitType))
{
result.TotalDamageTaken += dmg.TargetDamage;
result.AverageDamageTaken += dmg.TargetDamage / (float)appearCount;
if (dmg.IsKill) result.DeathCount++;
}
}
}
private void ProcessCollectData(CollectData collectData, TechStatisticResult result)
{
var learnTurn = -1;
foreach (var techCollectData in collectData.LearnTechs)
{
if (!MatchTech(techCollectData.Empire, techCollectData.TechType)) continue;
result.LearnCount++;
if (learnTurn < 0 || learnTurn > techCollectData.Turn) learnTurn = (int)techCollectData.Turn;
}
if (learnTurn > 0)
{
result.LearnTurn += learnTurn;
result.LearnMatchCount++;
}
LogSystem.LogInfo($"result.LearnCount {result.LearnCount} result.LearnMatchCount{result.LearnMatchCount}");
}
private void ProcessCollectData(CollectData collectData, EmpireStatisticResult result)
{
var resultCache = new EmpireStatisticResult();
foreach (var turnStartData in collectData.OnTurnStarts)
{
if (!MatchEmpire(turnStartData.Empire)) continue;
resultCache.CoinPerTurn += turnStartData.PlayerCoinPerTurn;
resultCache.TechPointPerTurn += turnStartData.PlayerTechPointPerTurn;
resultCache.CityCount += turnStartData.CityCount;
resultCache.MaxCityLevel += turnStartData.MaxCityLevel;
resultCache.CityLevel += turnStartData.AverageCityLevel;
resultCache.TechCount += turnStartData.TechCount;
resultCache.TurnCount++;
}
if (resultCache.TurnCount > 0)
{
result.MatchCount++;
result.CoinPerTurn += resultCache.CoinPerTurn / resultCache.TurnCount;
result.TechPointPerTurn += resultCache.TechPointPerTurn / resultCache.TurnCount;
result.CityCount += resultCache.CityCount / resultCache.TurnCount;
result.MaxCityLevel += resultCache.MaxCityLevel / resultCache.TurnCount;
result.CityLevel += resultCache.CityLevel / resultCache.TurnCount;
result.TechCount += resultCache.TechCount / resultCache.TurnCount;
}
}
// ─────────────────────────────────
// 匹配方法
// ─────────────────────────────────
private bool MatchUnitFullType(UnitFullType unitFullType)
{
if (_unitLimit.LimitUnitType && unitFullType.UnitType != _unitLimit.UnitType) return false;
if (_unitLimit.LimitGiantType && unitFullType.GiantType != _unitLimit.GiantType) return false;
if (_unitLimit.LimitLevel && unitFullType.UnitLevel != _unitLimit.Level) return false;
return true;
}
private bool MatchTech(Empire empire, TechType techType)
{
if (_techLimit.LimitEmpire && empire != _techLimit.Empire) return false;
if (_techLimit.LimitTechType && techType != _techLimit.TechType) return false;
return true;
}
private bool MatchEmpire(Empire empire)
{
if (_empireLimit.LimitEmpire && empire != _empireLimit.Empire) return false;
return true;
}
// ═══════════════════════════════════
// JSON 导出 (供 Dashboard 使用)
// ═══════════════════════════════════
[MenuItem("Tools/OSS 导出 JSON (Dashboard)")]
private static void ExportCollectDataToJson()
{
var dataPath = DefaultDataPath;
if (!Directory.Exists(dataPath))
{
Debug.LogError($"OSS 数据目录不存在: {dataPath}");
return;
}
var jsonOutDir = Path.Combine(dataPath, "JsonExport");
if (!Directory.Exists(jsonOutDir))
Directory.CreateDirectory(jsonOutDir);
var allDatFiles = Directory.GetFiles(dataPath, "*.dat", SearchOption.AllDirectories);
int success = 0, fail = 0, skip = 0;
for (int i = 0; i < allDatFiles.Length; i++)
{
var datFile = allDatFiles[i];
// 跳过 JsonExport 目录自身
if (datFile.Replace('\\', '/').Contains("/JsonExport/")) continue;
var relativePath = datFile.Substring(dataPath.Length).TrimStart(Path.DirectorySeparatorChar, '/');
var jsonRelPath = Path.ChangeExtension(relativePath, ".json");
var jsonOutPath = Path.Combine(jsonOutDir, jsonRelPath);
// 如果 JSON 已存在且比 dat 新,跳过
if (File.Exists(jsonOutPath) && File.GetLastWriteTimeUtc(jsonOutPath) >= File.GetLastWriteTimeUtc(datFile))
{
skip++;
continue;
}
EditorUtility.DisplayProgressBar("导出 JSON", $"{i + 1}/{allDatFiles.Length}: {relativePath}",
(float)i / allDatFiles.Length);
try
{
var bytes = File.ReadAllBytes(datFile);
var collectData = MemoryPackSerializer.Deserialize<CollectData>(bytes);
if (collectData == null) { fail++; continue; }
var json = CollectDataToJson(collectData, relativePath);
var dir = Path.GetDirectoryName(jsonOutPath);
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
File.WriteAllText(jsonOutPath, json, new UTF8Encoding(false));
success++;
}
catch (Exception e)
{
fail++;
Debug.LogWarning($"导出失败: {relativePath} - {e.Message}");
}
}
EditorUtility.ClearProgressBar();
Debug.Log($"<color=green>JSON 导出完成: 成功 {success}, 跳过 {skip}, 失败 {fail}</color>");
}
private static string CollectDataToJson(CollectData data, string sourcePath)
{
var sb = new StringBuilder(4096);
sb.Append("{");
// 元信息
sb.Append("\"memberId\":").Append(data.MemberId);
sb.Append(",\"version\":\"").Append(EscapeJson(data.Version ?? "")).Append("\"");
sb.Append(",\"source\":\"").Append(EscapeJson(sourcePath)).Append("\"");
// Damages
sb.Append(",\"damages\":[");
for (int i = 0; i < data.Damages.Count; i++)
{
if (i > 0) sb.Append(",");
var d = data.Damages[i];
sb.Append("{\"turn\":").Append(d.Turn)
.Append(",\"isKill\":").Append(d.IsKill ? "true" : "false")
.Append(",\"originCiv\":").Append((int)d.OriginEmpire.Civ)
.Append(",\"originForce\":").Append((int)d.OriginEmpire.Force)
.Append(",\"originUnitType\":").Append((int)d.OriginUnitType.UnitType)
.Append(",\"originGiantType\":").Append((int)d.OriginUnitType.GiantType)
.Append(",\"originLevel\":").Append(d.OriginUnitType.UnitLevel)
.Append(",\"originDamage\":").Append(d.OriginDamage)
.Append(",\"targetCiv\":").Append((int)d.TargetEmpire.Civ)
.Append(",\"targetForce\":").Append((int)d.TargetEmpire.Force)
.Append(",\"targetUnitType\":").Append((int)d.TargetUnitType.UnitType)
.Append(",\"targetGiantType\":").Append((int)d.TargetUnitType.GiantType)
.Append(",\"targetLevel\":").Append(d.TargetUnitType.UnitLevel)
.Append(",\"targetDamage\":").Append(d.TargetDamage)
.Append("}");
}
sb.Append("]");
// AddUnits
sb.Append(",\"addUnits\":[");
for (int i = 0; i < data.AddUnits.Count; i++)
{
if (i > 0) sb.Append(",");
var a = data.AddUnits[i];
sb.Append("{\"turn\":").Append(a.Turn)
.Append(",\"civ\":").Append((int)a.Empire.Civ)
.Append(",\"force\":").Append((int)a.Empire.Force)
.Append(",\"unitType\":").Append((int)a.UnitType.UnitType)
.Append(",\"giantType\":").Append((int)a.UnitType.GiantType)
.Append(",\"level\":").Append(a.UnitType.UnitLevel)
.Append("}");
}
sb.Append("]");
// TransformUnits
sb.Append(",\"transformUnits\":[");
for (int i = 0; i < data.TransformUnits.Count; i++)
{
if (i > 0) sb.Append(",");
var t = data.TransformUnits[i];
sb.Append("{\"turn\":").Append(t.Turn)
.Append(",\"civ\":").Append((int)t.Empire.Civ)
.Append(",\"force\":").Append((int)t.Empire.Force)
.Append(",\"originUnitType\":").Append((int)t.OriginUnitType.UnitType)
.Append(",\"originGiantType\":").Append((int)t.OriginUnitType.GiantType)
.Append(",\"originLevel\":").Append(t.OriginUnitType.UnitLevel)
.Append(",\"targetUnitType\":").Append((int)t.TargetUnitType.UnitType)
.Append(",\"targetGiantType\":").Append((int)t.TargetUnitType.GiantType)
.Append(",\"targetLevel\":").Append(t.TargetUnitType.UnitLevel)
.Append("}");
}
sb.Append("]");
// LearnTechs
sb.Append(",\"learnTechs\":[");
for (int i = 0; i < data.LearnTechs.Count; i++)
{
if (i > 0) sb.Append(",");
var l = data.LearnTechs[i];
sb.Append("{\"turn\":").Append(l.Turn)
.Append(",\"civ\":").Append((int)l.Empire.Civ)
.Append(",\"force\":").Append((int)l.Empire.Force)
.Append(",\"techType\":").Append((int)l.TechType)
.Append("}");
}
sb.Append("]");
// OnTurnStarts
sb.Append(",\"onTurnStarts\":[");
for (int i = 0; i < data.OnTurnStarts.Count; i++)
{
if (i > 0) sb.Append(",");
var o = data.OnTurnStarts[i];
sb.Append("{\"turn\":").Append(o.Turn)
.Append(",\"civ\":").Append((int)o.Empire.Civ)
.Append(",\"force\":").Append((int)o.Empire.Force)
.Append(",\"coinPerTurn\":").Append(o.PlayerCoinPerTurn)
.Append(",\"techPointPerTurn\":").Append(o.PlayerTechPointPerTurn)
.Append(",\"cityCount\":").Append(o.CityCount)
.Append(",\"maxCityLevel\":").Append(o.MaxCityLevel)
.Append(",\"avgCityLevel\":").Append(o.AverageCityLevel)
.Append(",\"techCount\":").Append(o.TechCount)
.Append("}");
}
sb.Append("]");
// MatchGameEnds
sb.Append(",\"matchGameEnds\":[");
for (int i = 0; i < data.MatchGameEnds.Count; i++)
{
if (i > 0) sb.Append(",");
var m = data.MatchGameEnds[i];
sb.Append("{\"turn\":").Append(m.Turn)
.Append(",\"civ\":").Append((int)m.Empire.Civ)
.Append(",\"force\":").Append((int)m.Empire.Force)
.Append("}");
}
sb.Append("]");
// PlayerGameEnds
sb.Append(",\"playerGameEnds\":[");
for (int i = 0; i < data.PlayerGameEnds.Count; i++)
{
if (i > 0) sb.Append(",");
var p = data.PlayerGameEnds[i];
sb.Append("{\"turn\":").Append(p.Turn)
.Append(",\"selfCiv\":").Append((int)p.SelfEmpire.Civ)
.Append(",\"selfForce\":").Append((int)p.SelfEmpire.Force)
.Append(",\"killerCiv\":").Append((int)p.KillerEmpire.Civ)
.Append(",\"killerForce\":").Append((int)p.KillerEmpire.Force)
.Append("}");
}
sb.Append("]");
sb.Append("}");
return sb.ToString();
}
private static string EscapeJson(string s)
{
if (string.IsNullOrEmpty(s)) return "";
return s.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r");
}
}
}