Make new item swap reversible

This commit is contained in:
Jan Bader
2026-06-16 21:15:53 +02:00
parent 4934255e96
commit 929cbc6bdd
4 changed files with 23 additions and 27 deletions
+1 -1
View File
@@ -130,7 +130,7 @@ The app models this with the New item comparison panel:
- Enter the new roll's base stat and effects. - Enter the new roll's base stat and effects.
- The app shows DPS and Regen change separately. - 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. - 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. - Pressing `Swap` exchanges the new roll with the current target. The replaced target becomes the New item candidate, so pressing `Swap` again restores the previous state.
## Profiles ## Profiles
+1 -1
View File
@@ -26,7 +26,7 @@ For the game rules, stat caps, formulas, assumptions, and known limitations used
- choose what it would replace, - choose what it would replace,
- enter the new roll's value and effects, - enter the new roll's value and effects,
- compare DPS and Regen changes before swapping. - compare DPS and Regen changes before swapping.
- use **Swap in new item** to apply it, with **Swap back** available if you want to undo. - press **Swap** to exchange the new roll with the current target; pressing **Swap** again exchanges them back.
7. Use **Best sacrifice candidates** as a quick shortlist of low-value pieces. 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. When comparing a new pet, choose `Pet`; the app automatically compares against your currently weakest pet.
+20 -23
View File
@@ -68,10 +68,10 @@ function createDefaultState() {
function createDefaultNewItem() { function createDefaultNewItem() {
return { return {
target: "item-0", target: "item-0",
targetPieceId: null,
baseDamage: 0, baseDamage: 0,
baseHealth: 0, baseHealth: 0,
effects: [{ type: "", value: 1 }, { type: "", value: 1 }], effects: [{ type: "", value: 1 }, { type: "", value: 1 }],
lastSwap: null,
}; };
} }
@@ -146,10 +146,10 @@ function normalizeNewItem(candidate) {
...fallback, ...fallback,
...candidate, ...candidate,
target: getReplacementOptions().some((option) => option.value === candidate?.target) ? candidate.target : fallback.target, target: getReplacementOptions().some((option) => option.value === candidate?.target) ? candidate.target : fallback.target,
targetPieceId: candidate?.targetPieceId || null,
baseDamage: Math.max(0, Number(candidate?.baseDamage || 0)), baseDamage: Math.max(0, Number(candidate?.baseDamage || 0)),
baseHealth: Math.max(0, Number(candidate?.baseHealth || 0)), baseHealth: Math.max(0, Number(candidate?.baseHealth || 0)),
effects, effects,
lastSwap: candidate?.lastSwap || null,
}; };
} }
@@ -367,9 +367,7 @@ function updateNewItemImpactHints(replacementTotals, replacementDps) {
} }
function updateSwapControls() { function updateSwapControls() {
const hasSwap = Boolean(state.newItem.lastSwap); $("#swapStatus").textContent = "Press Swap again to exchange back.";
$("#swapBackNewItem").disabled = !hasSwap;
$("#swapStatus").textContent = hasSwap ? `Can swap back ${state.newItem.lastSwap.piece?.name || "previous item"}.` : "";
} }
function calculateTotals() { function calculateTotals() {
@@ -407,6 +405,10 @@ function getFixedReplacementTarget(target) {
} }
function getReplacementTargetPiece(target, pieceScores) { function getReplacementTargetPiece(target, pieceScores) {
if (state.newItem.targetPieceId) {
const pinned = state.pieces.find((piece) => piece.id === state.newItem.targetPieceId);
if (pinned) return pinned;
}
if (target === "mount") return state.pieces.find((piece) => piece.type === "mount") || null; if (target === "mount") return state.pieces.find((piece) => piece.type === "mount") || null;
if (target === "pet") { if (target === "pet") {
return [...pieceScores] return [...pieceScores]
@@ -457,7 +459,6 @@ function cloneNewItem(newItem) {
return { return {
...newItem, ...newItem,
effects: newItem.effects.map((effect) => ({ ...effect })), effects: newItem.effects.map((effect) => ({ ...effect })),
lastSwap: null,
}; };
} }
@@ -472,25 +473,21 @@ function swapInNewItem() {
const previousPiece = clonePiece(targetPiece); const previousPiece = clonePiece(targetPiece);
const previousNewItem = cloneNewItem(state.newItem); const previousNewItem = cloneNewItem(state.newItem);
state.pieces[index] = createPieceFromCandidate(targetPiece, candidate); state.pieces[index] = createPieceFromCandidate(targetPiece, candidate);
state.newItem = { state.newItem = createNewItemFromPiece(previousPiece, previousNewItem.target, targetPiece.id);
...previousNewItem,
lastSwap: {
index,
target: previousNewItem.target,
piece: previousPiece,
newItem: previousNewItem,
swappedAt: new Date().toISOString(),
},
};
render(); render();
} }
function swapBackNewItem() { function createNewItemFromPiece(piece, target, targetPieceId = null) {
const lastSwap = state.newItem.lastSwap; return {
if (!lastSwap || !state.pieces[lastSwap.index]) return; target,
state.pieces[lastSwap.index] = clonePiece(lastSwap.piece); targetPieceId,
state.newItem = cloneNewItem(lastSwap.newItem); baseDamage: Number(piece.baseDamage || 0),
render(); baseHealth: Number(piece.baseHealth || 0),
effects: [0, 1].map((index) => ({
type: piece.effects?.[index]?.type || "",
value: piece.effects?.[index]?.value || 1,
})),
};
} }
function formatDelta(value, suffix = "") { function formatDelta(value, suffix = "") {
@@ -833,11 +830,11 @@ function bindEvents() {
$("#importFile").addEventListener("change", importUploadedJson); $("#importFile").addEventListener("change", importUploadedJson);
$("#newItemTarget").addEventListener("change", (event) => { $("#newItemTarget").addEventListener("change", (event) => {
state.newItem.target = event.target.value; state.newItem.target = event.target.value;
state.newItem.targetPieceId = null;
renderNewItemComparison(); renderNewItemComparison();
updateAnalysis(); updateAnalysis();
}); });
$("#swapNewItem").addEventListener("click", swapInNewItem); $("#swapNewItem").addEventListener("click", swapInNewItem);
$("#swapBackNewItem").addEventListener("click", swapBackNewItem);
$(".new-item-card").addEventListener("input", handleNewItemInput); $(".new-item-card").addEventListener("input", handleNewItemInput);
$(".new-item-card").addEventListener("change", handleNewItemInput); $(".new-item-card").addEventListener("change", handleNewItemInput);
} }
+1 -2
View File
@@ -105,8 +105,7 @@
<div><span>Regen change</span><strong id="replacementRegenDelta">0</strong></div> <div><span>Regen change</span><strong id="replacementRegenDelta">0</strong></div>
</div> </div>
<div class="swap-actions"> <div class="swap-actions">
<button id="swapNewItem" type="button">Swap in new item</button> <button id="swapNewItem" type="button">Swap</button>
<button id="swapBackNewItem" type="button" class="secondary" disabled>Swap back</button>
<span id="swapStatus" class="status"></span> <span id="swapStatus" class="status"></span>
</div> </div>
</section> </section>