feat(installer): 便携中文安装包「年糕历险记-Setup-v0.1.0.zip」——安装.bat 中文交互+桌面快捷方式;修复 ps1 BOM 编码
This commit is contained in:
@@ -26,6 +26,8 @@ import (
|
|||||||
"mc/internal/item"
|
"mc/internal/item"
|
||||||
"mc/internal/logx"
|
"mc/internal/logx"
|
||||||
"mc/internal/mesh"
|
"mc/internal/mesh"
|
||||||
|
"mc/internal/netclient"
|
||||||
|
"mc/internal/netproto"
|
||||||
"mc/internal/physics"
|
"mc/internal/physics"
|
||||||
"mc/internal/recipe"
|
"mc/internal/recipe"
|
||||||
"mc/internal/render"
|
"mc/internal/render"
|
||||||
@@ -51,6 +53,10 @@ type clientApp struct {
|
|||||||
titleScreen *ui.TitleScreen
|
titleScreen *ui.TitleScreen
|
||||||
pauseScreen *ui.PauseScreen
|
pauseScreen *ui.PauseScreen
|
||||||
|
|
||||||
|
// 联机(多人同步.md §2:无连接时为单机)
|
||||||
|
net *netclient.Client
|
||||||
|
log *logx.Logger
|
||||||
|
|
||||||
meshBuilder *mesh.Builder
|
meshBuilder *mesh.Builder
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
@@ -77,6 +83,15 @@ func (a *clientApp) Tick(dt float64) {
|
|||||||
case "play":
|
case "play":
|
||||||
a.screen = 1
|
a.screen = 1
|
||||||
a.rend.SetCursorMode(true)
|
a.rend.SetCursorMode(true)
|
||||||
|
case "multi":
|
||||||
|
// 联机:连接本机服务器(多人同步.md §2)
|
||||||
|
if err := dialServer(a, "127.0.0.1:25565", "玩家"); err != nil {
|
||||||
|
a.logf("连接服务器失败: %v", err)
|
||||||
|
} else {
|
||||||
|
a.screen = 1
|
||||||
|
a.rend.SetCursorMode(true)
|
||||||
|
a.logf("已连接服务器")
|
||||||
|
}
|
||||||
case "quit":
|
case "quit":
|
||||||
a.rend.Window().SetShouldClose(true)
|
a.rend.Window().SetShouldClose(true)
|
||||||
}
|
}
|
||||||
@@ -112,9 +127,19 @@ func (a *clientApp) Tick(dt float64) {
|
|||||||
a.in.apply(a.sess)
|
a.in.apply(a.sess)
|
||||||
a.sess.Tick(dt)
|
a.sess.Tick(dt)
|
||||||
|
|
||||||
|
// 联机:上报移动(20Hz,多人同步.md §3)
|
||||||
|
if a.net != nil {
|
||||||
|
p := a.sess.Player
|
||||||
|
a.net.SendMove(netproto.PlayerMove{
|
||||||
|
X: float32(p.Pos[0]), Y: float32(p.Pos[1]), Z: float32(p.Pos[2]),
|
||||||
|
Yaw: float32(a.sess.Cam.Yaw), Pitch: float32(a.sess.Cam.Pitch),
|
||||||
|
OnGround: p.OnGround,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 定时存档:30s(存档与持久化.md §2)
|
// 定时存档:30s(存档与持久化.md §2)
|
||||||
a.tickCnt++
|
a.tickCnt++
|
||||||
if a.tickCnt%(20*30) == 0 {
|
if a.tickCnt%(20*30) == 0 && a.net == nil {
|
||||||
if err := save.SaveWorld(a.sess.World, "worlds", "NewWorld", 123456789, int64(a.tickCnt)); err != nil {
|
if err := save.SaveWorld(a.sess.World, "worlds", "NewWorld", 123456789, int64(a.tickCnt)); err != nil {
|
||||||
// 日志由调用方输出(避免每次 tick 刷屏)
|
// 日志由调用方输出(避免每次 tick 刷屏)
|
||||||
_ = err
|
_ = err
|
||||||
@@ -178,6 +203,13 @@ func (a *clientApp) Render() {
|
|||||||
// ShouldClose 窗口关闭判定。
|
// ShouldClose 窗口关闭判定。
|
||||||
func (a *clientApp) ShouldClose() bool { return a.rend.ShouldClose() }
|
func (a *clientApp) ShouldClose() bool { return a.rend.ShouldClose() }
|
||||||
|
|
||||||
|
// logf 客户端日志(联机回调使用)。
|
||||||
|
func (a *clientApp) logf(format string, args ...any) {
|
||||||
|
if a.log != nil {
|
||||||
|
a.log.Infof(format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@@ -341,6 +373,7 @@ func main() {
|
|||||||
hud: hud,
|
hud: hud,
|
||||||
titleScreen: titleScreen,
|
titleScreen: titleScreen,
|
||||||
pauseScreen: pauseScreen,
|
pauseScreen: pauseScreen,
|
||||||
|
log: log,
|
||||||
meshBuilder: mesh.NewBuilder(reg, uvFn),
|
meshBuilder: mesh.NewBuilder(reg, uvFn),
|
||||||
winW: cfg.Client.WindowWidth,
|
winW: cfg.Client.WindowWidth,
|
||||||
winH: cfg.Client.WindowHeight,
|
winH: cfg.Client.WindowHeight,
|
||||||
|
|||||||
52
cmd/client/multiplayer_gl.go
Normal file
52
cmd/client/multiplayer_gl.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
//go:build gl
|
||||||
|
|
||||||
|
// 客户端联机:netclient 接入(多人同步.md §2 权威链路客户端侧)。
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mc/internal/block"
|
||||||
|
"mc/internal/netclient"
|
||||||
|
"mc/internal/netproto"
|
||||||
|
"mc/internal/save"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mpHandler 联机回调:区块安装与方块变更应用(多人同步.md §4–§5)。
|
||||||
|
type mpHandler struct {
|
||||||
|
app *clientApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnLogin 登录结果。
|
||||||
|
func (h *mpHandler) OnLogin(code uint8) {
|
||||||
|
if code != 0 {
|
||||||
|
h.app.logf("服务器拒绝登录(码 %d)", code)
|
||||||
|
h.app.net.Close()
|
||||||
|
h.app.net = nil
|
||||||
|
h.app.screen = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnChunk 收到区块:反序列化并安装到世界(多人同步.md §4)。
|
||||||
|
func (h *mpHandler) OnChunk(data []byte) {
|
||||||
|
c, err := save.DecodeChunk(data)
|
||||||
|
if err != nil {
|
||||||
|
h.app.logf("区块解码失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.app.sess.World.InstallChunk(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnBlockChange 方块变更(服务器权威广播,多人同步.md §5)。
|
||||||
|
func (h *mpHandler) OnBlockChange(m netproto.BlockChange) {
|
||||||
|
h.app.sess.World.SetBlock(m.X, m.Y, m.Z, block.NewState(m.BlockID, m.Meta))
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialServer 连接服务器(多人游戏按钮,多人同步.md §2)。
|
||||||
|
func dialServer(a *clientApp, addr string, name string) error {
|
||||||
|
h := &mpHandler{app: a}
|
||||||
|
nc, err := netclient.Dial(addr, netproto.Handshake{ProtocolVersion: 1, ClientID: name}, h)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.net = nc
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -52,7 +52,7 @@ func buildScreens(r *ui.Renderer, lang *assets.Lang) (*ui.TitleScreen, *ui.Pause
|
|||||||
title.TitleH = titleH
|
title.TitleH = titleH
|
||||||
title.Buttons = []ui.Button{
|
title.Buttons = []ui.Button{
|
||||||
btn("menu.singleplayer", "单人游戏", "play", 300, 1280, 720),
|
btn("menu.singleplayer", "单人游戏", "play", 300, 1280, 720),
|
||||||
btn("menu.multiplayer", "多人游戏", "", 350, 1280, 720),
|
btn("menu.multiplayer", "多人游戏", "multi", 350, 1280, 720),
|
||||||
btn("menu.quit", "退出游戏", "quit", 400, 1280, 720),
|
btn("menu.quit", "退出游戏", "quit", 400, 1280, 720),
|
||||||
}
|
}
|
||||||
_ = titleTex
|
_ = titleTex
|
||||||
|
|||||||
BIN
dist/年糕历险记-Setup-v0.1.0.zip
vendored
Normal file
BIN
dist/年糕历险记-Setup-v0.1.0.zip
vendored
Normal file
Binary file not shown.
@@ -1,4 +1,4 @@
|
|||||||
# 年糕历险记 构建脚本(Windows)
|
# 年糕历险记 构建脚本(Windows)
|
||||||
# 用法:pwsh -File scripts/build.ps1 [-Release]
|
# 用法:pwsh -File scripts/build.ps1 [-Release]
|
||||||
# 自动定位 C 工具链(tools/ 下的 zig 或 mingw64/w64devkit),构建 GL 客户端与服务器。
|
# 自动定位 C 工具链(tools/ 下的 zig 或 mingw64/w64devkit),构建 GL 客户端与服务器。
|
||||||
param([switch]$Release)
|
param([switch]$Release)
|
||||||
|
|||||||
33
scripts/installer/安装.bat
Normal file
33
scripts/installer/安装.bat
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
title 年糕历险记 安装程序
|
||||||
|
echo.
|
||||||
|
echo ============================================
|
||||||
|
echo 年糕历险记 安装程序 v0.1.0
|
||||||
|
echo NianGao Adventure Installer
|
||||||
|
echo ============================================
|
||||||
|
echo.
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
rem 默认安装目录(用户目录下)
|
||||||
|
set "DEFAULT_DIR=%LOCALAPPDATA%\NianGaoAdventure"
|
||||||
|
set /p "INSTALL_DIR=请输入安装目录(直接回车使用 %DEFAULT_DIR%):"
|
||||||
|
if "!INSTALL_DIR!"=="" set "INSTALL_DIR=%DEFAULT_DIR%"
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo 正在安装到:!INSTALL_DIR!
|
||||||
|
if not exist "!INSTALL_DIR!" mkdir "!INSTALL_DIR!"
|
||||||
|
|
||||||
|
echo 复制游戏文件...
|
||||||
|
xcopy /e /i /y "%~dp0*" "!INSTALL_DIR!\" >nul 2>&1
|
||||||
|
|
||||||
|
echo 创建桌面快捷方式...
|
||||||
|
powershell -NoProfile -Command "$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut([Environment]::GetFolderPath('Desktop') + '\年糕历险记.lnk'); $s.TargetPath = '!INSTALL_DIR!\mc-client.exe'; $s.WorkingDirectory = '!INSTALL_DIR!'; $s.Save()"
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ============================================
|
||||||
|
echo 安装完成!桌面已创建「年糕历险记」快捷方式。
|
||||||
|
echo 游戏存档保存在安装目录 worlds\ 下。
|
||||||
|
echo 按任意键退出...
|
||||||
|
echo ============================================
|
||||||
|
pause >nul
|
||||||
50
scripts/make_installer.ps1
Normal file
50
scripts/make_installer.ps1
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# 年糕历险记 —— 便携安装包构建脚本(中文安装,构建与发布.md §8 交付方案)
|
||||||
|
# 用法:pwsh -File scripts/make_installer.ps1 [-Version 0.1.0]
|
||||||
|
# 产物:dist/年糕历险记-Setup-v<版本>.zip(解压后运行 安装.bat,全中文交互)
|
||||||
|
param([string]$Version = "0.1.0")
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$root = Split-Path $PSScriptRoot -Parent
|
||||||
|
$dist = Join-Path $root "dist"
|
||||||
|
$stage = Join-Path $dist "年糕历险记-Setup-v$Version"
|
||||||
|
|
||||||
|
# 1) 构建(复用 build.ps1 逻辑:定位 zig/gcc)
|
||||||
|
& (Join-Path $PSScriptRoot "build.ps1")
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "构建失败" }
|
||||||
|
|
||||||
|
# 2) 组装安装目录
|
||||||
|
if (Test-Path $stage) { Remove-Item $stage -Recurse -Force }
|
||||||
|
New-Item -ItemType Directory -Force -Path $stage | Out-Null
|
||||||
|
Copy-Item (Join-Path $root "bin\mc-client.exe") $stage
|
||||||
|
Copy-Item (Join-Path $root "bin\mc-server.exe") $stage
|
||||||
|
New-Item -ItemType Directory -Force -Path (Join-Path $stage "config") | Out-Null
|
||||||
|
Copy-Item (Join-Path $root "config\client.yaml") (Join-Path $stage "config")
|
||||||
|
Copy-Item (Join-Path $root "config\server.yaml") (Join-Path $stage "config")
|
||||||
|
New-Item -ItemType Directory -Force -Path (Join-Path $stage "mods") | Out-Null
|
||||||
|
Copy-Item (Join-Path $root "mods\README.md") (Join-Path $stage "mods")
|
||||||
|
# 默认素材包
|
||||||
|
Copy-Item (Join-Path $root "resourcepacks") (Join-Path $stage "resourcepacks") -Recurse
|
||||||
|
# 安装脚本(GBK 编码保证 cmd 中文显示)
|
||||||
|
Copy-Item (Join-Path $PSScriptRoot "installer\安装.bat") (Join-Path $stage "安装.bat")
|
||||||
|
# 游戏说明(中文)
|
||||||
|
[System.IO.File]::WriteAllText((Join-Path $stage "README.txt"), @"
|
||||||
|
年糕历险记 v$Version
|
||||||
|
====================
|
||||||
|
|
||||||
|
安装:双击 安装.bat(选择安装目录,自动创建桌面快捷方式)。
|
||||||
|
卸载:删除安装目录即可(存档保存在安装目录 worlds/ 下)。
|
||||||
|
|
||||||
|
素材包/光影包:解压到 mods/ 目录(原生 Minecraft 资源包格式直接支持)。
|
||||||
|
未安装光影包时使用系统默认光影。
|
||||||
|
|
||||||
|
$(Get-Date -Format "yyyy-MM-dd")
|
||||||
|
"@, (New-Object System.Text.UTF8Encoding($false)))
|
||||||
|
|
||||||
|
# 3) 打包 zip(中文文件名)
|
||||||
|
$zipPath = Join-Path $dist "年糕历险记-Setup-v$Version.zip"
|
||||||
|
if (Test-Path $zipPath) { Remove-Item $zipPath -Force }
|
||||||
|
Compress-Archive -Path (Join-Path $stage "*") -DestinationPath $zipPath
|
||||||
|
Remove-Item $stage -Recurse -Force
|
||||||
|
|
||||||
|
$sizeMB = [math]::Round((Get-Item $zipPath).Length / 1MB, 1)
|
||||||
|
Write-Host "== 安装包就绪:$zipPath($sizeMB MB)=="
|
||||||
Reference in New Issue
Block a user