- Plane: ${preset.plane_id} • Floor: ${preset.floor_id} • Entry: ${preset.entry_id}
+
+
+

+
+
+
${info.name}
+
+ ${info.element}
+ ${info.path}
+ Ex-Rank ID: ${ex.ex_rank_id}
+
-
+
+
+
`;
- item.querySelector('.btn-warp').addEventListener('click', async () => {
- await executeTeleport({
- plane_id: preset.plane_id,
- floor_id: preset.floor_id,
- entry_id: preset.entry_id,
- x: preset.x,
- y: preset.y,
- z: preset.z
- }, displayName);
+ item.querySelector('.btn-toggle-ex').addEventListener('click', async (e) => {
+ const aid = parseInt(e.target.getAttribute('data-avatar'), 10);
+ const cur = e.target.getAttribute('data-unlocked') === 'true';
+ await toggleSingleExRank(aid, !cur);
});
- el.teleportPresetsList.appendChild(item);
+ el.exrankList.appendChild(item);
});
}
-// Execute Teleport API
-async function executeTeleport(payload, label = '') {
+async function toggleSingleExRank(avatarId, unlock) {
try {
- await apiCall('/api/teleport', 'POST', payload);
- showToast(t('warpSuccess', { label: label || `Plane ${payload.plane_id}` }), 'success');
+ await apiCall('/api/exrank/toggle', 'POST', { avatar_id: avatarId, unlocked: unlock });
+ showToast(unlock ? 'ปลดล็อค Ex-Rank สำเร็จ!' : 'ปิดใช้งาน Ex-Rank เรียบร้อย', 'success');
refreshStatus();
} catch (err) {
- showToast(t('warpFailed', { err: err.message }), 'error');
+ showToast(`Error: ${err.message}`, 'error');
}
}
-// Load and Switch JSON
+// Load and Switch JSON Tab Data
async function loadJsonData(mode) {
state.currentJsonMode = mode;
if (mode === 'freesr') {
el.btnSelectFreesr.className = 'btn btn-sm btn-primary active';
el.btnSelectPersistent.className = 'btn btn-sm btn-secondary';
- el.jsonStatusInfo.textContent = t('jsonStatusOpening', { name: 'freesr-data.json' });
+ el.jsonStatusInfo.textContent = 'กำลังโหลด freesr-data.json...';
try {
- const res = await apiCall('/api/freesr');
- el.jsonEditor.value = JSON.stringify(res.data, null, 2);
+ if (state.fullFreesrData) {
+ el.jsonEditor.value = JSON.stringify(state.fullFreesrData, null, 2);
+ el.jsonStatusInfo.textContent = 'freesr-data.json (Loaded from memory)';
+ } else {
+ const res = await apiCall('/api/freesr');
+ state.fullFreesrData = res.data;
+ el.jsonEditor.value = JSON.stringify(res.data, null, 2);
+ el.jsonStatusInfo.textContent = 'freesr-data.json (Loaded from server)';
+ }
} catch (err) {
- showToast(`Load failed: ${err.message}`, 'error');
+ el.jsonStatusInfo.textContent = `Offline: สามารถพิมพ์หรือวาง JSON เองได้`;
+ if (!el.jsonEditor.value) {
+ el.jsonEditor.value = JSON.stringify({ avatars: {}, lightcones: [], relics: [], battle_config: {} }, null, 2);
+ }
}
} else {
- el.btnSelectPersistent.className = 'btn btn-sm btn-primary active';
el.btnSelectFreesr.className = 'btn btn-sm btn-secondary';
- el.jsonStatusInfo.textContent = t('jsonStatusOpening', { name: 'persistent' });
+ el.btnSelectPersistent.className = 'btn btn-sm btn-primary active';
+ el.jsonStatusInfo.textContent = 'กำลังโหลด persistent...';
try {
- const res = await apiCall('/api/persistent');
- el.jsonEditor.value = JSON.stringify(res.persistent, null, 2);
+ if (state.persistentData) {
+ el.jsonEditor.value = JSON.stringify(state.persistentData, null, 2);
+ el.jsonStatusInfo.textContent = 'persistent (Loaded from memory)';
+ } else {
+ const res = await apiCall('/api/persistent');
+ state.persistentData = res.data;
+ el.jsonEditor.value = JSON.stringify(res.data, null, 2);
+ el.jsonStatusInfo.textContent = 'persistent (Loaded from server)';
+ }
} catch (err) {
- showToast(`Load failed: ${err.message}`, 'error');
+ el.jsonStatusInfo.textContent = `Offline: สามารถพิมพ์หรือวาง JSON เองได้`;
+ if (!el.jsonEditor.value) {
+ el.jsonEditor.value = JSON.stringify({ lineups: DEFAULT_LINEUP, main_character: "FemaleHarmony", march_type: "MarchHunt" }, null, 2);
+ }
}
}
}
-// Save JSON to Server
+// Save JSON Tab Data
async function saveJsonData() {
- const text = el.jsonEditor.value.trim();
- if (!text) {
- showToast('ไม่มีข้อมูล JSON ให้บันทึก', 'error');
- return;
- }
-
- let parsed;
try {
- parsed = JSON.parse(text);
- } catch (err) {
- showToast(`${t('jsonInvalid')}: ${err.message}`, 'error');
- return;
- }
-
- const endpoint = state.currentJsonMode === 'freesr' ? '/api/freesr' : '/api/persistent';
- try {
- await apiCall(endpoint, 'POST', parsed);
- showToast(t('jsonSaveSuccess', { name: state.currentJsonMode }), 'success');
+ const parsed = JSON.parse(el.jsonEditor.value);
+ if (state.currentJsonMode === 'freesr') {
+ state.fullFreesrData = parsed;
+ if (state.isConnected) {
+ await saveFreesrChangesToServer();
+ }
+ // Sync loaded characters to local list
+ if (parsed.avatars) {
+ for (const aid in parsed.avatars) {
+ const av = parsed.avatars[aid];
+ const found = state.avatarsList.find(a => a.avatar_id === parseInt(aid, 10));
+ if (found) {
+ found.level = av.level || 80;
+ found.promotion = av.promotion || 6;
+ found.rank = av.data?.rank ?? 6;
+ }
+ }
+ renderAvatarsGrid();
+ }
+ showToast('บันทึก freesr-data.json เรียบร้อย', 'success');
+ } else {
+ state.persistentData = parsed;
+ if (state.isConnected && state.serverMode === 'gac') {
+ await apiCall('/api/persistent', 'POST', parsed);
+ }
+ if (parsed.lineups) {
+ state.statusData = state.statusData || {};
+ state.statusData.lineups = parsed.lineups;
+ renderLineup(parsed.lineups);
+ }
+ showToast('บันทึกไฟล์ persistent เรียบร้อย', 'success');
+ }
refreshStatus();
} catch (err) {
- showToast(`Save failed: ${err.message}`, 'error');
+ showToast(`เกิดข้อผิดพลาด: ${err.message}`, 'error');
}
}
@@ -947,8 +1055,8 @@ function setupTabs() {
state.activeTab = targetId;
// On-demand tab refresh
- if (targetId === 'tab-avatars' && state.avatarsList.length === 0) {
- loadAvatars();
+ if (targetId === 'tab-avatars') {
+ renderAvatarsGrid();
} else if (targetId === 'tab-equipment') {
loadEquipment();
} else if (targetId === 'tab-persistent') {
@@ -961,7 +1069,7 @@ function setupTabs() {
});
}
-// Event Listeners setup
+// Setup Event Listeners
function setupEvents() {
// Quick Ping & Refresh
el.btnQuickPing.addEventListener('click', () => {
@@ -984,7 +1092,7 @@ function setupEvents() {
state.apiUrl = newUrl;
localStorage.setItem('gac_srtool_api_url', newUrl);
el.modalSettings.classList.remove('active');
- showToast(t('settingsSaved'), 'info');
+ showToast(t('settingsSaved'), 'success');
refreshStatus();
}
});
@@ -994,18 +1102,43 @@ function setupEvents() {
el.btnModalSaveAvatar.addEventListener('click', saveAvatarDetails);
el.btnModalMaxSkills.addEventListener('click', maxSkillsForAvatar);
+ // Quick Preset Buttons for Character Modal
+ document.querySelectorAll('.btn-lvl-preset').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const lvl = parseInt(btn.getAttribute('data-lvl'), 10);
+ el.modalCharLevel.value = lvl;
+ if (lvl > 70) el.modalCharPromotion.value = 6;
+ else if (lvl > 60) el.modalCharPromotion.value = 5;
+ else if (lvl > 50) el.modalCharPromotion.value = 4;
+ else if (lvl > 40) el.modalCharPromotion.value = 3;
+ else if (lvl > 20) el.modalCharPromotion.value = 1;
+ else el.modalCharPromotion.value = 0;
+ });
+ });
+
+ document.querySelectorAll('.btn-prom-preset').forEach(btn => {
+ btn.addEventListener('click', () => {
+ el.modalCharPromotion.value = btn.getAttribute('data-prom');
+ });
+ });
+
+ el.btnModalQuickE0?.addEventListener('click', () => {
+ if (state.selectedAvatarDetail) state.selectedAvatarDetail.rank = 0;
+ renderModalEidolonPips(0);
+ });
+
+ el.btnModalQuickE6?.addEventListener('click', () => {
+ if (state.selectedAvatarDetail) state.selectedAvatarDetail.rank = 6;
+ renderModalEidolonPips(6);
+ });
+
// Lineup Picker Modal Controls
el.btnClosePickerModal.addEventListener('click', () => el.modalLineupPicker.classList.remove('active'));
el.pickerSearch.addEventListener('input', renderPickerGrid);
- // Quick Action Buttons
+ // Quick Action Buttons on Dashboard
el.btnQuickMaxAll.addEventListener('click', maxAllAvatars);
el.btnMaxAllAvatars.addEventListener('click', maxAllAvatars);
-
- el.btnQuickWarpExpress.addEventListener('click', () => {
- const express = PRESET_TELEPORTS[0];
- executeTeleport(express, getTeleportName(express));
- });
el.btnQuickSyncPersistent.addEventListener('click', async () => {
try {
@@ -1077,20 +1210,6 @@ function setupEvents() {
}
});
- // Custom Teleport Form
- el.formCustomTeleport.addEventListener('submit', (e) => {
- e.preventDefault();
- const payload = {
- plane_id: parseInt(el.tpPlane.value, 10),
- floor_id: parseInt(el.tpFloor.value, 10),
- entry_id: parseInt(el.tpEntry.value, 10),
- x: el.tpX.value ? parseInt(el.tpX.value, 10) : 0,
- y: el.tpY.value ? parseInt(el.tpY.value, 10) : 0,
- z: el.tpZ.value ? parseInt(el.tpZ.value, 10) : 0
- };
- executeTeleport(payload, `Plane ${payload.plane_id}`);
- });
-
// JSON Tab Controls
el.btnSelectFreesr.addEventListener('click', () => loadJsonData('freesr'));
el.btnSelectPersistent.addEventListener('click', () => loadJsonData('persistent'));
@@ -1125,7 +1244,30 @@ function setupEvents() {
try {
const parsed = JSON.parse(event.target.result);
el.jsonEditor.value = JSON.stringify(parsed, null, 2);
- showToast(`โหลดเนื้อหาไฟล์ ${file.name} เรียบร้อย`, 'success');
+
+ // If freesr-data.json was uploaded
+ if (parsed.avatars) {
+ state.fullFreesrData = parsed;
+ for (const aid in parsed.avatars) {
+ const av = parsed.avatars[aid];
+ const found = state.avatarsList.find(a => a.avatar_id === parseInt(aid, 10));
+ if (found) {
+ found.level = av.level || 80;
+ found.promotion = av.promotion || 6;
+ found.rank = av.data?.rank ?? 6;
+ }
+ }
+ renderAvatarsGrid();
+ }
+
+ // If persistent or has lineups
+ if (parsed.lineups) {
+ state.statusData = state.statusData || {};
+ state.statusData.lineups = parsed.lineups;
+ renderLineup(parsed.lineups);
+ }
+
+ showToast(`โหลดไฟล์ ${file.name} สำเร็จและซิงค์ข้อมูลแล้ว`, 'success');
} catch (err) {
showToast(t('jsonInvalid'), 'error');
}
@@ -1159,9 +1301,27 @@ function setupEvents() {
showToast(t('settingsReset'), 'info');
refreshStatus();
});
+
+ // Modal Backdrop Click to close
+ [el.modalAvatarDetail, el.modalLineupPicker, el.modalSettings].forEach(modal => {
+ if (modal) {
+ modal.addEventListener('click', (e) => {
+ if (e.target === modal) {
+ modal.classList.remove('active');
+ }
+ });
+ }
+ });
+
+ // ESC Key to close any active modal
+ window.addEventListener('keydown', (e) => {
+ if (e.key === 'Escape') {
+ document.querySelectorAll('.modal-overlay.active').forEach(m => m.classList.remove('active'));
+ }
+ });
}
-// Background ping loop
+// Background Ping Loop
function startPingLoop() {
setInterval(() => {
refreshStatus();
@@ -1170,13 +1330,14 @@ function startPingLoop() {
// Initialize Application
function init() {
+ initAvatarsList();
setupServerModes();
setupLanguageSelector();
setupTabs();
setupEvents();
applyTranslations();
refreshStatus();
- loadAvatars();
+ renderAvatarsGrid();
startPingLoop();
}
diff --git a/js/avatars-data.js b/js/avatars-data.js
index c501f50..c896cf2 100644
--- a/js/avatars-data.js
+++ b/js/avatars-data.js
@@ -534,6 +534,144 @@ export const AVATAR_METADATA = {
path: "Remembrance",
element: "Wind",
rarity: 5
+ },
+ 1014: {
+ names: { en: "Saber", th: "Saber (เซเบอร์)", "zh-CN": "阿尔托莉雅·潘德拉贡 (塞巴)", "zh-TW": "阿爾托莉雅·潘德拉貢", ja: "セイバー", ko: "세이버", ru: "Сэйбер" },
+ path: "Destruction",
+ element: "Wind",
+ rarity: 5
+ },
+ 1015: {
+ names: { en: "Archer", th: "Archer (อาร์เชอร์)", "zh-CN": "卫宫士郎 (红A/阿茶)", "zh-TW": "衛宮士郎 (紅A)", ja: "アーチャー", ko: "아처", ru: "Арчер" },
+ path: "Hunt",
+ element: "Quantum",
+ rarity: 5
+ },
+ 1321: {
+ names: { en: "Dahlia", th: "Dahlia (ดาเลีย)", "zh-CN": "达莉亚", "zh-TW": "達莉亞", ja: "ダリア", ko: "달리아", ru: "Далия" },
+ path: "Nihility",
+ element: "Fire",
+ rarity: 5
+ },
+ 1406: {
+ names: { en: "Cipher", th: "Cipher (ไซเฟอร์)", "zh-CN": "赛弗", "zh-TW": "賽弗", ja: "サイファー", ko: "사이퍼", ru: "Сайфер" },
+ path: "Nihility",
+ element: "Quantum",
+ rarity: 5
+ },
+ 1412: {
+ names: { en: "Cerydra", th: "Cerydra (เซอรีดรา)", "zh-CN": "塞利德拉", "zh-TW": "塞利德拉", ja: "セリドラ", ko: "세리드라", ru: "Серидра" },
+ path: "Harmony",
+ element: "Wind",
+ rarity: 5
+ },
+ 1413: {
+ names: { en: "Evernight", th: "Evernight (เอเวอร์ไนท์)", "zh-CN": "永夜", "zh-TW": "永夜", ja: "エバーナイト", ko: "에버나이트", ru: "Эвернайт" },
+ path: "Remembrance",
+ element: "Ice",
+ rarity: 5
+ },
+ 1414: {
+ names: { en: "Dan Heng • Permeating Thunder", th: "Dan Heng PT (ดันเฮงร่างใหม่)", "zh-CN": "丹恒·行者", "zh-TW": "丹恆·行者", ja: "丹恒・行者", ko: "단항·행자", ru: "Дань Хэн • Странник" },
+ path: "Preservation",
+ element: "Physical",
+ rarity: 5
+ },
+ 1415: {
+ names: { en: "Cyrene", th: "Cyrene (ซีรีน)", "zh-CN": "赛琳", "zh-TW": "賽琳", ja: "サイリーン", ko: "키레네", ru: "Кирена" },
+ path: "Remembrance",
+ element: "Ice",
+ rarity: 5
+ },
+ 1501: {
+ names: { en: "Sparxie", th: "Sparxie (สปาร์กซี่)", "zh-CN": "斯帕克茜", "zh-TW": "斯帕克茜", ja: "スパーキー", ko: "스팍시", ru: "Спаркси" },
+ path: "Elation",
+ element: "Fire",
+ rarity: 5
+ },
+ 1502: {
+ names: { en: "Yaoguang", th: "Yaoguang (เย่ากวง)", "zh-CN": "瑶光", "zh-TW": "瑤光", ja: "瑤光", ko: "요광", ru: "Яогуан" },
+ path: "Elation",
+ element: "Physical",
+ rarity: 5
+ },
+ 1503: {
+ names: { en: "Pearl", th: "Pearl (เพิร์ล)", "zh-CN": "佩尔 (Pearl)", "zh-TW": "佩爾", ja: "パール", ko: "펄", ru: "Перл" },
+ path: "Elation",
+ element: "Ice",
+ rarity: 5
+ },
+ 1504: {
+ names: { en: "Ashveil", th: "Ashveil (แอชเวล)", "zh-CN": "灰纱", "zh-TW": "灰紗", ja: "アッシュヴェイル", ko: "애쉬베일", ru: "Эшвейл" },
+ path: "Hunt",
+ element: "Lightning",
+ rarity: 5
+ },
+ 1505: {
+ names: { en: "Evanescia", th: "Evanescia (เอวาเนสเซีย)", "zh-CN": "埃瓦内西亚", "zh-TW": "埃瓦內西亞", ja: "エヴァネシア", ko: "에바네시아", ru: "Эванесия" },
+ path: "Elation",
+ element: "Physical",
+ rarity: 5
+ },
+ 1506: {
+ names: { en: "Silver Wolf Lv.999", th: "Silver Wolf Lv.999 (ซิลเวอร์วูล์ฟ Lv.999)", "zh-CN": "银狼 Lv.999", "zh-TW": "銀狼 Lv.999", ja: "銀狼 Lv.999", ko: "은랑 Lv.999", ru: "Серебряный Волк Lv.999" },
+ path: "Elation",
+ element: "Imaginary",
+ rarity: 5
+ },
+ 1507: {
+ names: { en: "Mortenax Blade", th: "Mortenax Blade (มอร์เทแนกซ์ เบลด)", "zh-CN": "摩天之刃", "zh-TW": "摩天之刃", ja: "摩天の刃", ko: "모르테낙스 블레이드", ru: "Мортенакс Блейд" },
+ path: "Nihility",
+ element: "Fire",
+ rarity: 5
+ },
+ 1508: {
+ names: { en: "Tohsaka Rin", th: "Tohsaka Rin (โทซากะ ริน)", "zh-CN": "远坂凛", "zh-TW": "遠坂凜", ja: "遠坂凛", ko: "토오사카 린", ru: "Рин Тосака" },
+ path: "Erudition",
+ element: "Quantum",
+ rarity: 5
+ },
+ 1509: {
+ names: { en: "Gilgamesh", th: "Gilgamesh (กิลกาเมช)", "zh-CN": "吉尔伽美什 (金闪闪)", "zh-TW": "吉爾伽美什", ja: "ギルガメッシュ", ko: "길가메시", ru: "Гильгамеш" },
+ path: "Destruction",
+ element: "Lightning",
+ rarity: 5
+ },
+ 1510: {
+ names: { en: "Himeko Nova", th: "Himeko Nova (ฮิเมโกะ โนวา)", "zh-CN": "姬子·新星", "zh-TW": "姬子·新星", ja: "姫子・ノヴァ", ko: "히메코 노바", ru: "Химеко Нова" },
+ path: "Erudition",
+ element: "Fire",
+ rarity: 5
+ },
+ 1511: {
+ names: { en: "Nihilux", th: "Nihilux (นิฮิลักซ์)", "zh-CN": "虚无之神·尼希卢克斯", "zh-TW": "虛無之神·尼希盧克斯", ja: "ニヒラックス", ko: "니힐룩스", ru: "Нихилюкс" },
+ path: "Elation",
+ element: "Quantum",
+ rarity: 5
+ },
+ 1512: {
+ names: { en: "Robin • Summeretto", th: "Robin • Summeretto (โรบิน ซัมเมอร์)", "zh-CN": "知更鸟·夏日", "zh-TW": "知更鳥·夏日", ja: "ロビン・サマー", ko: "로빈·서머", ru: "Зарянка • Лето" },
+ path: "Remembrance",
+ element: "Wind",
+ rarity: 5
+ },
+ 1513: {
+ names: { en: "Aventurine • Waveflair", th: "Aventurine • Waveflair (อเวนทูรีน คลื่นทะเล)", "zh-CN": "砂金·夏日浪潮", "zh-TW": "砂金·夏日浪潮", ja: "アベンチュリン・サマー", ko: "어벤츄린·웨이브", ru: "Авантюрин • Волна" },
+ path: "Elation",
+ element: "Quantum",
+ rarity: 5
+ },
+ 8009: {
+ names: { en: "Trailblazer (Elation ♂)", th: "ผู้บุกเบิก (ปีติ ♂)", "zh-CN": "开拓者 (欢愉 ♂)", "zh-TW": "開拓者 (歡愉 ♂)", ja: "開拓者 (愉悦 ♂)", ko: "개척자 (환락 ♂)", ru: "Первопроходец (Радость ♂)" },
+ path: "Elation",
+ element: "Lightning",
+ rarity: 5
+ },
+ 8010: {
+ names: { en: "Trailblazer (Elation ♀)", th: "ผู้บุกเบิก (ปีติ ♀)", "zh-CN": "开拓者 (欢愉 ♀)", "zh-TW": "開拓者 (歡愉 ♀)", ja: "開拓者 (愉悦 ♀)", ko: "개척자 (환락 ♀)", ru: "Первопроходец (Радость ♀)" },
+ path: "Elation",
+ element: "Lightning",
+ rarity: 5
}
};
diff --git a/js/i18n.js b/js/i18n.js
index 292fbd5..f6c7402 100644
--- a/js/i18n.js
+++ b/js/i18n.js
@@ -119,7 +119,8 @@ export const TRANSLATIONS = {
Preservation: "อนุรักษ์",
Abundance: "เฟื่องฟู",
Remembrance: "ความทรงจำ",
- Unknown: "ไม่ระบุ"
+ Unknown: "ไม่ระบุ",
+ Elation: "ปีติ (Elation)"
}
},
@@ -215,7 +216,8 @@ export const TRANSLATIONS = {
Preservation: "Preservation",
Abundance: "Abundance",
Remembrance: "Remembrance",
- Unknown: "Unknown"
+ Unknown: "Unknown",
+ Elation: "Elation"
}
},
@@ -311,7 +313,8 @@ export const TRANSLATIONS = {
Preservation: "存护",
Abundance: "丰饶",
Remembrance: "记忆",
- Unknown: "未知"
+ Unknown: "未知",
+ Elation: "欢愉"
}
},
@@ -407,7 +410,8 @@ export const TRANSLATIONS = {
Preservation: "存護",
Abundance: "豐饒",
Remembrance: "記憶",
- Unknown: "未知"
+ Unknown: "未知",
+ Elation: "歡愉"
}
},
@@ -503,7 +507,8 @@ export const TRANSLATIONS = {
Preservation: "存護",
Abundance: "豊穣",
Remembrance: "記憶",
- Unknown: "不明"
+ Unknown: "不明",
+ Elation: "愉悦"
}
},
@@ -599,7 +604,8 @@ export const TRANSLATIONS = {
Preservation: "보존",
Abundance: "풍요",
Remembrance: "기억",
- Unknown: "알 수 없음"
+ Unknown: "알 수 없음",
+ Elation: "환락"
}
},
@@ -695,7 +701,8 @@ export const TRANSLATIONS = {
Preservation: "Bảo Hộ",
Abundance: "Trù Phú",
Remembrance: "Ký Ức",
- Unknown: "Không xác định"
+ Unknown: "Không xác định",
+ Elation: "Vui Vẻ"
}
},
@@ -791,7 +798,8 @@ export const TRANSLATIONS = {
Preservation: "Pelestarian",
Abundance: "Kelimpahan",
Remembrance: "Ingatan",
- Unknown: "Tidak Diketahui"
+ Unknown: "Tidak Diketahui",
+ Elation: "Kegembiraan"
}
},
@@ -887,7 +895,8 @@ export const TRANSLATIONS = {
Preservation: "Conservación",
Abundance: "Abundancia",
Remembrance: "Reminiscencia",
- Unknown: "Desconocido"
+ Unknown: "Desconocido",
+ Elation: "Exultación"
}
},
@@ -983,7 +992,8 @@ export const TRANSLATIONS = {
Preservation: "Préservation",
Abundance: "Abondance",
Remembrance: "Souvenir",
- Unknown: "Inconnu"
+ Unknown: "Inconnu",
+ Elation: "Allégresse"
}
},
@@ -1079,7 +1089,8 @@ export const TRANSLATIONS = {
Preservation: "Bewahrung",
Abundance: "Überfluss",
Remembrance: "Erinnerung",
- Unknown: "Unbekannt"
+ Unknown: "Unbekannt",
+ Elation: "Frohsinn"
}
},
@@ -1175,7 +1186,8 @@ export const TRANSLATIONS = {
Preservation: "Сохранение",
Abundance: "Изобилие",
Remembrance: "Память",
- Unknown: "Неизвестно"
+ Unknown: "Неизвестно",
+ Elation: "Радость"
}
},
@@ -1271,7 +1283,8 @@ export const TRANSLATIONS = {
Preservation: "Preservação",
Abundance: "Abundância",
Remembrance: "Recordação",
- Unknown: "Desconhecido"
+ Unknown: "Desconhecido",
+ Elation: "Alegria"
}
}
};
diff --git a/js/teleports-data.js b/js/teleports-data.js
deleted file mode 100644
index f006a30..0000000
--- a/js/teleports-data.js
+++ /dev/null
@@ -1,128 +0,0 @@
-import { getCurrentLanguage } from './i18n.js';
-
-export const PRESET_TELEPORTS = [
- {
- names: {
- th: "Astral Express - ขบวนรถไฟแอสทรัล (ห้องรับรอง)",
- en: "Astral Express - Parlor Car",
- "zh-CN": "星穹列车 - 观景车厢",
- "zh-TW": "星穹列車 - 觀景車廂",
- ja: "星穹列車 - 客室",
- ko: "은하열차 - 객실",
- ru: "Звёздный экспресс - Общий вагон"
- },
- plane_id: 20001,
- floor_id: 20001001,
- entry_id: 2000101,
- x: 0,
- y: 0,
- z: 0
- },
- {
- names: {
- th: "สถานีอวกาศ Herta - โซนควบคุมหลัก (Master Control Zone)",
- en: "Herta Space Station - Master Control Zone",
- "zh-CN": "空间站「黑塔」 - 主控舱段",
- "zh-TW": "太空站「黑塔」 - 主控艙段",
- ja: "宇宙ステーション「ヘルタ」 - 主制御部分",
- ko: "우주정거장 「헤르타」 - 메인 컨트롤 구역",
- ru: "Космическая станция «Герта» - Главная контрольная зона"
- },
- plane_id: 10001,
- floor_id: 10001001,
- entry_id: 1000101,
- x: 0,
- y: 0,
- z: 0
- },
- {
- names: {
- th: "Jarilo-VI (Belobog) - เขตการปกครอง (Administrative District)",
- en: "Jarilo-VI - Administrative District",
- "zh-CN": "雅利洛-VI - 行政区",
- "zh-TW": "雅利洛-VI - 行政區",
- ja: "ヤリーロ-VI - 行政区",
- ko: "야릴로-VI - 행정구역",
- ru: "Ярило-VI - Административный район"
- },
- plane_id: 10101,
- floor_id: 10101001,
- entry_id: 1010101,
- x: 10250,
- y: 51,
- z: 32670
- },
- {
- names: {
- th: "Jarilo-VI (Underworld) - เมือง Boulder (Boulder Town)",
- en: "Jarilo-VI - Boulder Town",
- "zh-CN": "雅利洛-VI - 磐岩镇",
- "zh-TW": "雅利洛-VI - 磐岩鎮",
- ja: "ヤリーロ-VI - ボルダータウン",
- ko: "야릴로-VI - 볼더 타운",
- ru: "Ярило-VI - Камнеград"
- },
- plane_id: 10102,
- floor_id: 10102001,
- entry_id: 1010201,
- x: -20560,
- y: -286,
- z: 57320
- },
- {
- names: {
- th: "Xianzhou Luofu - ท่าเรือเรือบินดาวกลาง (Central Starskiff Haven)",
- en: "Xianzhou Luofu - Central Starskiff Haven",
- "zh-CN": "仙舟「罗浮」 - 星槎海中枢",
- "zh-TW": "仙舟「羅浮」 - 星槎海中樞",
- ja: "仙舟「羅浮」 - 星槎海中枢",
- ko: "선주 「나부」 - 별뗏목 바다 중추",
- ru: "Сяньчжоу: Лофу - Центральная гавань Приюта звёздных яликов"
- },
- plane_id: 10202,
- floor_id: 10202001,
- entry_id: 1020201,
- x: 8320,
- y: -3486,
- z: -25080
- },
- {
- names: {
- th: "Penacony - The Reverie (Dreamscape Hotel)",
- en: "Penacony - The Reverie (Dreamscape)",
- "zh-CN": "匹诺康尼 - 「白日梦」酒店-梦境",
- "zh-TW": "匹諾康尼 - 「白日夢」飯店-夢境",
- ja: "ピノコニー - 「レバリー」ホテル-夢境",
- ko: "페나코니 - 「레버리」 호텔-꿈속",
- ru: "Пенакония - Отель «Грёзы» (Мир грёз)"
- },
- plane_id: 10301,
- floor_id: 10301001,
- entry_id: 1030101,
- x: 0,
- y: 0,
- z: 0
- },
- {
- names: {
- th: "Penacony - ชั่วโมงทอง (Golden Hour)",
- en: "Penacony - Golden Hour",
- "zh-CN": "匹诺康尼 - 黄金的时刻",
- "zh-TW": "匹諾康尼 - 黃金的時刻",
- ja: "ピノコニー - 黄金の刻",
- ko: "페나코니 - 황금의 순간",
- ru: "Пенакония - Золотой миг"
- },
- plane_id: 10302,
- floor_id: 10302001,
- entry_id: 1030201,
- x: -3556,
- y: 1539,
- z: -378220
- }
-];
-
-export function getTeleportName(preset, langOverride = null) {
- const lang = langOverride || getCurrentLanguage();
- return preset.names?.[lang] || preset.names?.en || preset.names?.th || "Teleport Point";
-}
diff --git a/scripts/fetch_avatars.js b/scripts/fetch_avatars.js
index 50fbacd..c9286c2 100644
--- a/scripts/fetch_avatars.js
+++ b/scripts/fetch_avatars.js
@@ -8,7 +8,10 @@ const ids = [
1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112,
1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210, 1211, 1212, 1213, 1214, 1215, 1217, 1218, 1220, 1221, 1222, 1223, 1225,
1005, 1006, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1310, 1312, 1313, 1314, 1315, 1317,
- 1401, 1402, 1403, 1404, 1405, 1407, 1408, 1409, 1410
+ 1401, 1402, 1403, 1404, 1405, 1407, 1408, 1409, 1410,
+ 1014, 1015, 1321, 1406, 1412, 1413, 1414, 1415,
+ 1501, 1502, 1503, 1504, 1505, 1506, 1507, 1508, 1509, 1510, 1511, 1512, 1513,
+ 8009, 8010
];
const targetDir = path.join(__dirname, '..', 'assets', 'avatars');
@@ -53,12 +56,20 @@ async function processId(id) {
ok = await fetchUrl(rawUrl, dest);
if (ok) return { id, status: 'ok (raw)' };
- // Fallbacks for trailblazer female variants if not directly mapped
- if ([8004, 8006, 8008].includes(id) && fs.existsSync(path.join(targetDir, '8002.png'))) {
+ // Fallbacks for special & collab variants
+ if (id === 1506 && fs.existsSync(path.join(targetDir, '1006.png'))) {
+ fs.copyFileSync(path.join(targetDir, '1006.png'), dest);
+ return { id, status: 'fallback 1006 (Silver Wolf)' };
+ }
+ if (id === 1511 && fs.existsSync(path.join(targetDir, '1308.png'))) {
+ fs.copyFileSync(path.join(targetDir, '1308.png'), dest);
+ return { id, status: 'fallback 1308 (Acheron)' };
+ }
+ if ([8004, 8006, 8008, 8010].includes(id) && fs.existsSync(path.join(targetDir, '8002.png'))) {
fs.copyFileSync(path.join(targetDir, '8002.png'), dest);
return { id, status: 'fallback 8002' };
}
- if ([8003, 8005, 8007].includes(id) && fs.existsSync(path.join(targetDir, '8001.png'))) {
+ if ([8003, 8005, 8007, 8009].includes(id) && fs.existsSync(path.join(targetDir, '8001.png'))) {
fs.copyFileSync(path.join(targetDir, '8001.png'), dest);
return { id, status: 'fallback 8001' };
}