Fix core item base stats
This commit is contained in:
@@ -16,6 +16,23 @@ Each equipped piece has:
|
|||||||
|
|
||||||
Effects start at `1%` and can be upgraded up to their cap.
|
Effects start at `1%` and can be upgraded up to their cap.
|
||||||
|
|
||||||
|
## Fixed item base stats
|
||||||
|
|
||||||
|
The 8 core item slots are fixed and cannot be renamed or have their base stat edited in the app:
|
||||||
|
|
||||||
|
| Slot | Item | Base stat |
|
||||||
|
| --- | --- | ---: |
|
||||||
|
| Item 1 | Helmet | 1,930,000 health |
|
||||||
|
| Item 2 | Armor | 1,890,000 health |
|
||||||
|
| Item 3 | Gloves | 237,000 damage |
|
||||||
|
| Item 4 | Collar | 237,000 damage |
|
||||||
|
| Item 5 | Ring | 244,000 damage |
|
||||||
|
| Item 6 | Weapon | 398,000 damage |
|
||||||
|
| Item 7 | Boots | 1,850,000 health |
|
||||||
|
| Item 8 | Belt | 1,150,000 health |
|
||||||
|
|
||||||
|
Mount and pet base stats remain editable.
|
||||||
|
|
||||||
## Supported effects
|
## Supported effects
|
||||||
|
|
||||||
| Effect | Maximum |
|
| Effect | Maximum |
|
||||||
|
|||||||
@@ -44,8 +44,18 @@ const PROFILES = {
|
|||||||
const STORAGE_KEY = "forgeMasterOptimizer.v1";
|
const STORAGE_KEY = "forgeMasterOptimizer.v1";
|
||||||
const DRAFT_KEY = "forgeMasterOptimizer.draft.v1";
|
const DRAFT_KEY = "forgeMasterOptimizer.draft.v1";
|
||||||
const EXPORT_VERSION = 1;
|
const EXPORT_VERSION = 1;
|
||||||
|
const FIXED_ITEMS = [
|
||||||
|
{ name: "Helmet", baseType: "health", baseValue: 1930000 },
|
||||||
|
{ name: "Armor", baseType: "health", baseValue: 1890000 },
|
||||||
|
{ name: "Gloves", baseType: "damage", baseValue: 237000 },
|
||||||
|
{ name: "Collar", baseType: "damage", baseValue: 237000 },
|
||||||
|
{ name: "Ring", baseType: "damage", baseValue: 244000 },
|
||||||
|
{ name: "Weapon", baseType: "damage", baseValue: 398000 },
|
||||||
|
{ name: "Boots", baseType: "health", baseValue: 1850000 },
|
||||||
|
{ name: "Belt", baseType: "health", baseValue: 1150000 },
|
||||||
|
];
|
||||||
const slots = [
|
const slots = [
|
||||||
...Array.from({ length: 8 }, (_, i) => ({ type: "item", title: `Item ${i + 1}` })),
|
...FIXED_ITEMS.map((item, i) => ({ type: "item", title: `Item ${i + 1}`, fixed: item })),
|
||||||
{ type: "mount", title: "Mount" },
|
{ type: "mount", title: "Mount" },
|
||||||
...Array.from({ length: 3 }, (_, i) => ({ type: "pet", title: `Pet ${i + 1}` })),
|
...Array.from({ length: 3 }, (_, i) => ({ type: "pet", title: `Pet ${i + 1}` })),
|
||||||
];
|
];
|
||||||
@@ -61,15 +71,32 @@ function createDefaultState() {
|
|||||||
profile: "balanced",
|
profile: "balanced",
|
||||||
combatStyle: "melee",
|
combatStyle: "melee",
|
||||||
recommendationCount: 8,
|
recommendationCount: 8,
|
||||||
pieces: slots.map((slot, index) => ({
|
pieces: slots.map((slot, index) => createDefaultPiece(slot, index)),
|
||||||
id: `${slot.type}-${index}`,
|
};
|
||||||
type: slot.type,
|
}
|
||||||
title: slot.title,
|
|
||||||
name: slot.title,
|
function createDefaultPiece(slot, index) {
|
||||||
baseType: index % 3 === 1 ? "health" : "damage",
|
return {
|
||||||
baseValue: 0,
|
id: `${slot.type}-${index}`,
|
||||||
effects: [{ type: "", value: 1 }, { type: "", value: 1 }],
|
type: slot.type,
|
||||||
})),
|
title: slot.title,
|
||||||
|
name: slot.fixed?.name || slot.title,
|
||||||
|
baseType: slot.fixed?.baseType || (index % 3 === 1 ? "health" : "damage"),
|
||||||
|
baseValue: slot.fixed?.baseValue || 0,
|
||||||
|
fixedBase: Boolean(slot.fixed),
|
||||||
|
effects: [{ type: "", value: 1 }, { type: "", value: 1 }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function enforceFixedPieceFields(piece, index) {
|
||||||
|
const fixed = FIXED_ITEMS[index];
|
||||||
|
if (!fixed || piece.type !== "item") return piece;
|
||||||
|
return {
|
||||||
|
...piece,
|
||||||
|
name: fixed.name,
|
||||||
|
baseType: fixed.baseType,
|
||||||
|
baseValue: fixed.baseValue,
|
||||||
|
fixedBase: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +108,7 @@ function normalizeState(candidate) {
|
|||||||
profile: PROFILES[candidate?.profile] ? candidate.profile : "balanced",
|
profile: PROFILES[candidate?.profile] ? candidate.profile : "balanced",
|
||||||
combatStyle: ["melee", "ranged", "skill"].includes(candidate?.combatStyle) ? candidate.combatStyle : "melee",
|
combatStyle: ["melee", "ranged", "skill"].includes(candidate?.combatStyle) ? candidate.combatStyle : "melee",
|
||||||
recommendationCount: Math.min(20, Math.max(3, Number(candidate?.recommendationCount || 8))),
|
recommendationCount: Math.min(20, Math.max(3, Number(candidate?.recommendationCount || 8))),
|
||||||
pieces: fallback.pieces.map((piece, index) => ({
|
pieces: fallback.pieces.map((piece, index) => enforceFixedPieceFields({
|
||||||
...piece,
|
...piece,
|
||||||
...(candidate?.pieces?.[index] || {}),
|
...(candidate?.pieces?.[index] || {}),
|
||||||
effects: [0, 1].map((effectIndex) => ({
|
effects: [0, 1].map((effectIndex) => ({
|
||||||
@@ -91,7 +118,7 @@ function normalizeState(candidate) {
|
|||||||
Number(candidate?.pieces?.[index]?.effects?.[effectIndex]?.value || 1),
|
Number(candidate?.pieces?.[index]?.effects?.[effectIndex]?.value || 1),
|
||||||
),
|
),
|
||||||
})),
|
})),
|
||||||
})),
|
}, index)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,10 +150,20 @@ function renderGear() {
|
|||||||
state.pieces.forEach((piece, pieceIndex) => {
|
state.pieces.forEach((piece, pieceIndex) => {
|
||||||
const node = template.content.firstElementChild.cloneNode(true);
|
const node = template.content.firstElementChild.cloneNode(true);
|
||||||
node.dataset.index = pieceIndex;
|
node.dataset.index = pieceIndex;
|
||||||
node.querySelector("h3").textContent = piece.title;
|
node.querySelector("h3").textContent = piece.fixedBase ? `${piece.title} · ${piece.name}` : piece.title;
|
||||||
node.querySelector('[data-field="name"]').value = piece.name;
|
node.querySelector('[data-field="name"]').value = piece.name;
|
||||||
node.querySelector('[data-field="baseType"]').value = piece.baseType;
|
node.querySelector('[data-field="baseType"]').value = piece.baseType;
|
||||||
node.querySelector('[data-field="baseValue"]').value = piece.baseValue;
|
node.querySelector('[data-field="baseValue"]').value = piece.baseValue;
|
||||||
|
if (piece.fixedBase) {
|
||||||
|
node.classList.add("fixed-piece");
|
||||||
|
node.querySelector('[data-field="name"]').readOnly = true;
|
||||||
|
node.querySelector('[data-field="baseType"]').disabled = true;
|
||||||
|
node.querySelector('[data-field="baseValue"]').readOnly = true;
|
||||||
|
const badge = document.createElement("span");
|
||||||
|
badge.className = "fixed-badge";
|
||||||
|
badge.textContent = "Fixed base";
|
||||||
|
node.querySelector(".gear-title-row").appendChild(badge);
|
||||||
|
}
|
||||||
|
|
||||||
const effects = node.querySelector(".effects");
|
const effects = node.querySelector(".effects");
|
||||||
piece.effects.forEach((effect, effectIndex) => {
|
piece.effects.forEach((effect, effectIndex) => {
|
||||||
@@ -513,6 +550,10 @@ function handleGearInput(event) {
|
|||||||
const piece = state.pieces[Number(card.dataset.index)];
|
const piece = state.pieces[Number(card.dataset.index)];
|
||||||
|
|
||||||
if (event.target.dataset.field) {
|
if (event.target.dataset.field) {
|
||||||
|
if (piece.fixedBase && ["name", "baseType", "baseValue"].includes(event.target.dataset.field)) {
|
||||||
|
event.target.value = piece[event.target.dataset.field];
|
||||||
|
return;
|
||||||
|
}
|
||||||
const field = event.target.dataset.field;
|
const field = event.target.dataset.field;
|
||||||
piece[field] = field === "baseValue" ? Math.max(0, Number(event.target.value || 0)) : event.target.value;
|
piece[field] = field === "baseValue" ? Math.max(0, Number(event.target.value || 0)) : event.target.value;
|
||||||
}
|
}
|
||||||
|
|||||||
+15
@@ -49,6 +49,11 @@ button:hover { filter: brightness(1.1); }
|
|||||||
button.secondary { background: var(--panel-2); }
|
button.secondary { background: var(--panel-2); }
|
||||||
button.danger { border-color: rgba(251, 113, 133, 0.5); background: rgba(251, 113, 133, 0.16); }
|
button.danger { border-color: rgba(251, 113, 133, 0.5); background: rgba(251, 113, 133, 0.16); }
|
||||||
textarea { width: 100%; min-height: 9rem; resize: vertical; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
textarea { width: 100%; min-height: 9rem; resize: vertical; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
||||||
|
input[readonly], select:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: var(--muted);
|
||||||
|
background: rgba(255, 255, 255, 0.035);
|
||||||
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -176,6 +181,16 @@ h3 { margin-bottom: 0; }
|
|||||||
.gear-card { display: grid; gap: 0.85rem; }
|
.gear-card { display: grid; gap: 0.85rem; }
|
||||||
.gear-title-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
|
.gear-title-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
|
||||||
.piece-score { color: var(--accent); font-weight: 800; }
|
.piece-score { color: var(--accent); font-weight: 800; }
|
||||||
|
.fixed-badge {
|
||||||
|
border: 1px solid rgba(125, 211, 252, 0.35);
|
||||||
|
border-radius: 999px;
|
||||||
|
color: var(--accent);
|
||||||
|
background: rgba(125, 211, 252, 0.08);
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 800;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
.inline-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
|
.inline-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
|
||||||
.effects { display: grid; gap: 0.75rem; }
|
.effects { display: grid; gap: 0.75rem; }
|
||||||
.effect-row { display: grid; grid-template-columns: 1fr 110px; gap: 0.75rem; align-items: end; }
|
.effect-row { display: grid; grid-template-columns: 1fr 110px; gap: 0.75rem; align-items: end; }
|
||||||
|
|||||||
Reference in New Issue
Block a user