Team Create (���������� ��������������) + ���������� ���� + ������ 16/17/20/40/44/05 #34

Merged
min merged 69 commits from restore/all-tasks into main 2026-06-08 01:13:01 +00:00
4 changed files with 76 additions and 0 deletions
Showing only changes of commit 33cd435d06 - Show all commits

View File

@ -49,13 +49,26 @@ export class AchievementsManager {
}
_loadSaved() {
// Резервная локальная копия (мгновенно, до ответа БД).
try {
const raw = localStorage.getItem(this._projectKey);
if (raw) for (const id of JSON.parse(raw)) this._unlocked.add(id);
} catch (e) { /* ignore */ }
}
/** Загрузить разблокированные достижения из БД (по игроку). Вызывать при Play. */
loadFromDB() {
const rt = this.s?.gameRuntime;
if (!rt || !rt.loadProgress) return;
rt.loadProgress('_achievements', (data) => {
if (Array.isArray(data)) {
for (const id of data) this._unlocked.add(id);
}
});
}
_persist() {
// 1) локально (быстрый кэш), 2) в БД (между сессиями, любое устройство).
try { localStorage.setItem(this._projectKey, JSON.stringify([...this._unlocked])); } catch (e) {}
try { this.s?.gameRuntime?.saveProgress?.('_achievements', [...this._unlocked]); } catch (e) {}
}
unlock(id, _playerId) {

View File

@ -6203,6 +6203,13 @@ export class BabylonScene {
// Старт через requestAnimationFrame — даём Babylon собрать сцену
requestAnimationFrame(() => {
if (this._isPlaying) this.gameRuntime?.start(this._scripts || []);
// Задача 20: подгрузить сохранённый прогресс игрока из БД ПОСЛЕ того,
// как скрипты вызвали define() (даём им 200мс на регистрацию статов).
setTimeout(() => {
if (!this._isPlaying) return;
try { this.achievements?.loadFromDB?.(); } catch (e) {}
try { this.leaderstats?.loadFromDB?.(); } catch (e) {}
}, 250);
});
// === Оружие ===

View File

@ -4435,6 +4435,27 @@ export class GameRuntime {
.then(j => this._saveReply(scriptId, reqId, j.namespaces || {}))
.catch(() => this._saveReply(scriptId, reqId, {}));
}
/** Публичный helper для движковых менеджеров (leaderstats/achievements):
* сохранить прогресс текущего игрока в БД (storys savegame). */
saveProgress(namespace, data) {
const url = this._saveBaseUrl(namespace);
if (!url) return;
try {
fetch(url, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data }),
}).catch(() => {});
} catch (e) { /* ignore */ }
}
/** Загрузить прогресс из БД (cb(data|null)). */
loadProgress(namespace, cb) {
const url = this._saveBaseUrl(namespace);
if (!url) { cb && cb(null); return; }
fetch(url).then(r => r.json())
.then(j => cb && cb(j.data ?? null))
.catch(() => cb && cb(null));
}
_saveSet(payload) {
const url = this._saveBaseUrl(payload?.namespace);
if (!url) return;

View File

@ -108,6 +108,41 @@ export class LeaderstatsManager {
for (const fn of this._onChange) {
try { fn(pid, name, nv, old); } catch (e) { /* ignore */ }
}
// Сохраняем статы текущего игрока в БД (дебаунс 1с) — между сессиями.
if (pid === this._resolveMe()) this._scheduleSave();
}
_scheduleSave() {
if (this._saveTimer) clearTimeout(this._saveTimer);
this._saveTimer = setTimeout(() => {
this._saveTimer = null;
try {
const me = this._resolveMe();
const m = this._stats.get(me);
if (!m) return;
const obj = {}; for (const [k, v] of m) obj[k] = v;
this.s?.gameRuntime?.saveProgress?.('_leaderstats', obj);
} catch (e) { /* ignore */ }
}, 1000);
}
/** Загрузить статы текущего игрока из БД (вызывать при Play, после define). */
loadFromDB() {
const rt = this.s?.gameRuntime;
if (!rt || !rt.loadProgress) return;
rt.loadProgress('_leaderstats', (data) => {
if (data && typeof data === 'object') {
const me = this._resolveMe();
for (const name of Object.keys(data)) {
// Применяем только к зарегистрированным статам, без повторного сейва.
if (this._defs.some(d => d.name === name)) {
this._ensure(me, name);
this._stats.get(me).set(name, Number(data[name]) || 0);
}
}
this._dirty = true;
}
});
}
add(playerId, name, delta) {