Add reversible new item swap

This commit is contained in:
Jan Bader
2026-06-16 21:00:21 +02:00
parent 75fa957981
commit 4934255e96
5 changed files with 108 additions and 3 deletions
+94 -3
View File
@@ -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() {
</label>
<label>Percent
<input data-new-item-effect-value="${effectIndex}" type="number" min="1" step="0.1" value="${effect.value}" />
<span class="cap-hint"></span>
</label>`;
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);
}