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();
 | 
						|
    }
 | 
						|
});
 |