feat(web): derive and register the watchpost artwork

This commit is contained in:
Bastian Wagner
2026-08-23 12:02:20 +02:00
parent b3ca4687dc
commit 06450a0d36
24 changed files with 199 additions and 1 deletions

View File

@@ -0,0 +1,146 @@
# tools/derive-monster-assets.ps1
# Derives the web assets each monster needs from the hand-made art in
# art/enemies and art/backgrounds.
#
# Per monster it writes four files:
# apps/web/public/images/monsters/<key>.png full painted artwork
# apps/web/public/images/monsters/runtime/<key>-560.jpg downscaled for the web
# apps/web/public/images/combat/sprites/<key>-<h>.png background-free cutout
# apps/web/public/images/combat/icons/<key>-128.png medallion icon
#
# The generated files are committed, so this only needs re-running when the
# source art changes. Re-runnable: it overwrites its own output and touches
# nothing else.
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing
$root = Split-Path -Parent $PSScriptRoot
$artEnemies = Join-Path $root 'art\enemies'
$artCutouts = Join-Path $artEnemies 'transparent-background'
$artBackgrounds = Join-Path $root 'art\backgrounds'
$webImages = Join-Path $root 'apps\web\public\images'
foreach ($dir in @(
(Join-Path $webImages 'monsters\runtime'),
(Join-Path $webImages 'combat\sprites'),
(Join-Path $webImages 'combat\icons'),
(Join-Path $webImages 'backgrounds\runtime'))) {
New-Item -ItemType Directory -Force -Path $dir | Out-Null
}
function Save-Scaled {
param(
[string] $SourcePath,
[string] $TargetPath,
[int] $TargetWidth,
[string] $Format,
[switch] $Opaque
)
$img = [System.Drawing.Image]::FromFile($SourcePath)
try {
$height = [int][Math]::Round($img.Height * $TargetWidth / $img.Width)
$bitmap = New-Object System.Drawing.Bitmap $TargetWidth, $height
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
if ($Opaque) {
$graphics.Clear([System.Drawing.Color]::Black)
}
$graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
$graphics.DrawImage($img, 0, 0, $TargetWidth, $height)
$graphics.Dispose()
if ($Format -eq 'jpg') {
$codec = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() |
Where-Object { $_.MimeType -eq 'image/jpeg' }
$params = New-Object System.Drawing.Imaging.EncoderParameters 1
$params.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter(
[System.Drawing.Imaging.Encoder]::Quality, 82)
$bitmap.Save($TargetPath, $codec, $params)
}
else {
$bitmap.Save($TargetPath, [System.Drawing.Imaging.ImageFormat]::Png)
}
$bitmap.Dispose()
}
finally {
$img.Dispose()
}
Write-Host "wrote $(Split-Path -Leaf $TargetPath)"
}
function Save-Icon {
param(
[string] $SourcePath,
[string] $TargetPath,
# Crop window as fractions of the source, chosen per monster so the icon
# lands on the head rather than on whatever the centre happens to be.
[double] $CropX, [double] $CropY, [double] $CropSize
)
$img = [System.Drawing.Image]::FromFile($SourcePath)
try {
$side = [int]([Math]::Min($img.Width, $img.Height) * $CropSize)
$x = [int]($img.Width * $CropX)
$y = [int]($img.Height * $CropY)
$bitmap = New-Object System.Drawing.Bitmap 128, 128
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
$graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
$graphics.DrawImage(
$img,
(New-Object System.Drawing.Rectangle 0, 0, 128, 128),
(New-Object System.Drawing.Rectangle $x, $y, $side, $side),
[System.Drawing.GraphicsUnit]::Pixel)
$graphics.Dispose()
$bitmap.Save($TargetPath, [System.Drawing.Imaging.ImageFormat]::Png)
$bitmap.Dispose()
}
finally {
$img.Dispose()
}
Write-Host "wrote $(Split-Path -Leaf $TargetPath)"
}
# `source` is the painted file, `cutout` the background-free one.
#
# NOTE the deliberate crossing on the first two rows: the file named
# raider-scout depicts the heavier, plated, spear-carrying figure and is the
# Veteran; raider-veteran depicts the leaner one and is the Scout. Slice 0.10
# design decision D7.
$monsters = @(
@{ key = 'raider-scout'; source = 'raider-veteran'; spriteHeight = 620; cropX = 0.40; cropY = 0.04; cropSize = 0.30 },
@{ key = 'raider-veteran'; source = 'raider-scout'; spriteHeight = 620; cropX = 0.40; cropY = 0.04; cropSize = 0.30 },
@{ key = 'burned-hound'; source = 'burned-hound'; spriteHeight = 760; cropX = 0.06; cropY = 0.12; cropSize = 0.34 },
@{ key = 'raider-captain'; source = 'Pluendererhauptmann'; spriteHeight = 620; cropX = 0.36; cropY = 0.02; cropSize = 0.28 }
)
foreach ($monster in $monsters) {
$key = $monster.key
$painted = Join-Path $artEnemies "$($monster.source).png"
$cutout = Join-Path $artCutouts "$($monster.source).png"
Copy-Item -Force $painted (Join-Path $webImages "monsters\$key.png")
Write-Host "wrote $key.png"
Save-Scaled -SourcePath $painted `
-TargetPath (Join-Path $webImages "monsters\runtime\$key-560.jpg") `
-TargetWidth 560 -Format 'jpg' -Opaque
Save-Scaled -SourcePath $cutout `
-TargetPath (Join-Path $webImages "combat\sprites\$key-$($monster.spriteHeight).png") `
-TargetWidth $monster.spriteHeight -Format 'png'
Save-Icon -SourcePath $cutout `
-TargetPath (Join-Path $webImages "combat\icons\$key-128.png") `
-CropX $monster.cropX -CropY $monster.cropY -CropSize $monster.cropSize
}
# The Raider Captain has a hand-made icon. Authored art beats a crop.
Copy-Item -Force (Join-Path $artEnemies 'PluendererhauptmannIcon.png') `
(Join-Path $webImages 'combat\icons\raider-captain-128.png')
Write-Host 'wrote raider-captain-128.png (hand-made)'
foreach ($background in 'Wachturm', 'Aschengrube') {
$source = Join-Path $artBackgrounds "$background.png"
Copy-Item -Force $source (Join-Path $webImages "backgrounds\$background.png")
Write-Host "wrote $background.png"
Save-Scaled -SourcePath $source `
-TargetPath (Join-Path $webImages "backgrounds\runtime\$background-960.jpg") `
-TargetWidth 960 -Format 'jpg' -Opaque
}