Fix data export formatting and implement robust modifier detection

Major improvements to data cleaning, modifier extraction, and role name handling:

## Data Cleaning & Normalization
- Enhanced StripColorTags() to remove ALL HTML/Unity tags using universal regex
- Added removal of modifier symbols (♥, #, ★, etc.) from player names
- Fixed winningTeam containing HTML tags by applying StripColorTags()
- Result: Clean player names without tags or symbols in exported JSON

## Modifier Detection System
- Implemented three-tier modifier detection:
  1. Symbol extraction from <size=60%>SYMBOL</size> tags (primary)
  2. Reflection-based GetModifiers<GameModifier>() (secondary)
  3. RoleString parsing fallback (tertiary)
- Fixed false positive Underdog detection from friend codes (username#1234)
- Symbols now extracted ONLY from size tags, not entire player name
- Fixed Dictionary duplicate key error (removed \u2665 duplicate of ♥)
- Supported symbols: ♥❤♡ (Lovers), # (Underdog), ★☆ (VIP), @ (Sleuth), etc.

## Role Name Handling
- Added fallback for roles without GetRoleName() method
- Extracts role names from type names: "GrenadierRole" → "Grenadier"
- Removes "Tou" suffix: "EngineerTou" → "Engineer"
- Skips generic "RoleBehaviour" type (not a real role)
- Result: Full role history now populated correctly

## Code Quality
- Added comprehensive logging for debugging modifier/role extraction
- Enhanced error handling with per-player try-catch blocks
- Improved code documentation with inline comments

## Documentation
- Added CLAUDE.md with complete architecture guide
- Documented modifier detection system
- Added data cleaning & normalization section
- Included troubleshooting guide and development workflows

Fixes: #1 (HTML tags in export), #2 (missing role history), #3 (false modifier detection)

🎮 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-08 21:42:55 +02:00
parent 1682f5bb60
commit 03d4673c00
3 changed files with 467 additions and 23 deletions

View File

@@ -317,18 +317,48 @@ public class TouMiraReflectionBridge
TownOfUsStatsPlugin.Logger.LogInfo($"Player {playerId}: RoleBehaviour type = {roleBehaviour.GetType().Name}");
// Get role name from RoleBehaviour.GetRoleName()
// Get role name from RoleBehaviour.GetRoleName() or fallback to type name
string? roleName = null;
var getRoleNameMethod = roleBehaviour.GetType().GetMethod("GetRoleName");
if (getRoleNameMethod == null)
if (getRoleNameMethod != null)
{
TownOfUsStatsPlugin.Logger.LogWarning($"GetRoleName method not found on {roleBehaviour.GetType().Name}");
roleName = getRoleNameMethod.Invoke(roleBehaviour, null) as string;
}
// Fallback: Extract role name from type name (e.g., "GrenadierRole" -> "Grenadier")
if (string.IsNullOrEmpty(roleName))
{
var typeName = roleBehaviour.GetType().Name;
// Remove "Role" suffix if present
if (typeName.EndsWith("Role"))
{
roleName = typeName.Substring(0, typeName.Length - 4);
}
// Remove "Tou" suffix (e.g., "EngineerTou" -> "Engineer")
else if (typeName.EndsWith("Tou"))
{
roleName = typeName.Substring(0, typeName.Length - 3);
}
else
{
roleName = typeName;
}
TownOfUsStatsPlugin.Logger.LogInfo($"Player {playerId}: Using type name as role: {roleName}");
}
if (string.IsNullOrEmpty(roleName))
{
TownOfUsStatsPlugin.Logger.LogWarning($"Could not determine role name for player {playerId}");
continue;
}
var roleName = getRoleNameMethod.Invoke(roleBehaviour, null) as string;
if (string.IsNullOrEmpty(roleName))
// Skip generic RoleBehaviour (not a real role)
if (roleName == "RoleBehaviour")
{
TownOfUsStatsPlugin.Logger.LogWarning($"GetRoleName returned null/empty for player {playerId}");
TownOfUsStatsPlugin.Logger.LogInfo($"Skipping generic RoleBehaviour for player {playerId}");
continue;
}
@@ -549,10 +579,9 @@ public class TouMiraReflectionBridge
return text;
}
text = Regex.Replace(text, @"<color=#[A-Fa-f0-9]+>", string.Empty);
text = text.Replace("</color>", string.Empty);
text = text.Replace("<b>", string.Empty).Replace("</b>", string.Empty);
text = text.Replace("<i>", string.Empty).Replace("</i>", string.Empty);
// Remove all Unity/HTML tags
// Pattern matches: <tag>, <tag=value>, <tag=#value>, </tag>
text = Regex.Replace(text, @"<[^>]+>", string.Empty);
return text.Trim();
}