using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Logging;
using FollowMePeak.Managers;
using FollowMePeak.Models;
using FollowMePeak.Patches;
using FollowMePeak.Services;
using FollowMePeak.UI;
using FollowMePeak.Utils;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
using Zorro.Core;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("FollowMePeak")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("FollowMe-Peak - Climb tracking plugin for Content Warning")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+5a6492bc5ccbf2b487c8bb1fe96530e754533871")]
[assembly: AssemblyProduct("FollowMePeak")]
[assembly: AssemblyTitle("FollowMePeak")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace FollowMePeak
{
[BepInPlugin("com.thomasaushh.followmepeak", "FollowMe-Peak", "0.1.0")]
public class Plugin : BaseUnityPlugin
{
[CompilerGenerated]
private sealed class <InitializePathSystem>d__19 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public Plugin <>4__this;
public Scene scene;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <InitializePathSystem>d__19(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Expected O, but got Unknown
//IL_0060: Unknown result type (might be due to invalid IL or missing references)
int num = <>1__state;
Plugin CS$<>8__locals0 = <>4__this;
switch (num)
{
default:
return false;
case 0:
<>1__state = -1;
<>2__current = (object)new WaitForSeconds(0.5f);
<>1__state = 1;
return true;
case 1:
{
<>1__state = -1;
NextLevelService service = GameHandler.GetService<NextLevelService>();
if (service != null && service.Data.IsSome)
{
int currentLevelIndex = service.Data.Value.CurrentLevelIndex;
CS$<>8__locals0._climbDataService.CurrentLevelID = $"{((Scene)(ref scene)).name}_{currentLevelIndex}";
((BaseUnityPlugin)CS$<>8__locals0).Logger.LogInfo((object)("Level erkannt: " + CS$<>8__locals0._climbDataService.CurrentLevelID));
CS$<>8__locals0._climbDataService.LoadClimbsFromFile();
if (CS$<>8__locals0._serverConfigService.Config.EnableCloudSync && CS$<>8__locals0._serverConfigService.Config.AutoDownload)
{
CS$<>8__locals0._climbDownloadService.DownloadAndMergeClimbs(CS$<>8__locals0._climbDataService.CurrentLevelID, delegate(int count, string error)
{
if (error == null && count > 0)
{
((BaseUnityPlugin)CS$<>8__locals0).Logger.LogInfo((object)$"Downloaded {count} new climbs from cloud for level {CS$<>8__locals0._climbDataService.CurrentLevelID}");
CS$<>8__locals0._visualizationManager.InitializeClimbVisibility();
}
else if (error != null && error != "Cloud sync disabled")
{
((BaseUnityPlugin)CS$<>8__locals0).Logger.LogWarning((object)("Cloud download failed: " + error));
}
});
}
CS$<>8__locals0._visualizationManager.InitializeClimbVisibility();
}
else
{
((BaseUnityPlugin)CS$<>8__locals0).Logger.LogError((object)"NextLevelService or its data could not be found!");
CS$<>8__locals0._climbDataService.CurrentLevelID = ((Scene)(ref scene)).name + "_unknown";
}
return false;
}
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
private ClimbDataService _climbDataService;
private ClimbRecordingManager _recordingManager;
private ClimbVisualizationManager _visualizationManager;
private FollowMeUI _ui;
private ServerConfigService _serverConfigService;
private VPSApiService _vpsApiService;
private ClimbUploadService _climbUploadService;
private ClimbDownloadService _climbDownloadService;
public static Plugin Instance { get; private set; }
public ClimbDataService ClimbDataService => _climbDataService;
private void Awake()
{
Instance = this;
((BaseUnityPlugin)this).Logger.LogInfo((object)("Plugin " + ((BaseUnityPlugin)this).Info.Metadata.GUID + " loaded!"));
InitializeServices();
SceneManager.sceneLoaded += OnSceneLoaded;
Harmony.CreateAndPatchAll(typeof(PluginPatches), (string)null);
((BaseUnityPlugin)this).Logger.LogInfo((object)"Harmony Patches applied.");
}
private void InitializeServices()
{
_climbDataService = new ClimbDataService(((BaseUnityPlugin)this).Logger);
_recordingManager = new ClimbRecordingManager(_climbDataService, ((BaseUnityPlugin)this).Logger, (MonoBehaviour)(object)this);
_visualizationManager = new ClimbVisualizationManager(_climbDataService);
_serverConfigService = new ServerConfigService(((BaseUnityPlugin)this).Logger);
_vpsApiService = new VPSApiService(((BaseUnityPlugin)this).Logger, _serverConfigService.Config, (MonoBehaviour)(object)this);
_climbUploadService = new ClimbUploadService(((BaseUnityPlugin)this).Logger, _vpsApiService, _serverConfigService);
_climbDownloadService = new ClimbDownloadService(((BaseUnityPlugin)this).Logger, _vpsApiService, _serverConfigService, _climbDataService);
_ui = new FollowMeUI(_climbDataService, _visualizationManager, _serverConfigService, _vpsApiService, _climbUploadService, _climbDownloadService);
((BaseUnityPlugin)this).Logger.LogInfo((object)"All services initialized successfully");
if (_serverConfigService.Config.EnableCloudSync)
{
_vpsApiService.CheckServerHealth(delegate(bool isHealthy)
{
((BaseUnityPlugin)this).Logger.LogInfo((object)("Initial server health check: " + (isHealthy ? "Connected" : "Failed")));
});
}
}
private void Update()
{
if (Input.GetKeyDown((KeyCode)282))
{
_ui.ShowMenu = !_ui.ShowMenu;
}
}
private void OnGUI()
{
_ui.OnGUI();
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
if (((Scene)(ref scene)).name.StartsWith("Level_"))
{
((MonoBehaviour)this).StartCoroutine(InitializePathSystem(scene));
_recordingManager.StartRecording();
}
else
{
_climbDataService.CurrentLevelID = "";
_visualizationManager.ClearVisuals();
}
}
[IteratorStateMachine(typeof(<InitializePathSystem>d__19))]
private IEnumerator InitializePathSystem(Scene scene)
{
//IL_000e: Unknown result type (might be due to invalid IL or missing references)
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <InitializePathSystem>d__19(0)
{
<>4__this = this,
scene = scene
};
}
public void OnCampfireLit(string biomeName)
{
_recordingManager.SaveCurrentClimb(biomeName);
_recordingManager.StartRecording();
}
public void ShowTagSelectionForNewClimb(ClimbData climbData)
{
if (_serverConfigService.Config.EnableCloudSync && _serverConfigService.Config.AutoUpload)
{
_climbUploadService.QueueForUpload(climbData, _climbDataService.CurrentLevelID);
((BaseUnityPlugin)this).Logger.LogInfo((object)$"Queued climb for upload: {climbData.Id}");
}
}
}
}
namespace FollowMePeak.Utils
{
public static class InputValidator
{
public static string SanitizePlayerName(string input)
{
if (string.IsNullOrWhiteSpace(input))
{
return "Anonymous";
}
string text = Regex.Replace(input, "[^a-zA-Z0-9_\\-\\s]", "");
if (text.Length > 50)
{
text = text.Substring(0, 50);
}
if (!string.IsNullOrWhiteSpace(text))
{
return text.Trim();
}
return "Anonymous";
}
public static string SanitizeBiomeName(string input)
{
if (string.IsNullOrWhiteSpace(input))
{
return "Unknown";
}
string text = Regex.Replace(input, "[^a-zA-Z0-9_\\-\\s]", "");
if (text.Length > 100)
{
text = text.Substring(0, 100);
}
if (!string.IsNullOrWhiteSpace(text))
{
return text.Trim();
}
return "Unknown";
}
public static bool IsValidLevelId(string levelId)
{
if (string.IsNullOrWhiteSpace(levelId))
{
return false;
}
return Regex.IsMatch(levelId, "^[a-zA-Z0-9_\\-\\.]{1,100}$");
}
public static bool IsValidPeakCode(string peakCode)
{
if (string.IsNullOrWhiteSpace(peakCode))
{
return false;
}
return Regex.IsMatch(peakCode, "^[A-Z0-9]{8}$");
}
public static string SanitizePeakCode(string input)
{
if (string.IsNullOrWhiteSpace(input))
{
return "";
}
string text = Regex.Replace(input.ToUpper(), "[^A-Z0-9]", "");
if (text.Length != 8)
{
return "";
}
return text;
}
public static float ClampDuration(float duration)
{
return Math.Max(1f, Math.Min(3600f, duration));
}
public static bool IsValidPointCount(int pointCount)
{
if (pointCount >= 2)
{
return pointCount <= 10000;
}
return false;
}
}
}
namespace FollowMePeak.UI
{
public class FollowMeUI
{
private enum SortColumn
{
CreationTime,
BiomeName,
Duration,
PlayerName
}
private readonly ClimbDataService _climbDataService;
private readonly ClimbVisualizationManager _visualizationManager;
private readonly CloudSyncUI _cloudSyncUI;
private readonly VPSApiService _vpsApiService;
private bool _showMenu;
private Rect _menuRect = new Rect(20f, 20f, 800f, 600f);
private Vector2 _scrollPosition = Vector2.zero;
private CursorLockMode _originalCursorLockState;
private bool _originalCursorVisible;
private string _biomeFilter = "";
private string _dateFilter = "";
private string _nameFilter = "";
private string _peakCodeSearch = "";
private bool _showOnlyLocalPaths;
private SortColumn _currentSortColumn;
private bool _sortAscending;
private int _selectedTab;
private readonly string[] _tabNames = new string[2] { "My Climbs", "Online Share" };
public bool ShowMenu
{
get
{
return _showMenu;
}
set
{
if (_showMenu != value)
{
_showMenu = value;
UpdateCursorState();
}
}
}
public FollowMeUI(ClimbDataService climbDataService, ClimbVisualizationManager visualizationManager, ServerConfigService serverConfigService, VPSApiService vpsApiService, ClimbUploadService climbUploadService, ClimbDownloadService climbDownloadService)
{
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
_climbDataService = climbDataService;
_visualizationManager = visualizationManager;
_vpsApiService = vpsApiService;
_cloudSyncUI = new CloudSyncUI(serverConfigService, vpsApiService, climbUploadService, climbDownloadService);
}
public void OnGUI()
{
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Unknown result type (might be due to invalid IL or missing references)
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Expected O, but got Unknown
//IL_0044: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Unknown result type (might be due to invalid IL or missing references)
if (_showMenu)
{
GUI.backgroundColor = new Color(0.1f, 0.1f, 0.1f, 0.9f);
_menuRect = GUILayout.Window(0, _menuRect, new WindowFunction(DrawMenuWindow), "FollowMe-Peak - Journal", Array.Empty<GUILayoutOption>());
}
}
private void DrawMenuWindow(int windowID)
{
//IL_0091: Unknown result type (might be due to invalid IL or missing references)
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
for (int i = 0; i < _tabNames.Length; i++)
{
bool flag = i == _selectedTab;
if (GUILayout.Toggle(flag, _tabNames[i], GUIStyle.op_Implicit("Button"), Array.Empty<GUILayoutOption>()) && !flag)
{
_selectedTab = i;
}
}
GUILayout.EndHorizontal();
GUILayout.Space(10f);
switch (_selectedTab)
{
case 0:
DrawClimbJournalTab();
break;
case 1:
DrawCloudSyncTab();
break;
}
GUI.DragWindow(new Rect(0f, 0f, 10000f, 20f));
}
private void DrawClimbJournalTab()
{
//IL_02e7: Unknown result type (might be due to invalid IL or missing references)
//IL_02f1: Unknown result type (might be due to invalid IL or missing references)
//IL_02f6: Unknown result type (might be due to invalid IL or missing references)
GUILayout.BeginVertical(GUIStyle.op_Implicit("box"), Array.Empty<GUILayoutOption>());
GUILayout.Label("Search & Filter", Array.Empty<GUILayoutOption>());
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.Label("Name:", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) });
_nameFilter = GUILayout.TextField(_nameFilter, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) });
GUILayout.Label("Biome:", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) });
_biomeFilter = GUILayout.TextField(_biomeFilter, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) });
GUILayout.Label("Peak Code:", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) });
_peakCodeSearch = GUILayout.TextField(_peakCodeSearch, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
if (GUILayout.Button("Search", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) }) && !string.IsNullOrEmpty(_peakCodeSearch) && _peakCodeSearch.Length >= 4)
{
SearchForPeakCode(_peakCodeSearch);
}
if (GUILayout.Button("Clear Filters", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) }))
{
_nameFilter = "";
_biomeFilter = "";
_peakCodeSearch = "";
_showOnlyLocalPaths = false;
}
GUILayout.FlexibleSpace();
bool flag = GUILayout.Toggle(_showOnlyLocalPaths, "Local climbs only", Array.Empty<GUILayoutOption>());
if (flag != _showOnlyLocalPaths)
{
_showOnlyLocalPaths = flag;
}
GUILayout.EndHorizontal();
GUILayout.EndVertical();
GUILayout.BeginHorizontal(GUIStyle.op_Implicit("box"), Array.Empty<GUILayoutOption>());
GUILayout.Label("Vis", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(30f) });
if (GUILayout.Button(GetSortLabel("Name", SortColumn.PlayerName), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) }))
{
SetSortColumn(SortColumn.PlayerName);
}
if (GUILayout.Button(GetSortLabel("Biome", SortColumn.BiomeName), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) }))
{
SetSortColumn(SortColumn.BiomeName);
}
if (GUILayout.Button(GetSortLabel("Duration", SortColumn.Duration), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }))
{
SetSortColumn(SortColumn.Duration);
}
if (GUILayout.Button(GetSortLabel("Date", SortColumn.CreationTime), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }))
{
SetSortColumn(SortColumn.CreationTime);
}
GUILayout.Label("Peak Code", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) });
GUILayout.Label("Actions", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) });
GUILayout.EndHorizontal();
_scrollPosition = GUILayout.BeginScrollView(_scrollPosition, Array.Empty<GUILayoutOption>());
List<ClimbData> processedClimbs = GetProcessedClimbs();
List<Guid> list = new List<Guid>();
foreach (ClimbData item in processedClimbs)
{
item.GenerateShareCode();
DrawClimbTableRow(item, list);
}
if (list.Count > 0)
{
_climbDataService.DeleteClimbs(list);
_visualizationManager.UpdateVisuals();
}
GUILayout.EndScrollView();
}
private void DrawClimbTableRow(ClimbData climbData, List<Guid> climbsToDelete)
{
GUILayout.BeginHorizontal(GUIStyle.op_Implicit("box"), Array.Empty<GUILayoutOption>());
bool flag = _visualizationManager.IsClimbVisible(climbData.Id);
if (GUILayout.Toggle(flag, "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(30f) }) != flag)
{
_visualizationManager.ToggleClimbVisibility(climbData.Id);
}
string text = climbData.GetDisplayName();
if (climbData.IsFromCloud)
{
text = "C: " + text;
}
GUILayout.Label(text, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) });
string text2 = climbData.BiomeName ?? "Unknown";
if (text2.Length > 12)
{
text2 = text2.Substring(0, 12) + "...";
}
GUILayout.Label(text2, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) });
TimeSpan timeSpan = TimeSpan.FromSeconds(climbData.DurationInSeconds);
GUILayout.Label($"{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) });
GUILayout.Label(climbData.CreationTime.ToString("dd.MM"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) });
GUILayout.Label(climbData.ShareCode, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) });
GUILayout.Label("", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) });
GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) });
if (GUILayout.Button("Copy", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) }))
{
Debug.Log((object)("Peak code copied: " + climbData.ShareCode));
GUIUtility.systemCopyBuffer = climbData.ShareCode;
}
if (GUILayout.Button("Del", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(35f) }))
{
climbsToDelete.Add(climbData.Id);
}
GUILayout.EndHorizontal();
GUILayout.EndHorizontal();
}
private void ShowOnlyCloudClimbs()
{
foreach (ClimbData allClimb in _climbDataService.GetAllClimbs())
{
if (allClimb.IsFromCloud)
{
_visualizationManager.SetClimbVisibility(allClimb.Id, visible: true);
}
else
{
_visualizationManager.SetClimbVisibility(allClimb.Id, visible: false);
}
}
}
private void ShowOnlyLocalClimbs()
{
foreach (ClimbData allClimb in _climbDataService.GetAllClimbs())
{
if (!allClimb.IsFromCloud)
{
_visualizationManager.SetClimbVisibility(allClimb.Id, visible: true);
}
else
{
_visualizationManager.SetClimbVisibility(allClimb.Id, visible: false);
}
}
}
private void DrawCloudSyncTab()
{
_cloudSyncUI.OnGUI();
}
private List<ClimbData> GetProcessedClimbs()
{
IEnumerable<ClimbData> source = _climbDataService.GetAllClimbs();
if (!string.IsNullOrWhiteSpace(_biomeFilter))
{
source = source.Where((ClimbData p) => (p.BiomeName ?? "").IndexOf(_biomeFilter, StringComparison.OrdinalIgnoreCase) >= 0);
}
if (!string.IsNullOrWhiteSpace(_dateFilter))
{
source = source.Where((ClimbData p) => p.CreationTime.ToString("dd.MM.yy").Contains(_dateFilter));
}
if (!string.IsNullOrWhiteSpace(_nameFilter))
{
source = source.Where((ClimbData p) => p.GetDisplayName().IndexOf(_nameFilter, StringComparison.OrdinalIgnoreCase) >= 0);
}
if (!string.IsNullOrWhiteSpace(_peakCodeSearch))
{
source = source.Where((ClimbData p) => (p.ShareCode ?? "").IndexOf(_peakCodeSearch, StringComparison.OrdinalIgnoreCase) >= 0);
}
if (_showOnlyLocalPaths)
{
source = source.Where((ClimbData p) => !p.IsFromCloud);
}
switch (_currentSortColumn)
{
case SortColumn.BiomeName:
source = (_sortAscending ? source.OrderBy((ClimbData p) => p.BiomeName) : source.OrderByDescending((ClimbData p) => p.BiomeName));
break;
case SortColumn.Duration:
source = (_sortAscending ? source.OrderBy((ClimbData p) => p.DurationInSeconds) : source.OrderByDescending((ClimbData p) => p.DurationInSeconds));
break;
case SortColumn.CreationTime:
source = (_sortAscending ? source.OrderBy((ClimbData p) => p.CreationTime) : source.OrderByDescending((ClimbData p) => p.CreationTime));
break;
case SortColumn.PlayerName:
source = (_sortAscending ? source.OrderBy((ClimbData p) => p.GetDisplayName()) : source.OrderByDescending((ClimbData p) => p.GetDisplayName()));
break;
}
return source.ToList();
}
private void SetSortColumn(SortColumn column)
{
if (_currentSortColumn == column)
{
_sortAscending = !_sortAscending;
return;
}
_currentSortColumn = column;
_sortAscending = true;
}
private string GetSortLabel(string label, SortColumn column)
{
if (_currentSortColumn != column)
{
return label;
}
return label + (_sortAscending ? " ▲" : " ▼");
}
private void SearchForPeakCode(string peakCode)
{
Debug.Log((object)("Searching for peak code: " + peakCode));
ClimbData climbData = _climbDataService.GetAllClimbs().FirstOrDefault((ClimbData p) => p.ShareCode != null && p.ShareCode.IndexOf(peakCode, StringComparison.OrdinalIgnoreCase) >= 0);
if (climbData != null)
{
_visualizationManager.SetClimbVisibility(climbData.Id, visible: true);
Debug.Log((object)("Peak code found: " + climbData.GetDisplayName()));
}
else
{
Debug.Log((object)("Peak code '" + peakCode + "' not found locally. Searching on server..."));
SearchForPeakCodeOnServer(peakCode);
}
}
private void SearchForPeakCodeOnServer(string peakCode)
{
if (_vpsApiService == null)
{
Debug.LogError((object)"VPS API Service not available for server search");
return;
}
_vpsApiService.SearchClimbByPeakCode(peakCode, delegate(ClimbData climbData, string error)
{
if (climbData != null)
{
climbData.IsFromCloud = true;
_climbDataService.AddClimb(climbData);
_visualizationManager.UpdateVisuals();
_visualizationManager.SetClimbVisibility(climbData.Id, visible: true);
Debug.Log((object)("Peak code '" + peakCode + "' found on server: " + climbData.GetDisplayName()));
}
else
{
Debug.Log((object)("Peak code '" + peakCode + "' not found. " + (error ?? "Climb does not exist.")));
}
});
}
private void UpdateCursorState()
{
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
if (_showMenu)
{
if ((int)Cursor.lockState != 0)
{
_originalCursorLockState = Cursor.lockState;
_originalCursorVisible = Cursor.visible;
Cursor.lockState = (CursorLockMode)0;
Cursor.visible = true;
}
}
else
{
Cursor.lockState = _originalCursorLockState;
Cursor.visible = _originalCursorVisible;
}
}
}
public class CloudSyncUI
{
private readonly ServerConfigService _configService;
private readonly VPSApiService _apiService;
private readonly ClimbUploadService _uploadService;
private readonly ClimbDownloadService _downloadService;
private string _playerNameInput = "";
public CloudSyncUI(ServerConfigService configService, VPSApiService apiService, ClimbUploadService uploadService, ClimbDownloadService downloadService)
{
_configService = configService;
_apiService = apiService;
_uploadService = uploadService;
_downloadService = downloadService;
ServerConfig config = _configService.Config;
_playerNameInput = config.PlayerName;
}
public void OnGUI()
{
ServerConfig config = _configService.Config;
GUILayout.BeginVertical(GUIStyle.op_Implicit("box"), Array.Empty<GUILayoutOption>());
GUILayout.Label("Share Climbs Online", GUI.skin.label, Array.Empty<GUILayoutOption>());
GUILayout.Space(5f);
GUI.skin.toggle.fontSize = 14;
bool flag = GUILayout.Toggle(config.EnableCloudSync, " Automatically share my climbs with others", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(30f) });
GUI.skin.toggle.fontSize = 12;
if (flag != config.EnableCloudSync)
{
_configService.SetCloudSyncEnabled(flag);
}
GUILayout.Space(10f);
if (config.EnableCloudSync)
{
DrawSimpleCloudStatus();
}
else
{
GUILayout.BeginVertical(GUIStyle.op_Implicit("box"), Array.Empty<GUILayoutOption>());
GUILayout.Label("Info: When enabled:", Array.Empty<GUILayoutOption>());
GUILayout.Label("- Your successful climbs will be automatically shared", Array.Empty<GUILayoutOption>());
GUILayout.Label("- You will receive climbs from other players", Array.Empty<GUILayoutOption>());
GUILayout.Label("- Everything happens automatically in the background", Array.Empty<GUILayoutOption>());
GUILayout.EndVertical();
}
if (config.EnableCloudSync)
{
GUILayout.Space(10f);
DrawPlayerNameSetting();
}
GUILayout.EndVertical();
}
private void DrawPlayerNameSetting()
{
GUILayout.BeginVertical(GUIStyle.op_Implicit("box"), Array.Empty<GUILayoutOption>());
GUILayout.Label("Player name for shared climbs:", Array.Empty<GUILayoutOption>());
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
_playerNameInput = GUILayout.TextField(_playerNameInput, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
if (GUILayout.Button("Change", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }) && !string.IsNullOrEmpty(_playerNameInput))
{
_configService.SetPlayerName(_playerNameInput);
}
GUILayout.EndHorizontal();
ServerConfig config = _configService.Config;
GUILayout.Label("Current: " + config.PlayerName, Array.Empty<GUILayoutOption>());
GUILayout.EndVertical();
}
private void DrawSimpleCloudStatus()
{
GUILayout.BeginVertical(GUIStyle.op_Implicit("box"), Array.Empty<GUILayoutOption>());
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
string text = (_apiService.IsServerReachable ? "● Connected" : "● Offline");
GUILayout.Label("Status: " + text, Array.Empty<GUILayoutOption>());
if (GUILayout.Button("Test Connection", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) }))
{
_apiService.CheckServerHealth();
}
GUILayout.EndHorizontal();
GUILayout.Space(5f);
GUILayout.Label("Upload Status: " + _uploadService.GetQueueStatus(), Array.Empty<GUILayoutOption>());
GUILayout.Label("Download Status: " + _downloadService.GetDownloadStats(), Array.Empty<GUILayoutOption>());
GUILayout.Space(5f);
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
if (GUILayout.Button("Upload Now", Array.Empty<GUILayoutOption>()))
{
_uploadService.ProcessQueue();
}
if (GUILayout.Button("Load New Climbs", Array.Empty<GUILayoutOption>()))
{
string currentLevelID = Plugin.Instance.ClimbDataService.CurrentLevelID;
if (!string.IsNullOrEmpty(currentLevelID))
{
_downloadService.DownloadAndMergeClimbs(currentLevelID);
}
}
GUILayout.EndHorizontal();
GUILayout.EndVertical();
}
}
}
namespace FollowMePeak.Patches
{
public class PluginPatches
{
public static string BiomeNameOfCompletedSegment { get; set; }
[HarmonyPatch(typeof(Campfire), "Light_Rpc")]
[HarmonyPrefix]
public static void CaptureBiomeNameBeforeCompletion()
{
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)Singleton<MapHandler>.Instance != (Object)null)
{
Segment currentSegment = Singleton<MapHandler>.Instance.GetCurrentSegment();
BiomeNameOfCompletedSegment = Enum.GetName(typeof(Segment), currentSegment);
Debug.Log((object)("[FollowMe-Peak] Biome captured: " + BiomeNameOfCompletedSegment));
}
}
[HarmonyPatch(typeof(Campfire), "Light_Rpc")]
[HarmonyPostfix]
public static void SavePathAfterCampfireLit()
{
if ((Object)(object)Plugin.Instance != (Object)null)
{
Debug.Log((object)"[FollowMe-Peak] Campfire.Light_Rpc() completed. Saving triggered.");
Plugin.Instance.OnCampfireLit(BiomeNameOfCompletedSegment);
}
}
}
}
namespace FollowMePeak.Services
{
public class ClimbDataService
{
private readonly ManualLogSource _logger;
private List<ClimbData> _allLoadedClimbs = new List<ClimbData>();
private string _currentLevelID = "";
public string CurrentLevelID
{
get
{
return _currentLevelID;
}
set
{
_currentLevelID = value;
}
}
public ClimbDataService(ManualLogSource logger)
{
_logger = logger;
}
public List<ClimbData> GetAllClimbs()
{
return _allLoadedClimbs;
}
public void AddClimb(ClimbData climbData)
{
_allLoadedClimbs.Add(climbData);
}
public void DeleteClimbs(List<Guid> climbIds)
{
_allLoadedClimbs.RemoveAll((ClimbData c) => climbIds.Contains(c.Id));
SaveClimbsToFile(addNewClimb: false);
}
public void SaveClimbsToFile(bool addNewClimb = true)
{
if (string.IsNullOrEmpty(_currentLevelID) || _currentLevelID.EndsWith("_unknown"))
{
return;
}
try
{
string text = Path.Combine(Paths.PluginPath, "FollowMePeak_Data");
Directory.CreateDirectory(text);
string text2 = Path.Combine(text, _currentLevelID + ".json");
string contents = JsonConvert.SerializeObject((object)_allLoadedClimbs, (Formatting)1);
File.WriteAllText(text2, contents);
_logger.LogInfo((object)$"Successfully saved {_allLoadedClimbs.Count} climbs to '{text2}'.");
}
catch (Exception arg)
{
_logger.LogError((object)$"Error saving climbs: {arg}");
}
}
public void LoadClimbsFromFile()
{
_allLoadedClimbs.Clear();
if (string.IsNullOrEmpty(_currentLevelID) || _currentLevelID.EndsWith("_unknown"))
{
return;
}
string path = Path.Combine(Paths.PluginPath, "FollowMePeak_Data", _currentLevelID + ".json");
if (!File.Exists(path))
{
_logger.LogInfo((object)("No climb file found for '" + _currentLevelID + "'."));
return;
}
try
{
string text = File.ReadAllText(path);
_allLoadedClimbs = JsonConvert.DeserializeObject<List<ClimbData>>(text) ?? new List<ClimbData>();
_logger.LogInfo((object)$"{_allLoadedClimbs.Count} climbs loaded for level '{_currentLevelID}'.");
}
catch (Exception ex)
{
_logger.LogError((object)("Error loading climbs (possibly old format?): " + ex.Message));
}
}
public void ClearClimbs()
{
_allLoadedClimbs.Clear();
}
}
public class ClimbDownloadService
{
private readonly ManualLogSource _logger;
private readonly VPSApiService _apiService;
private readonly ServerConfigService _configService;
private readonly ClimbDataService _climbDataService;
private DateTime _lastDownload = DateTime.MinValue;
private readonly Dictionary<string, DateTime> _levelDownloadTimes = new Dictionary<string, DateTime>();
public bool IsDownloading { get; private set; }
public DateTime LastDownload => _lastDownload;
public ClimbDownloadService(ManualLogSource logger, VPSApiService apiService, ServerConfigService configService, ClimbDataService climbDataService)
{
_logger = logger;
_apiService = apiService;
_configService = configService;
_climbDataService = climbDataService;
}
public void DownloadAndMergeClimbs(string levelId, Action<int, string> callback = null)
{
if (!_configService.Config.EnableCloudSync || !_configService.Config.AutoDownload)
{
_logger.LogInfo((object)"Cloud sync or auto-download disabled, skipping download");
callback?.Invoke(0, "Cloud sync disabled");
return;
}
if (IsDownloading)
{
_logger.LogInfo((object)"Download already in progress");
callback?.Invoke(0, "Download in progress");
return;
}
if (_levelDownloadTimes.ContainsKey(levelId) && DateTime.Now - _levelDownloadTimes[levelId] < TimeSpan.FromMinutes(5.0))
{
_logger.LogInfo((object)("Downloaded " + levelId + " recently, skipping"));
callback?.Invoke(0, "Downloaded recently");
return;
}
IsDownloading = true;
_logger.LogInfo((object)("Starting download for level: " + levelId));
_apiService.DownloadClimbs(levelId, delegate(List<ClimbData> downloadedClimbs, string error)
{
IsDownloading = false;
_lastDownload = DateTime.Now;
_levelDownloadTimes[levelId] = DateTime.Now;
if (error != null)
{
_logger.LogError((object)("Download failed for level " + levelId + ": " + error));
callback?.Invoke(0, error);
return;
}
try
{
int num = MergeDownloadedClimbs(downloadedClimbs, levelId);
_logger.LogInfo((object)$"Downloaded and merged {num} new climbs for level {levelId}");
callback?.Invoke(num, null);
}
catch (Exception ex)
{
_logger.LogError((object)("Failed to merge downloaded climbs: " + ex.Message));
callback?.Invoke(0, ex.Message);
}
});
}
private int MergeDownloadedClimbs(List<ClimbData> downloadedClimbs, string levelId)
{
if (downloadedClimbs == null || downloadedClimbs.Count == 0)
{
return 0;
}
List<ClimbData> allClimbs = _climbDataService.GetAllClimbs();
int num = 0;
foreach (ClimbData downloadedClimb in downloadedClimbs)
{
if (!allClimbs.Any((ClimbData x) => x.Id == downloadedClimb.Id))
{
if (IsSimilarClimbExists(downloadedClimb, allClimbs))
{
_logger.LogDebug((object)$"Skipping similar climb: {downloadedClimb.Id}");
continue;
}
_climbDataService.AddClimb(downloadedClimb);
num++;
_logger.LogDebug((object)$"Merged downloaded climb: {downloadedClimb.Id} from {downloadedClimb.BiomeName}");
}
}
if (num > 0)
{
_climbDataService.SaveClimbsToFile(addNewClimb: false);
}
return num;
}
private bool IsSimilarClimbExists(ClimbData newClimb, List<ClimbData> existingClimbs)
{
//IL_0074: Unknown result type (might be due to invalid IL or missing references)
//IL_0088: Unknown result type (might be due to invalid IL or missing references)
//IL_008d: Unknown result type (might be due to invalid IL or missing references)
//IL_009d: Unknown result type (might be due to invalid IL or missing references)
//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
foreach (ClimbData existingClimb in existingClimbs)
{
if (!(existingClimb.BiomeName != newClimb.BiomeName) && !(Math.Abs(existingClimb.DurationInSeconds - newClimb.DurationInSeconds) > 5f) && newClimb.Points.Count >= 2 && existingClimb.Points.Count >= 2)
{
Vector3 val = newClimb.Points.First().ToVector3();
Vector3 val2 = newClimb.Points.Last().ToVector3();
Vector3 val3 = existingClimb.Points.First().ToVector3();
Vector3 val4 = existingClimb.Points.Last().ToVector3();
float num = Vector3.Distance(val, val3);
float num2 = Vector3.Distance(val2, val4);
if (num < 2f && num2 < 2f)
{
return true;
}
}
}
return false;
}
public void DownloadRecentClimbs(Action<int, string> callback = null)
{
if (!_configService.Config.EnableCloudSync)
{
callback?.Invoke(0, "Cloud sync disabled");
return;
}
if (IsDownloading)
{
callback?.Invoke(0, "Download in progress");
return;
}
IsDownloading = true;
_logger.LogInfo((object)"Downloading recent climbs from all levels");
_apiService.DownloadClimbs("recent", delegate(List<ClimbData> recentClimbs, string error)
{
IsDownloading = false;
_lastDownload = DateTime.Now;
if (error != null)
{
_logger.LogError((object)("Failed to download recent climbs: " + error));
callback?.Invoke(0, error);
return;
}
try
{
int num = MergeDownloadedClimbs(recentClimbs, "all_levels");
_logger.LogInfo((object)$"Downloaded and merged {num} recent climbs");
callback?.Invoke(num, null);
}
catch (Exception ex)
{
_logger.LogError((object)("Failed to merge recent climbs: " + ex.Message));
callback?.Invoke(0, ex.Message);
}
});
}
public void CheckForUpdates(string levelId)
{
if (!_configService.Config.EnableCloudSync || !_configService.Config.AutoDownload || (_levelDownloadTimes.ContainsKey(levelId) && DateTime.Now - _levelDownloadTimes[levelId] < TimeSpan.FromMinutes(10.0)))
{
return;
}
DownloadAndMergeClimbs(levelId, delegate(int count, string error)
{
if (error == null && count > 0)
{
_logger.LogInfo((object)$"Auto-update found {count} new climbs for {levelId}");
}
});
}
public string GetDownloadStats()
{
int count = _levelDownloadTimes.Count;
string arg = ((_lastDownload == DateTime.MinValue) ? "Never" : $"{(DateTime.Now - _lastDownload).TotalMinutes:F0}m ago");
return $"Downloads: {count} levels, last: {arg}";
}
public void ClearDownloadHistory()
{
_levelDownloadTimes.Clear();
_lastDownload = DateTime.MinValue;
_logger.LogInfo((object)"Download history cleared");
}
}
public class ClimbUploadService
{
[CompilerGenerated]
private sealed class <WaitAndProcessNext>d__16 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public float delay;
public ClimbUploadService <>4__this;
public List<UploadQueueItem> items;
public int nextIndex;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <WaitAndProcessNext>d__16(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
//IL_002f: Expected O, but got Unknown
int num = <>1__state;
ClimbUploadService climbUploadService = <>4__this;
switch (num)
{
default:
return false;
case 0:
<>1__state = -1;
<>2__current = (object)new WaitForSeconds(delay);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
climbUploadService.ProcessNextItem(items, nextIndex);
return false;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
private readonly ManualLogSource _logger;
private readonly VPSApiService _apiService;
private readonly ServerConfigService _configService;
private readonly string _queueFilePath;
private List<UploadQueueItem> _uploadQueue = new List<UploadQueueItem>();
private bool _isProcessingQueue;
public int QueuedUploads => _uploadQueue.Count((UploadQueueItem x) => x.Status == UploadStatus.Pending || x.Status == UploadStatus.Failed);
public int CompletedUploads => _uploadQueue.Count((UploadQueueItem x) => x.Status == UploadStatus.Completed);
public int FailedUploads => _uploadQueue.Count((UploadQueueItem x) => x.Status == UploadStatus.Failed && !x.ShouldRetry());
public ClimbUploadService(ManualLogSource logger, VPSApiService apiService, ServerConfigService configService)
{
_logger = logger;
_apiService = apiService;
_configService = configService;
_queueFilePath = Path.Combine(Paths.PluginPath, "FollowMePeak_Data", "upload_queue.json");
LoadQueue();
}
public void QueueForUpload(ClimbData climbData, string levelId)
{
if (!_configService.Config.EnableCloudSync || !_configService.Config.AutoUpload)
{
_logger.LogInfo((object)"Cloud sync or auto-upload disabled, skipping upload");
return;
}
if (climbData == null)
{
_logger.LogError((object)"Cannot queue null climb data for upload");
return;
}
if (!InputValidator.IsValidLevelId(levelId))
{
_logger.LogError((object)("Cannot queue climb - invalid level ID: " + levelId));
return;
}
if (!InputValidator.IsValidPointCount(climbData.Points?.Count ?? 0))
{
_logger.LogError((object)$"Cannot queue climb - invalid point count: {climbData.Points?.Count ?? 0}");
return;
}
if (_uploadQueue.Any((UploadQueueItem x) => x.ClimbData.Id == climbData.Id))
{
_logger.LogInfo((object)$"Climb {climbData.Id} already in upload queue");
return;
}
UploadQueueItem uploadQueueItem = new UploadQueueItem
{
ClimbData = climbData
};
uploadQueueItem.ClimbData.BiomeName = climbData.BiomeName + "|" + levelId;
_uploadQueue.Add(uploadQueueItem);
SaveQueue();
_logger.LogInfo((object)$"Added climb {climbData.Id} to upload queue. Queue size: {QueuedUploads}");
if (!_isProcessingQueue)
{
ProcessQueue();
}
}
public void ProcessQueue()
{
if (_isProcessingQueue)
{
_logger.LogInfo((object)"Upload queue already being processed");
return;
}
_isProcessingQueue = true;
_logger.LogInfo((object)$"Starting upload queue processing. {QueuedUploads} items to process");
CleanupExpiredItems();
List<UploadQueueItem> list = (from x in _uploadQueue
where x.Status == UploadStatus.Pending || (x.Status == UploadStatus.Failed && x.ShouldRetry())
orderby x.CreatedAt
select x).ToList();
if (list.Count == 0)
{
_logger.LogInfo((object)"No items to process in upload queue");
_isProcessingQueue = false;
}
else
{
ProcessNextItem(list, 0);
}
}
private void ProcessNextItem(List<UploadQueueItem> items, int index)
{
if (index >= items.Count)
{
_logger.LogInfo((object)"Upload queue processing completed");
_isProcessingQueue = false;
SaveQueue();
return;
}
UploadQueueItem item = items[index];
if (item == null || item.ClimbData == null)
{
_logger.LogWarning((object)$"Skipping invalid upload queue item at index {index}");
ProcessNextItem(items, index + 1);
return;
}
if (!_configService.Config.CanUpload())
{
_logger.LogWarning((object)"Upload rate limit exceeded, pausing queue processing");
_isProcessingQueue = false;
return;
}
string text = item.ClimbData.BiomeName;
string levelId = "unknown";
if (text.Contains("|"))
{
string[] array = text.Split('|');
text = array[0];
levelId = array[1];
}
item.Status = UploadStatus.Uploading;
item.LastAttempt = DateTime.Now;
SaveQueue();
_logger.LogInfo((object)$"Uploading climb {item.ClimbData.Id} (attempt {item.RetryCount + 1})");
ClimbData climbData = new ClimbData
{
Id = item.ClimbData.Id,
CreationTime = item.ClimbData.CreationTime,
BiomeName = text,
DurationInSeconds = item.ClimbData.DurationInSeconds,
Points = item.ClimbData.Points
};
_apiService.UploadClimb(climbData, levelId, delegate(bool success, string error)
{
if (success)
{
item.Status = UploadStatus.Completed;
_logger.LogInfo((object)$"Successfully uploaded climb {item.ClimbData.Id}");
}
else
{
item.RetryCount++;
item.LastError = error;
if (item.ShouldRetry(_configService.Config.RetryAttempts))
{
item.Status = UploadStatus.Failed;
_logger.LogWarning((object)$"Upload failed for climb {item.ClimbData.Id}: {error}. Will retry ({item.RetryCount}/{_configService.Config.RetryAttempts})");
}
else
{
item.Status = UploadStatus.Failed;
_logger.LogError((object)$"Upload permanently failed for climb {item.ClimbData.Id}: {error}");
}
}
SaveQueue();
((MonoBehaviour)Plugin.Instance).StartCoroutine(WaitAndProcessNext(items, index + 1, 2f));
});
}
[IteratorStateMachine(typeof(<WaitAndProcessNext>d__16))]
private IEnumerator WaitAndProcessNext(List<UploadQueueItem> items, int nextIndex, float delay)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <WaitAndProcessNext>d__16(0)
{
<>4__this = this,
items = items,
nextIndex = nextIndex,
delay = delay
};
}
public void RetryFailedUploads()
{
List<UploadQueueItem> list = _uploadQueue.Where((UploadQueueItem x) => x.Status == UploadStatus.Failed && x.ShouldRetry()).ToList();
if (list.Count == 0)
{
_logger.LogInfo((object)"No failed uploads to retry");
return;
}
_logger.LogInfo((object)$"Retrying {list.Count} failed uploads");
foreach (UploadQueueItem item in list)
{
item.Status = UploadStatus.Pending;
}
SaveQueue();
ProcessQueue();
}
public void ClearCompletedUploads()
{
int count = _uploadQueue.Count;
_uploadQueue.RemoveAll((UploadQueueItem x) => x.Status == UploadStatus.Completed);
int num = count - _uploadQueue.Count;
if (num > 0)
{
_logger.LogInfo((object)$"Cleared {num} completed uploads from queue");
SaveQueue();
}
}
private void CleanupExpiredItems()
{
TimeSpan maxAge = TimeSpan.FromDays(7.0);
List<UploadQueueItem> list = _uploadQueue.Where((UploadQueueItem x) => x.IsExpired(maxAge)).ToList();
foreach (UploadQueueItem item in list)
{
item.Status = UploadStatus.Expired;
}
_uploadQueue.RemoveAll((UploadQueueItem x) => x.Status == UploadStatus.Expired);
if (list.Count > 0)
{
_logger.LogInfo((object)$"Removed {list.Count} expired items from upload queue");
}
}
private void SaveQueue()
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(_queueFilePath));
string contents = JsonConvert.SerializeObject((object)_uploadQueue, (Formatting)1);
File.WriteAllText(_queueFilePath, contents);
}
catch (Exception ex)
{
_logger.LogError((object)("Failed to save upload queue: " + ex.Message));
}
}
private void LoadQueue()
{
try
{
if (!File.Exists(_queueFilePath))
{
return;
}
string text = File.ReadAllText(_queueFilePath);
_uploadQueue = JsonConvert.DeserializeObject<List<UploadQueueItem>>(text) ?? new List<UploadQueueItem>();
_uploadQueue.RemoveAll((UploadQueueItem item) => item.ClimbData == null);
foreach (UploadQueueItem item in _uploadQueue.Where((UploadQueueItem x) => x.Status == UploadStatus.Uploading))
{
item.Status = UploadStatus.Pending;
}
_logger.LogInfo((object)$"Loaded upload queue with {_uploadQueue.Count} items");
}
catch (Exception ex)
{
_logger.LogError((object)("Failed to load upload queue: " + ex.Message));
_uploadQueue = new List<UploadQueueItem>();
}
}
public string GetQueueStatus()
{
return $"Queue: {QueuedUploads} pending, {CompletedUploads} completed, {FailedUploads} failed";
}
}
public class ServerConfigService
{
private readonly ManualLogSource _logger;
private readonly string _configPath;
private ServerConfig _config;
public ServerConfig Config => _config ?? LoadConfig();
public ServerConfigService(ManualLogSource logger)
{
_logger = logger;
_configPath = Path.Combine(Paths.PluginPath, "FollowMePeak_Data", "server_config.json");
}
public ServerConfig LoadConfig()
{
try
{
if (File.Exists(_configPath))
{
string text = File.ReadAllText(_configPath);
_config = JsonConvert.DeserializeObject<ServerConfig>(text);
_logger.LogInfo((object)"Server configuration loaded successfully");
}
else
{
_config = CreateDefaultConfig();
SaveConfig();
_logger.LogInfo((object)"Created default server configuration");
}
}
catch (Exception ex)
{
_logger.LogError((object)("Failed to load server config: " + ex.Message));
_config = CreateDefaultConfig();
}
return _config;
}
public void SaveConfig()
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(_configPath));
string contents = JsonConvert.SerializeObject((object)_config, (Formatting)1);
File.WriteAllText(_configPath, contents);
_logger.LogInfo((object)"Server configuration saved");
}
catch (Exception ex)
{
_logger.LogError((object)("Failed to save server config: " + ex.Message));
}
}
private ServerConfig CreateDefaultConfig()
{
return new ServerConfig
{
EnableCloudSync = true,
PlayerName = GenerateRandomPlayerName(),
LastUploadReset = DateTime.Now,
UploadsThisHour = 0
};
}
private string GenerateRandomPlayerName()
{
string[] array = new string[70]
{
"Quick", "Swift", "Fast", "Rapid", "Lightning", "Turbo", "Blazing", "Speedy", "Nimble", "Agile",
"Rocky", "Steep", "Alpine", "Icy", "Snowy", "Windy", "High", "Rugged", "Vertical", "Frozen",
"Summit", "Peak", "Ridge", "Cliff", "Stone", "Granite", "Crystal", "Misty", "Foggy", "Stormy",
"Brave", "Bold", "Fearless", "Daring", "Wild", "Crazy", "Mad", "Epic", "Legendary", "Mighty",
"Strong", "Tough", "Hardcore", "Extreme", "Silent", "Clever", "Smart", "Wise", "Sharp", "Keen",
"Dizzy", "Wobbly", "Clumsy", "Sleepy", "Hungry", "Thirsty", "Lost", "Confused", "Lucky", "Unlucky",
"Weird", "Strange", "Funky", "Silly", "Goofy", "Bouncy", "Fuzzy", "Sparkly", "Shiny", "Glowing"
};
string[] array2 = new string[65]
{
"Climber", "Mountaineer", "Alpinist", "Scrambler", "Boulderer", "Summiteer", "Peakbagger", "Ridgewalker", "FollowMe", "Trailblazer",
"Explorer", "Navigator", "Scout", "Wanderer", "Seeker", "Hiker", "Trekker", "Adventurer", "Voyager", "Pioneer",
"Ranger", "Guide", "Sherpa", "Basecamp", "Expedition", "Goat", "Yak", "Eagle", "Hawk", "Raven",
"Bear", "Wolf", "Fox", "Lynx", "Marmot", "Ibex", "Chamois", "Falcon", "Condor", "Vulture",
"SnowLeopard", "Bighorn", "Pika", "Ptarmigan", "Wolverine", "Potato", "Pretzel", "Pickle", "Pancake", "Waffle",
"Burrito", "Taco", "Pizza", "Bagel", "Donut", "Penguin", "Llama", "Walrus", "Hedgehog", "Squirrel",
"Narwhal", "Unicorn", "Dragon", "Phoenix", "Yeti"
};
Random random = new Random();
string arg = array[random.Next(array.Length)];
string arg2 = array2[random.Next(array2.Length)];
int num = random.Next(100, 999);
return $"{arg}{arg2}_{num}";
}
public void SetCloudSyncEnabled(bool enabled)
{
_config.EnableCloudSync = enabled;
SaveConfig();
_logger.LogInfo((object)("Cloud sync " + (enabled ? "enabled" : "disabled")));
}
public void SetPlayerName(string playerName)
{
if (string.IsNullOrWhiteSpace(playerName))
{
playerName = "Anonymous";
}
else
{
playerName = Regex.Replace(playerName, "[^a-zA-Z0-9_-]", "");
if (playerName.Length > 50)
{
playerName = playerName.Substring(0, 50);
}
if (string.IsNullOrWhiteSpace(playerName))
{
playerName = "Anonymous";
}
}
_config.PlayerName = playerName;
SaveConfig();
_logger.LogInfo((object)("Player name updated to: " + _config.PlayerName));
}
public void ResetUploadRateLimit()
{
_config.UploadsThisHour = 0;
_config.LastUploadReset = DateTime.Now;
SaveConfig();
_logger.LogInfo((object)"Upload rate limit reset");
}
public bool ValidateConfig()
{
return true;
}
}
public class VPSApiService
{
[CompilerGenerated]
private sealed class <CheckServerHealthCoroutine>d__13 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public VPSApiService <>4__this;
public Action<bool> callback;
private UnityWebRequest <request>5__2;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <CheckServerHealthCoroutine>d__13(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
int num = <>1__state;
if (num == -3 || num == 1)
{
try
{
}
finally
{
<>m__Finally1();
}
}
<request>5__2 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
//IL_00bf: Invalid comparison between Unknown and I4
try
{
int num = <>1__state;
VPSApiService vPSApiService = <>4__this;
switch (num)
{
default:
return false;
case 0:
{
<>1__state = -1;
string text = vPSApiService._config.BaseUrl + "/api/health";
<request>5__2 = UnityWebRequest.Get(text);
<>1__state = -3;
<request>5__2.timeout = vPSApiService._config.TimeoutSeconds;
<request>5__2.SetRequestHeader("X-API-Key", vPSApiService._config.ApiKey);
<>2__current = <request>5__2.SendWebRequest();
<>1__state = 1;
return true;
}
case 1:
<>1__state = -3;
vPSApiService.LastHealthCheck = DateTime.Now;
if ((int)<request>5__2.result == 1)
{
try
{
HealthResponse healthResponse = JsonConvert.DeserializeObject<HealthResponse>(<request>5__2.downloadHandler.text);
vPSApiService.IsServerReachable = healthResponse.Status == "healthy";
if (vPSApiService.IsServerReachable)
{
vPSApiService._logger.LogInfo((object)$"Server health check successful. {healthResponse.Stats.TotalClimbs} climbs in database.");
}
else
{
vPSApiService._logger.LogWarning((object)("Server unhealthy: " + healthResponse.Status));
vPSApiService.IsServerReachable = false;
}
}
catch (Exception ex)
{
vPSApiService._logger.LogError((object)("Failed to parse health response: " + ex.Message));
vPSApiService.IsServerReachable = false;
}
}
else
{
vPSApiService._logger.LogError((object)("Health check failed: " + <request>5__2.error));
vPSApiService.IsServerReachable = false;
}
callback?.Invoke(vPSApiService.IsServerReachable);
<>m__Finally1();
<request>5__2 = null;
return false;
}
}
catch
{
//try-fault
((IDisposable)this).Dispose();
throw;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
private void <>m__Finally1()
{
<>1__state = -1;
if (<request>5__2 != null)
{
((IDisposable)<request>5__2).Dispose();
}
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <DownloadClimbsCoroutine>d__17 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public string levelId;
public Action<List<ClimbData>, string> callback;
public VPSApiService <>4__this;
private UnityWebRequest <request>5__2;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <DownloadClimbsCoroutine>d__17(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
int num = <>1__state;
if (num == -3 || num == 1)
{
try
{
}
finally
{
<>m__Finally1();
}
}
<request>5__2 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
//IL_00ee: Invalid comparison between Unknown and I4
try
{
int num = <>1__state;
VPSApiService vPSApiService = <>4__this;
switch (num)
{
default:
return false;
case 0:
{
<>1__state = -1;
if (!InputValidator.IsValidLevelId(levelId))
{
callback?.Invoke(new List<ClimbData>(), "Invalid level ID format");
return false;
}
string text3 = vPSApiService._config.BaseUrl + "/api/climbs/" + levelId + "?limit=200";
<request>5__2 = UnityWebRequest.Get(text3);
<>1__state = -3;
<request>5__2.timeout = vPSApiService._config.TimeoutSeconds;
<request>5__2.SetRequestHeader("X-API-Key", vPSApiService._config.ApiKey);
<>2__current = <request>5__2.SendWebRequest();
<>1__state = 1;
return true;
}
case 1:
<>1__state = -3;
if ((int)<request>5__2.result == 1)
{
try
{
ClimbListResponse climbListResponse = JsonConvert.DeserializeObject<ClimbListResponse>(<request>5__2.downloadHandler.text);
List<ClimbData> list = new List<ClimbData>();
if (climbListResponse.Data != null)
{
foreach (ServerClimbData datum in climbListResponse.Data)
{
list.Add(datum.ToClimbData());
}
}
vPSApiService._logger.LogInfo((object)$"Downloaded {list.Count} climbs for level {levelId}");
callback?.Invoke(list, null);
}
catch (Exception ex)
{
string text = "Failed to parse download response: " + ex.Message;
vPSApiService._logger.LogError((object)text);
callback?.Invoke(new List<ClimbData>(), text);
}
}
else if (<request>5__2.responseCode == 404)
{
vPSApiService._logger.LogInfo((object)("No climbs found for level " + levelId));
callback?.Invoke(new List<ClimbData>(), null);
}
else
{
string text2 = $"Download request failed: {<request>5__2.error} (HTTP {<request>5__2.responseCode})";
vPSApiService._logger.LogError((object)text2);
callback?.Invoke(new List<ClimbData>(), text2);
}
<>m__Finally1();
<request>5__2 = null;
return false;
}
}
catch
{
//try-fault
((IDisposable)this).Dispose();
throw;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
private void <>m__Finally1()
{
<>1__state = -1;
if (<request>5__2 != null)
{
((IDisposable)<request>5__2).Dispose();
}
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <GetServerStatsCoroutine>d__21 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public VPSApiService <>4__this;
public Action<string, string> callback;
private UnityWebRequest <request>5__2;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <GetServerStatsCoroutine>d__21(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
int num = <>1__state;
if (num == -3 || num == 1)
{
try
{
}
finally
{
<>m__Finally1();
}
}
<request>5__2 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
//IL_00b4: Invalid comparison between Unknown and I4
try
{
int num = <>1__state;
VPSApiService vPSApiService = <>4__this;
switch (num)
{
default:
return false;
case 0:
{
<>1__state = -1;
string text2 = vPSApiService._config.BaseUrl + "/api/stats";
<request>5__2 = UnityWebRequest.Get(text2);
<>1__state = -3;
<request>5__2.timeout = vPSApiService._config.TimeoutSeconds;
<request>5__2.SetRequestHeader("X-API-Key", vPSApiService._config.ApiKey);
<>2__current = <request>5__2.SendWebRequest();
<>1__state = 1;
return true;
}
case 1:
<>1__state = -3;
if ((int)<request>5__2.result == 1)
{
callback?.Invoke(<request>5__2.downloadHandler.text, null);
}
else
{
string text = "Stats request failed: " + <request>5__2.error;
vPSApiService._logger.LogError((object)text);
callback?.Invoke(null, text);
}
<>m__Finally1();
<request>5__2 = null;
return false;
}
}
catch
{
//try-fault
((IDisposable)this).Dispose();
throw;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
private void <>m__Finally1()
{
<>1__state = -1;
if (<request>5__2 != null)
{
((IDisposable)<request>5__2).Dispose();
}
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <SearchClimbByPeakCodeCoroutine>d__19 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public VPSApiService <>4__this;
public string peakCode;
public Action<ClimbData, string> callback;
private UnityWebRequest <request>5__2;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <SearchClimbByPeakCodeCoroutine>d__19(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
int num = <>1__state;
if (num == -3 || num == 1)
{
try
{
}
finally
{
<>m__Finally1();
}
}
<request>5__2 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
//IL_00ba: Invalid comparison between Unknown and I4
try
{
int num = <>1__state;
VPSApiService vPSApiService = <>4__this;
switch (num)
{
default:
return false;
case 0:
{
<>1__state = -1;
string text3 = vPSApiService._config.BaseUrl + "/api/climbs/search/" + peakCode;
<request>5__2 = UnityWebRequest.Get(text3);
<>1__state = -3;
<request>5__2.timeout = vPSApiService._config.TimeoutSeconds;
<request>5__2.SetRequestHeader("X-API-Key", vPSApiService._config.ApiKey);
<>2__current = <request>5__2.SendWebRequest();
<>1__state = 1;
return true;
}
case 1:
<>1__state = -3;
if ((int)<request>5__2.result == 1)
{
try
{
ClimbSearchResponse climbSearchResponse = JsonConvert.DeserializeObject<ClimbSearchResponse>(<request>5__2.downloadHandler.text);
if (climbSearchResponse.Success && climbSearchResponse.Data != null)
{
ClimbData climbData = climbSearchResponse.Data.ToClimbData();
vPSApiService._logger.LogInfo((object)("Found climb with peak code " + peakCode + ": " + climbData.GetDisplayName()));
callback?.Invoke(climbData, null);
}
else
{
vPSApiService._logger.LogInfo((object)("No climb found with peak code " + peakCode));
callback?.Invoke(null, "Climb not found");
}
}
catch (Exception ex)
{
string text = "Failed to parse search response: " + ex.Message;
vPSApiService._logger.LogError((object)text);
callback?.Invoke(null, text);
}
}
else if (<request>5__2.responseCode == 404)
{
vPSApiService._logger.LogInfo((object)("No climb found with peak code " + peakCode));
callback?.Invoke(null, "Climb not found");
}
else
{
string text2 = $"Search request failed: {<request>5__2.error} (HTTP {<request>5__2.responseCode})";
vPSApiService._logger.LogError((object)text2);
callback?.Invoke(null, text2);
}
<>m__Finally1();
<request>5__2 = null;
return false;
}
}
catch
{
//try-fault
((IDisposable)this).Dispose();
throw;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
private void <>m__Finally1()
{
<>1__state = -1;
if (<request>5__2 != null)
{
((IDisposable)<request>5__2).Dispose();
}
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <UploadClimbCoroutine>d__15 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public string levelId;
public Action<bool, string> callback;
public ClimbData climbData;
public VPSApiService <>4__this;
private UnityWebRequest <request>5__2;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <UploadClimbCoroutine>d__15(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
int num = <>1__state;
if (num == -3 || num == 1)
{
try
{
}
finally
{
<>m__Finally1();
}
}
<request>5__2 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0239: Unknown result type (might be due to invalid IL or missing references)
//IL_023f: Invalid comparison between Unknown and I4
//IL_0192: Unknown result type (might be due to invalid IL or missing references)
//IL_019c: Expected O, but got Unknown
//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
//IL_01b6: Expected O, but got Unknown
//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
//IL_01c6: Expected O, but got Unknown
try
{
int num = <>1__state;
VPSApiService vPSApiService = <>4__this;
switch (num)
{
default:
return false;
case 0:
{
<>1__state = -1;
if (!InputValidator.IsValidLevelId(levelId))
{
callback?.Invoke(arg1: false, "Invalid level ID format");
return false;
}
if (!InputValidator.IsValidPointCount(climbData.Points?.Count ?? 0))
{
callback?.Invoke(arg1: false, "Invalid climb data - point count out of range");
return false;
}
string text2 = vPSApiService._config.BaseUrl + "/api/climbs";
List<ApiVector3> points = climbData.Points.Select((SerializableVector3 p) => p.ToApiVector3()).ToList();
points = vPSApiService.ReducePointsIfNeeded(points);
string text3 = JsonConvert.SerializeObject((object)new
{
levelId = levelId,
playerName = InputValidator.SanitizePlayerName(vPSApiService._config.PlayerName),
biomeName = InputValidator.SanitizeBiomeName(climbData.BiomeName),
duration = InputValidator.ClampDuration(climbData.DurationInSeconds),
points = points,
isSuccessful = true,
tags = new string[0]
});
if (text3.Length > vPSApiService.GetMaxPayloadSize())
{
string text4 = $"Payload too large ({text3.Length} bytes). Consider reducing climb complexity.";
vPSApiService._logger.LogError((object)text4);
callback?.Invoke(arg1: false, text4);
return false;
}
byte[] bytes = Encoding.UTF8.GetBytes(text3);
<request>5__2 = new UnityWebRequest(text2, "POST");
<>1__state = -3;
<request>5__2.uploadHandler = (UploadHandler)new UploadHandlerRaw(bytes);
<request>5__2.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
<request>5__2.SetRequestHeader("Content-Type", "application/json");
<request>5__2.SetRequestHeader("X-API-Key", vPSApiService._config.ApiKey);
<request>5__2.timeout = vPSApiService._config.TimeoutSeconds;
<>2__current = <request>5__2.SendWebRequest();
<>1__state = 1;
return true;
}
case 1:
<>1__state = -3;
if ((int)<request>5__2.result == 1)
{
try
{
ApiResponse<ClimbUploadResponse> apiResponse = JsonConvert.DeserializeObject<ApiResponse<ClimbUploadResponse>>(<request>5__2.downloadHandler.text);
if (apiResponse.Success)
{
vPSApiService._config.IncrementUploadCount();
vPSApiService._logger.LogInfo((object)("Climb uploaded successfully: " + apiResponse.Data.ClimbId));
callback?.Invoke(arg1: true, apiResponse.Data.ClimbId);
}
else
{
vPSApiService._logger.LogError((object)("Upload failed: " + apiResponse.Error));
callback?.Invoke(arg1: false, apiResponse.Error);
}
}
catch (Exception ex)
{
vPSApiService._logger.LogError((object)("Failed to parse upload response: " + ex.Message));
callback?.Invoke(arg1: false, "Parse error");
}
}
else
{
string text = $"Upload request failed: {<request>5__2.error} (HTTP {<request>5__2.responseCode})";
vPSApiService._logger.LogError((object)text);
callback?.Invoke(arg1: false, text);
}
<>m__Finally1();
<request>5__2 = null;
return false;
}
}
catch
{
//try-fault
((IDisposable)this).Dispose();
throw;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
private void <>m__Finally1()
{
<>1__state = -1;
if (<request>5__2 != null)
{
((IDisposable)<request>5__2).Dispose();
}
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
private readonly ManualLogSource _logger;
private readonly ServerConfig _config;
private readonly MonoBehaviour _coroutineRunner;
public bool IsServerReachable { get; private set; }
public DateTime LastHealthCheck { get; private set; } = DateTime.MinValue;
public VPSApiService(ManualLogSource logger, ServerConfig config, MonoBehaviour coroutineRunner)
{
_logger = logger;
_config = config;
_coroutineRunner = coroutineRunner;
}
public void CheckServerHealth(Action<bool> callback = null)
{
_coroutineRunner.StartCoroutine(CheckServerHealthCoroutine(callback));
}
[IteratorStateMachine(typeof(<CheckServerHealthCoroutine>d__13))]
private IEnumerator CheckServerHealthCoroutine(Action<bool> callback)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <CheckServerHealthCoroutine>d__13(0)
{
<>4__this = this,
callback = callback
};
}
public void UploadClimb(ClimbData climbData, string levelId, Action<bool, string> callback)
{
if (!_config.EnableCloudSync)
{
callback?.Invoke(arg1: false, "Cloud sync disabled");
}
else if (!_config.CanUpload())
{
callback?.Invoke(arg1: false, "Upload rate limit exceeded");
}
else
{
_coroutineRunner.StartCoroutine(UploadClimbCoroutine(climbData, levelId, callback));
}
}
[IteratorStateMachine(typeof(<UploadClimbCoroutine>d__15))]
private IEnumerator UploadClimbCoroutine(ClimbData climbData, string levelId, Action<bool, string> callback)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <UploadClimbCoroutine>d__15(0)
{
<>4__this = this,
climbData = climbData,
levelId = levelId,
callback = callback
};
}
public void DownloadClimbs(string levelId, Action<List<ClimbData>, string> callback)
{
if (!_config.EnableCloudSync)
{
callback?.Invoke(new List<ClimbData>(), "Cloud sync disabled");
}
else
{
_coroutineRunner.StartCoroutine(DownloadClimbsCoroutine(levelId, callback));
}
}
[IteratorStateMachine(typeof(<DownloadClimbsCoroutine>d__17))]
private IEnumerator DownloadClimbsCoroutine(string levelId, Action<List<ClimbData>, string> callback)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <DownloadClimbsCoroutine>d__17(0)
{
<>4__this = this,
levelId = levelId,
callback = callback
};
}
public void SearchClimbByPeakCode(string peakCode, Action<ClimbData, string> callback)
{
if (!_config.EnableCloudSync)
{
callback?.Invoke(null, "Cloud sync disabled");
return;
}
string peakCode2 = InputValidator.SanitizePeakCode(peakCode);
if (!InputValidator.IsValidPeakCode(peakCode2))
{
callback?.Invoke(null, "Peak code must be 8 alphanumeric characters");
}
else
{
_coroutineRunner.StartCoroutine(SearchClimbByPeakCodeCoroutine(peakCode2, callback));
}
}
[IteratorStateMachine(typeof(<SearchClimbByPeakCodeCoroutine>d__19))]
private IEnumerator SearchClimbByPeakCodeCoroutine(string peakCode, Action<ClimbData, string> callback)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <SearchClimbByPeakCodeCoroutine>d__19(0)
{
<>4__this = this,
peakCode = peakCode,
callback = callback
};
}
public void GetServerStats(Action<string, string> callback)
{
_coroutineRunner.StartCoroutine(GetServerStatsCoroutine(callback));
}
[IteratorStateMachine(typeof(<GetServerStatsCoroutine>d__21))]
private IEnumerator GetServerStatsCoroutine(Action<string, string> callback)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <GetServerStatsCoroutine>d__21(0)
{
<>4__this = this,
callback = callback
};
}
private int GetMaxPayloadSize()
{
return 2097152;
}
private List<ApiVector3> ReducePointsIfNeeded(List<ApiVector3> points)
{
if (points.Count <= 2000)
{
return points;
}
_logger.LogWarning((object)$"Path has {points.Count} points, reducing to {2000} for upload");
List<ApiVector3> list = new List<ApiVector3>();
float num = (float)points.Count / 2000f;
for (int i = 0; i < 2000; i++)
{
int num2 = Mathf.RoundToInt((float)i * num);
if (num2 < points.Count)
{
list.Add(points[num2]);
}
}
if (list.Count > 0 && !list.Last().Equals(points.Last()))
{
list[list.Count - 1] = points.Last();
}
return list;
}
}
}
namespace FollowMePeak.Models
{
public class ApiResponse<T>
{
public bool Success { get; set; }
public T Data { get; set; }
public string Message { get; set; }
public string Error { get; set; }
}
public class ClimbUploadResponse
{
public string ClimbId { get; set; }
public string LevelId { get; set; }
public long UploadTime { get; set; }
}
public class ClimbListResponse
{
public List<ServerClimbData> Data { get; set; }
public ClimbListMeta Meta { get; set; }
}
public class ClimbSearchResponse
{
public bool Success { get; set; }
public ServerClimbData Data { get; set; }
public string Message { get; set; }
public string Error { get; set; }
}
public class ClimbListMeta
{
public string Level_Id { get; set; }
public int Count { get; set; }
public int Limit { get; set; }
public int Offset { get; set; }
}
public class ServerClimbData
{
public string Id { get; set; }
public string Level_Id { get; set; }
public string Player_Name { get; set; }
public string Biome_Name { get; set; }
public float Duration { get; set; }
public List<ApiVector3> Points { get; set; }
public bool Is_Successful { get; set; }
public string Share_Code { get; set; }
public long Upload_Time { get; set; }
public string Created_At { get; set; }
public ClimbData ToClimbData()
{
List<SerializableVector3> points = Points?.Select((ApiVector3 p) => p.ToSerializableVector3()).ToList() ?? new List<SerializableVector3>();
ClimbData climbData = new ClimbData
{
Id = Guid.Parse(Id),
CreationTime = DateTime.Parse(Created_At),
BiomeName = (Biome_Name ?? "Unknown"),
DurationInSeconds = Duration,
Points = points,
PlayerName = (Player_Name ?? "Unknown"),
IsFromCloud = true
};
if (!string.IsNullOrEmpty(Share_Code))
{
climbData.ShareCode = Share_Code;
}
else
{
climbData.GenerateShareCode();
}
return climbData;
}
}
public class HealthResponse
{
public string Status { get; set; }
public string Timestamp { get; set; }
public string Database { get; set; }
public HealthStats Stats { get; set; }
public double Uptime { get; set; }
public string Version { get; set; }
}
public class HealthStats
{
public int TotalClimbs { get; set; }
public int TotalLevels { get; set; }
}
public class ClimbData
{
public Guid Id { get; set; }
public DateTime CreationTime { get; set; }
public string BiomeName { get; set; }
public float DurationInSeconds { get; set; }
public List<SerializableVector3> Points { get; set; }
public string SaveName { get; set; } = "";
public string PlayerName { get; set; } = "Local";
public bool IsFromCloud { get; set; }
public string ShareCode { get; set; } = "";
public string GetDisplayName()
{
if (!string.IsNullOrEmpty(SaveName))
{
return SaveName;
}
TimeSpan timeSpan = TimeSpan.FromSeconds(DurationInSeconds);
return $"{BiomeName} - {timeSpan.Minutes:D2}m{timeSpan.Seconds:D2}s";
}
public void GenerateShareCode()
{
if (string.IsNullOrEmpty(ShareCode))
{
ShareCode = Id.ToString("N").Substring(0, 8).ToUpper();
}
}
}
public struct SerializableVector3
{
public float X;
public float Y;
public float Z;
public SerializableVector3(Vector3 vec)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_000d: Unknown result type (might be due to invalid IL or missing references)
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
X = vec.x;
Y = vec.y;
Z = vec.z;
}
public Vector3 ToVector3()
{
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
return new Vector3(X, Y, Z);
}
public ApiVector3 ToApiVector3()
{
ApiVector3 result = default(ApiVector3);
result.x = X;
result.y = Y;
result.z = Z;
return result;
}
}
public struct ApiVector3
{
public float x;
public float y;
public float z;
public ApiVector3(Vector3 vec)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_000d: Unknown result type (might be due to invalid IL or missing references)
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
x = vec.x;
y = vec.y;
z = vec.z;
}
public ApiVector3(SerializableVector3 vec)
{
x = vec.X;
y = vec.Y;
z = vec.Z;
}
public Vector3 ToVector3()
{
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
return new Vector3(x, y, z);
}
public SerializableVector3 ToSerializableVector3()
{
SerializableVector3 result = default(SerializableVector3);
result.X = x;
result.Y = y;
result.Z = z;
return result;
}
}
public class ServerConfig
{
[JsonIgnore]
public string BaseUrl => "https://followme-peak.ddns.net";
[JsonIgnore]
public bool AutoUpload => true;
[JsonIgnore]
public bool AutoDownload => true;
[JsonIgnore]
public int TimeoutSeconds => 30;
[JsonIgnore]
public int RetryAttempts => 3;
[JsonIgnore]
public int MaxUploadsPerHour => 50;
[JsonIgnore]
public string ApiKey => "FMP_2024_PEAK_CLIMB_AUTH_v1_7B8C9D2E";
public bool EnableCloudSync { get; set; }
public string PlayerName { get; set; } = "Anonymous";
public DateTime LastUploadReset { get; set; } = DateTime.MinValue;
public int UploadsThisHour { get; set; }
public bool CanUpload()
{
if (DateTime.Now - LastUploadReset > TimeSpan.FromHours(1.0))
{
UploadsThisHour = 0;
LastUploadReset = DateTime.Now;
}
return UploadsThisHour < MaxUploadsPerHour;
}
public void IncrementUploadCount()
{
UploadsThisHour++;
}
}
public class UploadQueueItem
{
public Guid Id { get; set; } = Guid.NewGuid();
public ClimbData ClimbData { get; set; }
public int RetryCount { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.Now;
public DateTime? LastAttempt { get; set; }
public string LastError { get; set; }
public UploadStatus Status { get; set; }
public bool ShouldRetry(int maxRetries = 3)
{
if (RetryCount < maxRetries)
{
return Status == UploadStatus.Failed;
}
return false;
}
public bool IsExpired(TimeSpan maxAge)
{
return DateTime.Now - CreatedAt > maxAge;
}
}
public enum UploadStatus
{
Pending,
Uploading,
Completed,
Failed,
Expired
}
}
namespace FollowMePeak.Managers
{
public class ClimbRecordingManager
{
[CompilerGenerated]
private sealed class <RecordClimbRoutine>d__13 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public ClimbRecordingManager <>4__this;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <RecordClimbRoutine>d__13(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
//IL_0057: Expected O, but got Unknown
//IL_003d: Unknown result type (might be due to invalid IL or missing references)
int num = <>1__state;
ClimbRecordingManager climbRecordingManager = <>4__this;
switch (num)
{
default:
return false;
case 0:
<>1__state = -1;
break;
case 1:
<>1__state = -1;
break;
}
if (climbRecordingManager._isRecording)
{
if ((Object)(object)Camera.main != (Object)null)
{
climbRecordingManager._currentRecordedClimb.Add(((Component)Camera.main).transform.position);
}
<>2__current = (object)new WaitForSeconds(2f);
<>1__state = 1;
return true;
}
return false;
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
private readonly ClimbDataService _climbDataService;
private readonly ManualLogSource _logger;
private List<Vector3> _currentRecordedClimb = new List<Vector3>();
private bool _isRecording;
private float _recordingStartTime;
private MonoBehaviour _coroutineRunner;
public bool IsRecording => _isRecording;
public ClimbRecordingManager(ClimbDataService climbDataService, ManualLogSource logger, MonoBehaviour coroutineRunner)
{
_climbDataService = climbDataService;
_logger = logger;
_coroutineRunner = coroutineRunner;
}
public void StartRecording()
{
if (!_isRecording)
{
_isRecording = true;
_currentRecordedClimb.Clear();
_recordingStartTime = Time.time;
_logger.LogInfo((object)"Kletter-Aufzeichnung gestartet!");
_coroutineRunner.StartCoroutine(RecordClimbRoutine());
}
}
public void StopRecording()
{
if (_isRecording)
{
_isRecording = false;
_logger.LogInfo((object)$"Aufzeichnung gestoppt. {_currentRecordedClimb.Count} Punkte zwischengespeichert.");
}
}
public void SaveCurrentClimb(string biomeName)
{
StopRecording();
if (_currentRecordedClimb.Count >= 2)
{
ClimbData climbData = new ClimbData
{
Id = Guid.NewGuid(),
CreationTime = DateTime.Now,
BiomeName = (biomeName ?? "Unbekannt"),
DurationInSeconds = Time.time - _recordingStartTime,
Points = _currentRecordedClimb.Select((Vector3 vec) => new SerializableVector3(vec)).ToList()
};
climbData.GenerateShareCode();
_climbDataService.AddClimb(climbData);
_climbDataService.SaveClimbsToFile(addNewClimb: false);
ShowTagSelectionForNewClimb(climbData);
}
}
private void ShowTagSelectionForNewClimb(ClimbData climbData)
{
_logger.LogInfo((object)("Neue Kletterroute erstellt: " + climbData.GetDisplayName() + " (Code: " + climbData.ShareCode + ")"));
Plugin.Instance.ShowTagSelectionForNewClimb(climbData);
}
[IteratorStateMachine(typeof(<RecordClimbRoutine>d__13))]
private IEnumerator RecordClimbRoutine()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <RecordClimbRoutine>d__13(0)
{
<>4__this = this
};
}
}
public class ClimbVisualizationManager
{
private readonly ClimbDataService _climbDataService;
private Dictionary<Guid, bool> _climbVisibility = new Dictionary<Guid, bool>();
private Dictionary<Guid, GameObject> _climbVisualizerObjects = new Dictionary<Guid, GameObject>();
public ClimbVisualizationManager(ClimbDataService climbDataService)
{
_climbDataService = climbDataService;
}
public bool IsClimbVisible(Guid climbId)
{
if (_climbVisibility.ContainsKey(climbId))
{
return _climbVisibility[climbId];
}
return false;
}
public void SetClimbVisibility(Guid climbId, bool visible)
{
_climbVisibility[climbId] = visible;
UpdateVisuals();
}
public void ToggleClimbVisibility(Guid climbId)
{
if (_climbVisibility.ContainsKey(climbId))
{
_climbVisibility[climbId] = !_climbVisibility[climbId];
}
else
{
_climbVisibility[climbId] = true;
}
UpdateVisuals();
}
public void ShowAllClimbs()
{
foreach (ClimbData allClimb in _climbDataService.GetAllClimbs())
{
_climbVisibility[allClimb.Id] = true;
}
UpdateVisuals();
}
public void HideAllClimbs()
{
foreach (ClimbData allClimb in _climbDataService.GetAllClimbs())
{
_climbVisibility[allClimb.Id] = false;
}
UpdateVisuals();
}
public void InitializeClimbVisibility()
{
_climbVisibility.Clear();
foreach (ClimbData allClimb in _climbDataService.GetAllClimbs())
{
_climbVisibility[allClimb.Id] = false;
}
}
public void UpdateVisuals()
{
foreach (GameObject value in _climbVisualizerObjects.Values)
{
if ((Object)(object)value != (Object)null)
{
Object.Destroy((Object)(object)value);
}
}
_climbVisualizerObjects.Clear();
foreach (ClimbData allClimb in _climbDataService.GetAllClimbs())
{
if (_climbVisibility.ContainsKey(allClimb.Id) && _climbVisibility[allClimb.Id])
{
CreateClimbVisualizer(allClimb);
}
}
}
private void CreateClimbVisualizer(ClimbData climbData)
{
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_0055: Expected O, but got Unknown
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Expected O, but got Unknown
//IL_0092: Unknown result type (might be due to invalid IL or missing references)
//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
//IL_0085: Unknown result type (might be due to invalid IL or missing references)
List<Vector3> list = climbData.Points.Select((SerializableVector3 p) => p.ToVector3()).ToList();
if (list.Count >= 2)
{
GameObject val = new GameObject($"ClimbVisualizer_{climbData.Id}");
LineRenderer val2 = val.AddComponent<LineRenderer>();
((Renderer)val2).material = new Material(Shader.Find("Legacy Shaders/Particles/Alpha Blended Premultiply"));
if (climbData.IsFromCloud)
{
val2.startColor = Color.blue;
val2.endColor = Color.cyan;
}
else
{
val2.startColor = Color.red;
val2.endColor = new Color(1f, 0.5f, 0f);
}
val2.startWidth = 0.2f;
val2.endWidth = 0.2f;
val2.positionCount = list.Count;
val2.SetPositions(list.ToArray());
_climbVisualizerObjects[climbData.Id] = val;
}
}
public void ClearVisuals()
{
foreach (GameObject value in _climbVisualizerObjects.Values)
{
if ((Object)(object)value != (Object)null)
{
Object.Destroy((Object)(object)value);
}
}
_climbVisualizerObjects.Clear();
_climbVisibility.Clear();
}
}
}
namespace FollowMePeak.Config
{
internal static class ApiKeys
{
public const string ServerApiKey = "FMP_2024_PEAK_CLIMB_AUTH_v1_7B8C9D2E";
public static bool IsConfigured => !"FMP_2024_PEAK_CLIMB_AUTH_v1_7B8C9D2E".Contains("PLACEHOLDER");
}
}