forked from Simnation/Main
253 lines
6.9 KiB
JavaScript
253 lines
6.9 KiB
JavaScript
const lockpickContainer = document.querySelector("#lockpick-container"),
|
|
pin = document.querySelector("#pin"),
|
|
cyl = document.querySelector("#cylinder"),
|
|
driver = document.querySelector("#driver");
|
|
|
|
const minRot = -90,
|
|
maxRot = 90,
|
|
solvePadding = 4,
|
|
maxDistFromSolve = 45,
|
|
mouseSmoothing = 2,
|
|
keyRepeatRate = 25,
|
|
cylRotSpeed = 3,
|
|
pinDamage = 20,
|
|
pinDamageInterval = 150;
|
|
|
|
let solveDeg = Math.random() * (maxRot - minRot + 1) + minRot,
|
|
pinRot = 0,
|
|
cylRot = 0,
|
|
lastMousePos = 0,
|
|
pinHealth = 100,
|
|
numPins = 1,
|
|
userPushingCyl = false,
|
|
gameOver = false,
|
|
gamePaused = false,
|
|
cylRotationInterval,
|
|
pinLastDamaged;
|
|
|
|
const clamp = (val, min, max) => Math.min(Math.max(val, min), max);
|
|
const convertRanges = (value, oldMin, oldMax, newMin, newMax) => ((value - oldMin) * (newMax - newMin)) / (oldMax - oldMin) + newMin;
|
|
|
|
const closeLockpick = () => {
|
|
solveDeg = Math.random() * (maxRot - minRot + 1) + minRot;
|
|
pinRot = 0;
|
|
cylRot = 0;
|
|
lastMousePos = 0;
|
|
pinHealth = 100;
|
|
numPins = 1;
|
|
userPushingCyl = false;
|
|
gameOver = true;
|
|
gamePaused = true;
|
|
lockpickContainer.style.display = "none";
|
|
if (cylRotationInterval) {
|
|
cancelAnimationFrame(cylRotationInterval);
|
|
cylRotationInterval = null;
|
|
}
|
|
};
|
|
|
|
const unlock = () => {
|
|
closeLockpick();
|
|
fetch(`https://${GetParentResourceName()}/lockpickFinish`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ success: true }),
|
|
});
|
|
};
|
|
|
|
const pushCyl = () => {
|
|
cancelAnimationFrame(cylRotationInterval);
|
|
userPushingCyl = true;
|
|
|
|
let distFromSolve = Math.abs(pinRot - solveDeg) - solvePadding;
|
|
distFromSolve = clamp(distFromSolve, 0, maxDistFromSolve);
|
|
|
|
let cylRotationAllowance = convertRanges(distFromSolve, 0, maxDistFromSolve, maxRot, maxRot * 0.02);
|
|
|
|
function updateCylinderRotation() {
|
|
if (!userPushingCyl || cylRot >= maxRot || gameOver) {
|
|
cancelAnimationFrame(cylRotationInterval);
|
|
if (cylRot >= maxRot) {
|
|
unlock();
|
|
}
|
|
return;
|
|
}
|
|
|
|
cylRot += cylRotSpeed;
|
|
cylRot = Math.min(cylRot, cylRotationAllowance);
|
|
|
|
if (cylRot >= cylRotationAllowance) {
|
|
damagePin();
|
|
}
|
|
|
|
cyl.style.transform = `translate(-50%, -50%) rotateZ(${cylRot}deg)`;
|
|
driver.style.transform = `rotateZ(${cylRot}deg)`;
|
|
|
|
cylRotationInterval = requestAnimationFrame(updateCylinderRotation);
|
|
}
|
|
|
|
cylRotationInterval = requestAnimationFrame(updateCylinderRotation);
|
|
};
|
|
|
|
const unpushCyl = () => {
|
|
userPushingCyl = false;
|
|
cancelAnimationFrame(cylRotationInterval);
|
|
|
|
function updateCylinderRotationBackward() {
|
|
if (cylRot <= 0 || gameOver) {
|
|
cylRot = 0;
|
|
cancelAnimationFrame(cylRotationInterval);
|
|
return;
|
|
}
|
|
|
|
cylRot -= cylRotSpeed;
|
|
cylRot = Math.max(cylRot, 0);
|
|
|
|
cyl.style.transform = `translate(-50%, -50%) rotateZ(${cylRot}deg)`;
|
|
driver.style.transform = `rotateZ(${cylRot}deg)`;
|
|
|
|
cylRotationInterval = requestAnimationFrame(updateCylinderRotationBackward);
|
|
}
|
|
|
|
cylRotationInterval = requestAnimationFrame(updateCylinderRotationBackward);
|
|
};
|
|
|
|
const damagePin = () => {
|
|
if (!pinLastDamaged || Date.now() - pinLastDamaged > pinDamageInterval) {
|
|
pinHealth -= pinDamage;
|
|
pinLastDamaged = Date.now();
|
|
|
|
const keyframes = [{ transform: `rotateZ(${pinRot}deg)` }, { transform: `rotateZ(${pinRot - 2}deg)` }, { transform: `rotateZ(${pinRot}deg)` }];
|
|
|
|
const options = {
|
|
duration: pinDamageInterval / 2,
|
|
easing: "ease-out",
|
|
};
|
|
|
|
pin.animate(keyframes, options);
|
|
|
|
if (pinHealth <= 0) {
|
|
breakPin();
|
|
}
|
|
}
|
|
};
|
|
|
|
const reset = () => {
|
|
cylRot = 0;
|
|
pinHealth = 100;
|
|
pinRot = 0;
|
|
|
|
pin.style.transform = `rotateZ(${pinRot}deg)`;
|
|
cyl.style.transform = `translate(-50%, -50%) rotateZ(${cylRot}deg)`;
|
|
driver.style.transform = `rotateZ(${cylRot}deg)`;
|
|
|
|
const pinTop = pin.querySelector(".top");
|
|
const pinBott = pin.querySelector(".bott");
|
|
|
|
[pinTop, pinBott].forEach((el) => {
|
|
if (el) {
|
|
el.style.transform = "rotateZ(0deg) translateX(0) translateY(0)";
|
|
el.style.opacity = "1";
|
|
}
|
|
});
|
|
|
|
if (cylRotationInterval) {
|
|
cancelAnimationFrame(cylRotationInterval);
|
|
cylRotationInterval = null;
|
|
}
|
|
};
|
|
|
|
const outOfPins = () => {
|
|
closeLockpick();
|
|
fetch(`https://${GetParentResourceName()}/lockpickFinish`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ success: false }),
|
|
});
|
|
};
|
|
|
|
const breakPin = () => {
|
|
gamePaused = true;
|
|
cancelAnimationFrame(cylRotationInterval);
|
|
|
|
numPins--;
|
|
|
|
const pinTop = pin.querySelector(".top");
|
|
const pinBott = pin.querySelector(".bott");
|
|
|
|
const animateOptions = {
|
|
duration: 700,
|
|
};
|
|
|
|
pinTop.animate(
|
|
[
|
|
{ transform: "rotateZ(0deg) translateX(0) translateY(0)", opacity: 1 },
|
|
{ transform: "rotateZ(-400deg) translateX(-200px) translateY(-100px)", opacity: 0 },
|
|
],
|
|
animateOptions
|
|
);
|
|
|
|
const bottomAnimation = pinBott.animate(
|
|
[
|
|
{ transform: "rotateZ(0deg) translateX(0) translateY(0)", opacity: 1 },
|
|
{ transform: "rotateZ(400deg) translateX(200px) translateY(100px)", opacity: 0 },
|
|
],
|
|
animateOptions
|
|
);
|
|
|
|
bottomAnimation.onfinish = () => {
|
|
if (numPins > 0) {
|
|
gamePaused = false;
|
|
reset();
|
|
} else {
|
|
outOfPins();
|
|
}
|
|
};
|
|
};
|
|
|
|
document.addEventListener("mousemove", (e) => {
|
|
if (!gameOver && !gamePaused) {
|
|
let pinRotChange = (e.clientX - lastMousePos) / mouseSmoothing;
|
|
pinRot += pinRotChange;
|
|
pinRot = clamp(pinRot, minRot, maxRot);
|
|
pin.style.transform = `rotateZ(${pinRot}deg)`;
|
|
}
|
|
lastMousePos = e.clientX;
|
|
});
|
|
|
|
document.addEventListener("mouseleave", () => (lastMousePos = 0));
|
|
|
|
const keyActionMap = {
|
|
w: pushCyl,
|
|
a: pushCyl,
|
|
s: pushCyl,
|
|
d: pushCyl,
|
|
escape: () => {
|
|
closeLockpick();
|
|
fetch(`https://${GetParentResourceName()}/lockpickExit`, { method: "POST" }).catch(console.error);
|
|
},
|
|
};
|
|
|
|
document.addEventListener("keydown", (e) => {
|
|
let action = keyActionMap[e.key.toLowerCase()];
|
|
if (action && !userPushingCyl && !gameOver && !gamePaused) {
|
|
action();
|
|
}
|
|
});
|
|
|
|
document.addEventListener("keyup", (e) => {
|
|
let action = keyActionMap[e.key.toLowerCase()];
|
|
if (action && !gameOver) {
|
|
unpushCyl();
|
|
}
|
|
});
|
|
|
|
window.addEventListener("message", (event) => {
|
|
const eventData = event.data;
|
|
if (eventData.action === "startLockpick") {
|
|
lockpickContainer.style.display = "block";
|
|
numPins = eventData.pins;
|
|
gameOver = false;
|
|
gamePaused = false;
|
|
reset();
|
|
}
|
|
});
|