diff --git a/GAME.md b/GAME.md index ce8eecd..ac95fe9 100644 --- a/GAME.md +++ b/GAME.md @@ -130,6 +130,7 @@ The app models this with the New item comparison panel: - Enter the new roll's base stat and effects. - The app shows DPS and Regen change separately. - If `Pet` is selected, the app chooses the current worst pet by piece score as the replacement target. +- Swapping in the new roll stores the replaced piece and prior New item fields so the user can swap back. ## Profiles diff --git a/README.md b/README.md index 78aa1c2..7ec6ad3 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ For the game rules, stat caps, formulas, assumptions, and known limitations used - choose what it would replace, - enter the new roll's value and effects, - compare DPS and Regen changes before swapping. + - use **Swap in new item** to apply it, with **Swap back** available if you want to undo. 7. Use **Best sacrifice candidates** as a quick shortlist of low-value pieces. When comparing a new pet, choose `Pet`; the app automatically compares against your currently weakest pet. diff --git a/app.js b/app.js index 321d068..0221a7a 100644 --- a/app.js +++ b/app.js @@ -71,6 +71,7 @@ function createDefaultNewItem() { baseDamage: 0, baseHealth: 0, effects: [{ type: "", value: 1 }, { type: "", value: 1 }], + lastSwap: null, }; } @@ -93,13 +94,14 @@ function createDefaultPiece(slot, index) { function enforceFixedPieceFields(piece, index) { const fixed = FIXED_ITEMS[index]; if (!fixed || piece.type !== "item") return piece; + const baseValue = Number(piece.baseValue || (fixed.baseType === "damage" ? piece.baseDamage : piece.baseHealth) || fixed.baseValue); return { ...piece, name: fixed.name, baseType: fixed.baseType, - baseValue: fixed.baseValue, - baseDamage: fixed.baseType === "damage" ? fixed.baseValue : 0, - baseHealth: fixed.baseType === "health" ? fixed.baseValue : 0, + baseValue, + baseDamage: fixed.baseType === "damage" ? baseValue : 0, + baseHealth: fixed.baseType === "health" ? baseValue : 0, fixedBase: true, }; } @@ -147,6 +149,7 @@ function normalizeNewItem(candidate) { baseDamage: Math.max(0, Number(candidate?.baseDamage || 0)), baseHealth: Math.max(0, Number(candidate?.baseHealth || 0)), effects, + lastSwap: candidate?.lastSwap || null, }; } @@ -296,6 +299,7 @@ function renderNewItemComparison() { `; row.querySelector("select").value = effect.type; effects.appendChild(row); @@ -343,6 +347,29 @@ function renderReplacementComparison(totals, currentDps, currentRegen, pieceScor $("#replacementDpsDelta").className = deltaClass(dpsDelta); $("#replacementRegenDelta").textContent = formatDelta(regenDelta, "%"); $("#replacementRegenDelta").className = deltaClass(regenDelta); + updateNewItemImpactHints(replacementTotals, replacementDps); + updateSwapControls(); +} + +function updateNewItemImpactHints(replacementTotals, replacementDps) { + document.querySelectorAll("#newItemEffects .effect-row").forEach((row, effectIndex) => { + const effect = state.newItem.effects[effectIndex]; + const hint = row.querySelector(".cap-hint"); + if (!effect?.type) { + hint.textContent = "Ignored"; + return; + } + const current = clampEffectValue(effect.type, Number(effect.value)); + const withoutThis = calculateDps(cloneTotalsWithEffectDelta(replacementTotals, effect.type, -current), state.combatStyle); + const currentGain = percentChange(withoutThis, replacementDps); + hint.textContent = `Actual: ${fmt(currentGain)}% DPS`; + }); +} + +function updateSwapControls() { + const hasSwap = Boolean(state.newItem.lastSwap); + $("#swapBackNewItem").disabled = !hasSwap; + $("#swapStatus").textContent = hasSwap ? `Can swap back ${state.newItem.lastSwap.piece?.name || "previous item"}.` : ""; } function calculateTotals() { @@ -404,6 +431,68 @@ function createNewItemCandidate(target) { }; } +function createPieceFromCandidate(existingPiece, candidate) { + const baseDamage = Number(candidate.baseDamage || 0); + const baseHealth = Number(candidate.baseHealth || 0); + return enforceFixedPieceFields({ + ...existingPiece, + baseDamage, + baseHealth, + baseType: existingPiece.fixedBase ? existingPiece.baseType : baseHealth > baseDamage ? "health" : "damage", + baseValue: existingPiece.fixedBase + ? (existingPiece.baseType === "health" ? baseHealth : baseDamage) + : Math.max(baseDamage, baseHealth), + effects: candidate.effects.map((effect) => ({ ...effect })), + }, state.pieces.indexOf(existingPiece)); +} + +function clonePiece(piece) { + return { + ...piece, + effects: (piece.effects || []).map((effect) => ({ ...effect })), + }; +} + +function cloneNewItem(newItem) { + return { + ...newItem, + effects: newItem.effects.map((effect) => ({ ...effect })), + lastSwap: null, + }; +} + +function swapInNewItem() { + const profile = PROFILES[state.profile]; + const totals = calculateTotals(); + const pieceScores = state.pieces.map((piece) => scorePiece(piece, profile, totals)); + const targetPiece = getReplacementTargetPiece(state.newItem.target, pieceScores); + if (!targetPiece) return; + const index = state.pieces.indexOf(targetPiece); + const candidate = createNewItemCandidate(state.newItem.target); + const previousPiece = clonePiece(targetPiece); + const previousNewItem = cloneNewItem(state.newItem); + state.pieces[index] = createPieceFromCandidate(targetPiece, candidate); + state.newItem = { + ...previousNewItem, + lastSwap: { + index, + target: previousNewItem.target, + piece: previousPiece, + newItem: previousNewItem, + swappedAt: new Date().toISOString(), + }, + }; + render(); +} + +function swapBackNewItem() { + const lastSwap = state.newItem.lastSwap; + if (!lastSwap || !state.pieces[lastSwap.index]) return; + state.pieces[lastSwap.index] = clonePiece(lastSwap.piece); + state.newItem = cloneNewItem(lastSwap.newItem); + render(); +} + function formatDelta(value, suffix = "") { const sign = value > 0 ? "+" : ""; return `${sign}${fmt(value)}${suffix}`; @@ -747,6 +836,8 @@ function bindEvents() { renderNewItemComparison(); updateAnalysis(); }); + $("#swapNewItem").addEventListener("click", swapInNewItem); + $("#swapBackNewItem").addEventListener("click", swapBackNewItem); $(".new-item-card").addEventListener("input", handleNewItemInput); $(".new-item-card").addEventListener("change", handleNewItemInput); } diff --git a/index.html b/index.html index c7f8d48..f5fecd0 100644 --- a/index.html +++ b/index.html @@ -104,6 +104,11 @@