feat: implement crafting table
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
public
|
||||
themes/hugo-book/**
|
||||
resources/_gen
|
||||
*.log
|
||||
assets/items/*.png
|
||||
@@ -0,0 +1,41 @@
|
||||
# 物品贴图库
|
||||
|
||||
本目录存放 `crafting` 合成组件使用的物品贴图(PNG)。
|
||||
|
||||
## 命名规则
|
||||
|
||||
一个物品对应一个文件:`<名称>.png`,文件名(不含 `.png`)就是引用时的名字。
|
||||
|
||||
例如放入 `diamond.png` 后,在合成组件中写 `a1="diamond"` 即可显示钻石。
|
||||
|
||||
> 推荐使用 16×16(或 32×32)的 Minecraft 像素风贴图,`image-rendering: pixelated` 会保证缩放后依然清晰锐利。
|
||||
|
||||
## 自动同步(推荐)
|
||||
|
||||
构建 / 开发时由 `scripts/sync-items.ps1` 自动维护本目录:
|
||||
|
||||
- sfcraft 自定义贴图(`amethyst_cauldron_blank`、`exp_totem`、`lunch_box`、`pearl_token` 等)
|
||||
从 [sfcraft 仓库](https://github.com/saltedfishclub/sfcraft/tree/rev/26.2/src/main/resources/assets/sfcraft/textures/item)
|
||||
自动下载,缺失或远端变更时更新;
|
||||
- 原版物品(`diamond`、`stick` 等)在本地缺失时从 Minecraft wiki 自动获取;
|
||||
- 手动放入的贴图不会被覆盖;找不到的贴图会报错并列出物品 id,阻止构建 / 开发。
|
||||
|
||||
请使用 `scripts\build.ps1` / `scripts\dev.ps1` 而非直接运行 `hugo`,详见 `scripts/README.md`。
|
||||
|
||||
## 组件用法
|
||||
|
||||
```markdown
|
||||
{{< crafting
|
||||
a1="diamond" a2="diamond" a3="diamond"
|
||||
b1="" b2="stick" b3=""
|
||||
c1="" c2="stick" c3=""
|
||||
out="diamond_pickaxe"
|
||||
caption="钻石镐(可选)"
|
||||
>}}
|
||||
```
|
||||
|
||||
- `a1`~`c3`:3×3 合成格的九个格子,`out`:输出格;
|
||||
- 值 = 贴图文件名(不含 `.png`),**留空 / 省略 = 空格子**;
|
||||
- 需要显示堆叠数量时写 `名称:数量`,例如 `b2="stick:2"`;
|
||||
- `caption` 可选,显示在组件下方居中说明文字;
|
||||
- 未运行自动同步且贴图缺失时,格子内会显示一个 `?` 占位,方便发现写错的名字。
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,29 @@
|
||||
{{- $val := trim (.ctx.Get .key) " " -}}
|
||||
{{- $item := "" -}}
|
||||
{{- $count := 0 -}}
|
||||
{{- if $val -}}
|
||||
{{- $parts := split $val ":" -}}
|
||||
{{- $item = index $parts 0 -}}
|
||||
{{- if gt (len $parts) 1 -}}
|
||||
{{- $count = int (index $parts 1) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $img := "" -}}
|
||||
{{- if $item -}}
|
||||
{{- $img = resources.Get (printf "items/%s.png" $item) -}}
|
||||
{{- end -}}
|
||||
|
||||
<div class="sfc-slot sfc-{{ .cls }}"
|
||||
style="left: {{ .x }}; top: {{ .y }};"
|
||||
{{ with $item }}data-name="{{ . }}"{{ end }}>
|
||||
{{- if $img -}}
|
||||
<img class="sfc-item" src="{{ $img.RelPermalink }}"
|
||||
alt="{{ $item }}" loading="lazy">
|
||||
{{- if gt $count 1 -}}
|
||||
<span class="sfc-count">{{ $count }}</span>
|
||||
{{- end -}}
|
||||
{{- else if $item -}}
|
||||
<span class="sfc-missing" title="未找到 assets/items/{{ $item }}.png">?</span>
|
||||
{{- end -}}
|
||||
</div>
|
||||
@@ -0,0 +1,124 @@
|
||||
{{- /* SFCraft 合成配方组件
|
||||
- 调用: 页面内写 shortcode, 名为 crafting
|
||||
- a1..c3 为 3x3 合成格, out 为输出格
|
||||
- 值为 assets/items/ 下贴图的文件名(不含 .png), 留空表示空格子
|
||||
- 名称后可加 ":数量" 显示堆叠数, 例如 a1="stick:2"
|
||||
- 引用的贴图不存在时, 格子内显示 ? 占位提示
|
||||
- caption 参数可选, 显示在组件下方
|
||||
*/ -}}
|
||||
|
||||
{{- $table := resources.Get "table.png" -}}
|
||||
{{- $slots := slice
|
||||
(dict "key" "a1" "cls" "grid" "x" "13.75%" "y" "20%")
|
||||
(dict "key" "a2" "cls" "grid" "x" "25%" "y" "20%")
|
||||
(dict "key" "a3" "cls" "grid" "x" "36.25%" "y" "20%")
|
||||
(dict "key" "b1" "cls" "grid" "x" "13.75%" "y" "42.5%")
|
||||
(dict "key" "b2" "cls" "grid" "x" "25%" "y" "42.5%")
|
||||
(dict "key" "b3" "cls" "grid" "x" "36.25%" "y" "42.5%")
|
||||
(dict "key" "c1" "cls" "grid" "x" "13.75%" "y" "65%")
|
||||
(dict "key" "c2" "cls" "grid" "x" "25%" "y" "65%")
|
||||
(dict "key" "c3" "cls" "grid" "x" "36.25%" "y" "65%")
|
||||
(dict "key" "out" "cls" "out" "x" "70%" "y" "37.5%")
|
||||
-}}
|
||||
|
||||
{{- $cssOnce := .Page.Store.Get "sfc_crafting_css" -}}
|
||||
{{- if not $cssOnce -}}
|
||||
{{- .Page.Store.Set "sfc_crafting_css" true -}}
|
||||
<style>
|
||||
.sfc-crafting {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 540px;
|
||||
aspect-ratio: 320 / 160;
|
||||
margin: 1rem 0 1.25rem;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
container-type: inline-size;
|
||||
}
|
||||
.sfc-slot {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.sfc-grid { width: 10.625%; height: 21.25%; }
|
||||
.sfc-out { width: 15.625%; height: 31.25%; }
|
||||
.sfc-item {
|
||||
width: 88%;
|
||||
height: 88%;
|
||||
object-fit: contain;
|
||||
image-rendering: pixelated;
|
||||
filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.35));
|
||||
}
|
||||
.sfc-count {
|
||||
position: absolute;
|
||||
right: 5%;
|
||||
bottom: 3%;
|
||||
font-family: "Minecraft", ui-monospace, Consolas, monospace;
|
||||
font-size: 12px;
|
||||
font-size: 2.8cqw;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 0 #3f3f3f, 1px 0 0 #3f3f3f,
|
||||
0 -1px 0 #3f3f3f, -1px 0 0 #3f3f3f,
|
||||
0 2px 3px rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
.sfc-missing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 86%;
|
||||
height: 86%;
|
||||
font-size: 4cqw;
|
||||
font-weight: 700;
|
||||
color: rgba(0, 0, 0, 0.28);
|
||||
border: 2px dashed rgba(0, 0, 0, 0.18);
|
||||
border-radius: 4px;
|
||||
background:
|
||||
repeating-linear-gradient(45deg, rgba(255,255,255,0.22), rgba(255,255,255,0.22) 4px, rgba(0,0,0,0.04) 4px, rgba(0,0,0,0.04) 8px);
|
||||
}
|
||||
.sfc-slot[data-name]::after {
|
||||
content: attr(data-name);
|
||||
position: absolute;
|
||||
bottom: calc(100% + 6px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 3px 9px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
border-radius: 5px;
|
||||
background: rgba(18, 18, 18, 0.92);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.35);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.12s ease;
|
||||
z-index: 30;
|
||||
}
|
||||
.sfc-slot[data-name]:hover::after { opacity: 1; }
|
||||
.sfc-caption {
|
||||
margin: 0.35rem 0 0;
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
color: var(--gray-600, #6c757d);
|
||||
}
|
||||
</style>
|
||||
{{- end -}}
|
||||
|
||||
<figure class="sfc-crafting" style="background-image: url('{{ $table.RelPermalink }}');">
|
||||
{{- range $slots -}}
|
||||
{{- partial "crafting-slot.html"
|
||||
(dict
|
||||
"ctx" $
|
||||
"key" .key
|
||||
"cls" .cls
|
||||
"x" .x
|
||||
"y" .y
|
||||
) -}}
|
||||
{{- end -}}
|
||||
{{- with .Get "caption" -}}
|
||||
<figcaption class="sfc-caption">{{ . }}</figcaption>
|
||||
{{- end -}}
|
||||
</figure>
|
||||
@@ -0,0 +1,40 @@
|
||||
# 构建 / 开发脚本
|
||||
|
||||
## 用途
|
||||
|
||||
`sync-items.ps1` 会在构建和开发前自动保证物品贴图齐全:
|
||||
|
||||
1. 下载 [sfcraft 仓库](https://github.com/saltedfishclub/sfcraft/tree/rev/26.2/src/main/resources/assets/sfcraft/textures/item) 的
|
||||
全部自定义贴图到 `assets/items/`;
|
||||
2. 扫描 `content/` 中所有 `crafting` 组件用到的物品 id;
|
||||
3. 本地缺失的原版物品从 Minecraft wiki 自动获取(自动挑选最新版本渲染);
|
||||
4. 有任何物品无法找到时, 打印缺失清单并以非零码退出, 阻断构建 / 开发启动。
|
||||
|
||||
## 使用
|
||||
|
||||
```powershell
|
||||
.\scripts\build.ps1 # 同步贴图 + hugo 构建
|
||||
.\scripts\dev.ps1 # 同步贴图 + hugo server
|
||||
.\scripts\dev.ps1 -NoSync # 仅启动开发服务器, 不重新同步
|
||||
```
|
||||
|
||||
若 PowerShell 执行策略阻止脚本, 请用:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File scripts\dev.ps1
|
||||
```
|
||||
|
||||
脚本通过 `$env:HUGO` 或 PATH 查找 hugo, 找不到时也会探测 winget 安装位置。
|
||||
|
||||
## 规则细节
|
||||
|
||||
- sfcraft 自定义贴图: 每次构建比对远端文件大小, 缺失或变化时更新;
|
||||
- 原版贴图: 只在本地缺失时下载, **不会覆盖**手动放入 `assets/items/` 的贴图;
|
||||
- 物品 id 即 `assets/items/` 下的文件名(不含 `.png`);
|
||||
- wiki 命名规则: `<英文名>_JE<版本>[_BE<版本>].png`, 特殊命名(如 `TNT`、`Flint_and_Steel`)
|
||||
由 `sync-items.ps1` 中的变体生成与别名表处理。
|
||||
|
||||
## 网络要求
|
||||
|
||||
需要能访问 `api.github.com`、`raw.githubusercontent.com` 与 `zh.minecraft.wiki`。
|
||||
若 CI 或离线环境不允许联网, 建议把 `assets/items/` 下的贴图提交进仓库。
|
||||
@@ -0,0 +1,35 @@
|
||||
<#
|
||||
SFCraft 文档构建入口: 先同步物品贴图, 再执行 hugo 构建。
|
||||
贴图同步失败(有物品找不到)时终止构建。
|
||||
用法: powershell -ExecutionPolicy Bypass -File scripts\build.ps1 [hugo 参数...]
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(ValueFromRemainingArguments = $true)]
|
||||
[string[]]$HugoArgs
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
|
||||
function Get-HugoPath {
|
||||
if ($env:HUGO) { return $env:HUGO }
|
||||
$cmd = Get-Command hugo -ErrorAction SilentlyContinue
|
||||
if ($cmd) { return $cmd.Source }
|
||||
$p = Get-ChildItem -Path (Join-Path $env:LOCALAPPDATA 'Microsoft\WinGet\Packages') -Recurse -Filter hugo.exe -ErrorAction SilentlyContinue |
|
||||
Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||
if ($p) { return $p.FullName }
|
||||
throw '未找到 Hugo。请安装 Hugo, 或将 hugo 可执行文件路径写入环境变量 HUGO。'
|
||||
}
|
||||
|
||||
Write-Host "`n[1/2] 同步物品贴图" -ForegroundColor Cyan
|
||||
& (Join-Path $scriptDir 'sync-items.ps1')
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "[构建终止] 物品贴图同步失败, 未执行 hugo 构建。" -ForegroundColor Red
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
Write-Host "`n[2/2] hugo 构建" -ForegroundColor Cyan
|
||||
$hugo = Get-HugoPath
|
||||
& $hugo @HugoArgs
|
||||
exit $LASTEXITCODE
|
||||
@@ -0,0 +1,42 @@
|
||||
<#
|
||||
SFCraft 文档开发服务器入口: 先同步物品贴图, 再启动 hugo server。
|
||||
贴图同步失败(有物品找不到)时终止启动。
|
||||
用法: powershell -ExecutionPolicy Bypass -File scripts\dev.ps1 [-NoSync] [hugo server 参数...]
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$NoSync,
|
||||
[Parameter(ValueFromRemainingArguments = $true)]
|
||||
[string[]]$HugoArgs
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
|
||||
function Get-HugoPath {
|
||||
if ($env:HUGO) { return $env:HUGO }
|
||||
$cmd = Get-Command hugo -ErrorAction SilentlyContinue
|
||||
if ($cmd) { return $cmd.Source }
|
||||
$p = Get-ChildItem -Path (Join-Path $env:LOCALAPPDATA 'Microsoft\WinGet\Packages') -Recurse -Filter hugo.exe -ErrorAction SilentlyContinue |
|
||||
Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||
if ($p) { return $p.FullName }
|
||||
throw '未找到 Hugo。请安装 Hugo, 或将 hugo 可执行文件路径写入环境变量 HUGO。'
|
||||
}
|
||||
|
||||
if (-not $NoSync) {
|
||||
Write-Host "`n[1/2] 同步物品贴图" -ForegroundColor Cyan
|
||||
& (Join-Path $scriptDir 'sync-items.ps1')
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "[启动终止] 物品贴图同步失败, 未启动 hugo server。" -ForegroundColor Red
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
} else {
|
||||
Write-Host "[跳过] 已指定 -NoSync, 不执行贴图同步" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host "`n[2/2] 启动 hugo server" -ForegroundColor Cyan
|
||||
$hugo = Get-HugoPath
|
||||
$serverArgs = @('server')
|
||||
if ($HugoArgs.Count -gt 0) { $serverArgs += $HugoArgs }
|
||||
& $hugo @serverArgs
|
||||
exit $LASTEXITCODE
|
||||
@@ -0,0 +1,233 @@
|
||||
<#
|
||||
SFCraft 文档 · 物品贴图自动同步
|
||||
|
||||
流程:
|
||||
1. 将 sfcraft 仓库 textures/item 目录下所有贴图同步到 assets/items
|
||||
2. 扫描 content 中所有 crafting 组件, 收集用到的物品 id
|
||||
3. 对本地缺失的 id, 从 Minecraft wiki 自动获取(按命名规则挑选最新版本)
|
||||
4. 仍有无法获取的 id 时, 列出并退出(返回码 1), 阻断 build / dev
|
||||
|
||||
用法:
|
||||
powershell -ExecutionPolicy Bypass -File scripts\sync-items.ps1 [-Force]
|
||||
|
||||
说明:
|
||||
- sfcraft 自定义贴图每次与远端比对大小, 缺失或变更时更新
|
||||
- 原版贴图只在本地缺失时下载, 不会覆盖手动放入 assets/items 的贴图
|
||||
- 需要联网访问 api.github.com / zh.minecraft.wiki
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$itemsDir = Join-Path $repoRoot 'assets\items'
|
||||
$contentDir = Join-Path $repoRoot 'content'
|
||||
$ua = 'sfcraft-docs-sync/1.0 (https://github.com/saltedfishclub/sfcraft-docs)'
|
||||
|
||||
New-Item -ItemType Directory -Path $itemsDir -Force | Out-Null
|
||||
|
||||
# ---------- 数据源 ----------
|
||||
$sfOwner = 'saltedfishclub'
|
||||
$sfRepo = 'sfcraft'
|
||||
$sfRef = 'rev/26.2'
|
||||
$sfDir = 'src/main/resources/assets/sfcraft/textures/item'
|
||||
$sfListUrl = "https://api.github.com/repos/$sfOwner/$sfRepo/contents/$sfDir`?ref=$sfRef"
|
||||
$wikiApi = 'https://zh.minecraft.wiki/api.php'
|
||||
$wikiImg = 'https://zh.minecraft.wiki/images/'
|
||||
|
||||
function Write-Step([string]$s) { Write-Host "`n== $s" -ForegroundColor Cyan }
|
||||
|
||||
function Invoke-Get([string]$url, [int]$maxRetry = 2) {
|
||||
for ($i = 0; ; $i++) {
|
||||
try {
|
||||
return Invoke-RestMethod -Uri $url -Headers @{ 'User-Agent' = $ua } -TimeoutSec 30
|
||||
} catch {
|
||||
if ($i -ge $maxRetry) { throw }
|
||||
Start-Sleep -Milliseconds 800
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------- 1. sfcraft 自定义贴图 ----------
|
||||
Write-Step '1/4 同步 sfcraft 自定义贴图'
|
||||
$sfCount = 0
|
||||
$sfNew = 0
|
||||
try {
|
||||
$list = Invoke-Get $sfListUrl
|
||||
foreach ($f in $list) {
|
||||
$target = Join-Path $itemsDir $f.name
|
||||
$need = $Force -or
|
||||
-not (Test-Path -LiteralPath $target) -or
|
||||
((Get-Item -LiteralPath $target).Length -ne [int64]$f.size)
|
||||
if ($need) {
|
||||
Invoke-WebRequest -Uri $f.download_url -Headers @{ 'User-Agent' = $ua } -OutFile $target -TimeoutSec 60
|
||||
$sfNew++
|
||||
}
|
||||
$sfCount++
|
||||
}
|
||||
Write-Host " $sfCount 个文件, 本次更新 $sfNew 个"
|
||||
} catch {
|
||||
Write-Host " [警告] 无法访问 GitHub, 跳过 sfcraft 贴图同步: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# ---------- 2. 扫描 crafting 组件 ----------
|
||||
Write-Step '2/4 扫描组件物品 id'
|
||||
$ids = @{}
|
||||
$shortcodePat = [regex]'(?s){{<[ ]*crafting\b(.*?)}}'
|
||||
$paramPat = [regex]'(?:a[123]|b[123]|c[123]|out)="([^"]*)"'
|
||||
|
||||
Get-ChildItem -Path $contentDir -Recurse -Filter '*.md' -File | ForEach-Object {
|
||||
$text = Get-Content -LiteralPath $_.FullName -Raw -Encoding UTF8
|
||||
foreach ($m in $shortcodePat.Matches($text)) {
|
||||
foreach ($p in $paramPat.Matches($m.Groups[1].Value)) {
|
||||
$val = $p.Groups[1].Value.Trim()
|
||||
if ($val.Length -gt 0) {
|
||||
$id = ($val -split ':')[0].Trim()
|
||||
if ($id.Length -gt 0) { $ids[$id] = $true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$all = @($ids.Keys | Sort-Object)
|
||||
$need = @($all | Where-Object { -not (Test-Path -LiteralPath (Join-Path $itemsDir "${_}.png")) })
|
||||
Write-Host " 使用 $($all.Count) 个物品 id, 本地已有 $($all.Count - $need.Count) 个"
|
||||
|
||||
# ---------- 3. 从 Minecraft wiki 获取缺失贴图 ----------
|
||||
Write-Step '3/4 从 Minecraft wiki 获取缺失贴图'
|
||||
|
||||
function Get-WikiCandidates([string]$name) {
|
||||
$regex = '^' + [regex]::Escape($name) + '(_\(item\))?_JE\d+(\.\d+)*(_BE\d+)?\.png$'
|
||||
# 两个查询互补:
|
||||
# <name>_ 宽前缀, 覆盖 (item) 等变体, 如 Diamond_Boots_(item)_JE3_BE2.png
|
||||
# <name>_JE 窄前缀, 在宽前缀因数量被截断时兜底(裸物品如 diamond 的 JE 渲染排序靠前)
|
||||
$urls = @(
|
||||
"$wikiApi`?action=query&list=allimages&aiprefix=$([uri]::EscapeDataString($name + '_'))&ailimit=500&format=json",
|
||||
"$wikiApi`?action=query&list=allimages&aiprefix=$([uri]::EscapeDataString($name + '_JE'))&ailimit=200&format=json"
|
||||
)
|
||||
$seen = @{}
|
||||
$all = New-Object System.Collections.ArrayList
|
||||
foreach ($u in $urls) {
|
||||
try {
|
||||
$r = Invoke-Get $u
|
||||
if ($null -ne $r.query.allimages) {
|
||||
foreach ($n in $r.query.allimages) {
|
||||
if (-not $seen.ContainsKey($n.name)) {
|
||||
[void]$seen.Add($n.name, $true)
|
||||
[void]$all.Add($n.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-Host " [警告] wiki 查询失败 ($name): $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
Start-Sleep -Milliseconds 250
|
||||
}
|
||||
return @($all | Where-Object { $_ -match $regex })
|
||||
}
|
||||
|
||||
function Select-BestCandidate([string]$name, [string[]]$candidates) {
|
||||
$regex = '^' + [regex]::Escape($name) + '(_\(item\))?_JE(\d+)(\.\d+)*(_BE(\d+))?\.png$'
|
||||
$best = $null
|
||||
$bestKey = $null
|
||||
foreach ($c in $candidates) {
|
||||
$m = [regex]::Match($c, $regex)
|
||||
if (-not $m.Success) { continue }
|
||||
$key = @(
|
||||
$(if ($m.Groups[1].Success) { 1 } else { 0 }), # 优先物品图标 (item)
|
||||
[int]$m.Groups[2].Value, # JE 版本
|
||||
$(if ($m.Groups[5].Success) { [int]$m.Groups[5].Value } else { -1 }) # BE 版本
|
||||
)
|
||||
if ($null -eq $bestKey) { $best = $c; $bestKey = $key; continue }
|
||||
for ($i = 0; $i -lt 3; $i++) {
|
||||
if ($key[$i] -ne $bestKey[$i]) {
|
||||
if ($key[$i] -gt $bestKey[$i]) { $best = $c; $bestKey = $key }
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return $best
|
||||
}
|
||||
|
||||
function Get-NameVariants([string]$id) {
|
||||
$words = @($id -split '_' | Where-Object { $_ })
|
||||
$title = @($words | ForEach-Object { $_.Substring(0, 1).ToUpper() + $_.Substring(1) }) -join '_'
|
||||
|
||||
$connectors = @('and', 'of', 'the', 'for', 'with', 'to', 'from', 'by', 'in', 'on', 'at')
|
||||
$mixed = @($words | ForEach-Object {
|
||||
if ($connectors -contains $_) { $_ } else { $_.Substring(0, 1).ToUpper() + $_.Substring(1) }
|
||||
}) -join '_'
|
||||
|
||||
$variants = New-Object System.Collections.ArrayList
|
||||
[void]$variants.Add($title)
|
||||
if ($mixed -cne $title) { [void]$variants.Add($mixed) }
|
||||
if ($id.Length -le 4 -and $id -notmatch '[aeiou]') { [void]$variants.Add($id.ToUpper()) }
|
||||
$oneWord = @($words | ForEach-Object { $_.Substring(0, 1).ToUpper() + $_.Substring(1) }) -join ''
|
||||
if ($oneWord -cne $title) { [void]$variants.Add($oneWord) }
|
||||
|
||||
$aliases = @{
|
||||
'tnt' = 'TNT'
|
||||
'tnt_minecart' = 'TNT_Minecart'
|
||||
'dragon_breath' = "Dragon's_Breath"
|
||||
'slime_ball' = 'Slime'
|
||||
'snow_golem' = 'Snow_Golem'
|
||||
'ocelot' = 'Ocelot'
|
||||
}
|
||||
if ($aliases.ContainsKey($id)) { [void]$variants.Insert(0, $aliases[$id]) }
|
||||
return @($variants | Select-Object -Unique)
|
||||
}
|
||||
|
||||
$failed = @()
|
||||
foreach ($id in $need) {
|
||||
$file = $null
|
||||
foreach ($name in Get-NameVariants $id) {
|
||||
$cands = Get-WikiCandidates $name
|
||||
if ($cands.Count -gt 0) { $file = Select-BestCandidate $name $cands; break }
|
||||
}
|
||||
|
||||
if ($file) {
|
||||
$target = Join-Path $itemsDir "$id.png"
|
||||
try {
|
||||
Invoke-WebRequest -Uri ($wikiImg + [uri]::EscapeDataString($file)) -Headers @{ 'User-Agent' = $ua } -OutFile $target -TimeoutSec 60
|
||||
$fs = [System.IO.File]::OpenRead($target)
|
||||
$sig = New-Object byte[] 8
|
||||
[void]$fs.Read($sig, 0, 8)
|
||||
$fs.Close()
|
||||
if (-not ($sig[0] -eq 137 -and $sig[1] -eq 80 -and $sig[2] -eq 78 -and $sig[3] -eq 71)) {
|
||||
throw '下载内容不是有效 PNG'
|
||||
}
|
||||
Write-Host " ok $id <- $file" -ForegroundColor Green
|
||||
continue
|
||||
} catch {
|
||||
Write-Host " fail $id 下载失败: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host " fail $id 在 wiki 上未找到" -ForegroundColor Yellow
|
||||
}
|
||||
$failed += $id
|
||||
}
|
||||
|
||||
# ---------- 4. 汇总错误 ----------
|
||||
if ($failed.Count -gt 0) {
|
||||
Write-Host "`n[错误] 以下物品贴图无法自动获取:" -ForegroundColor Red
|
||||
$failed | Sort-Object | ForEach-Object { Write-Host " - $_" -ForegroundColor Red }
|
||||
Write-Host @"
|
||||
|
||||
可能原因:
|
||||
1. 物品 id 拼写错误(组件中填写的是 assets/items 下的文件名)
|
||||
2. 该物品是 sfcraft 自定义物品, 但 textures/item 目录中没有同名贴图
|
||||
3. 原版物品在 Minecraft wiki 上的命名特殊, 未被自动规则覆盖
|
||||
|
||||
解决办法:
|
||||
- 将贴图手动放入 assets/items/<id>.png 后重新运行
|
||||
- 修正组件中的物品 id
|
||||
- 若是 wiki 命名特例, 可在 scripts/sync-items.ps1 的 Get-NameVariants 别名表中补充
|
||||
"@ -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "`n全部贴图就绪 ✓" -ForegroundColor Green
|
||||
exit 0
|
||||
Submodule
+1
Submodule themes/hugo-book added at bc3454c1db
Reference in New Issue
Block a user