해방 완료일 시뮬레이션 기반 로직으로 교체
포인트 이월(캐스케이드) 및 주간/월간 리셋을 정확히 반영하기 위해 weeksNeeded 공식 대신 이벤트 시뮬레이션으로 완료일을 계산. - 시작일 당일: (주간 - 완료된 주간 몫) + (이번 달 월간, 검은 마법사 미완료 시) - 이후 매주 목요일에 주간, 매월 1일에 월간 적립 - 누적이 잔여 흔적을 처음 넘는 이벤트 날짜가 해방일 메이플로드/츄츄지지 계산기 결과와 동일하게 동작함을 확인. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8eaf27d143
commit
1163f77266
1 changed files with 51 additions and 40 deletions
|
|
@ -146,59 +146,70 @@ export default function Liberation() {
|
||||||
return result
|
return result
|
||||||
}, [state])
|
}, [state])
|
||||||
|
|
||||||
const currentChapterCap = GENESIS_CHAPTERS[state.startChapter]?.required ?? 0
|
// 포인트 이월 계산: 현재 퀘스트의 required를 초과하면 자동으로 다음 퀘스트로 넘어감
|
||||||
const currentChapterFilled = Math.min(state.currentPoints, currentChapterCap)
|
const priorConsumed = GENESIS_CHAPTERS
|
||||||
const initialAccumulated = GENESIS_CHAPTERS
|
|
||||||
.slice(0, state.startChapter)
|
.slice(0, state.startChapter)
|
||||||
.reduce((s, c) => s + c.required, 0) + currentChapterFilled
|
.reduce((s, c) => s + c.required, 0)
|
||||||
|
let cascadeIdx = state.startChapter
|
||||||
|
let cascadeRemain = state.currentPoints
|
||||||
|
let cascadeConsumed = 0
|
||||||
|
while (cascadeIdx < GENESIS_CHAPTERS.length && cascadeRemain >= GENESIS_CHAPTERS[cascadeIdx].required) {
|
||||||
|
cascadeConsumed += GENESIS_CHAPTERS[cascadeIdx].required
|
||||||
|
cascadeRemain -= GENESIS_CHAPTERS[cascadeIdx].required
|
||||||
|
cascadeIdx++
|
||||||
|
}
|
||||||
|
const initialAccumulated = priorConsumed + cascadeConsumed + cascadeRemain
|
||||||
const alreadyDone = initialAccumulated >= GENESIS_TOTAL
|
const alreadyDone = initialAccumulated >= GENESIS_TOTAL
|
||||||
const weeklyEarn = calcWeekPoints(state.weekly)
|
const weeklyEarn = calcWeekPoints(state.weekly)
|
||||||
const remaining = Math.max(GENESIS_TOTAL - initialAccumulated, 0)
|
const remaining = Math.max(GENESIS_TOTAL - initialAccumulated, 0)
|
||||||
// 주간/월간 분리 계산
|
// 주간/월간 분리
|
||||||
const doneEarn = calcDoneEarn(state.weekly)
|
const doneEarn = calcDoneEarn(state.weekly)
|
||||||
const monthlyEarn = calcMonthlyEarn(state.weekly)
|
const monthlyEarn = calcMonthlyEarn(state.weekly)
|
||||||
const monthlyDoneEarn = calcMonthlyDoneEarn(state.weekly)
|
const monthlyDoneThisMonth = !!state.weekly.blackMage?.done
|
||||||
|
|
||||||
function monthResetsBetween(start, end) {
|
// 날짜 이벤트 시뮬레이션으로 해방일 계산
|
||||||
let count = 0
|
function computeCompletionDate() {
|
||||||
let cursor = start.add(1, 'month').startOf('month')
|
if (alreadyDone) return todayKST()
|
||||||
while (!cursor.isAfter(end)) {
|
if (weeklyEarn === 0 && monthlyEarn === 0) return null
|
||||||
count++
|
if (remaining <= 0) return dayjs(state.startDate).tz('Asia/Seoul').startOf('day').toDate()
|
||||||
cursor = cursor.add(1, 'month').startOf('month')
|
|
||||||
|
const startKST = dayjs(state.startDate).tz('Asia/Seoul').startOf('day')
|
||||||
|
const events = []
|
||||||
|
|
||||||
|
// 시작일 당일: (주간 - 완료된 주간) + (이번 달 월간, 아직 안 잡았을 때)
|
||||||
|
const day0Weekly = Math.max(weeklyEarn - doneEarn, 0)
|
||||||
|
const day0Monthly = monthlyEarn > 0 && !monthlyDoneThisMonth ? monthlyEarn : 0
|
||||||
|
events.push({ date: startKST, amount: day0Weekly + day0Monthly })
|
||||||
|
|
||||||
|
// 다음 목요일부터 매주 주간 적립
|
||||||
|
const dow = startKST.day()
|
||||||
|
const daysToNextThu = dow < 4 ? 4 - dow : 11 - dow
|
||||||
|
let nextThu = startKST.add(daysToNextThu, 'day')
|
||||||
|
for (let i = 0; i < 520; i++) {
|
||||||
|
events.push({ date: nextThu, amount: weeklyEarn })
|
||||||
|
nextThu = nextThu.add(1, 'week')
|
||||||
}
|
}
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
const startKST = dayjs(state.startDate).tz('Asia/Seoul')
|
// 다음 달 1일부터 매월 월간 적립
|
||||||
const currentMonthBMAvailable = monthlyEarn > 0 && monthlyDoneEarn === 0 ? monthlyEarn : 0
|
if (monthlyEarn > 0) {
|
||||||
const adjustedRemaining = remaining + doneEarn + monthlyDoneEarn
|
let nextMonth = startKST.add(1, 'month').startOf('month')
|
||||||
let weeksNeeded = null
|
for (let i = 0; i < 120; i++) {
|
||||||
if (!alreadyDone && (weeklyEarn > 0 || monthlyEarn > 0)) {
|
events.push({ date: nextMonth, amount: monthlyEarn })
|
||||||
for (let N = 1; N <= 520; N++) {
|
nextMonth = nextMonth.add(1, 'month')
|
||||||
const weeklyCum = N * weeklyEarn
|
|
||||||
const endKST = startKST.add(N, 'week')
|
|
||||||
const monthlyCum = currentMonthBMAvailable + monthResetsBetween(startKST, endKST) * monthlyEarn
|
|
||||||
if (weeklyCum + monthlyCum >= adjustedRemaining) {
|
|
||||||
weeksNeeded = N
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
events.sort((a, b) => a.date.diff(b.date))
|
||||||
|
let cumulative = 0
|
||||||
|
for (const e of events) {
|
||||||
|
cumulative += e.amount
|
||||||
|
if (cumulative >= remaining) return e.date.toDate()
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 시작 날짜 기준 다음(또는 당일) 목요일
|
const completionDate = computeCompletionDate()
|
||||||
const startDay = dayjs(state.startDate).tz('Asia/Seoul')
|
const isDone = completionDate !== null
|
||||||
const dow = startDay.day() // 0=일 ... 4=목
|
|
||||||
const daysToThu = dow <= 4 ? 4 - dow : 11 - dow
|
|
||||||
const upcomingThursday = startDay.add(daysToThu, 'day').startOf('day')
|
|
||||||
|
|
||||||
const isDone = alreadyDone || weeksNeeded !== null
|
|
||||||
// 시작일의 주 = 1주차, 다음 목요일 = 2주차 시작
|
|
||||||
// 완료일 = N주차의 목요일 = upcomingThursday + (weeksNeeded - 2) * 7일
|
|
||||||
const completionDate = alreadyDone
|
|
||||||
? todayKST()
|
|
||||||
: weeksNeeded !== null
|
|
||||||
? upcomingThursday.add(weeksNeeded - 2, 'week').toDate()
|
|
||||||
: null
|
|
||||||
|
|
||||||
const updateWeek = (idx, newWeekData) => {
|
const updateWeek = (idx, newWeekData) => {
|
||||||
setState((prev) => ({
|
setState((prev) => ({
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue