월별 글 목록: 2024년 3월월

시버리 2 Listplayers 정리기

이 프로그램은 Chivalry 2에서 플레이어 데이터를 정리하기 위한 유저 친화적인 그래픽 인터페이스를 제공합니다. 플레이어 정보 관리, 닉네임 변경 추적, 특정 플레이어 우선 순위 지정 등의 과정을 간소화합니다.

주요 기능:

  • 그래픽 사용자 인터페이스: GUI 프로그램은 접근성과 유저 친화성을 높입니다. 인터페이스에는 데이터 처리, Pinned Player 불러오기, 변경 사항 저장을 위한 버튼뿐만 아니라 Pinned Player를 표시하고 편집하기 위한 텍스트 박스가 포함되어 있습니다.
  • 플레이어 데이터베이스 업데이트: Chivalry2PlayersDatabase.log라는 새 파일을 자동으로 업데이트하거나 생성합니다. PlayFabPlayerId 및 관련 닉네임, 마지막 업데이트 시간을 명확하고 구조화된 형식으로 플레이어를 정리합니다.
  • 닉네임 변경 추적: 각 PlayFabPlayerId에 대한 닉네임의 기록을 유지하여 시간이 지남에 따라 플레이어 닉네임의 변경 사항을 추적할 수 있습니다.
  • Pinned Player 우선 순위 지정: Chivalry2PinnedPlayers.log에 나열된 플레이어는 우선 순위가 주어지며 Chivalry2PlayersDatabase.log의 상단에 “*Pinned Player” 표시와 함께 배치됩니다. 이는 특정 플레이어를 추적하는 데 특히 유용합니다.
  • 실시간 클립보드 처리: “Process ListPlayers” 버튼을 사용하면 클립보드에서 직접 플레이어 데이터를 처리할 수 있어 수동 파일 관리의 필요성을 없앱니다. 이 기능은 특히 활발한 게임 세션 중에 효율성과 사용 용이성을 위해 설계되었습니다.

사용 방법:

메인 GUI 창
1. Chivalry 2 서버에서 물결 키(~)를 눌러 콘솔을 열고 “listplayers”를 입력한 다음 엔터 키를 누릅니다.
2. 창에 있는 “Process ListPlayers” 버튼을 클릭하여 정리된 데이터를 얻습니다.
3. 오른쪽 텍스트 박스에 PlayFabPlayerId를 입력합니다(한 줄당 하나의 PlayFabPlayerId). 그리고 “Save Pinned Players”를 클릭합니다.
4. 클립보드에 listplayers가 있을 때 “Process ListPlayers” 버튼을 다시 클릭하면 Pinned Player가 상단에 표시됩니다.
여러 개의 닉네임이 기록된 플레이어들

1.36 버전의 변경 사항

  • 처리하는 동안 처리 버튼이 비활성화 됩니다.

PowerShell
# By vintertid from Discord

Add-Type -AssemblyName PresentationFramework

Add-Type -Name Window -Namespace Console -MemberDefinition '
[DllImport("Kernel32.dll")]
public static extern IntPtr GetConsoleWindow();

