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.
- 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.
- 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
+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,
- 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.
- 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.
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() {
return {
target: "item-0",
targetPieceId: null,
baseDamage: 0,
baseHealth: 0,
effects: [{ type: "", value: 1 }, { type: "", value: 1 }],
lastSwap: null,
};
}
@@ -146,10 +146,10 @@ function normalizeNewItem(candidate) {
...fallback,
...candidate,
target: getReplacementOptions().some((option) => option.value === candidate?.target) ? candidate.target : fallback.target,
targetPieceId: candidate?.targetPieceId || null,
baseDamage: Math.max(0, Number(candidate?.baseDamage || 0)),
baseHealth: Math.max(0, Number(candidate?.baseHealth || 0)),
effects,
lastSwap: candidate?.lastSwap || null,
};
}
@@ -367,9 +367,7 @@ function updateNewItemImpactHints(replacementTotals, replacementDps) {
}
function updateSwapControls() {
const hasSwap = Boolean(state.newItem.lastSwap);
$("#swapBackNewItem").disabled = !hasSwap;
$("#swapStatus").textContent = hasSwap ? `Can swap back ${state.newItem.lastSwap.piece?.name || "previous item"}.` : "";
$("#swapStatus").textContent = "Press Swap again to exchange back.";
}
function calculateTotals() {
@@ -407,6 +405,10 @@ function getFixedReplacementTarget(target) {
}
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 === "pet") {
return [...pieceScores]
@@ -457,7 +459,6 @@ function cloneNewItem(newItem) {
return {
...newItem,
effects: newItem.effects.map((effect) => ({ ...effect })),
lastSwap: null,
};
}
@@ -472,25 +473,21 @@ function swapInNewItem() {
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(),
},
};
state.newItem = createNewItemFromPiece(previousPiece, previousNewItem.target, targetPiece.id);
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 createNewItemFromPiece(piece, target, targetPieceId = null) {
return {
target,
targetPieceId,
baseDamage: Number(piece.baseDamage || 0),
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 = "") {
@@ -833,11 +830,11 @@ function bindEvents() {
$("#importFile").addEventListener("change", importUploadedJson);
$("#newItemTarget").addEventListener("change", (event) => {
state.newItem.target = event.target.value;
state.newItem.targetPieceId = null;
renderNewItemComparison();
updateAnalysis();
});
$("#swapNewItem").addEventListener("click", swapInNewItem);
$("#swapBackNewItem").addEventListener("click", swapBackNewItem);
$(".new-item-card").addEventListener("input", 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>
<div class="swap-actions">
<button id="swapNewItem" type="button">Swap in new item</button>
<button id="swapBackNewItem" type="button" class="secondary" disabled>Swap back</button>
<button id="swapNewItem" type="button">Swap</button>
<span id="swapStatus" class="status"></span>
</div>
</section>