feat: expand multiplayer, chat, and release support

- add network NPC synchronization and dialogue interactions
- add world bulletin publishing and display
- improve authentication, session refresh, and appearance sync
- synchronize player direction and movement animations
- improve input focus and progressive Web loading
- add macOS/Windows builds and deployment configuration
- include required fonts, shaders, and runtime assets
This commit is contained in:
2026-09-08 21:37:43 +08:00
parent 175621f66c
commit fc6c3c1bd3
87 changed files with 4546 additions and 506 deletions

View File

@@ -21,7 +21,7 @@
</style>
</head>
<body>
<canvas id="canvas">当前浏览器不支持游戏画布。</canvas>
<canvas id="canvas" tabindex="0" aria-label="WhaleTown 游戏画布">当前浏览器不支持游戏画布。</canvas>
<div id="loading-shell" aria-live="polite">
<div class="loading">
<p class="status" id="status">正在加载小镇 0%</p>
@@ -36,30 +36,39 @@
const GODOT_THREADS_ENABLED = $GODOT_THREADS_ENABLED;
const CORE_PACK_URL = '__WHALETOWN_CORE_PACK__';
const CORE_PACK_SIZE = __WHALETOWN_CORE_PACK_SIZE__;
const AUTH_PACK_SIZE = __WHALETOWN_AUTH_PACK_SIZE__;
GODOT_CONFIG.mainPack = CORE_PACK_URL;
GODOT_CONFIG.fileSizes[CORE_PACK_URL] = CORE_PACK_SIZE;
const shell = document.getElementById('loading-shell');
const status = document.getElementById('status');
const bar = document.getElementById('bar');
const failure = document.getElementById('failure');
const canvas = document.getElementById('canvas');
canvas.addEventListener('pointerdown', function () { canvas.focus(); });
canvas.addEventListener('keydown', function (event) {
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' ', 'w', 'a', 's', 'd', 'W', 'A', 'S', 'D'].includes(event.key)) {
event.preventDefault();
}
});
const engine = new Engine(GODOT_CONFIG);
let corePhaseTotal = 0;
function updateTownProgress(loaded, total) {
if (total <= 0) return;
const percent = Math.min(100, Math.round(loaded / total * 100));
bar.style.width = percent + '%';
status.textContent = '正在加载小镇 ' + percent + '%';
}
window.whaletownGodotReady = function () {
bar.style.width = '100%';
shell.classList.add('done');
window.setTimeout(function () { shell.style.display = 'none'; }, 220);
document.getElementById('canvas').focus();
canvas.focus();
};
window.whaletownPackProgress = function (downloaded, total) {
const downloadedMb = downloaded / 1048576;
if (total > 0) {
const totalMb = total / 1048576;
const percent = Math.min(100, Math.round(downloaded / total * 100));
bar.style.width = percent + '%';
status.textContent = '正在加载完整登录界面 ' + downloadedMb.toFixed(1) + ' / ' + totalMb.toFixed(1) + ' MB';
} else {
status.textContent = '正在加载完整登录界面 ' + downloadedMb.toFixed(1) + ' MB';
}
const authTotal = total > 0 ? total : AUTH_PACK_SIZE;
updateTownProgress(corePhaseTotal + downloaded, corePhaseTotal + authTotal);
};
const missing = Engine.getMissingFeatures({ threads: GODOT_THREADS_ENABLED });
@@ -70,13 +79,17 @@
engine.startGame({
onProgress(current, total) {
if (total <= 0) return;
const percent = Math.min(100, Math.round(current / total * 100));
bar.style.width = percent + '%';
status.textContent = '正在加载小镇 ' + percent + '%';
corePhaseTotal = total;
if (current >= total) {
const percent = Math.min(100, Math.round(current / (total + AUTH_PACK_SIZE) * 100));
bar.style.width = percent + '%';
status.textContent = '正在加载小镇,正在启动登录界面...';
} else {
updateTownProgress(current, total + AUTH_PACK_SIZE);
}
},
}).then(function () {
status.textContent = '正在加载完整登录界面...';
bar.style.width = '0';
status.textContent = '正在加载小镇,正在启动登录界面...';
}).catch(function (error) {
failure.style.display = 'block';
failure.textContent = error && error.message ? error.message : '主程序加载失败,请刷新重试';