Poprawka czasu gry
This commit is contained in:
25
CLAUDE.md
25
CLAUDE.md
@@ -18,6 +18,13 @@ The plugin **never directly references TOU Mira types**. All access is via refle
|
|||||||
### Harmony Patching Strategy
|
### Harmony Patching Strategy
|
||||||
Uses `[HarmonyPriority(Priority.Low)]` on the `AmongUsClient.OnGameEnd` patch to ensure TOU Mira's `BuildEndGameData()` runs first and populates `EndGameData.PlayerRecords` before the export begins.
|
Uses `[HarmonyPriority(Priority.Low)]` on the `AmongUsClient.OnGameEnd` patch to ensure TOU Mira's `BuildEndGameData()` runs first and populates `EndGameData.PlayerRecords` before the export begins.
|
||||||
|
|
||||||
|
### Game Duration Tracking
|
||||||
|
The plugin tracks actual gameplay duration (not lobby time) using `GameStartTimePatch`:
|
||||||
|
- Patches `ShipStatus.Begin` to capture when the game round starts
|
||||||
|
- Stores start time as `Time.time` when gameplay begins
|
||||||
|
- Calculates duration as difference between game end time and start time
|
||||||
|
- Resets on each game to ensure accurate per-game timing
|
||||||
|
|
||||||
### Async Export Pattern
|
### Async Export Pattern
|
||||||
Export runs in a fire-and-forget Task.Run() to avoid blocking the game UI. The entire export process (reflection, data transformation, API call) happens asynchronously.
|
Export runs in a fire-and-forget Task.Run() to avoid blocking the game UI. The entire export process (reflection, data transformation, API call) happens asynchronously.
|
||||||
|
|
||||||
@@ -63,20 +70,27 @@ If the `AmongUs` environment variable is set, the DLL auto-copies to `$(AmongUs)
|
|||||||
1. `TownOfUsStatsPlugin.Load()` - BepInEx entry point
|
1. `TownOfUsStatsPlugin.Load()` - BepInEx entry point
|
||||||
2. `TouMiraReflectionBridge.Initialize()` - Find TOU Mira assembly, cache reflection metadata
|
2. `TouMiraReflectionBridge.Initialize()` - Find TOU Mira assembly, cache reflection metadata
|
||||||
3. `VersionCompatibility.CheckVersion()` - Verify TOU Mira version compatibility
|
3. `VersionCompatibility.CheckVersion()` - Verify TOU Mira version compatibility
|
||||||
4. `Harmony.PatchAll()` - Apply patches (EndGameExportPatch)
|
4. `Harmony.PatchAll()` - Apply patches (EndGameExportPatch, GameStartTimePatch)
|
||||||
|
|
||||||
|
### Game Start Flow
|
||||||
|
1. **ShipStatus.Begin** fires (game round starts, gameplay begins)
|
||||||
|
2. **GameStartTimePatch.OnShipStatusBegin** runs - records `Time.time` as game start time
|
||||||
|
|
||||||
### Export Flow (Game End)
|
### Export Flow (Game End)
|
||||||
1. **AmongUsClient.OnGameEnd** fires (game ends)
|
1. **AmongUsClient.OnGameEnd** fires (game ends)
|
||||||
2. **TOU Mira's patch** runs (High priority) - populates EndGameData.PlayerRecords
|
2. **GameStartTimePatch.OnGameEndPrefix** runs - logs game duration
|
||||||
3. **EndGameExportPatch.Postfix** runs (Low priority) - starts export
|
3. **TOU Mira's patch** runs (High priority) - populates EndGameData.PlayerRecords
|
||||||
|
4. **EndGameExportPatch.Postfix** runs (Low priority) - starts export
|
||||||
- Checks for Hide & Seek mode (skip if true)
|
- Checks for Hide & Seek mode (skip if true)
|
||||||
- Fires Task.Run() with `StatsExporter.ExportGameStatsAsync()`
|
- Fires Task.Run() with `StatsExporter.ExportGameStatsAsync()`
|
||||||
4. **StatsExporter.ExportGameStatsAsync()**
|
5. **StatsExporter.ExportGameStatsAsync()**
|
||||||
- Reads `ApiSet.ini` config
|
- Reads `ApiSet.ini` config
|
||||||
- Calls reflection bridge methods to collect data
|
- Calls reflection bridge methods to collect data
|
||||||
- Transforms data via `DataTransformer.TransformToExportFormat()`
|
- Transforms data via `DataTransformer.TransformToExportFormat()`
|
||||||
|
- Uses `GameStartTimePatch.GetGameDuration()` for accurate game time
|
||||||
- Saves local backup if enabled
|
- Saves local backup if enabled
|
||||||
- POSTs to API via `ApiClient.SendToApiAsync()`
|
- POSTs to API via `ApiClient.SendToApiAsync()`
|
||||||
|
6. **GameStartTimePatch.OnGameEndPostfix** runs - clears game start time for next game
|
||||||
|
|
||||||
### Key Reflection Patterns
|
### Key Reflection Patterns
|
||||||
|
|
||||||
@@ -164,7 +178,7 @@ GameStatsData
|
|||||||
│ ├── Timestamp (UTC)
|
│ ├── Timestamp (UTC)
|
||||||
│ ├── LobbyCode (e.g., "ABCDEF")
|
│ ├── LobbyCode (e.g., "ABCDEF")
|
||||||
│ ├── GameMode (Normal/HideNSeek)
|
│ ├── GameMode (Normal/HideNSeek)
|
||||||
│ ├── Duration (seconds)
|
│ ├── Duration (seconds, actual gameplay time from intro end to game end)
|
||||||
│ └── Map (string)
|
│ └── Map (string)
|
||||||
├── Players[] (list)
|
├── Players[] (list)
|
||||||
│ ├── PlayerId (byte)
|
│ ├── PlayerId (byte)
|
||||||
@@ -309,3 +323,4 @@ Symbols are extracted **before** text cleaning to preserve information:
|
|||||||
- No compile-time type safety (all reflection is string-based)
|
- No compile-time type safety (all reflection is string-based)
|
||||||
- Breaking changes in TOU Mira require plugin updates
|
- Breaking changes in TOU Mira require plugin updates
|
||||||
- Symbol encoding may vary by console/log viewer (e.g., `♥` may display as `ÔÖą` in logs)
|
- Symbol encoding may vary by console/log viewer (e.g., `♥` may display as `ÔÖą` in logs)
|
||||||
|
- to memorize
|
||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using TownOfUsStatsExporter.Models;
|
using TownOfUsStatsExporter.Models;
|
||||||
|
using TownOfUsStatsExporter.Patches;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace TownOfUsStatsExporter.Export;
|
namespace TownOfUsStatsExporter.Export;
|
||||||
@@ -72,13 +73,16 @@ public static class DataTransformer
|
|||||||
|
|
||||||
private static GameInfoData BuildGameInfo()
|
private static GameInfoData BuildGameInfo()
|
||||||
{
|
{
|
||||||
|
// Get actual game duration (from intro end to game end) instead of total Time.time
|
||||||
|
var gameDuration = GameStartTimePatch.GetGameDuration();
|
||||||
|
|
||||||
return new GameInfoData
|
return new GameInfoData
|
||||||
{
|
{
|
||||||
GameId = Guid.NewGuid().ToString(),
|
GameId = Guid.NewGuid().ToString(),
|
||||||
Timestamp = DateTime.UtcNow,
|
Timestamp = DateTime.UtcNow,
|
||||||
LobbyCode = InnerNet.GameCode.IntToGameName(AmongUsClient.Instance.GameId),
|
LobbyCode = InnerNet.GameCode.IntToGameName(AmongUsClient.Instance.GameId),
|
||||||
GameMode = GameOptionsManager.Instance?.CurrentGameOptions?.GameMode.ToString() ?? "Unknown",
|
GameMode = GameOptionsManager.Instance?.CurrentGameOptions?.GameMode.ToString() ?? "Unknown",
|
||||||
Duration = Time.time,
|
Duration = gameDuration,
|
||||||
Map = GetMapName((byte)(GameOptionsManager.Instance?.CurrentGameOptions?.MapId ?? 0)),
|
Map = GetMapName((byte)(GameOptionsManager.Instance?.CurrentGameOptions?.MapId ?? 0)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
79
Patches/GameStartTimePatch.cs
Normal file
79
Patches/GameStartTimePatch.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace TownOfUsStatsExporter.Patches;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tracks when the game actually starts (after intro cutscene ends).
|
||||||
|
/// This allows calculating actual game duration instead of lobby time.
|
||||||
|
/// </summary>
|
||||||
|
[HarmonyPatch]
|
||||||
|
public static class GameStartTimePatch
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Time when the game started (after intro cutscene), in Unity Time.time format.
|
||||||
|
/// Null if game hasn't started yet.
|
||||||
|
/// </summary>
|
||||||
|
public static float? GameStartTime { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Duration of the last completed game in seconds.
|
||||||
|
/// Set when the game ends, persists until next game starts.
|
||||||
|
/// </summary>
|
||||||
|
public static float LastGameDuration { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the duration of the last completed game in seconds.
|
||||||
|
/// This value is set when the game ends and remains available for async export.
|
||||||
|
/// </summary>
|
||||||
|
public static float GetGameDuration()
|
||||||
|
{
|
||||||
|
return LastGameDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Patch on ShipStatus.Begin - called when the game round starts.
|
||||||
|
/// This is more reliable than IntroCutscene.OnDestroy as it's always called when gameplay begins.
|
||||||
|
/// </summary>
|
||||||
|
[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Begin))]
|
||||||
|
[HarmonyPostfix]
|
||||||
|
public static void OnShipStatusBegin()
|
||||||
|
{
|
||||||
|
GameStartTime = Time.time;
|
||||||
|
LastGameDuration = 0f; // Reset duration for new game
|
||||||
|
TownOfUsStatsPlugin.Logger.LogInfo($"Game started at Time.time = {GameStartTime.Value:F2}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate and store game duration when game ends.
|
||||||
|
/// This runs before the export, so duration is available for async export.
|
||||||
|
/// </summary>
|
||||||
|
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameEnd))]
|
||||||
|
[HarmonyPrefix]
|
||||||
|
public static void OnGameEndPrefix()
|
||||||
|
{
|
||||||
|
if (GameStartTime.HasValue)
|
||||||
|
{
|
||||||
|
LastGameDuration = Time.time - GameStartTime.Value;
|
||||||
|
TownOfUsStatsPlugin.Logger.LogInfo($"Game ended. Duration: {LastGameDuration:F2} seconds ({LastGameDuration / 60:F2} minutes)");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LastGameDuration = 0f;
|
||||||
|
TownOfUsStatsPlugin.Logger.LogWarning("Game ended but GameStartTime was not set! Duration will be 0.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear game start time after game ends to prepare for next game.
|
||||||
|
/// LastGameDuration is preserved for async export.
|
||||||
|
/// </summary>
|
||||||
|
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameEnd))]
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPriority(Priority.Last)]
|
||||||
|
public static void OnGameEndPostfix()
|
||||||
|
{
|
||||||
|
GameStartTime = null;
|
||||||
|
TownOfUsStatsPlugin.Logger.LogInfo("Game start time cleared (duration preserved for export)");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
<Description>Town Of Us Stats Exporter - Standalone plugin for exporting game statistics</Description>
|
<Description>Town Of Us Stats Exporter - Standalone plugin for exporting game statistics</Description>
|
||||||
<RootNamespace>TownOfUsStatsExporter</RootNamespace>
|
<RootNamespace>TownOfUsStatsExporter</RootNamespace>
|
||||||
<Version>1.0.3</Version>
|
<Version>1.0.4</Version>
|
||||||
<Authors>ToU Mira Team</Authors>
|
<Authors>ToU Mira Team</Authors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class TownOfUsStatsPlugin : BasePlugin
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Plugin version.
|
/// Plugin version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string PluginVersion = "1.0.3";
|
public const string PluginVersion = "1.0.4";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Logger instance for the plugin.
|
/// Logger instance for the plugin.
|
||||||
|
|||||||
Reference in New Issue
Block a user