Make new item swap reversible
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user