feat: upgrade character build modal with skills info, traces, energy, and lightcones
This commit is contained in:
1 parent
edc2843f0c
commit
42b6d6a5a9
4 files changed
+761
-106
No files matched your search
@@ -106,6 +106,47 @@ const el = {
|
||||
btnModalMaxSkills: document.getElementById('btn-modal-max-skills'),
|
||||
btnModalSaveAvatar: document.getElementById('btn-modal-save-avatar'),
|
||||
|
||||
// Skills Info Steppers (Inspired by srtools.punklorde.org/skills-info)
|
||||
valSkillBasic: document.getElementById('val-skill-basic'),
|
||||
rangeSkillBasic: document.getElementById('range-skill-basic'),
|
||||
btnStepDecBasic: document.getElementById('btn-step-dec-basic'),
|
||||
btnStepIncBasic: document.getElementById('btn-step-inc-basic'),
|
||||
btnSkillMaxBasic: document.getElementById('btn-skill-max-basic'),
|
||||
|
||||
valSkillSkill: document.getElementById('val-skill-skill'),
|
||||
rangeSkillSkill: document.getElementById('range-skill-skill'),
|
||||
btnStepDecSkill: document.getElementById('btn-step-dec-skill'),
|
||||
btnStepIncSkill: document.getElementById('btn-step-inc-skill'),
|
||||
btnSkillMaxSkill: document.getElementById('btn-skill-max-skill'),
|
||||
|
||||
valSkillUltra: document.getElementById('val-skill-ultra'),
|
||||
rangeSkillUltra: document.getElementById('range-skill-ultra'),
|
||||
btnStepDecUltra: document.getElementById('btn-step-dec-ultra'),
|
||||
btnStepIncUltra: document.getElementById('btn-step-inc-ultra'),
|
||||
btnSkillMaxUltra: document.getElementById('btn-skill-max-ultra'),
|
||||
|
||||
valSkillTalent: document.getElementById('val-skill-talent'),
|
||||
rangeSkillTalent: document.getElementById('range-skill-talent'),
|
||||
btnStepDecTalent: document.getElementById('btn-step-dec-talent'),
|
||||
btnStepIncTalent: document.getElementById('btn-step-inc-talent'),
|
||||
btnSkillMaxTalent: document.getElementById('btn-skill-max-talent'),
|
||||
|
||||
checkTraceTechnique: document.getElementById('check-trace-technique'),
|
||||
checkTraceA2: document.getElementById('check-trace-a2'),
|
||||
checkTraceA4: document.getElementById('check-trace-a4'),
|
||||
checkTraceA6: document.getElementById('check-trace-a6'),
|
||||
checkTraceStats: document.getElementById('check-trace-stats'),
|
||||
|
||||
btnModalEnergy50: document.getElementById('btn-modal-energy-50'),
|
||||
btnModalEnergy100: document.getElementById('btn-modal-energy-100'),
|
||||
rangeCharEnergy: document.getElementById('range-char-energy'),
|
||||
valCharEnergy: document.getElementById('val-char-energy'),
|
||||
|
||||
modalLcId: document.getElementById('modal-lc-id'),
|
||||
modalLcLevel: document.getElementById('modal-lc-level'),
|
||||
btnModalMaxLc: document.getElementById('btn-modal-max-lc'),
|
||||
modalLcRankBtns: document.getElementById('modal-lc-rank-btns'),
|
||||
|
||||
// Lineup Picker Modal
|
||||
modalLineupPicker: document.getElementById('modal-lineup-picker'),
|
||||
btnClosePickerModal: document.getElementById('btn-close-picker-modal'),
|
||||
@@ -671,15 +712,52 @@ async function maxSingleAvatar(avatarId) {
|
||||
}
|
||||
}
|
||||
|
||||
// Character Detail / Build Modal
|
||||
// Active Lightcone Superimposition Rank in Modal (1 - 5)
|
||||
let selectedLcRank = 5;
|
||||
|
||||
// Modal Sub-tabs Handler
|
||||
function setupModalSubtabs() {
|
||||
const tabBtns = document.querySelectorAll('.modal-subtab-btn');
|
||||
tabBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
tabBtns.forEach(b => b.classList.remove('active'));
|
||||
document.querySelectorAll('.modal-subtab-content').forEach(c => {
|
||||
c.style.display = 'none';
|
||||
c.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
const targetId = btn.getAttribute('data-modaltab');
|
||||
const targetContent = document.getElementById(targetId);
|
||||
if (targetContent) {
|
||||
targetContent.style.display = 'block';
|
||||
targetContent.classList.add('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateLcRankPips(rank) {
|
||||
selectedLcRank = rank;
|
||||
document.querySelectorAll('.lc-super-pip').forEach(btn => {
|
||||
const r = parseInt(btn.getAttribute('data-rank'), 10);
|
||||
if (r === rank) {
|
||||
btn.classList.add('active');
|
||||
} else {
|
||||
btn.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Character Detail / Build Modal (Inspired by srtools.punklorde.org/skills-info)
|
||||
function openAvatarDetailModal(avatarItem) {
|
||||
state.selectedAvatarDetail = avatarItem;
|
||||
const meta = getAvatarInfo(avatarItem.avatar_id);
|
||||
const aid = avatarItem.avatar_id;
|
||||
const meta = getAvatarInfo(aid);
|
||||
|
||||
el.modalCharTitle.textContent = `ปรับแต่งตัวละคร: ${meta.name} (ID: ${avatarItem.avatar_id})`;
|
||||
el.modalCharImg.src = `./assets/avatars/${avatarItem.avatar_id}.png`;
|
||||
el.modalCharTitle.textContent = `ปรับแต่งตัวละคร: ${meta.name} (ID: ${aid})`;
|
||||
el.modalCharImg.src = `./assets/avatars/${aid}.png`;
|
||||
el.modalCharImg.onerror = function() {
|
||||
this.src = `https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/avatar/${avatarItem.avatar_id}.png`;
|
||||
this.src = `https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/avatar/${aid}.png`;
|
||||
};
|
||||
el.modalCharName.textContent = meta.name;
|
||||
el.modalCharMeta.innerHTML = `
|
||||
@@ -688,10 +766,97 @@ function openAvatarDetailModal(avatarItem) {
|
||||
<span class="avatar-badge-rarity rarity-${meta.rarity}" style="position: static;">★ ${meta.rarity}</span>
|
||||
`;
|
||||
|
||||
// Reset to first sub-tab (Skills & Traces)
|
||||
document.querySelectorAll('.modal-subtab-btn').forEach(b => b.classList.remove('active'));
|
||||
document.querySelectorAll('.modal-subtab-content').forEach(c => {
|
||||
c.style.display = 'none';
|
||||
c.classList.remove('active');
|
||||
});
|
||||
const firstTabBtn = document.querySelector('.modal-subtab-btn[data-modaltab="modaltab-skills"]');
|
||||
if (firstTabBtn) firstTabBtn.classList.add('active');
|
||||
const firstTabContent = document.getElementById('modaltab-skills');
|
||||
if (firstTabContent) {
|
||||
firstTabContent.style.display = 'block';
|
||||
firstTabContent.classList.add('active');
|
||||
}
|
||||
|
||||
// Basic Status
|
||||
el.modalCharLevel.value = avatarItem.level || 80;
|
||||
el.modalCharPromotion.value = avatarItem.promotion || 6;
|
||||
|
||||
renderModalEidolonPips(avatarItem.rank ?? 6);
|
||||
|
||||
// Load avatar data from fullFreesrData if available
|
||||
const av = state.fullFreesrData?.avatars?.[aid];
|
||||
const skills = av?.data?.skills || {};
|
||||
const anchorSkills = av?.data?.skills_by_anchor_type || {};
|
||||
|
||||
// 1. Basic Attack: anchor 1 or aid + '001' (1-6)
|
||||
const basicVal = anchorSkills['1'] ?? skills[`${aid}001`] ?? 6;
|
||||
if (el.rangeSkillBasic && el.valSkillBasic) {
|
||||
el.rangeSkillBasic.value = Math.min(6, Math.max(1, basicVal));
|
||||
el.valSkillBasic.textContent = el.rangeSkillBasic.value;
|
||||
}
|
||||
|
||||
// 2. Skill: anchor 2 or aid + '002' (1-10)
|
||||
const skillVal = anchorSkills['2'] ?? skills[`${aid}002`] ?? 10;
|
||||
if (el.rangeSkillSkill && el.valSkillSkill) {
|
||||
el.rangeSkillSkill.value = Math.min(10, Math.max(1, skillVal));
|
||||
el.valSkillSkill.textContent = el.rangeSkillSkill.value;
|
||||
}
|
||||
|
||||
// 3. Ultimate: anchor 3 or aid + '003' (1-10)
|
||||
const ultraVal = anchorSkills['3'] ?? skills[`${aid}003`] ?? 10;
|
||||
if (el.rangeSkillUltra && el.valSkillUltra) {
|
||||
el.rangeSkillUltra.value = Math.min(10, Math.max(1, ultraVal));
|
||||
el.valSkillUltra.textContent = el.rangeSkillUltra.value;
|
||||
}
|
||||
|
||||
// 4. Talent: anchor 4 or aid + '004' (1-10)
|
||||
const talentVal = anchorSkills['4'] ?? skills[`${aid}004`] ?? 10;
|
||||
if (el.rangeSkillTalent && el.valSkillTalent) {
|
||||
el.rangeSkillTalent.value = Math.min(10, Math.max(1, talentVal));
|
||||
el.valSkillTalent.textContent = el.rangeSkillTalent.value;
|
||||
}
|
||||
|
||||
// Traces & Technique
|
||||
if (el.checkTraceTechnique) {
|
||||
el.checkTraceTechnique.checked = (anchorSkills['5'] ?? skills[`${aid}007`] ?? 1) > 0;
|
||||
}
|
||||
if (el.checkTraceA2) {
|
||||
el.checkTraceA2.checked = (anchorSkills['6'] ?? skills[`${aid}101`] ?? 1) > 0;
|
||||
}
|
||||
if (el.checkTraceA4) {
|
||||
el.checkTraceA4.checked = (anchorSkills['7'] ?? skills[`${aid}102`] ?? 1) > 0;
|
||||
}
|
||||
if (el.checkTraceA6) {
|
||||
el.checkTraceA6.checked = (anchorSkills['8'] ?? skills[`${aid}103`] ?? 1) > 0;
|
||||
}
|
||||
if (el.checkTraceStats) {
|
||||
el.checkTraceStats.checked = (anchorSkills['9'] ?? skills[`${aid}201`] ?? 1) > 0;
|
||||
}
|
||||
|
||||
// Ultimate Energy
|
||||
const maxSp = av?.sp_max ?? 140;
|
||||
const currentSp = av?.sp_value ?? maxSp;
|
||||
if (el.rangeCharEnergy && el.valCharEnergy) {
|
||||
el.rangeCharEnergy.max = maxSp;
|
||||
el.rangeCharEnergy.value = currentSp;
|
||||
el.valCharEnergy.textContent = `${currentSp}/${maxSp}`;
|
||||
}
|
||||
|
||||
// Lightcone
|
||||
const equippedLc = (state.fullFreesrData?.lightcones || []).find(lc => lc.equip_avatar === aid);
|
||||
if (equippedLc) {
|
||||
if (el.modalLcId) el.modalLcId.value = equippedLc.item_id || '';
|
||||
if (el.modalLcLevel) el.modalLcLevel.value = equippedLc.level || 80;
|
||||
selectedLcRank = equippedLc.rank || 5;
|
||||
} else {
|
||||
if (el.modalLcId) el.modalLcId.value = '';
|
||||
if (el.modalLcLevel) el.modalLcLevel.value = 80;
|
||||
selectedLcRank = 5;
|
||||
}
|
||||
updateLcRankPips(selectedLcRank);
|
||||
|
||||
el.modalAvatarDetail.classList.add('active');
|
||||
}
|
||||
|
||||
@@ -746,6 +911,63 @@ async function setAvatarRank(avatarId, rank) {
|
||||
}
|
||||
}
|
||||
|
||||
// Max Skills & Traces for character
|
||||
function maxSkillsForAvatar() {
|
||||
if (el.rangeSkillBasic && el.valSkillBasic) {
|
||||
el.rangeSkillBasic.value = 6;
|
||||
el.valSkillBasic.textContent = 6;
|
||||
}
|
||||
if (el.rangeSkillSkill && el.valSkillSkill) {
|
||||
el.rangeSkillSkill.value = 10;
|
||||
el.valSkillSkill.textContent = 10;
|
||||
}
|
||||
if (el.rangeSkillUltra && el.valSkillUltra) {
|
||||
el.rangeSkillUltra.value = 10;
|
||||
el.valSkillUltra.textContent = 10;
|
||||
}
|
||||
if (el.rangeSkillTalent && el.valSkillTalent) {
|
||||
el.rangeSkillTalent.value = 10;
|
||||
el.valSkillTalent.textContent = 10;
|
||||
}
|
||||
if (el.checkTraceTechnique) el.checkTraceTechnique.checked = true;
|
||||
if (el.checkTraceA2) el.checkTraceA2.checked = true;
|
||||
if (el.checkTraceA4) el.checkTraceA4.checked = true;
|
||||
if (el.checkTraceA6) el.checkTraceA6.checked = true;
|
||||
if (el.checkTraceStats) el.checkTraceStats.checked = true;
|
||||
|
||||
showToast('⭐ ปรับสกิลและร่องรอยเต็มทั้งหมดแล้ว', 'info');
|
||||
}
|
||||
|
||||
// Max All Build For Current Avatar (Lv.80, Prom 6, E6, Max Skills, Max Traces, 100% Energy, LC Lv.80 S5)
|
||||
async function maxAllBuildForCurrentAvatar() {
|
||||
if (!state.selectedAvatarDetail) return;
|
||||
const aid = state.selectedAvatarDetail.avatar_id;
|
||||
|
||||
// Level & Eidolon
|
||||
el.modalCharLevel.value = 80;
|
||||
el.modalCharPromotion.value = 6;
|
||||
state.selectedAvatarDetail.level = 80;
|
||||
state.selectedAvatarDetail.promotion = 6;
|
||||
state.selectedAvatarDetail.rank = 6;
|
||||
renderModalEidolonPips(6);
|
||||
|
||||
// Skills & Traces
|
||||
maxSkillsForAvatar();
|
||||
|
||||
// Energy 100%
|
||||
if (el.rangeCharEnergy && el.valCharEnergy) {
|
||||
el.rangeCharEnergy.value = el.rangeCharEnergy.max;
|
||||
el.valCharEnergy.textContent = `${el.rangeCharEnergy.max}/${el.rangeCharEnergy.max}`;
|
||||
}
|
||||
|
||||
// Lightcone
|
||||
if (el.modalLcLevel) el.modalLcLevel.value = 80;
|
||||
updateLcRankPips(5);
|
||||
|
||||
// Save changes
|
||||
await saveAvatarDetails();
|
||||
}
|
||||
|
||||
// Save avatar details from modal
|
||||
async function saveAvatarDetails() {
|
||||
if (!state.selectedAvatarDetail) return;
|
||||
@@ -765,55 +987,133 @@ async function saveAvatarDetails() {
|
||||
target.rank = rank;
|
||||
}
|
||||
|
||||
if (state.fullFreesrData?.avatars?.[aid]) {
|
||||
state.fullFreesrData.avatars[aid].level = level;
|
||||
state.fullFreesrData.avatars[aid].promotion = promotion;
|
||||
state.fullFreesrData.avatars[aid].data.rank = rank;
|
||||
if (!state.fullFreesrData) {
|
||||
state.fullFreesrData = { avatars: {}, lightcones: [], relics: [] };
|
||||
}
|
||||
if (!state.fullFreesrData.avatars) {
|
||||
state.fullFreesrData.avatars = {};
|
||||
}
|
||||
if (!state.fullFreesrData.avatars[aid]) {
|
||||
state.fullFreesrData.avatars[aid] = {
|
||||
owner_uid: 0,
|
||||
avatar_id: aid,
|
||||
level,
|
||||
promotion,
|
||||
techniques: [],
|
||||
sp_value: parseInt(el.rangeCharEnergy?.value, 10) || 140,
|
||||
sp_max: parseInt(el.rangeCharEnergy?.max, 10) || 140,
|
||||
data: {
|
||||
rank,
|
||||
skills: {},
|
||||
skills_by_anchor_type: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const av = state.fullFreesrData.avatars[aid];
|
||||
av.level = level;
|
||||
av.promotion = promotion;
|
||||
av.data = av.data || {};
|
||||
av.data.rank = rank;
|
||||
if (el.rangeCharEnergy) {
|
||||
av.sp_value = parseInt(el.rangeCharEnergy.value, 10) || av.sp_max || 140;
|
||||
}
|
||||
|
||||
// Extract skills values
|
||||
const basicVal = parseInt(el.rangeSkillBasic?.value, 10) || 6;
|
||||
const skillVal = parseInt(el.rangeSkillSkill?.value, 10) || 10;
|
||||
const ultraVal = parseInt(el.rangeSkillUltra?.value, 10) || 10;
|
||||
const talentVal = parseInt(el.rangeSkillTalent?.value, 10) || 10;
|
||||
const techVal = el.checkTraceTechnique?.checked ? 1 : 0;
|
||||
const a2Val = el.checkTraceA2?.checked ? 1 : 0;
|
||||
const a4Val = el.checkTraceA4?.checked ? 1 : 0;
|
||||
const a6Val = el.checkTraceA6?.checked ? 1 : 0;
|
||||
const statVal = el.checkTraceStats?.checked ? 1 : 0;
|
||||
|
||||
av.data.skills = av.data.skills || {};
|
||||
av.data.skills[`${aid}001`] = basicVal;
|
||||
av.data.skills[`${aid}002`] = skillVal;
|
||||
av.data.skills[`${aid}003`] = ultraVal;
|
||||
av.data.skills[`${aid}004`] = talentVal;
|
||||
av.data.skills[`${aid}007`] = techVal;
|
||||
av.data.skills[`${aid}101`] = a2Val;
|
||||
av.data.skills[`${aid}102`] = a4Val;
|
||||
av.data.skills[`${aid}103`] = a6Val;
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const pad = i < 10 ? `0${i}` : `${i}`;
|
||||
av.data.skills[`${aid}2${pad}`] = statVal;
|
||||
}
|
||||
|
||||
av.data.skills_by_anchor_type = {
|
||||
"1": basicVal,
|
||||
"2": skillVal,
|
||||
"3": ultraVal,
|
||||
"4": talentVal,
|
||||
"5": techVal,
|
||||
"6": a2Val,
|
||||
"7": a4Val,
|
||||
"8": a6Val,
|
||||
"9": statVal,
|
||||
"10": statVal,
|
||||
"11": statVal,
|
||||
"12": statVal,
|
||||
"13": statVal,
|
||||
"14": statVal,
|
||||
"15": statVal,
|
||||
"16": statVal,
|
||||
"17": statVal,
|
||||
"18": statVal
|
||||
};
|
||||
|
||||
// Lightcone update
|
||||
state.fullFreesrData.lightcones = state.fullFreesrData.lightcones || [];
|
||||
const lcId = parseInt(el.modalLcId?.value, 10);
|
||||
const lcLevel = parseInt(el.modalLcLevel?.value, 10) || 80;
|
||||
const lcRank = selectedLcRank || 5;
|
||||
const lcProm = lcLevel > 70 ? 6 : (lcLevel > 60 ? 5 : (lcLevel > 50 ? 4 : (lcLevel > 40 ? 3 : (lcLevel > 20 ? 1 : 0))));
|
||||
|
||||
const existingLcIndex = state.fullFreesrData.lightcones.findIndex(lc => lc.equip_avatar === aid);
|
||||
if (lcId && lcId > 0) {
|
||||
if (existingLcIndex !== -1) {
|
||||
state.fullFreesrData.lightcones[existingLcIndex].item_id = lcId;
|
||||
state.fullFreesrData.lightcones[existingLcIndex].level = lcLevel;
|
||||
state.fullFreesrData.lightcones[existingLcIndex].rank = lcRank;
|
||||
state.fullFreesrData.lightcones[existingLcIndex].promotion = lcProm;
|
||||
} else {
|
||||
state.fullFreesrData.lightcones.push({
|
||||
internal_uid: state.fullFreesrData.lightcones.length,
|
||||
item_id: lcId,
|
||||
equip_avatar: aid,
|
||||
level: lcLevel,
|
||||
rank: lcRank,
|
||||
promotion: lcProm
|
||||
});
|
||||
}
|
||||
} else if (existingLcIndex !== -1) {
|
||||
state.fullFreesrData.lightcones[existingLcIndex].equip_avatar = null;
|
||||
}
|
||||
|
||||
renderAvatarsGrid();
|
||||
loadEquipment();
|
||||
el.modalAvatarDetail.classList.remove('active');
|
||||
|
||||
if (state.isConnected) {
|
||||
try {
|
||||
if (state.serverMode === 'gac') {
|
||||
await apiCall('/api/freesr/avatars', 'POST', { avatar_id: aid, level, promotion, rank });
|
||||
await saveFreesrChangesToServer();
|
||||
} else {
|
||||
await saveFreesrChangesToServer();
|
||||
}
|
||||
showToast(`บันทึกตัวละคร ${getAvatarInfo(aid).name} เรียบร้อย (Lv. ${level}, Prom. ${promotion}, E${rank})`, 'success');
|
||||
showToast(`บันทึกตัวละคร ${getAvatarInfo(aid).name} เรียบร้อย`, 'success');
|
||||
} catch (err) {
|
||||
showToast(`บันทึกในเครื่องแล้ว (ส่งเซิร์ฟเวอร์ไม่ได้: ${err.message})`, 'warning');
|
||||
}
|
||||
} else {
|
||||
showToast(`บันทึกตัวละคร ${getAvatarInfo(aid).name} เรียบร้อย (Lv. ${level}, Prom. ${promotion}, E${rank})`, 'success');
|
||||
showToast(`บันทึกตัวละคร ${getAvatarInfo(aid).name} เรียบร้อย`, 'success');
|
||||
}
|
||||
}
|
||||
|
||||
// Max Skills & Traces for character
|
||||
async function maxSkillsForAvatar() {
|
||||
if (!state.selectedAvatarDetail) return;
|
||||
const aid = state.selectedAvatarDetail.avatar_id;
|
||||
el.modalCharLevel.value = 80;
|
||||
el.modalCharPromotion.value = 6;
|
||||
state.selectedAvatarDetail.level = 80;
|
||||
state.selectedAvatarDetail.promotion = 6;
|
||||
state.selectedAvatarDetail.rank = 6;
|
||||
renderModalEidolonPips(6);
|
||||
|
||||
if (state.fullFreesrData?.avatars?.[aid]) {
|
||||
const av = state.fullFreesrData.avatars[aid];
|
||||
av.level = 80;
|
||||
av.promotion = 6;
|
||||
av.data.rank = 6;
|
||||
if (av.data.skills) {
|
||||
for (const k in av.data.skills) av.data.skills[k] = 10;
|
||||
}
|
||||
}
|
||||
|
||||
showToast(`อัพเลเวล 80, เลื่อนขั้น 6, E6 และ Traces 10/10 สำหรับ ${getAvatarInfo(aid).name} สำเร็จ!`, 'success');
|
||||
}
|
||||
|
||||
// Max all avatars
|
||||
async function maxAllAvatars() {
|
||||
if (!confirm(t('confirmMaxAll'))) return;
|
||||
@@ -1184,17 +1484,62 @@ function setupEvents() {
|
||||
document.getElementById('btn-modal-cancel-avatar')?.addEventListener('click', () => el.modalAvatarDetail.classList.remove('active'));
|
||||
el.btnModalSaveAvatar.addEventListener('click', saveAvatarDetails);
|
||||
el.btnModalMaxSkills.addEventListener('click', maxSkillsForAvatar);
|
||||
document.getElementById('btn-modal-max-all-char')?.addEventListener('click', maxAllBuildForCurrentAvatar);
|
||||
|
||||
document.getElementById('btn-modal-max-all-char')?.addEventListener('click', async () => {
|
||||
if (!state.selectedAvatarDetail) return;
|
||||
el.modalCharLevel.value = 80;
|
||||
el.modalCharPromotion.value = 6;
|
||||
state.selectedAvatarDetail.level = 80;
|
||||
state.selectedAvatarDetail.promotion = 6;
|
||||
state.selectedAvatarDetail.rank = 6;
|
||||
renderModalEidolonPips(6);
|
||||
await maxSkillsForAvatar();
|
||||
await saveAvatarDetails();
|
||||
// Steppers for 4 Skills (Basic, Skill, Ultra, Talent)
|
||||
const setupSkillStepper = (rangeEl, valEl, decBtn, incBtn, maxBtn, maxVal) => {
|
||||
if (!rangeEl || !valEl) return;
|
||||
rangeEl.addEventListener('input', () => { valEl.textContent = rangeEl.value; });
|
||||
decBtn?.addEventListener('click', () => {
|
||||
let cur = parseInt(rangeEl.value, 10);
|
||||
if (cur > parseInt(rangeEl.min, 10)) {
|
||||
rangeEl.value = cur - 1;
|
||||
valEl.textContent = rangeEl.value;
|
||||
}
|
||||
});
|
||||
incBtn?.addEventListener('click', () => {
|
||||
let cur = parseInt(rangeEl.value, 10);
|
||||
if (cur < parseInt(rangeEl.max, 10)) {
|
||||
rangeEl.value = cur + 1;
|
||||
valEl.textContent = rangeEl.value;
|
||||
}
|
||||
});
|
||||
maxBtn?.addEventListener('click', () => {
|
||||
rangeEl.value = maxVal;
|
||||
valEl.textContent = maxVal;
|
||||
});
|
||||
};
|
||||
|
||||
setupSkillStepper(el.rangeSkillBasic, el.valSkillBasic, el.btnStepDecBasic, el.btnStepIncBasic, el.btnSkillMaxBasic, 6);
|
||||
setupSkillStepper(el.rangeSkillSkill, el.valSkillSkill, el.btnStepDecSkill, el.btnStepIncSkill, el.btnSkillMaxSkill, 10);
|
||||
setupSkillStepper(el.rangeSkillUltra, el.valSkillUltra, el.btnStepDecUltra, el.btnStepIncUltra, el.btnSkillMaxUltra, 10);
|
||||
setupSkillStepper(el.rangeSkillTalent, el.valSkillTalent, el.btnStepDecTalent, el.btnStepIncTalent, el.btnSkillMaxTalent, 10);
|
||||
|
||||
// Ultimate Energy Controls
|
||||
el.rangeCharEnergy?.addEventListener('input', () => {
|
||||
el.valCharEnergy.textContent = `${el.rangeCharEnergy.value}/${el.rangeCharEnergy.max}`;
|
||||
});
|
||||
el.btnModalEnergy50?.addEventListener('click', () => {
|
||||
const half = Math.floor(parseInt(el.rangeCharEnergy.max, 10) * 0.5);
|
||||
el.rangeCharEnergy.value = half;
|
||||
el.valCharEnergy.textContent = `${half}/${el.rangeCharEnergy.max}`;
|
||||
});
|
||||
el.btnModalEnergy100?.addEventListener('click', () => {
|
||||
el.rangeCharEnergy.value = el.rangeCharEnergy.max;
|
||||
el.valCharEnergy.textContent = `${el.rangeCharEnergy.max}/${el.rangeCharEnergy.max}`;
|
||||
});
|
||||
|
||||
// Lightcone Controls in Modal
|
||||
document.querySelectorAll('.lc-super-pip').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const r = parseInt(btn.getAttribute('data-rank'), 10);
|
||||
updateLcRankPips(r);
|
||||
});
|
||||
});
|
||||
el.btnModalMaxLc?.addEventListener('click', () => {
|
||||
if (el.modalLcLevel) el.modalLcLevel.value = 80;
|
||||
updateLcRankPips(5);
|
||||
showToast('ปรับกรวยแสงเป็นเลเวล 80 ขัดเกลา 5 เรียบร้อย', 'info');
|
||||
});
|
||||
|
||||
// Quick Preset Buttons for Character Modal
|
||||
@@ -1430,6 +1775,7 @@ function init() {
|
||||
setupServerModes();
|
||||
setupLanguageSelector();
|
||||
setupTabs();
|
||||
setupModalSubtabs();
|
||||
setupEvents();
|
||||
applyTranslations();
|
||||
refreshStatus();
|
||||
|
||||
+8
-8
@@ -46,8 +46,8 @@ export const TRANSLATIONS = {
|
||||
btnSyncPersistent: "💾 ซิงค์ข้อมูล Persistent ล่าสุด",
|
||||
currentLineupTitle: "🎮 ทีมปัจจุบัน",
|
||||
slot: "ตำแหน่งที่",
|
||||
slotEmpty: "(ว่าง)",
|
||||
coords: "พิกัดตัวละคร (X, Y, Z)",
|
||||
slotEmpty: "ว่าง",
|
||||
coords: "พิกัดตัวละคร XYZ",
|
||||
// Avatars
|
||||
searchPlaceholder: "🔍 ค้นหาชื่อหรือ ID ตัวละคร...",
|
||||
filterAllRarity: "ทุกระดับดาว",
|
||||
@@ -56,7 +56,7 @@ export const TRANSLATIONS = {
|
||||
btnReload: "🔄 รีโหลด",
|
||||
eidolonLabel: "Eidolon:",
|
||||
confirmMaxAll: "ยืนยันปรับตัวละครทั้งหมดให้เป็นเลเวล 80 และปลด E6 หรือไม่?",
|
||||
maxAllSuccess: "ปรับ Max ทุกตัวละครสำเร็จแล้ว!",
|
||||
maxAllSuccess: "ปรับเต็มทุกตัวละครสำเร็จแล้ว!",
|
||||
avatarRankSuccess: "ปรับตัวละคร {id} เป็น E{rank} สำเร็จ",
|
||||
// Ex-Rank
|
||||
exrankTitle: "⚡ ระบบปลดล็อค Ex-Rank Skill",
|
||||
@@ -74,20 +74,20 @@ export const TRANSLATIONS = {
|
||||
customTeleportSubtitle: "ใส่ PlaneID, FloorID และ EntryID ตามต้องการ",
|
||||
btnExecuteWarp: "🚀 ยืนยันการวาร์ป",
|
||||
warpBtn: "🚀 วาร์ป",
|
||||
warpSuccess: "วาร์ปไปยัง {label} สำเร็จ (บันทึกลง Persistent)",
|
||||
warpSuccess: "วาร์ปไปยัง {label} สำเร็จ",
|
||||
warpFailed: "วาร์ปไม่สำเร็จ: {err}",
|
||||
// JSON
|
||||
btnFreesr: "📄 freesr-data.json",
|
||||
btnPersistent: "💾 ไฟล์เซฟ persistent",
|
||||
btnBeautify: "✨ จัดรูปแบบ JSON",
|
||||
btnDownload: "📥 ดาวน์โหลด Backup",
|
||||
btnDownload: "📥 ดาวน์โหลดสำรอง",
|
||||
btnUpload: "📤 อัปโหลดไฟล์",
|
||||
btnSaveServer: "💾 บันทึกไปยังเซิร์ฟเวอร์",
|
||||
jsonTip: "Tip: สามารถแก้ไขค่าดิบแล้วกด 'บันทึกไปยังเซิร์ฟเวอร์' ได้ทันที",
|
||||
jsonTip: "คำแนะนำ: สามารถแก้ไขค่าดิบแล้วกด 'บันทึกไปยังเซิร์ฟเวอร์' ได้ทันที",
|
||||
jsonStatusReady: "พร้อมใช้งาน",
|
||||
jsonStatusOpening: "กำลังเปิด: {name}",
|
||||
jsonSaveSuccess: "บันทึกไฟล์ {name} เรียบร้อยแล้ว",
|
||||
jsonInvalid: "JSON ไม่ถูกต้อง",
|
||||
jsonInvalid: "รูปแบบ JSON ไม่ถูกต้อง",
|
||||
// Settings
|
||||
apiSettingsTitle: "⚙️ ตั้งค่าการเชื่อมต่อ API Gateway",
|
||||
apiSettingsSubtitle: "ปรับเปลี่ยนพอร์ตหรือ IP ได้ตามไฟล์ server.json ของคุณ",
|
||||
@@ -95,7 +95,7 @@ export const TRANSLATIONS = {
|
||||
lblPingInterval: "ความถี่ในการตรวจสอบสถานะ (วินาที)",
|
||||
btnSaveSettings: "💾 บันทึกการตั้งค่า",
|
||||
btnResetDefault: "คืนค่าเริ่มต้น",
|
||||
hostGuideTitle: "🌐 วิธีนำขึ้นโฮสต์ GitHub Pages (ฟรี 100%)",
|
||||
hostGuideTitle: "🌐 วิธีนำขึ้นโฮสต์ GitHub Pages",
|
||||
hostGuideSubtitle: "สามารถเปิดใช้งานเว็บนี้บนอินเทอร์เน็ตได้โดยไม่ต้องเสียค่าโฮสต์ใดๆ",
|
||||
settingsSaved: "บันทึกการตั้งค่าสำเร็จ!",
|
||||
settingsReset: "คืนค่าเริ่มต้นเรียบร้อย",
|
||||
|
||||
Reference in new issue
Block a user