Support limited glass inventory and yellow laser

Introduce per-level glass inventory and UI for draggable glass pieces, including disabled state and count labels. glassOptions now hold objects with color, maxAmount and currentAmount; palette creation uses these values to enable/disable dragging, show remaining counts, and update on place/remove. Drag-and-drop logic now tracks a draggedGlassColor fallback, prevents placing when inventory is empty, decrements/increments inventory on place/remove, and rebuilds the palette. Reset inventory on level start and when advancing levels. Also adjust laser tracing: yellow lasers are saved as segments and terminate on target, and yellow interacts with demi-wall corners by saving the segment instead of reflecting (blue still reflects). Add CSS for .glass-item:disabled and .glass-item-label. Overall fixes inventory handling and yellow-laser behavior.
This commit is contained in:
Sysy's
2026-03-31 14:05:35 +02:00
parent ab4043defa
commit 34d213d5f4
2 changed files with 144 additions and 13 deletions

View File

@@ -341,6 +341,24 @@ main {
font-weight: bold; font-weight: bold;
} }
.glass-item:disabled {
cursor: not-allowed;
opacity: 0.45;
}
.glass-item-label {
position: absolute;
right: 4px;
bottom: 3px;
z-index: 2;
font-size: 0.7rem;
font-weight: 700;
color: #223;
background: rgba(255, 255, 255, 0.88);
padding: 1px 5px;
border-radius: 999px;
}
.glass-item::after, .glass-item::after,
.cell-glass::after { .cell-glass::after {
content: ""; content: "";

View File

@@ -31,9 +31,21 @@ const laserColors = {
}; };
const glassOptions = [ const glassOptions = [
laserColors.red, [
laserColors.blue, { color: laserColors.red, maxAmount: 1, currentAmount: 1 },
laserColors.yellow, { color: laserColors.blue, maxAmount: 1, currentAmount: 1 },
{ color: laserColors.yellow, maxAmount: 1, currentAmount: 1 },
],
[
{ color: laserColors.red, maxAmount: 1, currentAmount: 1 },
{ color: laserColors.blue, maxAmount: 1, currentAmount: 1 },
{ color: laserColors.yellow, maxAmount: 1, currentAmount: 1 },
],
[
{ color: laserColors.red, maxAmount: 2, currentAmount: 2 },
{ color: laserColors.blue, maxAmount: 1, currentAmount: 1 },
{ color: laserColors.yellow, maxAmount: 2, currentAmount: 2 },
],
]; ];
let levels = [ let levels = [
@@ -149,6 +161,7 @@ let activeRotatorButtons = {};
let rotatorIntervals = {}; let rotatorIntervals = {};
let toggledDoors = {}; let toggledDoors = {};
let poweredCaptors = {}; let poweredCaptors = {};
let draggedGlassColor = null;
function getCurrentLevel() { function getCurrentLevel() {
return levels[currentLevelIndex]; return levels[currentLevelIndex];
@@ -158,6 +171,30 @@ function getCurrentLevelConfig(configByLevel) {
return configByLevel[currentLevelIndex] || {}; return configByLevel[currentLevelIndex] || {};
} }
function getCurrentGlassOptions() {
return glassOptions[currentLevelIndex] || [];
}
function getGlassOptionByColor(color) {
const currentGlassOptions = getCurrentGlassOptions();
for (let i = 0; i < currentGlassOptions.length; i++) {
if (currentGlassOptions[i].color === color) {
return currentGlassOptions[i];
}
}
return null;
}
function resetGlassInventory() {
const currentGlassOptions = getCurrentGlassOptions();
for (let i = 0; i < currentGlassOptions.length; i++) {
currentGlassOptions[i].currentAmount = currentGlassOptions[i].maxAmount;
}
}
function normalizeLaserDirection(dx, dy) { function normalizeLaserDirection(dx, dy) {
const epsilon = 0.0001; const epsilon = 0.0001;
const normalizedDx = Math.abs(dx) < epsilon ? 0 : Math.sign(dx); const normalizedDx = Math.abs(dx) < epsilon ? 0 : Math.sign(dx);
@@ -367,17 +404,40 @@ function createPalette() {
palette.innerHTML = ""; palette.innerHTML = "";
for (let i = 0; i < glassOptions.length; i++) { const currentGlassOptions = getCurrentGlassOptions();
const glassColor = glassOptions[i];
for (let i = 0; i < currentGlassOptions.length; i++) {
const glassOption = currentGlassOptions[i];
const glassColor = glassOption.color;
const glassButton = document.createElement("button"); const glassButton = document.createElement("button");
glassButton.type = "button"; glassButton.type = "button";
glassButton.classList.add("glass-item", `glass-${glassColor}`); glassButton.classList.add("glass-item", `glass-${glassColor}`);
glassButton.draggable = true; glassButton.draggable = glassOption.currentAmount > 0;
if (glassOption.currentAmount <= 0) {
glassButton.disabled = true;
}
const glassLabel = document.createElement("span");
glassLabel.classList.add("glass-item-label");
glassLabel.textContent = `${glassOption.currentAmount}/${glassOption.maxAmount}`;
glassButton.appendChild(glassLabel);
glassButton.addEventListener("dragstart", (event) => { glassButton.addEventListener("dragstart", (event) => {
if (glassOption.currentAmount <= 0) {
event.preventDefault();
return;
}
draggedGlassColor = glassColor;
event.dataTransfer.effectAllowed = "copy"; event.dataTransfer.effectAllowed = "copy";
event.dataTransfer.setData("application/x-glass-color", glassColor); event.dataTransfer.setData("application/x-glass-color", glassColor);
}); });
glassButton.addEventListener("dragend", () => {
draggedGlassColor = null;
});
palette.appendChild(glassButton); palette.appendChild(glassButton);
} }
} }
@@ -388,7 +448,8 @@ function addDropEvents(cell, x, y) {
return; return;
} }
if (!event.dataTransfer.types.includes("application/x-glass-color")) { const transferTypes = event.dataTransfer ? Array.from(event.dataTransfer.types || []) : [];
if (!draggedGlassColor) {
return; return;
} }
@@ -397,6 +458,13 @@ function addDropEvents(cell, x, y) {
return; return;
} }
const selectedColor = draggedGlassColor;
const glassOption = getGlassOptionByColor(selectedColor);
if (!glassOption || glassOption.currentAmount <= 0) {
return;
}
event.preventDefault(); event.preventDefault();
cell.classList.add("can-drop"); cell.classList.add("can-drop");
}); });
@@ -410,7 +478,8 @@ function addDropEvents(cell, x, y) {
return; return;
} }
const selectedColor = event.dataTransfer.getData("application/x-glass-color"); const transferredColor = event.dataTransfer ? event.dataTransfer.getData("application/x-glass-color") : "";
const selectedColor = transferredColor || draggedGlassColor;
cell.classList.remove("can-drop"); cell.classList.remove("can-drop");
if (!selectedColor) { if (!selectedColor) {
@@ -421,8 +490,21 @@ function addDropEvents(cell, x, y) {
return; return;
} }
if (isGlassOnCell(x, y)) {
return;
}
const glassOption = getGlassOptionByColor(selectedColor);
if (!glassOption || glassOption.currentAmount <= 0) {
return;
}
event.preventDefault(); event.preventDefault();
glassPlacements[`${y},${x}`] = selectedColor; glassPlacements[`${y},${x}`] = selectedColor;
glassOption.currentAmount--;
draggedGlassColor = null;
createPalette();
traceLaser(); traceLaser();
}); });
@@ -432,7 +514,16 @@ function addDropEvents(cell, x, y) {
} }
if (isGlassOnCell(x, y)) { if (isGlassOnCell(x, y)) {
const glassColor = glassPlacements[`${y},${x}`];
const glassOption = getGlassOptionByColor(glassColor);
delete glassPlacements[`${y},${x}`]; delete glassPlacements[`${y},${x}`];
if (glassOption) {
glassOption.currentAmount = Math.min(glassOption.currentAmount + 1, glassOption.maxAmount);
}
createPalette();
traceLaser(); traceLaser();
} }
}); });
@@ -642,6 +733,17 @@ function traceLaser() {
currentLaserColor = glassColor; currentLaserColor = glassColor;
} }
if (currentLaserColor === laserColors.yellow) {
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
if (cellType === legend.target) {
laserActive = false;
isLevelFinished = true;
}
continue;
}
switch (cellType) { switch (cellType) {
case legend.laser: case legend.laser:
case legend.coloredLaser: case legend.coloredLaser:
@@ -743,25 +845,33 @@ function traceLaser() {
break; break;
case legend.demiWallCornerUpLeft: case legend.demiWallCornerUpLeft:
if(currentLaserColor === laserColors.blue) { if (currentLaserColor === laserColors.yellow) {
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
} else if (currentLaserColor === laserColors.blue) {
laserDirection = reflectLaser(laserDirection, 135); laserDirection = reflectLaser(laserDirection, 135);
} }
break; break;
case legend.demiWallCornerUpRight: case legend.demiWallCornerUpRight:
if(currentLaserColor === laserColors.blue) { if (currentLaserColor === laserColors.yellow) {
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
} else if (currentLaserColor === laserColors.blue) {
laserDirection = reflectLaser(laserDirection, 45); laserDirection = reflectLaser(laserDirection, 45);
} }
break; break;
case legend.demiWallCornerDownLeft: case legend.demiWallCornerDownLeft:
if(currentLaserColor === laserColors.blue) { if (currentLaserColor === laserColors.yellow) {
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
} else if (currentLaserColor === laserColors.blue) {
laserDirection = reflectLaser(laserDirection, 225); laserDirection = reflectLaser(laserDirection, 225);
} }
break; break;
case legend.demiWallCornerDownRight: case legend.demiWallCornerDownRight:
if(currentLaserColor === laserColors.blue) { if (currentLaserColor === laserColors.yellow) {
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
} else if (currentLaserColor === laserColors.blue) {
laserDirection = reflectLaser(laserDirection, 315); laserDirection = reflectLaser(laserDirection, 315);
} }
break; break;
@@ -799,10 +909,12 @@ function nextLevel() {
} }
initializeMirrorOrientations(); initializeMirrorOrientations();
glassPlacements = {};
resetGlassInventory();
createPalette();
loadGrid(); loadGrid();
laserSegments = {}; laserSegments = {};
mirrorOrientations = {}; mirrorOrientations = {};
glassPlacements = {};
activatedButtons = {}; activatedButtons = {};
openedDoors = {}; openedDoors = {};
toggledDoors = {}; toggledDoors = {};
@@ -813,6 +925,7 @@ function nextLevel() {
winOverlay.style.visibility = "hidden"; winOverlay.style.visibility = "hidden";
} }
resetGlassInventory();
createPalette(); createPalette();
initializeMirrorOrientations(); initializeMirrorOrientations();
blockBrowserDrop(); blockBrowserDrop();