<# .SYNOPSIS symlink_models.ps1 -- place in the "Sven Co-op" root folder alongside tags.json. .DESCRIPTION Creates a relative symlink for every model: svencoop_addon\models\player\\.mdl -> ..\..\..\..\ svencoop\models\player\helmet\helmet.mdl Sources (pick one, or default to tags.json): default -- model IDs from tags.json, grouped by category -Server PATH -- scan any svencoop_addon\models\player\ directory for .mdl files -FromFile FILE -- read one model ID per line from a text file Dump server model IDs to a file (run on the server): Get-ChildItem -Path "C:\path\to\svencoop_addon\models\player" -Recurse -Filter "*.mdl" | Where-Object { $_.DirectoryName -ne "...\player" } | ForEach-Object { $_.BaseName } | Sort-Object -Unique | Set-Content server_models.txt .NOTES Requires PowerShell 5.1+ and either: - Developer Mode enabled (Settings > For developers) - Running as Administrator #> [CmdletBinding()] param( [switch]$DryRun, [string]$Server, [string]$FromFile ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" # --- Paths --- $scriptDir = $PSScriptRoot $helmetAbs = Join-Path $scriptDir "svencoop\models\player\helmet\helmet.mdl" $addonRoot = Join-Path $scriptDir "svencoop_addon\models\player" $symlinkTarget = "..\..\..\..\svencoop\models\player\helmet\helmet.mdl" # --- Pre-flight --- if (-not (Test-Path $helmetAbs)) { Write-Error "helmet.mdl not found: $helmetAbs`nRun from the Sven Co-op root folder." exit 1 } $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator ) if (-not $isAdmin) { $devMode = $false try { $dm = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" -ErrorAction SilentlyContinue $devMode = $dm.AllowDevelopmentWithoutDevLicense -eq 1 } catch {} if (-not $devMode) { Write-Warning @" Symlink creation may fail. To fix, either: - Enable Developer Mode (Settings > For developers > Developer Mode) - Re-run this script as Administrator "@ } } # --- Shared: symlink one model ID --- function Invoke-Symlink { param([string]$ModelId) $modelDir = Join-Path $addonRoot $ModelId $mdlPath = Join-Path $modelDir "$ModelId.mdl" if (-not (Test-Path $modelDir -PathType Container)) { if (-not $DryRun) { New-Item -ItemType Directory -Path $modelDir -Force | Out-Null } } $item = Get-Item $mdlPath -ErrorAction SilentlyContinue if ($item -and $item.LinkType) { if ($item.Target -eq $symlinkTarget) { return "skipped" } if (-not $DryRun) { Remove-Item $mdlPath -Force } Write-Host " replace : $ModelId" $result = "replaced" } elseif ($item) { if (-not $DryRun) { Remove-Item $mdlPath -Force } Write-Host " replace : $ModelId (was real file)" $result = "replaced" } else { Write-Host " create : $ModelId" $result = "created" } if (-not $DryRun) { try { New-Item -ItemType SymbolicLink -Path $mdlPath -Target $symlinkTarget | Out-Null } catch { Write-Host " ERROR : $ModelId -- $_" return "error" } } return $result } function Write-Summary { param([hashtable]$Counts) $total = $Counts.created + $Counts.replaced $prefix = if ($DryRun) { "[DRY RUN] " } else { "" } $verb = if ($DryRun) { "would be written" } else { "written" } $errors = if ($Counts.error) { ", $($Counts.error) errors" } else { "" } Write-Host "`n${prefix}$total symlinks $verb ($($Counts.created) new, $($Counts.replaced) replaced), $($Counts.skipped) already correct${errors}" } $counts = @{ created = 0; replaced = 0; skipped = 0; error = 0 } # --- -Server: scan a player/ directory --- if ($Server) { if (-not (Test-Path $Server -PathType Container)) { Write-Error "Server path not found: $Server" exit 1 } $modelIds = Get-ChildItem -Path $Server -Recurse -Filter "*.mdl" | Where-Object { $_.DirectoryName -ne $Server } | ForEach-Object { $_.BaseName } | Sort-Object -Unique Write-Host "`n[server] -- $($modelIds.Count) models found in $Server`n" foreach ($id in $modelIds) { $counts[(Invoke-Symlink $id)]++ } Write-Summary $counts return } # --- -FromFile: read model IDs from a text file --- if ($FromFile) { if (-not (Test-Path $FromFile)) { Write-Error "File not found: $FromFile" exit 1 } $modelIds = Get-Content $FromFile | Where-Object { $_.Trim() -ne "" } | ForEach-Object { $_.Trim() } Write-Host "`n[from file] -- $($modelIds.Count) models in $FromFile`n" foreach ($id in $modelIds) { $counts[(Invoke-Symlink $id)]++ } Write-Summary $counts return } # --- Default: tags.json --- $tagsPath = Join-Path $scriptDir "tags.json" if (-not (Test-Path $tagsPath)) { Write-Error "tags.json not found next to script ($tagsPath)" exit 1 } $tags = Get-Content $tagsPath -Raw -Encoding UTF8 | ConvertFrom-Json foreach ($category in $tags.PSObject.Properties) { Write-Host "`n[$($category.Name)] -- $($category.Value.Count) models" foreach ($id in $category.Value) { $counts[(Invoke-Symlink $id)]++ } } Write-Summary $counts