[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
'

$consoleWindow = [Console.Window]::GetConsoleWindow()
[Console.Window]::ShowWindow($consoleWindow, 0)

function Process-ListPlayers {
    $currentTime = Get-Date -Format "yyyy-MM-dd-HH:mm:ss UTCK"
    Add-Type -AssemblyName System.Windows.Forms
    $content = [System.Windows.Forms.Clipboard]::GetText()
    
    $listplayersDir = ".\listplayers"
    if (-not (Test-Path $listplayersDir)) {
        New-Item -Path $listplayersDir -ItemType Directory
    }
    
    $filename = "listplayers_" + (Get-Date -Format "yyyy-MM-dd-HH-mm-ss") + ".log"
    $filepath = Join-Path $listplayersDir $filename
    
    if ($content -match "^ServerName - .*\r?\nName -  PlayFabPlayerId - EOSPlayerId - Score - Kills - Deaths - Ping\r?\n(.+\r?\n)+") {
        Set-Content -Path $filepath -Value $content -Encoding UTF8
    } else {
        return
    }

    if (-not $content -match '\b[A-F0-9]{10,16}\b') { return }
    $players = $content -split "\r?\n" | Where-Object { $_ -match '^.+ - \b[A-F0-9]{10,16}\b - ' }
    if (-not $players) { return }
    $existingPlayers = @{}
    $updatedPlayerIds = @()
    $pinnedPlayers = @()

    if (Test-Path .\Chivalry2PinnedPlayers.log) {
        $pinnedPlayers = Get-Content -Path .\Chivalry2PinnedPlayers.log -Encoding UTF8
    }

    if (Test-Path .\Chivalry2PlayersDatabase.log) {
        $existingContent = Get-Content -Path .\Chivalry2PlayersDatabase.log -Encoding UTF8
        $currentId = $null
        foreach ($line in $existingContent) {
            if ($line -match '^\*Pinned Player$') {
                $isPinned = $true
                continue
            }
            if ($line -match '^[\w-]{13,16}$') {
                $currentId = $line
                $existingPlayers[$currentId] = @{ 'Nicknames' = @(); 'IsPinned' = $isPinned; 'TimeData' = 'no-time-data-old-version' }
                $isPinned = $false
            } elseif ($line -match 'listplayered (.+)$') {
                $existingPlayers[$currentId]['TimeData'] = $matches[1]
            } elseif ($line -match '^\s+-\s+(.*)') {
                if (-not $existingPlayers[$currentId]['Nicknames'].Contains($matches[1])) {
                    $existingPlayers[$currentId]['Nicknames'] += $matches[1]
                }
            }
        }
    }

    foreach ($player in $players) {
        $playerInfo = $player.Split(' - ')
        $Nickname = $playerInfo[-1].Trim()
        $PlayFabPlayerId = $playerInfo[-2].Trim()

        if ($PlayFabPlayerId -eq "NULL") {
            continue
        }

        if ($PlayFabPlayerId -and $existingPlayers.ContainsKey($PlayFabPlayerId)) {
            if (-not $existingPlayers[$PlayFabPlayerId]['Nicknames'].Contains($Nickname)) {
                $existingPlayers[$PlayFabPlayerId]['Nicknames'] += $Nickname
            }
            $existingPlayers[$PlayFabPlayerId]['TimeData'] = $currentTime
        } elseif ($PlayFabPlayerId) {
            $existingPlayers[$PlayFabPlayerId] = @{ 'Nicknames' = @($Nickname); 'IsPinned' = $false; 'TimeData' = $currentTime }
        }
        $updatedPlayerIds += $PlayFabPlayerId
    }

    foreach ($existingPlayer in $existingPlayers.Keys) {
        if ($existingPlayers[$existingPlayer]['IsPinned'] -and $pinnedPlayers -notcontains $existingPlayer) {
            $existingPlayers[$existingPlayer]['IsPinned'] = $false
        }
    }

    foreach ($id in $pinnedPlayers) {
        if ($id -and $existingPlayers.ContainsKey($id)) {
            $existingPlayers[$id]['IsPinned'] = $true
        }
    }

    $pinnedContent = @()
    $mostNicknamesContent = @()
    $mostRecentUpdatesContent = @()
    $restContent = @()

    foreach ($id in $existingPlayers.Keys) {
        $nicknames = $existingPlayers[$id]['Nicknames'] -join "`n    - "
        $timeData = $existingPlayers[$id]['TimeData']
        if ($updatedPlayerIds -contains $id -and $timeData -eq 'no-time-data-old-version') {
            $timeData = $currentTime
        }
        if ($existingPlayers[$id]['IsPinned']) {
            $entry = "-----------------------------------`n*Pinned Player`n$id`n    - $nicknames`nlistplayered $timeData"
            $pinnedContent += $entry
        } elseif ($existingPlayers[$id]['Nicknames'].Count -gt 1) {
            $entry = "-----------------------------------`n$id`n    - $nicknames`nlistplayered $timeData"
            $mostNicknamesContent += $entry
        } elseif ($timeData -eq $currentTime) {
            $entry = "-----------------------------------`n$id`n    - $nicknames`nlistplayered $timeData"
            $mostRecentUpdatesContent += $entry
        } else {
            $entry = "-----------------------------------`n$id`n    - $nicknames`nlistplayered $timeData"
            $restContent += $entry
        }
    }

    $mostNicknamesContent = $mostNicknamesContent | Sort-Object { ($_ -split "`n").Count } -Descending

    $newContent = $pinnedContent + $mostNicknamesContent + $mostRecentUpdatesContent + $restContent
    $newContent -join "`n" | Set-Content -Path .\Chivalry2PlayersDatabase.log -Encoding UTF8
}

$window = New-Object System.Windows.Window
$window.Title = "Chivalry 2 listplayers Organizer 1.37 by vintertid from Discord"
$window.Width = 1200
$window.Height = 800

$mainGrid = New-Object System.Windows.Controls.Grid
$column1 = New-Object System.Windows.Controls.ColumnDefinition
$column2 = New-Object System.Windows.Controls.ColumnDefinition
$mainGrid.ColumnDefinitions.Add($column1)
$mainGrid.ColumnDefinitions.Add($column2)

$stackPanel = New-Object System.Windows.Controls.StackPanel
$stackPanel2 = New-Object System.Windows.Controls.StackPanel

$processButton = New-Object System.Windows.Controls.Button
$processButton.Content = "Process ListPlayers"
$processButton.HorizontalAlignment = "Center"
$processButton.VerticalAlignment = "Top"
$processButton.Margin = New-Object System.Windows.Thickness(10)

$processButton.Add_Click({
    $processButton.IsEnabled = $false
    Process-ListPlayers
    $textBox.Text = Get-Content -Path .\Chivalry2PlayersDatabase.log -Encoding UTF8 -Raw
    $processButton.IsEnabled = $true
})

$timeTextBlock = New-Object System.Windows.Controls.TextBlock
$timeTextBlock.HorizontalAlignment = "Center"
$timeTextBlock.Margin = New-Object System.Windows.Thickness(10)

$timer = New-Object System.Windows.Threading.DispatcherTimer
$timer.Interval = [TimeSpan]::FromSeconds(1)
$timer.Add_Tick({
    $timeTextBlock.Text = "Current Time: " + (Get-Date -Format "yyyy-MM-dd-HH:mm:ss UTCK")
})
$timer.Start()

$textBox = New-Object System.Windows.Controls.TextBox
$textBox.VerticalScrollBarVisibility = "Visible"
$textBox.HorizontalAlignment = "Stretch"
$textBox.Height = 500
$textBox.Margin = New-Object System.Windows.Thickness(10)
$textBox.IsReadOnly = $true

$searchBox = New-Object System.Windows.Controls.TextBox
$searchBox.HorizontalAlignment = "Stretch"
$searchBox.Margin = New-Object System.Windows.Thickness(10)
$searchBox.Height = 20

$searchButton = New-Object System.Windows.Controls.Button
$searchButton.Content = "Search"
$searchButton.HorizontalAlignment = "Center"
$searchButton.Margin = New-Object System.Windows.Thickness(10)
$searchButton.Add_Click({
    $searchText = $searchBox.Text
    if ([string]::IsNullOrWhiteSpace($searchText)) { return }

    $startIndex = $textBox.Text.IndexOf($searchText, $textBox.SelectionStart + $textBox.SelectionLength, [System.StringComparison]::OrdinalIgnoreCase)
    if ($startIndex -eq -1) {
        $startIndex = $textBox.Text.IndexOf($searchText, [System.StringComparison]::OrdinalIgnoreCase)
        if ($startIndex -eq -1) { return }
    }

    $textBox.Select($startIndex, $searchText.Length)
    $textBox.Focus()
    $textBox.ScrollToLine($textBox.GetLineIndexFromCharacterIndex($startIndex))
})

$textBox2 = New-Object System.Windows.Controls.TextBox
$textBox2.VerticalScrollBarVisibility = "Visible"
$textBox2.HorizontalAlignment = "Stretch"
$textBox2.Height = 500
$textBox2.Margin = New-Object System.Windows.Thickness(10)
$textBox2.AcceptsReturn = $true

$loadButton = New-Object System.Windows.Controls.Button
$loadButton.Content = "Load Pinned Players"
$loadButton.HorizontalAlignment = "Center"
$loadButton.VerticalAlignment = "Top"
$loadButton.Margin = New-Object System.Windows.Thickness(10)
$loadButton.Add_Click({
    if (Test-Path .\Chivalry2PinnedPlayers.log) {
        $textBox2.Text = Get-Content -Path .\Chivalry2PinnedPlayers.log -Raw
    }
})

$saveButton = New-Object System.Windows.Controls.Button
$saveButton.Content = "Save Pinned Players"
$saveButton.HorizontalAlignment = "Center"
$saveButton.VerticalAlignment = "Top"
$saveButton.Margin = New-Object System.Windows.Thickness(10)
$saveButton.Add_Click({
    $textBox2.Text | Set-Content -Path .\Chivalry2PinnedPlayers.log -Encoding UTF8
})

$stackPanel.Children.Add($processButton)
$stackPanel.Children.Add($timeTextBlock)
$stackPanel.Children.Add($textBox)

if (Test-Path .\Chivalry2PlayersDatabase.log) {
    $textBox.Text = Get-Content -Path .\Chivalry2PlayersDatabase.log -Encoding UTF8 -Raw
}

$stackPanel.Children.Add($searchBox)
$stackPanel.Children.Add($searchButton)

$stackPanel.SetValue([System.Windows.Controls.Grid]::ColumnProperty, 0)
$stackPanel2.Children.Add($textBox2)

if (Test-Path .\Chivalry2PinnedPlayers.log) {
    $textBox2.Text = Get-Content -Path .\Chivalry2PinnedPlayers.log -Raw
} else {
    New-Item -Path .\Chivalry2PinnedPlayers.log -ItemType File
}

$stackPanel2.Children.Add($loadButton)
$stackPanel2.Children.Add($saveButton)
$stackPanel2.SetValue([System.Windows.Controls.Grid]::ColumnProperty, 1)

$mainGrid.Children.Add($stackPanel)
$mainGrid.Children.Add($stackPanel2)

$window.Content = $mainGrid

$window.ShowDialog()

↓ 오래된 버전


1.35 버전의 변경 사항

  • 닉네임 처리 방법을 개선했습니다.

1.34 버전의 변경 사항

  • Process ListPlayers 버튼을 누를 시 클립보드의 listplayers 원본 데이터를 저장합니다.
  • PlayFabPlayerId 처리 방법을 개선했습니다.

1.32 버전의 변경 사항

  • 닉네임에 큰따옴표가 있는 플레이어가 기록되지 않는 문제를 해결했습니다.

1.31 버전의 변경 사항

  • 검색 기능은 알파벳의 대소문자를 구분하지 않습니다.
  • “Process ListPlayers” 버튼 하단에 현재 시간을 표시합니다.

1.3 버전의 변경 사항

  • 그래픽 창과 버튼이 추가되었습니다.

↓ 레거시 버전


이 PowerShell 스크립트는 Chivalry 2 게임의 플레이어 데이터를 효과적으로 정리하고, 사용자가 이름 변경을 추적하고 특정 플레이어를 모니터링할 수 있도록 설계되었습니다.

이 스크립트는 두 가지 주요 로그 파일과 상호 작용합니다:

Chivalry2ListPlayers.log: 현재 플레이어 목록, 플레이어 닉네임 및 고유 PlayFabPlayerId를 포함합니다.

ServerName - example-server--beginner-10p--chivalry-2-live--12345678-1234-1234-1234-1234567890ab 192.0.2.1:52760
Name - PlayFabPlayerId - EOSPlayerId - Score - Kills - Deaths - Ping
<Nickname1> - <PlayFabPlayerId1> - -2030777152 - 250 - 5 - 1 ms
<Nickname2> - <PlayFabPlayerId2> - -2030777152 - 300 - 6 - 2 ms
...

Chivalry2PinnedPlayers.log (선택 사항): 데이터베이스에서 가장 위쪽에 고정하고자 하는 플레이어의 PlayFabPlayerId를 나열합니다. 해당 플레이어는 데이터베이스에 “*Pinned Player” 라인이 추가됩니다.

<PlayFabPlayerId1>
<PlayFabPlayerId2>
<PlayFabPlayerId3>
...

스크립트의 기능은 다음과 같습니다:

  • 플레이어 데이터베이스 업데이트: Chivalry2PlayersDatabase.log라는 새 파일을 업데이트하거나 생성하여 플레이어를 PlayFabPlayerId 및 관련 닉네임으로 구조화된 형식으로 정리합니다.
  • 닉네임 변경 추적: 각 PlayFabPlayerId에 대해, 스크립트는 닉네임 기록을 유지하여 시간이 지남에 따른 플레이어 닉네임 변경을 추적합니다.
  • 고정 플레이어 지정: Chivalry2PinnedPlayers.log에 나열된 플레이어는 Chivalry2PlayersDatabase.log 파일 상단에 배치되며, “*Pinned Player” 상태를 강조하는 특별한 표시로 표시됩니다. 이 기능은 관리자나 게임 내 친구 또는 주목할 만한 개인을 밀접하게 모니터링하고자 하는 사용자에게 특히 유용합니다.

1.2 버전의 변경 사항

  • 가장 많은 닉네임을 가진 PlayFabPlayerId 다음에는 가장 최근에 업데이트가 된 PlayFabPlayerId부터 배치됩니다.
  • listplayers 출력으로 데이터베이스를 업데이트 할 시 listplayers 출력에 포함된 PlayFabPlayerId에는 현재 시간이 기록됩니다.

1.1 버전의 변경 사항

  • Pinned Player 다음에는 가장 많은 닉네임을 가진 PlayFabPlayerId부터 배치됩니다.


시버리 2 공식 한국 64명 팀 서버 맵 로테이션

Seoul – 64-player Mixed Modes

게임 모드맵 이름맵 특징 1맵 특징 2맵 특징 3
팀 임무Galencourt배 폭파유물 파괴왕 무덤 파괴
팀 임무Rudhelm Siege공성탑 이동텐트 방화메이슨 계승자 살해
팀 데스매치Courtyard안 뜰비무장으로 시작
팀 임무Darkforest호송대바리케이트 방화공작 살해
팀 임무Baudwyn (Bulwark)공성 무기 파괴대포 이동두꺼운 성벽 파괴
팀 데스매치Tournament Grounds토너먼트 경기장때리면 돌아가는 함정
팀 임무Coxwell마을 방화금 약탈병사 학살
팀 임무Lionspire공성추 이동광장 점령트레뷰셋 파괴
팀 데스매치Frozen Wreck눈보라부서지는 얼음바닥
팀 임무Montcrux사막 방어 요새두꺼운 벽 폭파
팀 임무Bridgetown농장 방화시장 파괴귀족 낙사시키기
팀 데스매치Desert사막발리스타
팀 임무Thayic (Stronghold)눈보라투석기로 성벽 파괴아르곤 왕 호위
팀 임무Falmire인질 구출다리 점령챔피언 호위
팀 데스매치Wardenglade잔디안개비 오는 소리
팀 임무Askandir (Library)등대 파괴문화재 파괴도서관 방화
팀 임무Aberfell (Raid)돼지/농부 약탈모노리스 파괴드루이드 살해
팀 데스매치Fighting Pit아레나불 함정중앙 가시 구멍