const socket = io(); const canvas = document.getElementById("gameCanvas"); const ctx = canvas.getContext("2d"); const chatInput = document.getElementById("chatInput"); const chatMessages = document.getElementById("chatMessages"); let players = {}; let mobs = {}; // Store all mobs dynamically let petals = {}; let myPlayerId = null; let cameraOffsetX = 0; let cameraOffsetY = 0; function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } let showHitbox = false; resizeCanvas(); window.addEventListener("resize", resizeCanvas); function drawPetal(petal) { // Save the current canvas state ctx.save(); const petalScreenX = petal.x - cameraOffsetX; const petalScreenY = petal.y - cameraOffsetY; // Scale based on mob radius (arbitrary base size of 50) ctx.scale(petal.radius / 50, petal.radius / 50); // Translate and rotate based on mob's angle ctx.translate(petalScreenX, petalScreenY); ctx.rotate(petal.angle); // Rotate based on mob's angle ctx.fillStyle = "#363298"; ctx.beginPath(); ctx.arc(0,0,50,0,Math.PI * 2); ctx.closePath(); ctx.fill(); } // Global mouse position variables let mouseX = 0; let mouseY = 0; let isMouseDown = false; // Track whether the mouse button is held down // Mouse move event listener to update mouse coordinates document.addEventListener('mousemove', function(event) { mouseX = event.clientX; mouseY = event.clientY; }); // Mouse down event listener to detect when the mouse button is held document.addEventListener('mousedown', function() { isMouseDown = true; }); // Mouse up event listener to detect when the mouse button is released document.addEventListener('mouseup', function() { isMouseDown = false; }); // Function to draw the player function drawPlayer(player) { // Save the current canvas state (to reset transformations later) ctx.save(); // Translate the context to the player's position ctx.translate(player.x - cameraOffsetX, player.y - cameraOffsetY); ctx.lineCap = "round"; // Get the mouse position relative to the player's position (local coordinates) let rect = canvas.getBoundingClientRect(); let localMouseX = mouseX - (rect.left + canvas.width / 2 + cameraOffsetX); let localMouseY = mouseY - (rect.top + canvas.height / 2 + cameraOffsetY); // Draw the player as a circle at the translated position (0, 0) ctx.beginPath(); ctx.arc(0, 0, 20, 0, Math.PI * 2); // Drawing circle at (0, 0) ctx.closePath(); ctx.fillStyle = "#f8ff66"; ctx.lineWidth = 2; ctx.strokeStyle = "#c6cc52"; ctx.fill(); ctx.stroke(); // Right eye (no stroke outline) ctx.lineWidth = 0; // Remove outline for the right eye ctx.strokeStyle = "rgba(255, 255, 255, 0)"; // Transparent stroke ctx.fillStyle = "#222222"; // Black fill for the eye ctx.beginPath(); ctx.ellipse(6, -5, 2.5, 5, 0, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); // Left eye (no stroke outline) ctx.lineWidth = 0; // Ensure no stroke for the left eye ctx.strokeStyle = "rgba(255, 255, 255, 0)"; // Transparent stroke ctx.fillStyle = "#222222"; // Black fill for the eye ctx.beginPath(); ctx.ellipse(-6, -5, 2.5, 5, 0, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); // Smile or Frown (based on whether the mouse button is held down) ctx.lineWidth = 2; ctx.strokeStyle = "#222222"; ctx.beginPath(); if (isMouseDown) { // Draw a frown if the mouse button is held down ctx.moveTo(-5, 10); ctx.quadraticCurveTo(0, 5, 5, 10); } else { // Draw a smile if the mouse button is not held down ctx.moveTo(-5, 10); ctx.quadraticCurveTo(0, 15, 5, 10); } ctx.stroke(); // Right pupil (calculating based on mouse position) let rightEyeX = 6; // Right eye center (X position) let rightEyeY = -5; // Right eye center (Y position) let rightEyeRadius = 2.5; let dxRight = localMouseX - rightEyeX; let dyRight = localMouseY - rightEyeY; let distanceRight = Math.sqrt(dxRight * dxRight + dyRight * dyRight); // Normalize to stay within the eye let rightPupilX, rightPupilY; if (distanceRight < rightEyeRadius) { rightPupilX = rightEyeX + dxRight * 0.3; // Pupils follow the mouse (scaled) rightPupilY = rightEyeY + dyRight * 0.3; } else { rightPupilX = rightEyeX + dxRight * (rightEyeRadius / distanceRight) * 0.3; rightPupilY = rightEyeY + dyRight * (rightEyeRadius / distanceRight) * 0.3; } ctx.lineWidth = 0; ctx.strokeStyle = "rgba(255, 255, 255, 0)"; // Transparent stroke ctx.fillStyle = "#ffffff"; // White fill for the pupil ctx.beginPath(); ctx.ellipse(rightPupilX, rightPupilY, 1, 2, 0, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); // Left pupil (calculating based on mouse position) let leftEyeX = -6; // Left eye center (X position) let leftEyeY = -5; // Left eye center (Y position) let leftEyeRadius = 2.5; let dxLeft = localMouseX - leftEyeX; let dyLeft = localMouseY - leftEyeY; let distanceLeft = Math.sqrt(dxLeft * dxLeft + dyLeft * dyLeft); // Normalize to stay within the eye let leftPupilX, leftPupilY; if (distanceLeft < leftEyeRadius) { leftPupilX = leftEyeX + dxLeft * 0.3; // Pupils follow the mouse (scaled) leftPupilY = leftEyeY + dyLeft * 0.3; } else { leftPupilX = leftEyeX + dxLeft * (leftEyeRadius / distanceLeft) * 0.3; leftPupilY = leftEyeY + dyLeft * (leftEyeRadius / distanceLeft) * 0.3; } ctx.lineWidth = 0; ctx.strokeStyle = "rgba(255, 255, 255, 0)"; // Transparent stroke ctx.fillStyle = "#ffffff"; // White fill for the pupil ctx.beginPath(); ctx.ellipse(leftPupilX, leftPupilY, 1, 2, 0, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); // Draw health bar below the player with rounded ends const healthBarWidth = 40; const healthBarHeight = 5; let healthPercentage = player.health / player.hp; const barWidth = healthBarWidth * healthPercentage; // Draw the health bar (translation already handled by translate above) ctx.beginPath(); ctx.lineCap = "round"; ctx.strokeStyle = "black"; ctx.lineWidth = 5; ctx.moveTo(-healthBarWidth / 2, 25); // Centered on player ctx.lineTo(healthBarWidth / 2, 25); ctx.stroke(); ctx.beginPath(); ctx.lineCap = "round"; ctx.strokeStyle = "green"; ctx.lineWidth = 3.5; ctx.moveTo(-healthBarWidth / 2, 25); // Centered on player ctx.lineTo(-healthBarWidth / 2 + barWidth, 25); ctx.stroke(); // Draw health percentage text textRender((healthPercentage * 100).toFixed(1) + "%", -15, 35, "#ffffff"); // Restore the canvas state to undo translation ctx.restore(); } function textRender(text, x, y, color) { ctx.font = "bold 10px Ubuntu"; // Set the font ctx.fillStyle = "black"; // Fill color ctx.strokeStyle = "black"; // Outline color ctx.lineWidth = 1.6; // Thickness of the outline // Draw the outline ctx.strokeText(text, x, y); // Fill the text ctx.fillText(text, x, y); ctx.fillStyle = color; // Fill color ctx.lineWidth = 0; // Thickness of the outline // Draw the outline ctx.strokeText(text, x, y); // Fill the text ctx.fillText(text, x, y); } function renderSoldierAnt(color, outline) { //let xx = 18 + (30 - 18) * (Math.sin(t/3) * 0.5 + 0.5) ctx.lineWidth = 20.5; ctx.lineCap = "round"; // Set the line cap to round for rounded ends // Draw the mandibles first (so they are behind the body) ctx.strokeStyle = "#292929"; // Mandible outline color ctx.beginPath(); // Start a new path for the mandibles ctx.moveTo(35, 25); // Left mandible ctx.lineTo(80, time); ctx.moveTo(35, -25); // Right mandible ctx.lineTo(80, -time); ctx.stroke(); // Draw mandibles // Now draw the body (which will be above the mandibles) ctx.strokeStyle = outline; // Outline color for body ctx.fillStyle = color; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(-20, 0, 29.7, 0, Math.PI * 2); ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle // Set the transparency of the blue color (RGBA where A is the alpha value) ctx.fillStyle = "rgba(255, 255, 255, 0.2)"; // blue //ctx.rotate(-xx); ctx.beginPath(); ctx.ellipse(-20, 20, 150 / 5, 100 / 5, 0, 0, Math.PI * 2); ctx.fill(); //ctx.rotate(xx) ctx.beginPath(); ctx.ellipse(-20, -20, 150 / 5, 100 / 5, 0, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = outline; // Outline color for body ctx.fillStyle = color; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(20, 0, 39.7, 0, Math.PI * 2); // Radius of 39.7 ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle } // Helper function to convert degrees to radians function convertAngleToRadians(angle) { return angle * (Math.PI / 180); } // Helper function to generate random values within a range function getRandomInRange(min, max) { return Math.random() * (max - min) + min; } // Helper function to generate random angle and radius to place dots within the bounding circle function getRandomPositionInCircle(radius) { // Random angle (0 to 2 * PI) const angle = Math.random() * Math.PI * 2; // Random distance from center, ensuring it stays inside the circle const distance = Math.random() * radius; // Calculate x and y positions using polar coordinates const x = Math.cos(angle) * distance; const y = Math.sin(angle) * distance; return { x, y }; } //credit to flowr devs lmao const memoizedColors = {}; function rgbToHex(r, g, b) { return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); } function componentToHex(c) { var hex = c.toString(16); return hex.length == 1 ? "0" + hex : hex; } function blendColor(color1, color2, t) { const memoizedIndex = color1 + "_" + color2 + "_" + t; if (memoizedColors[memoizedIndex] !== undefined) { return memoizedColors[memoizedIndex]; } const rgb1 = { r: parseInt(color1.slice(1, 3), 16), g: parseInt(color1.slice(3, 5), 16), b: parseInt(color1.slice(5, 7), 16), }; const rgb2 = { r: parseInt(color2.slice(1, 3), 16), g: parseInt(color2.slice(3, 5), 16), b: parseInt(color2.slice(5, 7), 16), }; const result = rgbToHex( Math.floor(rgb1.r * (1 - t) + rgb2.r * t), Math.floor(rgb1.g * (1 - t) + rgb2.g * t), Math.floor(rgb1.b * (1 - t) + rgb2.b * t) ); memoizedColors[memoizedIndex] = result; return result; } function drawPolygon(ctx, x, y, sides, radius, angle, widthStretch, heightStretch) { const step = 2 * Math.PI / sides; // Step between each vertex in the polygon ctx.beginPath(); // Loop through each vertex and plot it for (let i = 0; i < sides; i++) { const currentAngle = angle + i * step; // Rotate the angle for each vertex const vx = x + Math.cos(currentAngle) * radius * widthStretch; // Adjust x based on width stretch const vy = y + Math.sin(currentAngle) * radius * heightStretch; // Adjust y based on height stretch if (i === 0) { ctx.moveTo(vx, vy); // Move to the first vertex } else { ctx.lineTo(vx, vy); // Draw lines to the next vertex } } ctx.closePath(); ctx.stroke(); // Apply stroke (outlines the polygon) ctx.fill(); // Apply fill (optional, can be removed if only the outline is desired) } // Function to draw the mob's appearance (body) function drawMob(mob) { // Save the current canvas state ctx.save(); let scaledMobSize = mob.radius / 50; // Translate to the mob's position (adjusted by camera offset) const mobScreenX = mob.x - cameraOffsetX; const mobScreenY = mob.y - cameraOffsetY; // Translate and rotate based on mob's angle ctx.translate(mobScreenX, mobScreenY); ctx.rotate(mob.angle); // Rotate based on mob's angle // Scale based on mob radius (arbitrary base size of 50) ctx.scale(mob.radius / 50, mob.radius / 50); ctx.beginPath(); let prevX = mob.x; let prevY = mob.y; // Calculate velocity based on position changes let velocity = Math.sqrt((mob.x - prevX) ** 2 + (mob.y - prevY) ** 2); // Fixed power to 2 for realistic velocity calculation // Set the velocityEffect variable to control how much velocity affects t const velocityEffect = 1.5; // Adjust this value to control the sensitivity of the effect const velocityExponent = 2; // The higher the exponent, the faster t will change with speed // Adjust `t` to move faster based on velocity and the velocityEffect let t = performance.now() * Math.max(Math.pow(velocity * velocityEffect, velocityExponent), 1) / 500; // Update previous position prevX = mob.x; prevY = mob.y; let time = 11 + (19 - 11) * (Math.sin(t) * 0.5 + 0.5); let ngpo = -5 + (5 + 5) * (Math.sin(t) * 0.5 + 0.5); ctx.lineJoin = "round"; ctx.lineCap = "round"; switch (mob.name) { case "Stonefly": let xx = time * 0.015 - 60; ctx.lineCap = "round"; // Set the line cap to round for rounded ends // Draw the mandibles first (so they are behind the body) ctx.lineWidth = 13.5; ctx.strokeStyle = "#292929"; // Mandible outline color ctx.beginPath(); // Start a new path for the mandibles ctx.moveTo(35, 23); // Left mandible ctx.quadraticCurveTo(52.5, 15, 70 + -time / 3, time * 2.7); ctx.moveTo(35, -23); // Right mandible ctx.quadraticCurveTo(52.5, -15, 70 + -time / 3, -time * 2.7); ctx.stroke(); // Draw mandibles ctx.lineWidth = 17.5; // Now draw the body ctx.strokeStyle = "#754545"; // Outline color for body ctx.fillStyle = "#855555"; // Body color (Baby Ant) ctx.beginPath(); ctx.ellipse(-50, 0, 90, 26.7, 0, 0, Math.PI * 2); ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle // Set the transparency of the blue color (RGBA where A is the alpha value) ctx.fillStyle = "rgba(255, 255, 255, 0.2)"; // blue // Save the current context state to avoid affecting the body ctx.save(); // Rotate the wings (you can modify `xx` as the rotation angle) ctx.translate(-80, 20); // Move the rotation point to the wing's position ctx.rotate(xx); // Rotate the wings by `xx` radians ctx.beginPath(); ctx.ellipse(50, 0, 750 / 5, 100 / 5, 0, 0, Math.PI * 2); ctx.fill(); ctx.restore(); // Restore the context to its original state // Save the current context state to avoid affecting the body again ctx.save(); // Rotate the other wing ctx.translate(-80, -20); // Move the rotation point to the second wing's position ctx.rotate(-xx); // Rotate the second wing by `-xx` radians ctx.beginPath(); ctx.ellipse(50, 0, 750 / 5, 100 / 5, 0, 0, Math.PI * 2); ctx.fill(); ctx.restore(); // Restore the context to its original state // Draw the body part again (which should remain stationary) ctx.strokeStyle = "#754545"; // Outline color for body ctx.fillStyle = "#855555"; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(15, 0, 29.7, 0, Math.PI * 2); // Radius of 39.7 ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle break; case "Baby Ant": ctx.lineWidth = 20.5; ctx.lineCap = "round"; // Set the line cap to round for rounded ends // Draw the mandibles first (so they are behind the body) ctx.strokeStyle = "#292929"; // Mandible outline color ctx.beginPath(); // Start a new path for the mandibles ctx.moveTo(15, 25); // Left mandible ctx.lineTo(60, time); ctx.moveTo(15, -25); // Right mandible ctx.lineTo(60, -time); ctx.stroke(); // Draw mandibles // Now draw the body (which will be above the mandibles) ctx.strokeStyle = "#454545"; // Outline color for body ctx.fillStyle = "#555555"; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(0, 0, 39.7, 0, Math.PI * 2); // Radius of 39.7 ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle break; case "Worker Ant": ctx.lineWidth = 20.5; ctx.lineCap = "round"; // Set the line cap to round for rounded ends // Draw the mandibles first (so they are behind the body) ctx.strokeStyle = "#292929"; // Mandible outline color ctx.beginPath(); // Start a new path for the mandibles ctx.moveTo(35, 25); // Left mandible ctx.lineTo(80, time); ctx.moveTo(35, -25); // Right mandible ctx.lineTo(80, -time); ctx.stroke(); // Draw mandibles // Now draw the body (which will be above the mandibles) ctx.strokeStyle = "#454545"; // Outline color for body ctx.fillStyle = "#555555"; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(-20, 0, 29.7, 0, Math.PI * 2); ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle ctx.beginPath(); ctx.arc(20, 0, 39.7, 0, Math.PI * 2); // Radius of 39.7 ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle break; case "Baby Fire Ant": ctx.lineWidth = 20.5; ctx.lineCap = "round"; // Set the line cap to round for rounded ends // Draw the mandibles first (so they are behind the body) ctx.strokeStyle = "#292929"; // Mandible outline color ctx.beginPath(); // Start a new path for the mandibles ctx.moveTo(15, 25); // Left mandible ctx.lineTo(60, time); ctx.moveTo(15, -25); // Right mandible ctx.lineTo(60, -time); ctx.stroke(); // Draw mandibles // Now draw the body (which will be above the mandibles) ctx.strokeStyle = "#882200"; // Outline color for body ctx.fillStyle = "#a82a00"; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(0, 0, 39.7, 0, Math.PI * 2); // Radius of 39.7 ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle break; case "Worker Fire Ant": ctx.lineWidth = 20.5; ctx.lineCap = "round"; // Set the line cap to round for rounded ends // Draw the mandibles first (so they are behind the body) ctx.strokeStyle = "#292929"; // Mandible outline color ctx.beginPath(); // Start a new path for the mandibles ctx.moveTo(35, 25); // Left mandible ctx.lineTo(80, time); ctx.moveTo(35, -25); // Right mandible ctx.lineTo(80, -time); ctx.stroke(); // Draw mandibles // Now draw the body (which will be above the mandibles) ctx.strokeStyle = "#882200"; // Outline color for body ctx.fillStyle = "#a82a00"; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(-20, 0, 29.7, 0, Math.PI * 2); ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle ctx.beginPath(); ctx.arc(20, 0, 39.7, 0, Math.PI * 2); // Radius of 39.7 ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle break; case "Soldier Ant": //let xx = 18 + (30 - 18) * (Math.sin(t/3) * 0.5 + 0.5) ctx.lineWidth = 20.5; ctx.lineCap = "round"; // Set the line cap to round for rounded ends // Draw the mandibles first (so they are behind the body) ctx.strokeStyle = "#292929"; // Mandible outline color ctx.beginPath(); // Start a new path for the mandibles ctx.moveTo(35, 25); // Left mandible ctx.lineTo(80, time); ctx.moveTo(35, -25); // Right mandible ctx.lineTo(80, -time); ctx.stroke(); // Draw mandibles // Now draw the body (which will be above the mandibles) ctx.strokeStyle = "#454545"; // Outline color for body ctx.fillStyle = "#555555"; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(-20, 0, 29.7, 0, Math.PI * 2); ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle // Set the transparency of the blue color (RGBA where A is the alpha value) ctx.fillStyle = "rgba(255, 255, 255, 0.2)"; // blue //ctx.rotate(-xx); ctx.beginPath(); ctx.ellipse(-20, 20, 150 / 5, 100 / 5, 0, 0, Math.PI * 2); ctx.fill(); //ctx.rotate(xx) ctx.beginPath(); ctx.ellipse(-20, -20, 150 / 5, 100 / 5, 0, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = "#454545"; // Outline color for body ctx.fillStyle = "#555555"; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(20, 0, 39.7, 0, Math.PI * 2); // Radius of 39.7 ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle //renderSoldierAnt("#454545", "#555555"); break; case "Soldier Fire Ant": //let xx = 18 + (30 - 18) * (Math.sin(t/3) * 0.5 + 0.5) ctx.lineWidth = 20.5; ctx.lineCap = "round"; // Set the line cap to round for rounded ends // Draw the mandibles first (so they are behind the body) ctx.strokeStyle = "#292929"; // Mandible outline color ctx.beginPath(); // Start a new path for the mandibles ctx.moveTo(35, 25); // Left mandible ctx.lineTo(80, time); ctx.moveTo(35, -25); // Right mandible ctx.lineTo(80, -time); ctx.stroke(); // Draw mandibles // Now draw the body (which will be above the mandibles) ctx.strokeStyle = "#882200"; // Outline color for body ctx.fillStyle = "#a82a00"; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(-20, 0, 29.7, 0, Math.PI * 2); ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle // Set the transparency of the blue color (RGBA where A is the alpha value) ctx.fillStyle = "rgba(255, 255, 255, 0.2)"; // blue //ctx.rotate(-xx); ctx.beginPath(); ctx.ellipse(-20, 20, 150 / 5, 100 / 5, 0, 0, Math.PI * 2); ctx.fill(); //ctx.rotate(xx) ctx.beginPath(); ctx.ellipse(-20, -20, 150 / 5, 100 / 5, 0, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = "#882200"; // Outline color for body ctx.fillStyle = "#a82a00"; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(20, 0, 39.7, 0, Math.PI * 2); // Radius of 39.7 ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle //renderSoldierAnt("#454545", "#555555"); break; case "Beetle": let rr = performance.now() / 100; let ny = 15 + (30 - 15) * (Math.sin(rr) * 0.5 + 0.5); ctx.lineJoin = "round"; ctx.lineWidth = 8.7; ctx.lineCap = "round"; // Set the line cap to round for rounded ends //pincers ctx.strokeStyle = "#202020"; // Set pincer stroke color ctx.fillStyle = "#202020"; // Set pincer fill color ctx.beginPath(); ctx.moveTo(30, 30); ctx.quadraticCurveTo(55, 22 + ny / 2, 80, 4.6 + ny / 3); ctx.quadraticCurveTo(45, 22, 30, 10); ctx.closePath(); // Ensure the path is closed ctx.fill(); ctx.stroke(); ctx.strokeStyle = "#202020"; // Set pincer stroke color ctx.fillStyle = "#202020"; // Set pincer fill color //ctx.beginPath(); ctx.moveTo(30, -30); ctx.quadraticCurveTo(55, -(22 + ny / 2), 80, -(4.6 + ny / 3)); ctx.quadraticCurveTo(45, -22, 30, -10); ctx.closePath(); // Ensure the path is closed ctx.fill(); ctx.stroke(); // Now, for the body ctx.beginPath(); // Start a new path for the body ctx.fillStyle = "#8f5db0"; ctx.strokeStyle = "#764b90"; // Set body outline color const pathData = "M46.3219 0c0 21.7437-17.422 33.3645-46.3219 33.3645S-46.3219 21.7437-46.3219 0-28.8999-33.3645-0-33.3645 46.3219-21.7437 46.3219 0z"; // Create a Path2D object from the SVG path data const path = new Path2D(pathData); // Set the fill color and draw the path ctx.fill(path); // Set the outline width, adjust as needed ctx.stroke(path); ctx.moveTo(-20, 0); ctx.lineTo(20, 0); ctx.stroke(); ctx.lineWidth = 6; for (let i = 0; i < 3 * 2; i++) { const x = ((i % 1.5) + 1) * 30 - 45; const y = Math.floor(i / 3 + 1) * 30 - 45; ctx.strokeStyle = "#764b90"; // body outline color ctx.beginPath(); ctx.arc(x, y, 2, 0, 2 * Math.PI); ctx.fillStyle = "#764b90"; ctx.fill(); ctx.stroke(); } break; case "Dragonfly": ctx.lineWidth = 4; ctx.lineCap = "round"; // Set the line cap to round for rounded ends let sizea = 0.15; let sizeb = 0.25; let offsetA = 15; // Draw the mandibles first (so they are behind the body) ctx.strokeStyle = "#292929"; // Mandible outline color ctx.beginPath(); // Start a new path for the mandibles ctx.moveTo(55 * sizea + offsetA, 25 * sizeb); // Left mandible ctx.lineTo(110 * sizea + offsetA, time * sizeb); ctx.moveTo(55 * sizea + offsetA, -25 * sizeb); // Right mandible ctx.lineTo(110 * sizea + offsetA, -time * sizeb); ctx.stroke(); // Draw mandibles //tail ctx.lineWidth = 15; ctx.strokeStyle = "#093259"; ctx.beginPath(); ctx.moveTo(-17, 0); ctx.lineTo(17, 0); ctx.stroke(); ctx.lineWidth = 7; ctx.strokeStyle = "#0e4f8c"; ctx.beginPath(); ctx.moveTo(-17, 0); ctx.lineTo(17, 0); ctx.stroke(); ctx.closePath(); //wings ctx.lineWidth = 0; ctx.strokeStyle = "rgba(0, 0, 0, 0)"; // Transparent stroke ctx.fillStyle = "rgba(250, 250, 255, 0.2)"; // blue // hind wings ctx.beginPath(); ctx.ellipse( 2, 7 + time / 3, 100 / 10, (130 / 10) * (time / 25), 0, 0, Math.PI * 2 ); ctx.fill(); // Only fill the ellipse; do not call ctx.stroke() ctx.closePath(); ctx.beginPath(); ctx.ellipse( 2, -7 - time / 3, 100 / 10, (130 / 10) * (time / 25), 0, 0, Math.PI * 2 ); ctx.fill(); // Only fill the ellipse; do not call ctx.stroke() ctx.closePath(); // front wings ctx.beginPath(); ctx.ellipse( 12, 9 + time / 3, 100 / 10, (150 / 10) * (time / 25), 0, 0, Math.PI * 2 ); ctx.fill(); // Only fill the ellipse; do not call ctx.stroke() ctx.closePath(); ctx.beginPath(); ctx.ellipse( 12, -9 - time / 3, 100 / 10, (150 / 10) * (time / 25), 0, 0, Math.PI * 2 ); ctx.fill(); // Only fill the ellipse; do not call ctx.stroke() ctx.closePath(); //head ctx.lineWidth = 5; ctx.strokeStyle = "#10589c"; ctx.fillStyle = "#1267b6"; ctx.beginPath(); ctx.arc(10.5, 0, 7, 0, Math.PI * 2); ctx.fill(); // Fill the head ctx.stroke(); // Stroke the head (if outline is needed) ctx.closePath(); ctx.lineWidth = 5; ctx.strokeStyle = "#10589c"; ctx.fillStyle = "#1267b6"; ctx.beginPath(); ctx.arc(17.5, 0, 8, 0, Math.PI * 2); ctx.fill(); // Fill the head ctx.stroke(); // Stroke the head (if outline is needed) ctx.closePath(); break; case "Hornet": let scarynumber = 100 / 2.2; let offsep = 10; let miss = 20; //missile ctx.lineCap = "round"; ctx.strokeStyle = "#333333"; ctx.fillStyle = "#333333"; ctx.lineWidth = 2.2; ctx.moveTo(-50, miss); ctx.beginPath(); ctx.lineTo(-50, -miss); ctx.lineTo(-100, -2); ctx.lineTo(-100, 2); ctx.lineTo(-50, miss); ctx.stroke(); ctx.fill(); ctx.closePath(); //body ctx.lineWidth = 2.2; ctx.strokeStyle = "rgba(0, 0, 0, 0)"; ctx.fillStyle = "#ffd363"; ctx.beginPath(); ctx.ellipse(offsep, 0, 135 / 2.2, 100 / 2.2, 0, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); //stripes ctx.fillStyle = "#333333"; ctx.beginPath(); // Start a new path for the rectangle ctx.rect(0, -scarynumber, 19.5, scarynumber * 2); ctx.closePath(); ctx.fill(); ctx.fillStyle = "#333333"; ctx.beginPath(); ctx.rect(40, -scarynumber * 0.75, 19.5, scarynumber * 2 * 0.75); ctx.closePath(); ctx.fill(); ctx.fillStyle = "#333333"; ctx.beginPath(); ctx.rect(-40, -scarynumber * 0.75, 19.5, scarynumber * 2 * 0.75); ctx.closePath(); ctx.fill(); //outline ctx.lineWidth = 10.5; ctx.strokeStyle = "#d3ad46"; ctx.fillStyle = "rgba(0, 0, 0, 0)"; ctx.beginPath(); ctx.ellipse(offsep, 0, 135 / 2.2, 100 / 2.2, 0, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.stroke(); // Don't forget to actually stroke the body outline // Reset strokeStyle before drawing antennae let an1 = 15.3; let an2 = 48.6; let an3 = 8.6; ctx.lineWidth = 8.5; ctx.fillStyle = "#333333"; ctx.strokeStyle = "#333333"; // Set antennae stroke color //ctx.lineWidth = 10.5; // Adjust the line width as needed ctx.beginPath(); ctx.moveTo(70, an1); ctx.lineTo(115, (an2 * time) / 20); ctx.quadraticCurveTo(85, an3, 50, an1); ctx.stroke(); // Actually stroke the antennae line ctx.fill(); // Reset strokeStyle before drawing antennae ctx.beginPath(); ctx.moveTo(70, -an1); ctx.lineTo(115, (-an2 * time) / 20); ctx.quadraticCurveTo(85, -an3, 50, -an1); ctx.stroke(); // Actually stroke the antennae line ctx.fill(); break; case "Missile": //missile ctx.lineCap = "round"; ctx.strokeStyle = "#333333"; ctx.fillStyle = "#333333"; ctx.lineWidth = 2.2; ctx.moveTo(0, 20); ctx.beginPath(); ctx.lineTo(0, -20); ctx.lineTo(50, -2); ctx.lineTo(50, 2); ctx.lineTo(0, 20); ctx.stroke(); ctx.fill(); ctx.closePath(); break; case "Queen Ant": //let xx = 18 + (30 - 18) * (Math.sin(t/3) * 0.5 + 0.5) ctx.lineWidth = 15.5; ctx.lineCap = "round"; // Set the line cap to round for rounded ends // Draw the mandibles first (so they are behind the body) ctx.strokeStyle = "#292929"; // Mandible outline color ctx.beginPath(); // Start a new path for the mandibles ctx.moveTo(35, 25); // Left mandible ctx.lineTo(80, time); ctx.moveTo(35, -25); // Right mandible ctx.lineTo(80, -time); ctx.stroke(); // Draw mandibles // this is for drawing the queen ant's giant gyatt 🤤 ctx.strokeStyle = "#454545"; // Outline color for body ctx.fillStyle = "#555555"; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(-65, 0, 59.7, 0, Math.PI * 2); ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle // Now draw the body (which will be above the mandibles) ctx.strokeStyle = "#454545"; // Outline color for body ctx.fillStyle = "#555555"; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(-20, 0, 49.7, 0, Math.PI * 2); ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle // Set the transparency of the blue color (RGBA where A is the alpha value) ctx.fillStyle = "rgba(255, 255, 255, 0.2)"; // blue //ctx.rotate(-xx); ctx.beginPath(); ctx.ellipse(-45, 20, 300 / 4, 100 / 4, 0, 0, Math.PI * 2); ctx.fill(); //ctx.rotate(xx) ctx.beginPath(); ctx.ellipse(-45, -20, 300 / 4, 100 / 4, 0, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = "#454545"; // Outline color for body ctx.fillStyle = "#555555"; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(20, 0, 39.7, 0, Math.PI * 2); // Radius of 39.7 ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle break; case "Bubble": ctx.lineWidth = 4; ctx.strokeStyle = "#efefef"; ctx.fillStyle = "rgba(0, 230, 240, 0.05)"; ctx.beginPath(); ctx.arc(0, 0, 50, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); ctx.closePath(); ctx.beginPath(); ctx.strokeStyle = "rgba(0,0,0,0)"; ctx.fillStyle = "rgba(255, 255, 255, 0.45)"; ctx.arc(-25, 25, 10, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); break; case "Crab": let crabSize = 2.7; let crabtime = 11 + (19 - 11) * (Math.sin(t * 2.4) * 0.5 + 0.5); let orabtime = 11 + (19 - 11) * (Math.cos(t * 2.4) * 0.5 + 0.5); let avgtime = (crabtime + orabtime) / 2; ctx.lineWidth = 8.5; ctx.lineCap = "round"; //claws ctx.strokeStyle = "#4d2621"; ctx.fillStyle = "#4d2621"; ctx.beginPath(); // Start a new path ctx.moveTo(0, 50); ctx.quadraticCurveTo(50,40,55,20 + ngpo); ctx.lineTo(0,50); ctx.stroke(); // Draws the line ctx.closePath(); ctx.fill(); ctx.beginPath(); // Start a new path ctx.moveTo(0, 50); ctx.quadraticCurveTo(50,40,45,15 + ngpo); ctx.stroke(); // Draws the line ctx.closePath(); ctx.fill(); ctx.beginPath(); // Start a new path ctx.moveTo(0, -50); ctx.quadraticCurveTo(50,-40,55,-20 - ngpo); ctx.lineTo(0,-50); ctx.stroke(); // Draws the line ctx.closePath(); ctx.fill(); ctx.beginPath(); // Start a new path ctx.moveTo(0, -50); ctx.quadraticCurveTo(50,-40,45,-15 - ngpo); ctx.stroke(); // Draws the line ctx.closePath(); ctx.fill(); //right legs ctx.moveTo(25, 24); ctx.lineTo(12 * (crabtime / 10) + 5, 50); ctx.lineTo(20 * (crabtime / 10) + 5, 53); ctx.stroke(); ctx.moveTo(-25, 24); ctx.lineTo(-(12 * (orabtime / 10)), 50); ctx.lineTo(-(20 * (orabtime / 10)), 53); ctx.stroke(); ctx.moveTo(12.5, 27); ctx.lineTo(6 * (crabtime / 10) + 5, 55); ctx.lineTo(10 * (crabtime / 10) + 5, 58); ctx.stroke(); ctx.moveTo(-12.5, 27); ctx.lineTo(-(6 * (orabtime / 10)), 55); ctx.lineTo(-(10 * (orabtime / 10)), 58); ctx.stroke(); //left legs ctx.moveTo(25, 24); ctx.lineTo(12 * (crabtime / 10) + 5, -50); ctx.lineTo(20 * (crabtime / 10) + 5, -53); ctx.stroke(); ctx.moveTo(-25, 24); ctx.lineTo(-(12 * (orabtime / 10)), -50); ctx.lineTo(-(20 * (orabtime / 10)), -53); ctx.stroke(); ctx.moveTo(12.5, 27); ctx.lineTo(6 * (crabtime / 10) + 5, -55); ctx.lineTo(10 * (crabtime / 10) + 5, -62); ctx.stroke(); ctx.moveTo(-12.5, 27); ctx.lineTo(-(6 * (orabtime / 10)), -55); ctx.lineTo(-(10 * (orabtime / 10)), -62); ctx.stroke(); //body ctx.strokeStyle = "#b15a3d"; ctx.fillStyle = "#db6f4b"; ctx.beginPath(); ctx.ellipse(0, 0, 100 / crabSize, 125 / crabSize, 0, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); ctx.stroke(); ctx.beginPath(); ctx.moveTo(18.5, 15); ctx.quadraticCurveTo(0, 10, -18.5, 15); ctx.stroke(); ctx.beginPath(); ctx.moveTo(18.5, -15); ctx.quadraticCurveTo(0, -10, -18.5, -15); ctx.stroke(); //ctx.closePath(); break; case "Cactus": const sides = mob.rarity + 9; // Total number of sides const radius = 50; // Fixed radius for consistent size ctx.beginPath(); for (let i = 0; i <= sides; i++) { // Calculate the angle for each vertex const angle = (i / sides) * 2 * Math.PI; // Calculate the vertex position const x = Math.cos(angle) * radius; const y = Math.sin(angle) * radius; if (i === 0) { // Move to the first vertex ctx.moveTo(x, y); } else { // Calculate the midpoint between the current and previous vertex const prevAngle = ((i - 1) / sides) * 2 * Math.PI; const midX = Math.cos(prevAngle + (angle - prevAngle) / 2) * radius * 0.8; // Bulge inward const midY = Math.sin(prevAngle + (angle - prevAngle) / 2) * radius * 0.8; // Bulge inward // Draw a quadratic curve ctx.quadraticCurveTo(midX, midY, x, y); } } ctx.closePath(); // Close the polygon shape ctx.strokeStyle = "#288841"; // Outline color ctx.lineWidth = 105 / mob.radius; // Outline width ctx.fillStyle = "#32a953"; // Fill color ctx.fill(); // Fill the polygon ctx.stroke(); // Draw the outline break; case "Fire Ant Burrow": ctx.strokeStyle = "rgba(255, 255, 255, 0)"; ctx.fillStyle = "#c43829"; ctx.beginPath(); ctx.arc(0, 0, 50, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.fillStyle = "#992c20"; ctx.beginPath(); ctx.arc(0, 0, 35, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.fillStyle = "#702018"; ctx.beginPath(); ctx.arc(0, 0, 15, 0, Math.PI * 2); ctx.closePath(); break; case "Ant Hole": ctx.strokeStyle = "rgba(255, 255, 255, 0)"; ctx.fillStyle = "rgb(172, 90, 13)"; ctx.beginPath(); ctx.arc(0, 0, 50, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.fillStyle = "rgb(142, 60, 3)"; ctx.beginPath(); ctx.arc(0, 0, 35, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.fillStyle = "rgb(112, 30, 0)"; ctx.beginPath(); ctx.arc(0, 0, 15, 0, Math.PI * 2); ctx.closePath(); break; case "Ladybug": const lastX = 50 * Math.cos(-Math.PI / 4); // Correcting the angle to radians directly const lastY = 50 * Math.sin(-Math.PI / 4); // Set up styling ctx.lineCap = "round"; ctx.lineWidth = 7.5; // Reset path for the smaller circle (to avoid coloring the whole shape) ctx.beginPath(); // Start a new path for the second smaller circle // Draw the smaller dark circle ctx.strokeStyle = blendColor("#202020", "#000000", 0.19); ctx.fillStyle = "#202020"; ctx.arc(24.5, 0, 25, 0, Math.PI * 2); // Draw half-circle ctx.closePath(); ctx.fill(); // Fill the smaller circle ctx.stroke(); // Stroke for smaller circle ctx.strokeStyle = blendColor("#df0101", "#000000", 0.19); ctx.fillStyle = "#df0101"; // Draw the circle and dent ctx.beginPath(); ctx.arc( 0, 0, 50, convertAngleToRadians(45), convertAngleToRadians(-45), false ); // Draw 3/4 of a circle ctx.lineTo(25, 0); // Line to create the dent ctx.closePath(); ctx.fill(); // Fill the shape ctx.stroke(); // Stroke for visualization ctx.beginPath(); ctx.strokeStyle = "#202020"; // Set stroke style to visible ctx.fillStyle = "#202020"; // Set fill color for the spots // Increase the scaling further to spread the spots more ctx.beginPath(); ctx.strokeStyle = "#202020"; // Set stroke style to visible ctx.fillStyle = "#202020"; // Set fill color for the spots // Adjusting the scale and ensuring the spots are centered const spreadFactor = 50; // Adjust this value for more or less spread const offsetFactor = 0; // Keep the spots closer to the center let spotty = 1 + (1.15 ** mob.rarity - 1); // Save the current context before translating ctx.save(); // Translate the origin to the center (0, 0) ctx.translate(-75, -75); // Spot 1: Centered on (0, 0) ctx.beginPath(); ctx.arc( mob.ran1 * spreadFactor - offsetFactor, mob.ran2 * spreadFactor - offsetFactor, 5 + mob.ran2 * spotty, 0, Math.PI * 2 ); ctx.fill(); // Fill the shape // Spot 2: Centered on (0, 0) ctx.beginPath(); ctx.arc( mob.ran2 * spreadFactor - offsetFactor, mob.ran3 * spreadFactor - offsetFactor, 5 + mob.ran1 * spotty, 0, Math.PI * 2 ); ctx.fill(); // Fill the shape // Spot 3: Centered on (0, 0) ctx.beginPath(); ctx.arc( mob.ran1 * spreadFactor - offsetFactor, mob.ran3 * spreadFactor - offsetFactor, 5 + mob.ran3 * spotty, 0, Math.PI * 2 ); ctx.fill(); // Fill the shape // Spot 4: Centered on (0, 0) ctx.beginPath(); ctx.arc( mob.ran2 * spreadFactor - offsetFactor, mob.ran3 * spreadFactor - offsetFactor, 5 + mob.ran2 * spotty, 0, Math.PI * 2 ); ctx.fill(); // Fill the shape // Restore the context back to its original state ctx.restore(); break; case "Rock": ctx.lineWidth = 105 / mob.radius; // Outline width ctx.strokeStyle = blendColor("#808080", "#000000", 0.19); ctx.fillStyle = "#808080"; const rockside = 5 + mob.rarity; // Set up polygon path ctx.beginPath(); for (let i = 0; i < rockside; i++) { const angle = (i * 2 * Math.PI) / rockside; // Calculate angle for each vertex const x = 50 * Math.cos(angle); // Calculate x coordinate const y = 50 * Math.sin(angle); // Calculate y coordinate if (i === 0) { ctx.moveTo(x, y); // Move to the first point } else { ctx.lineTo(x, y); // Draw lines to subsequent points } } ctx.closePath(); // Close the path to form the polygon ctx.stroke(); // Draw the polygon break; case "Spider": ctx.lineWidth = 14.5; // Define the leg length and angular range for clustering ctx.lineCap = "round"; // Set the line cap to round for rounded ends ctx.strokeStyle = "#333333"; // Leg color const longsection = 65; const shortsection = 45; const controlPoint = -20 + ngpo; ctx.save(); ctx.rotate(convertAngleToRadians(ngpo * 2.5)); ctx.beginPath(); ctx.moveTo(longsection, shortsection); // Start at the center ctx.quadraticCurveTo(10, controlPoint, 0, 0); // Draw the leg outward ctx.quadraticCurveTo(-10, -controlPoint, -longsection, -shortsection); // Draw the leg outward ctx.stroke(); // Draw the line ctx.beginPath(); ctx.moveTo(longsection, -shortsection); // Start at the center ctx.quadraticCurveTo(0, -controlPoint, 0, 0); // Draw the leg outward ctx.quadraticCurveTo(0, controlPoint, -longsection, shortsection); // Draw the leg outward ctx.stroke(); // Draw the line ctx.restore(); const alongsection = 35; const ashortsection = 67; const acontrolPoint = -10; ctx.save(); ctx.rotate(convertAngleToRadians(-ngpo * 1.5)); ctx.beginPath(); ctx.moveTo(alongsection, ashortsection); // Start at the center ctx.quadraticCurveTo(10, acontrolPoint, 0, 0); // Draw the leg outward ctx.quadraticCurveTo(-10, -acontrolPoint, -alongsection, -ashortsection); // Draw the leg outward ctx.stroke(); // Draw the line ctx.beginPath(); ctx.moveTo(alongsection, -ashortsection); // Start at the center ctx.quadraticCurveTo(0, -acontrolPoint, 0, 0); // Draw the leg outward ctx.quadraticCurveTo(0, acontrolPoint, -alongsection, ashortsection); // Draw the leg outward ctx.stroke(); // Draw the line ctx.restore(); // Draw the body ctx.strokeStyle = "#403525"; // Outline color for body ctx.fillStyle = "#4f412e"; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(0, 0, 42, 0, Math.PI * 2); // Radius of 42 ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle break; case "Bee": let scarynumbe = 100 / 2.2; let offsett = 10; let missile = 20; // Missile ctx.lineCap = "round"; ctx.strokeStyle = "#333333"; ctx.fillStyle = "#333333"; ctx.lineWidth = 2.2; ctx.moveTo(-35, missile); ctx.beginPath(); ctx.lineTo(-35, -missile); ctx.lineTo(-70, -2); ctx.lineTo(-70, 2); ctx.lineTo(-35, missile); ctx.stroke(); ctx.fill(); ctx.closePath(); // Body ctx.lineWidth = 2.2; ctx.strokeStyle = "rgba(0, 0, 0, 0)"; ctx.fillStyle = "#ffe763"; ctx.beginPath(); ctx.ellipse(offsett, 0, 135 / 2.2, 100 / 2.2, 0, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); // Stripes ctx.fillStyle = "#333333"; ctx.beginPath(); // Start a new path for the rectangle ctx.rect(0, -scarynumbe, 19.5, scarynumbe * 2); ctx.closePath(); ctx.fill(); ctx.fillStyle = "#333333"; ctx.beginPath(); ctx.rect(40, -scarynumbe * 0.75, 19.5, scarynumbe * 2 * 0.75); ctx.closePath(); ctx.fill(); ctx.fillStyle = "#333333"; ctx.beginPath(); ctx.rect(-40, -scarynumbe * 0.75, 19.5, scarynumbe * 2 * 0.75); ctx.closePath(); ctx.fill(); // Outline ctx.lineWidth = 10.5; ctx.strokeStyle = "#d3ad46"; ctx.fillStyle = "rgba(0, 0, 0, 0)"; ctx.beginPath(); ctx.ellipse(offsett, 0, 135 / 2.2, 100 / 2.2, 0, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.stroke(); // Don't forget to actually stroke the body outline // Antennae let ann1 = 15.3; let ann2 = 48.6; let ann3 = 8.6; ctx.lineWidth = 8.5; ctx.strokeStyle = "#333333"; // Set stroke color for the outlines // Left antenna ctx.moveTo(90, ann1); // Move to the starting point ctx.beginPath(); // Start a new path ctx.lineTo(115, (ann2 * time) / 20); // Draw the first segment ctx.quadraticCurveTo(65, ann3, 50, ann1); // Draw the curve ctx.stroke(); // Stroke the path without closing it // Right antenna ctx.moveTo(90, -ann1); // Move to the starting point ctx.beginPath(); // Start a new path ctx.lineTo(115, (-ann2 * time) / 20); // Draw the first segment ctx.quadraticCurveTo(65, -ann3, 50, -ann1); // Draw the curve ctx.stroke(); // Stroke the path without closing it //antennae tip ctx.beginPath(); // Start a new path ctx.fillStyle = "#333333"; ctx.ellipse(105, (-ann2 * time) / 20, 10, 10, 0, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); // Stroke the path without closing it ctx.beginPath(); // Start a new path ctx.ellipse(105, (ann2 * time) / 20, 10, 10, 0, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); // Stroke the path without closing it break; case "Starfish": ctx.save(); // Save the current canvas state before applying transformations // Apply rotation to the mob ctx.rotate(t * 3); // Rotation // Apply the transformation for drawing ctx.strokeStyle = "#aa403f"; // Set stroke color for the larger pentagon ctx.fillStyle = "rgba(0, 0, 0, 0)"; ctx.lineJoin = "round"; ctx.lineWidth = 26; const length = 50; // Length of each line for the larger pentagon const curveHeight = 30; // Height of the concave curve (adjust as needed) // Draw the larger pentagon for (let i = 0; i < 5; i++) { const angle = (i * 2 * Math.PI) / 5; // Divide the circle into 5 equal parts const x1 = Math.cos(angle) * length; const y1 = Math.sin(angle) * length; const x2 = Math.cos(angle + (2 * Math.PI) / 5) * length; const y2 = Math.sin(angle + (2 * Math.PI) / 5) * length; // Calculate the control point for the inward concave curve const cx = (x1 + x2) / 2 - Math.cos(angle + Math.PI / 4) * curveHeight; const cy = (y1 + y2) / 2 - Math.sin(angle + Math.PI / 4) * curveHeight; ctx.beginPath(); // Start a new path to avoid leftover paths ctx.moveTo(x1, y1); ctx.quadraticCurveTo(cx, cy, x2, y2); // Draw the inward concave curve ctx.stroke(); } // Draw the smaller pentagon ctx.strokeStyle = "#d14f4d"; // Set stroke color for the smaller pentagon ctx.fillStyle = "#d14f4d"; // Set fill color to match stroke color ctx.lineWidth = 12.5; // Smaller line width for (let i = 0; i < 5; i++) { const angle = (i * 2 * Math.PI) / 5; const x1 = Math.cos(angle) * length; const y1 = Math.sin(angle) * length; const x2 = Math.cos(angle + (2 * Math.PI) / 5) * length; const y2 = Math.sin(angle + (2 * Math.PI) / 5) * length; const cx = (x1 + x2) / 2 - Math.cos(angle + Math.PI / 4) * curveHeight; const cy = (y1 + y2) / 2 - Math.sin(angle + Math.PI / 4) * curveHeight; ctx.beginPath(); // Start a new path to avoid leftover paths ctx.moveTo(x1, y1); ctx.quadraticCurveTo(cx, cy, x2, y2); // Draw the inward concave curve ctx.stroke(); } // Draw an even smaller pentagon const smallsize = 20; ctx.lineWidth = 23.2; for (let i = 0; i < 5; i++) { const angle = (i * 2 * Math.PI) / 5; const x1 = Math.cos(angle) * smallsize; const y1 = Math.sin(angle) * smallsize; const x2 = Math.cos(angle + (2 * Math.PI) / 5) * smallsize; const y2 = Math.sin(angle + (2 * Math.PI) / 5) * smallsize; const cx = (x1 + x2) / 2 - Math.cos(angle + Math.PI / 4) * curveHeight; const cy = (y1 + y2) / 2 - Math.sin(angle + Math.PI / 4) * curveHeight; ctx.beginPath(); // Start a new path to avoid leftover paths ctx.moveTo(x1, y1); ctx.quadraticCurveTo(cx, cy, x2, y2); // Draw the inward concave curve ctx.stroke(); } ctx.restore(); // Restore canvas state after transformations break; case "Fly": ctx.lineWidth = 20.5; ctx.lineCap = "round"; // Set the line cap to round for rounded ends let flystuff = (time / 10 - 190) * 0.8; // Draw the body part (stationary) ctx.strokeStyle = "#454545"; // Outline color for body ctx.fillStyle = "#555555"; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(0, 0, 39.7, 0, Math.PI * 2); // Radius of 39.7 ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle ctx.closePath(); // Set the transparency of the blue color (RGBA where A is the alpha value) ctx.strokeStyle = "rgba(255, 255, 255, 0)"; // blue ctx.fillStyle = "rgba(255, 255, 255, 0.2)"; // blue // Draw the first wing with rotation ctx.fillStyle = "rgba(255, 255, 255, 0.2)"; // blue ctx.save(); // Save the current canvas state ctx.translate(0, 0); // Translate to the point where the wing meets the body (adjusted from the previous center) ctx.rotate(-flystuff); // Rotate based on time ctx.beginPath(); ctx.ellipse(-170 / 5, 120 / 5, 170 / 5, 120 / 5, 0, 0, Math.PI * 2); // Adjusted the coordinates to rotate around the tip ctx.fill(); ctx.restore(); // Restore the canvas to the previous state, undoing the rotation // Draw the second wing with rotation ctx.fillStyle = "rgba(255, 255, 255, 0.2)"; // blue ctx.save(); // Save the current canvas state ctx.translate(0, 0); // Translate to the point where the wing meets the body (adjusted) ctx.rotate(flystuff); // Rotate in the opposite direction for the second wing ctx.beginPath(); ctx.ellipse(-170 / 5, -120 / 5, 170 / 5, 120 / 5, 0, 0, Math.PI * 2); // Adjusted the coordinates to rotate around the tip ctx.fill(); ctx.restore(); // Restore the canvas to the previous state, undoing the rotation break; case "Leech": // Draw the first line with the first style ctx.lineJoin = "round"; ctx.lineWidth = 15; // Set the new line width // Draw the mandibles first (so they are behind the body) ctx.strokeStyle = "#202020"; // Mandible outline color ctx.beginPath(); // Start a new path for the mandibles ctx.moveTo(15, 35); // Left mandible ctx.lineTo(80, time * 1.2); ctx.moveTo(15, -35); // Right mandible ctx.lineTo(80, -time * 1.2); ctx.stroke(); // Draw mandibles ctx.beginPath(); ctx.lineWidth = 100; ctx.strokeStyle = "#292929"; ctx.fillStyle = "rgba(255, 255, 255, 0)"; // Save the original canvas state before translation ctx.save(); // Apply rotation only to the mob ctx.rotate(-mob.angle); // Only rotate the mob, not the nodes // Start the line at the mob's center (mob.x, mob.y) ctx.moveTo(0, 0); // Start at the mob's translated origin // Defensive check: Ensure mob.nodes exists and is an array before accessing its length if (Array.isArray(mob.nodes)) { // Save the state before drawing nodes if needed for (let i = 0; i < mob.nodes.length; i++) { const node = mob.nodes[i]; const nodeScreenX = i === 0 ? 0 : node.x - mob.x; const nodeScreenY = i === 0 ? 0 : node.y - mob.y; // Draw the node line if (i === 0) { ctx.moveTo(nodeScreenX, nodeScreenY); } else { ctx.lineTo(nodeScreenX, nodeScreenY); } ctx.stroke(); // Draw the node stroke } } else { } // Restore the canvas state back to the original before the second line ctx.restore(); // Draw the second line with a new style ctx.beginPath(); ctx.lineWidth = 75; // Set the new line width ctx.strokeStyle = "#404040"; // Set the new stroke style // Save the original canvas state again if you need to apply transformations to the second path ctx.save(); // Apply rotation only to the mob for the second line ctx.rotate(-mob.angle); // Defensive check: Ensure mob.nodes exists and is an array before accessing its length if (Array.isArray(mob.nodes)) { // Now draw the second line from the same starting point, but with different styles for (let i = 0; i < mob.nodes.length; i++) { const node = mob.nodes[i]; const nodeScreenX = i === 0 ? 0 : node.x - mob.x; const nodeScreenY = i === 0 ? 0 : node.y - mob.y; if (i === 0) { ctx.moveTo(nodeScreenX, nodeScreenY); } else { ctx.lineTo(nodeScreenX, nodeScreenY); } } ctx.stroke(); // Draw the second line } else { } // Restore the canvas state after the second line ctx.restore(); break; case "Sponge": let spongeradius = 50; const numLines = 15; if (mob.ran1 < 1.33) { ctx.strokeStyle = blendColor("#c1a37d", "#FF0000", 0); } else if (1.33 < mob.ran1 < 1.66) { ctx.strokeStyle = blendColor("#977d90", "#FF0000", 0); } else if (1.66 < mob.ran1 < 2) { ctx.strokeStyle = blendColor("#9b81b9", "#FF0000", 0); } else { ctx.strokeStyle = blendColor("#c1a37d", "#FF0000", 0); } // Loop to draw the 16 radial lines for (let i = 0; i < numLines; i++) { const angle = (i * 2 * Math.PI) / numLines; // Angle for each line const spongexEnd = spongeradius * Math.cos(angle); // X coordinate of the line's end const spongeyEnd = spongeradius * Math.sin(angle); // Y coordinate of the line's end // Draw the line from the center of the circle to the end point ctx.lineWidth = 25; ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(spongexEnd, spongeyEnd); ctx.stroke(); } if (mob.ran1 < 1.33) { ctx.strokeStyle = blendColor("#efc99b", "#FF0000", 0); } else if (1.33 < mob.ran1 < 1.66) { ctx.strokeStyle = blendColor("#ad90a3", "#FF0000", 0); } else if (1.66 < mob.ran1 < 2) { ctx.strokeStyle = blendColor("#9b81b9", "#FF0000", 0); } else { ctx.strokeStyle = blendColor("#efc99b", "#FF0000", 0); } // Loop to draw the 16 radial lines for (let i = 0; i < numLines; i++) { const angle = (i * 2 * Math.PI) / numLines; // Angle for each line const spongexEnd = 45 * Math.cos(angle); // X coordinate of the line's end const spongeyEnd = 45 * Math.sin(angle); // Y coordinate of the line's end // Draw the line from the center of the circle to the end point ctx.lineWidth = 22.5; ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(spongexEnd, spongeyEnd); ctx.stroke(); } // Calculate positions of the 5 circles arranged in a pentagonal pattern const pentagonRadius = 42.5; // Radius of the larger circle that holds the centers of the 5 smaller circles const numCircles = 5; if (mob.ran1 < 1.33) { ctx.strokeStyle = blendColor("#c1a37d", "#FF0000", 0); ctx.fillStyle = blendColor("#c1a37d", "#FF0000", 0); } else if (1.33 < mob.ran1 < 1.66) { ctx.strokeStyle = blendColor("#977d90", "#FF0000", 0); ctx.fillStyle = blendColor("#977d90", "#FF0000", 0); } else if (1.66 < mob.ran1 < 2) { ctx.strokeStyle = blendColor("#9b81b9", "#FF0000", 0); ctx.fillStyle = blendColor("#9b81b9", "#FF0000", 0); } else { ctx.strokeStyle = blendColor("#c1a37d", "#FF0000", 0); ctx.fillStyle = blendColor("#c1a37d", "#FF0000", 0); } for (let i = 0; i < numCircles; i++) { const spongeangle = (i * 2 * Math.PI) / numCircles + convertAngleToRadians(36); // Angle for each circle center const spongexCenter = pentagonRadius * Math.cos(spongeangle); // X position of the circle center const spongeyCenter = pentagonRadius * Math.sin(spongeangle); // Y position of the circle center // Draw the circle at this position ctx.beginPath(); ctx.arc(spongexCenter, spongeyCenter, 7, 0, 2 * Math.PI); // Circle with radius 25 ctx.fill(); ctx.lineWidth = 2; ctx.stroke(); // Draw the circle at this position ctx.beginPath(); ctx.arc(spongexCenter * 0.65, spongeyCenter * 0.65, 4, 0, 2 * Math.PI); // Circle with radius 25 ctx.fill(); ctx.lineWidth = 2; ctx.stroke(); // Draw the circle at this position ctx.beginPath(); ctx.arc(spongexCenter * 0.35, spongeyCenter * 0.35, 3, 0, 2 * Math.PI); // Circle with radius 25 ctx.fill(); ctx.lineWidth = 2; ctx.stroke(); } break; case "Stalagmite": ctx.lineWidth = 8; ctx.lineJoin = "round"; ctx.lineCap = "round"; const stalagcolor = "#304a6c"; ctx.strokeStyle = blendColor(stalagcolor, "#000000", 0.25); ctx.fillStyle = stalagcolor; let stalSides = (mob.rarity + 1) * 5; let stalradius = 50; // Add longer protruding lines from the corners const protrudeLength = 20; // Length of protruding lines for (let i = 0; i < stalSides; i++) { let angle = (i * 2 * Math.PI) / stalSides; const cornerX = stalradius * Math.cos(angle); // Corner point x const cornerY = stalradius * Math.sin(angle); // Corner point y // Protruding line: Extend from the corner const protrudeX = cornerX + protrudeLength * Math.cos(angle); const protrudeY = cornerY + protrudeLength * Math.sin(angle); ctx.beginPath(); ctx.moveTo(cornerX, cornerY); // Start at the corner ctx.lineTo(protrudeX, protrudeY); // Draw to the protruding point ctx.stroke(); } // Draw the main shape (polygon) ctx.beginPath(); // Start path before the loop for (let i = 0; i < stalSides; i++) { let stalagAngle = (i * 2 * Math.PI) / stalSides; const stX = stalradius * Math.cos(stalagAngle); // Calculate x coordinate const stY = stalradius * Math.sin(stalagAngle); // Calculate y coordinate if (i === 0) { ctx.moveTo(stX, stY); // Move to the first point } else { ctx.lineTo(stX, stY); // Draw line to the next point } } ctx.closePath(); // Close the path after the loop ctx.fill(); // Fill the shape ctx.stroke(); // Outline the shape // Draw the smaller pentagon (inner shape) ctx.fillStyle = blendColor(stalagcolor, "#ffffff", 0.25); ctx.beginPath(); // Start path before the loop for (let i = 0; i < stalSides + 1; i++) { let stalagAngle = (i * 2 * Math.PI) / stalSides + convertAngleToRadians(360 / stalSides) / 2; const stX = 25 * Math.cos(stalagAngle); // Calculate x coordinate for the inner pentagon const stY = 25 * Math.sin(stalagAngle); // Calculate y coordinate for the inner pentagon if (i === 0) { ctx.moveTo(stX, stY); // Move to the first point } else { ctx.quadraticCurveTo(0, 0, stX, stY); // Draw curve to the next point } } ctx.closePath(); // Close the path after the loop ctx.fill(); // Fill the shape // Add shorter lines from the edges const edgeLength = 20; // Length of shorter lines from edges for (let i = 0; i < stalSides; i++) { let angle = (i * 2 * Math.PI) / stalSides; // Directly use the angle for the edge, not the midpoint const edgeX = stalradius * Math.cos(angle); // Edge point x const edgeY = stalradius * Math.sin(angle); // Edge point y // Shorter line from the edge: Extend inward const shortEdgeX = edgeX + edgeLength * Math.cos(angle); const shortEdgeY = edgeY + edgeLength * Math.sin(angle); ctx.beginPath(); ctx.moveTo(edgeX, edgeY); // Start at the edge ctx.lineTo(shortEdgeX, shortEdgeY); // Draw inward ctx.stroke(); } // Add longer protruding lines from the corners again if necessary // If you don't want this repeated, remove the second block for protruding lines break; case "Tank": ctx.lineWidth = 6; let barrelCount = 1; // Default barrel count switch (mob.rarity) { case 0: case 1: barrelCount = 1; break; case 2: case 3: barrelCount = 2; break; case 4: case 5: barrelCount = 4; break; case 6: case 7: barrelCount = 8; break; } const barrelLength = 95; const barrelWidth = 120 / 3; const barrelDistance = 50; // Distance from the center (same as the body radius) for (let i = 0; i < barrelCount; i++) { const angle = (Math.PI * 2 / barrelCount) * i; ctx.save(); ctx.rotate(angle); // Draw barrel ctx.strokeStyle = "#727272"; ctx.fillStyle = "#999999"; ctx.lineWidth = 6; ctx.beginPath(); ctx.rect(0, -barrelWidth / 2, barrelLength, barrelWidth); ctx.closePath(); ctx.fill(); ctx.stroke(); ctx.restore(); } // main body ctx.beginPath(); ctx.fillStyle = "#00b2e1"; ctx.strokeStyle = "#0285a7"; ctx.arc(0, 0, 50, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.stroke(); break; case "Bullet": // main body ctx.beginPath(); // Start a new path before drawing ctx.fillStyle = "#00b2e1"; ctx.strokeStyle = "#0285a7"; // Use `strokeStyle` instead of `stroke` ctx.lineWidth = 15; ctx.arc(0, 0, 50, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.stroke(); break; case "Moth": ctx.lineWidth = 16.5; ctx.lineEnd = "round"; // Set the line cap to round for rounded ends let mothstuff = (time / 10 - 190) * 0.8; // Draw the body part (stationary) ctx.strokeStyle = "#543e3e"; // Outline color for body ctx.fillStyle = "#705353"; // Body color (Baby Ant) ctx.beginPath(); ctx.arc(0, 0, 39.7, 0, Math.PI * 2); // Radius of 39.7 ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle ctx.closePath(); // Set the transparency of the blue color (RGBA where A is the alpha value) ctx.strokeStyle = "rgba(255, 255, 255, 0)"; // blue ctx.fillStyle = "rgba(255, 255, 255, 0.2)"; // blue // Draw the first wing with rotation ctx.fillStyle = "rgba(255, 255, 255, 0.2)"; // blue ctx.save(); // Save the current canvas state ctx.translate(0, 0); // Translate to the point where the wing meets the body (adjusted from the previous center) ctx.rotate(-mothstuff); // Rotate based on time ctx.beginPath(); ctx.ellipse(-170 / 5, 120 / 5, 170 / 5, 120 / 5, 0, 0, Math.PI * 2); // Adjusted the coordinates to rotate around the tip ctx.fill(); ctx.restore(); // Restore the canvas to the previous state, undoing the rotation // Draw the second wing with rotation ctx.fillStyle = "rgba(255, 255, 255, 0.2)"; // blue ctx.save(); // Save the current canvas state ctx.translate(0, 0); // Translate to the point where the wing meets the body (adjusted) ctx.rotate(mothstuff); // Rotate in the opposite direction for the second wing ctx.beginPath(); ctx.ellipse(-170 / 5, -120 / 5, 170 / 5, 120 / 5, 0, 0, Math.PI * 2); // Adjusted the coordinates to rotate around the tip ctx.fill(); ctx.restore(); // Restore the canvas to the previous state, undoing the rotation ctx.strokeStyle = "#222222"; ctx.fillStyle = "#222222"; ctx.lineWidth = 8; ctx.beginPath(); ctx.moveTo(20,10); ctx.quadraticCurveTo(45,5,70,30 - ngpo*1.4); ctx.stroke(); ctx.beginPath(); ctx.strokeStyle = "rgba(255, 255, 255, 0)"; ctx.arc(70,30 - ngpo*1.4,10,0,Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.strokeStyle = "#222222"; ctx.moveTo(20,-10); ctx.quadraticCurveTo(45,-5,70,-30 + ngpo*1.4); ctx.stroke(); ctx.beginPath(); ctx.strokeStyle = "rgba(255, 255, 255, 0)"; ctx.arc(70,-30 + ngpo*1.4,10,0,Math.PI * 2); ctx.closePath(); ctx.fill(); break; case "Pill Bug": let notscarynumber = 100 / 2.2; let notoffsep = 10; //body ctx.lineWidth = 2.2; ctx.strokeStyle = "rgba(0, 0, 0, 0)"; ctx.fillStyle = "#555555"; ctx.beginPath(); ctx.ellipse(notoffsep, 0, 135 / 2.2, 100 / 2.2, 0, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); //outline ctx.lineWidth = 10.5; ctx.strokeStyle = "#444444"; ctx.fillStyle = "rgba(0, 0, 0, 0)"; ctx.beginPath(); ctx.ellipse(notoffsep, 0, 135 / 2.2, 100 / 2.2, 0, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.stroke(); // Don't forget to actually stroke the body outline ctx.beginPath(); ctx.ellipse(notoffsep, 0, 95 / 2.2, 100 / 2.2, 0, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); // Don't forget to actually stroke the body outline ctx.beginPath(); ctx.ellipse(notoffsep, 0, 45 / 2.2, 100 / 2.2, 0, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); // Don't forget to actually stroke the body outline //spots ctx.beginPath(); ctx.arc(10,20,4 - ngpo/2,0,Math.PI*2); ctx.stroke(); // Don't forget to actually stroke the body outline ctx.beginPath(); ctx.arc(10,-20,4 + ngpo/2,0,Math.PI*2); ctx.stroke(); // Don't forget to actually stroke the body outline // Reset strokeStyle before drawing antennae let notan1 = 15.3; let notan2 = 62.5; let notan3 = 8.6; ctx.lineWidth = 8.5; ctx.fillStyle = "#333333"; ctx.strokeStyle = "#333333"; // Set antennae stroke color //ctx.lineWidth = 10.5; // Adjust the line width as needed ctx.beginPath(); ctx.moveTo(70, notan1); ctx.lineTo(95, (notan2 * time) / 20); ctx.quadraticCurveTo(85, notan3, 50, notan1); ctx.stroke(); // Actually stroke the antennae line ctx.fill(); // Reset strokeStyle before drawing antennae ctx.beginPath(); ctx.moveTo(70, -notan1); ctx.lineTo(95, (-notan2 * time) / 20); ctx.quadraticCurveTo(85, -notan3, 50, -notan1); ctx.stroke(); // Actually stroke the antennae line ctx.fill(); break; case "Scorpion": ctx.lineWidth = 7.5; // Define the leg length and angular range for clustering ctx.lineCap = "round"; // Set the line cap to round for rounded ends ctx.strokeStyle = "#333333"; // Leg color const slongsection = 35; const sshortsection = 35; const scontrolPoint = -20 + ngpo; ctx.save(); ctx.rotate(convertAngleToRadians(ngpo * 2.5)); ctx.beginPath(); ctx.moveTo(slongsection, sshortsection); // Start at the center ctx.quadraticCurveTo(10, scontrolPoint, 0, 0); // Draw the leg outward ctx.quadraticCurveTo(-10, -scontrolPoint, -slongsection, -sshortsection); // Draw the leg outward ctx.stroke(); // Draw the line ctx.beginPath(); ctx.moveTo(slongsection, -sshortsection); // Start at the center ctx.quadraticCurveTo(0, -scontrolPoint, 0, 0); // Draw the leg outward ctx.quadraticCurveTo(0, scontrolPoint, -slongsection, sshortsection); // Draw the leg outward ctx.stroke(); // Draw the line ctx.restore(); const salongsection = 10; const sashortsection = 47; const sacontrolPoint = -10; ctx.save(); ctx.rotate(convertAngleToRadians(-ngpo * 1.5)); ctx.beginPath(); ctx.moveTo(salongsection, sashortsection); // Start at the center ctx.quadraticCurveTo(10, sacontrolPoint, 0, 0); // Draw the leg outward ctx.quadraticCurveTo(-10, -sacontrolPoint, -salongsection, -sashortsection); // Draw the leg outward ctx.stroke(); // Draw the line ctx.beginPath(); ctx.moveTo(salongsection, -sashortsection); // Start at the center ctx.quadraticCurveTo(0, -sacontrolPoint, 0, 0); // Draw the leg outward ctx.quadraticCurveTo(0, sacontrolPoint, -salongsection, sashortsection); // Draw the leg outward ctx.stroke(); // Draw the line ctx.restore(); // Draw the body ctx.strokeStyle = "#9e7d24"; // Outline color for body ctx.fillStyle = "#c69a2c"; // Body color (Baby Ant) let tallSideA = -20; let controlPointScorp = -20; ctx.beginPath(); ctx.moveTo(tallSideA,40); ctx.bezierCurveTo(-55,-controlPointScorp,-55,controlPointScorp,tallSideA,-40); ctx.quadraticCurveTo(110,0,tallSideA,40); ctx.closePath(); ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle ctx.fillStyle = "rgba(0, 0, 0, 0)"; ctx.moveTo(20,10); ctx.quadraticCurveTo(30,0,20,-10); ctx.moveTo(6,15); ctx.quadraticCurveTo(9,0,6,-15); ctx.moveTo(-6,20); ctx.quadraticCurveTo(-9,0,-6,-20); ctx.moveTo(-20,15); ctx.quadraticCurveTo(-23,0,-20,-15); ctx.stroke(); break; case "Ant Egg": ctx.lineWidth = 10; ctx.fillStyle = "#fff0b8"; // Default color for other mobs ctx.strokeStyle = "#cfc295"; // Default color for other mobs ctx.beginPath(); ctx.arc(0, 0, 50, 0, Math.PI * 2); // Radius of 50 is the base size break; case "Mecha Spider": ctx.lineWidth = 14.5; // Define the leg length and angular range for clustering ctx.lineCap = "round"; // Set the line cap to round for rounded ends ctx.strokeStyle = "#333333"; // Leg color const legLongSection = 65; const legShortSection = 45; const legControlPoint = -20 + ngpo; ctx.save(); ctx.rotate(convertAngleToRadians(ngpo * 2.5)); ctx.beginPath(); ctx.moveTo(legLongSection, legShortSection); // Start at the center ctx.quadraticCurveTo(10, legControlPoint, 0, 0); // Draw the leg outward ctx.quadraticCurveTo(-10, -legControlPoint, -legLongSection, -legShortSection); // Draw the leg outward ctx.stroke(); // Draw the line ctx.beginPath(); ctx.moveTo(legLongSection, -legShortSection); // Start at the center ctx.quadraticCurveTo(0, -legControlPoint, 0, 0); // Draw the leg outward ctx.quadraticCurveTo(0, legControlPoint, -legLongSection, legShortSection); // Draw the leg outward ctx.stroke(); // Draw the line ctx.restore(); const armLongSection = 35; const armShortSection = 67; const armControlPoint = -10; ctx.save(); ctx.rotate(convertAngleToRadians(-ngpo * 1.5)); ctx.beginPath(); ctx.moveTo(armLongSection, armShortSection); // Start at the center ctx.quadraticCurveTo(10, armControlPoint, 0, 0); // Draw the arm outward ctx.quadraticCurveTo(-10, -armControlPoint, -armLongSection, -armShortSection); // Draw the arm outward ctx.stroke(); // Draw the line ctx.beginPath(); ctx.moveTo(armLongSection, -armShortSection); // Start at the center ctx.quadraticCurveTo(0, -armControlPoint, 0, 0); // Draw the arm outward ctx.quadraticCurveTo(0, armControlPoint, -armLongSection, armShortSection); // Draw the arm outward ctx.stroke(); // Draw the line ctx.restore(); // Draw the body ctx.strokeStyle = "#8a8a8a"; // Outline color for body ctx.fillStyle = "#9b9b9b"; // Body color (Baby Ant) ctx.beginPath(); drawPolygon(ctx, 0, 0, 10, 45, 0, 1, 1); ctx.fill(); // Fill the body with the specified fillStyle ctx.stroke(); // Outline with strokeStyle ctx.beginPath(); ctx.moveTo(0,40); ctx.lineTo(0,-40); ctx.closePath(); ctx.stroke(); ctx.strokeStyle = "rgba(0, 0, 0, 0)"; // Outline color for body // Draw the small gray circles at (+/-15, +/-20) ctx.fillStyle = "#8a8a8a"; // Set the gray color for the small circles ctx.beginPath(); ctx.arc(15, 20, 5, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.arc(-15, 20, 5, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.arc(15, -20, 5, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.arc(-15, -20, 5, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); // Draw the red circle at (0, 0) ctx.fillStyle = "#ee0000"; // Set the color to red ctx.beginPath(); ctx.arc(0, 0, 15, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); break; /*case "Gambler": // Assuming `players` is an object containing all the player positions let closestPlayer = null; let closestDistance = Infinity; // Loop through all players to find the closest one Object.values(players).forEach((otherPlayer) => { let dx = otherPlayer.x - mob.x; // Using `mob.x` and `mob.y` for the mob's position let dy = otherPlayer.y - mob.y; let distance = Math.sqrt(dx * dx + dy * dy); // If this player is closer, update the closest player if (distance < closestDistance) { closestDistance = distance; closestPlayer = otherPlayer; } }); // Scale factor const scaleFactor = 2.5; // Draw the mob as a circle at the translated position (0, 0), scaled by 2.5 ctx.beginPath(); ctx.arc(0, 0, 20 * scaleFactor, 0, Math.PI * 2); // Drawing circle at (0, 0) with scaling ctx.closePath(); ctx.fillStyle = "#f8ff66"; ctx.lineWidth = 100 / mob.radius; ctx.strokeStyle = "#c6cc52"; ctx.fill(); ctx.stroke(); // Right eye (no stroke outline) scaled by 2.5 ctx.lineWidth = 0; // Remove outline for the right eye ctx.strokeStyle = "rgba(255, 255, 255, 0)"; // Transparent stroke ctx.fillStyle = "#222222"; // Black fill for the eye ctx.beginPath(); ctx.ellipse(6 * scaleFactor, -5 * scaleFactor, 2.5 * scaleFactor, 5 * scaleFactor, 0, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); // Left eye (no stroke outline) scaled by 2.5 ctx.lineWidth = 0; // Ensure no stroke for the left eye ctx.strokeStyle = "rgba(255, 255, 255, 0)"; // Transparent stroke ctx.fillStyle = "#222222"; // Black fill for the eye ctx.beginPath(); ctx.ellipse(-6 * scaleFactor, -5 * scaleFactor, 2.5 * scaleFactor, 5 * scaleFactor, 0, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); // Smile or Frown (based on whether the mouse button is held down) ctx.lineWidth = 5; ctx.strokeStyle = "#222222"; ctx.beginPath(); // Draw a frown if the mouse button is held down ctx.moveTo(-5 * scaleFactor, 10 * scaleFactor); ctx.quadraticCurveTo(0, 5 * scaleFactor, 5 * scaleFactor, 10 * scaleFactor); ctx.stroke(); // Check if the mob has a closest player to look at if (closestPlayer) { // Right pupil (calculating based on the nearest player) scaled by 2.5 let rightEyeX = 6 * scaleFactor; // Right eye center (X position) let rightEyeY = -5 * scaleFactor; // Right eye center (Y position) let rightEyeRadius = 2.5 * scaleFactor; let dxRight = closestPlayer.x - (mob.x + rightEyeX); // Use `mob.x` and `mob.y` for the mob's position let dyRight = closestPlayer.y - (mob.y + rightEyeY); let distanceRight = Math.sqrt(dxRight * dxRight + dyRight * dyRight); // Normalize to stay within the eye let rightPupilX, rightPupilY; if (distanceRight < rightEyeRadius) { rightPupilX = rightEyeX + dxRight * 0.3; // Pupils follow the nearest player (scaled) rightPupilY = rightEyeY + dyRight * 0.3; } else { rightPupilX = rightEyeX + dxRight * (rightEyeRadius / distanceRight) * 0.3; rightPupilY = rightEyeY + dyRight * (rightEyeRadius / distanceRight) * 0.3; } ctx.lineWidth = 0; ctx.strokeStyle = "rgba(255, 255, 255, 0.)"; // Transparent stroke ctx.fillStyle = "#ffffff"; // White fill for the pupil ctx.beginPath(); ctx.ellipse(rightPupilX, rightPupilY, 1 * scaleFactor, 2 * scaleFactor, 0, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); // Left pupil (calculating based on the nearest player) scaled by 2.5 let leftEyeX = -6 * scaleFactor; // Left eye center (X position) let leftEyeY = -5 * scaleFactor; // Left eye center (Y position) let leftEyeRadius = 2.5 * scaleFactor; let dxLeft = closestPlayer.x - (mob.x + leftEyeX); // Use `mob.x` and `mob.y` for the mob's position let dyLeft = closestPlayer.y - (mob.y + leftEyeY); let distanceLeft = Math.sqrt(dxLeft * dxLeft + dyLeft * dyLeft); // Normalize to stay within the eye let leftPupilX, leftPupilY; if (distanceLeft < leftEyeRadius) { leftPupilX = leftEyeX + dxLeft * 0.3; // Pupils follow the nearest player (scaled) leftPupilY = leftEyeY + dyLeft * 0.3; } else { leftPupilX = leftEyeX + dxLeft * (leftEyeRadius / distanceLeft) * 0.3; leftPupilY = leftEyeY + dyLeft * (leftEyeRadius / distanceLeft) * 0.3; } ctx.lineWidth = 0; ctx.strokeStyle = "rgba(255, 255, 255, 0)"; // Transparent stroke ctx.fillStyle = "#ffffff"; // White fill for the pupil ctx.beginPath(); ctx.ellipse(leftPupilX, leftPupilY, 1 * scaleFactor, 2 * scaleFactor, 0, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); } break; */ case "Mecha Flower": ctx.save(); ctx.rotate(-mob.angle); // Assuming `players` is an object containing all the player positions let closestPlayer = null; let closestDistance = Infinity; // Loop through all players to find the closest one Object.values(players).forEach((otherPlayer) => { let dx = otherPlayer.x - mob.x; // Using `mob.x` and `mob.y` for the mob's position let dy = otherPlayer.y - mob.y; let distance = Math.sqrt(dx * dx + dy * dy); // If this player is closer, update the closest player if (distance < closestDistance) { closestDistance = distance; closestPlayer = otherPlayer; } }); // Scale factor const scaleFactor = 2.5; // Draw the mob as a circle at the translated position (0, 0), scaled by 2.5 ctx.save(); ctx.rotate(t); ctx.beginPath(); drawPolygon(ctx, 0, 0, 16, 20 * scaleFactor, 0, 1, 1); ctx.closePath(); ctx.strokeStyle = "#8a8a8a"; // Outline color for body ctx.fillStyle = "#9b9b9b"; // Body color (Baby Ant) ctx.lineWidth = 5; ctx.fill(); ctx.stroke(); ctx.restore(); // Right eye (no stroke outline) scaled by 2.5 ctx.lineWidth = 0; // Remove outline for the right eye ctx.strokeStyle = "rgba(255, 255, 255, 0)"; // Transparent stroke ctx.fillStyle = "#222222"; // Black fill for the eye ctx.beginPath(); ctx.ellipse(6 * scaleFactor, -5 * scaleFactor, 2.5 * scaleFactor, 5 * scaleFactor, 0, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); // Left eye (no stroke outline) scaled by 2.5 ctx.lineWidth = 0; // Ensure no stroke for the left eye ctx.strokeStyle = "rgba(255, 255, 255, 0)"; // Transparent stroke ctx.fillStyle = "#222222"; // Black fill for the eye ctx.beginPath(); ctx.ellipse(-6 * scaleFactor, -5 * scaleFactor, 2.5 * scaleFactor, 5 * scaleFactor, 0, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); // Smile or Frown (based on whether the mouse button is held down) ctx.lineWidth = 5; ctx.strokeStyle = "#222222"; ctx.beginPath(); // Draw a frown if the mouse button is held down ctx.moveTo(-5 * scaleFactor, 10 * scaleFactor); ctx.quadraticCurveTo(0, 5 * scaleFactor, 5 * scaleFactor, 10 * scaleFactor); ctx.stroke(); // Check if the mob has a closest player to look at if (closestPlayer) { // Right pupil (calculating based on the nearest player) scaled by 2.5 let rightEyeX = 6 * scaleFactor; // Right eye center (X position) let rightEyeY = -5 * scaleFactor; // Right eye center (Y position) let rightEyeRadius = 2.5 * scaleFactor; let dxRight = closestPlayer.x - (mob.x + rightEyeX); // Use `mob.x` and `mob.y` for the mob's position let dyRight = closestPlayer.y - (mob.y + rightEyeY); let distanceRight = Math.sqrt(dxRight * dxRight + dyRight * dyRight); // Normalize to stay within the eye let rightPupilX, rightPupilY; if (distanceRight < rightEyeRadius) { rightPupilX = rightEyeX + dxRight * 0.3; // Pupils follow the nearest player (scaled) rightPupilY = rightEyeY + dyRight * 0.3; } else { rightPupilX = rightEyeX + dxRight * (rightEyeRadius / distanceRight) * 0.3; rightPupilY = rightEyeY + dyRight * (rightEyeRadius / distanceRight) * 0.3; } ctx.lineWidth = 0; ctx.strokeStyle = "rgba(255, 255, 255, 0.)"; // Transparent stroke ctx.fillStyle = "#ffffff"; // White fill for the pupil ctx.beginPath(); ctx.ellipse(rightPupilX, rightPupilY, 1 * scaleFactor, 2 * scaleFactor, 0, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); // Left pupil (calculating based on the nearest player) scaled by 2.5 let leftEyeX = -6 * scaleFactor; // Left eye center (X position) let leftEyeY = -5 * scaleFactor; // Left eye center (Y position) let leftEyeRadius = 2.5 * scaleFactor; let dxLeft = closestPlayer.x - (mob.x + leftEyeX); // Use `mob.x` and `mob.y` for the mob's position let dyLeft = closestPlayer.y - (mob.y + leftEyeY); let distanceLeft = Math.sqrt(dxLeft * dxLeft + dyLeft * dyLeft); // Normalize to stay within the eye let leftPupilX, leftPupilY; if (distanceLeft < leftEyeRadius) { leftPupilX = leftEyeX + dxLeft * 0.3; // Pupils follow the nearest player (scaled) leftPupilY = leftEyeY + dyLeft * 0.3; } else { leftPupilX = leftEyeX + dxLeft * (leftEyeRadius / distanceLeft) * 0.3; leftPupilY = leftEyeY + dyLeft * (leftEyeRadius / distanceLeft) * 0.3; } ctx.lineWidth = 0; ctx.strokeStyle = "rgba(255, 255, 255, 0)"; // Transparent stroke ctx.fillStyle = "#ffffff"; // White fill for the pupil ctx.beginPath(); ctx.ellipse(leftPupilX, leftPupilY, 1 * scaleFactor, 2 * scaleFactor, 0, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); ctx.restore(); } break; case "Wasp": ctx.lineCap = "round"; // Missile ctx.strokeStyle = "#333333"; ctx.fillStyle = "#333333"; ctx.lineWidth = 2.2; ctx.moveTo(-48, 25); ctx.beginPath(); ctx.lineTo(-48, -25); ctx.lineTo(-100, -2); ctx.lineTo(-100, 2); ctx.lineTo(-48, 25); ctx.stroke(); ctx.fill(); ctx.closePath(); // Body ctx.lineWidth = 2.2; ctx.strokeStyle = "rgba(0, 0, 0, 0)"; ctx.fillStyle = "#c8803c"; ctx.beginPath(); ctx.ellipse(10, 0, 135 / 2.2, 100 / 2.2, 0, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); // Stripes ctx.fillStyle = "#333333"; ctx.beginPath(); ctx.rect(0, -100 / 2.2, 19.5, (100 / 2.2) * 2); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.rect(40, -(100 / 2.2) * 0.75, 19.5, (100 / 2.2) * 2 * 0.75); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.rect(-40, -(100 / 2.2) * 0.75, 19.5, (100 / 2.2) * 2 * 0.75); ctx.closePath(); ctx.fill(); // Outline ctx.lineWidth = 10.5; ctx.strokeStyle = "#b77334"; ctx.fillStyle = "rgba(0, 0, 0, 0)"; ctx.beginPath(); ctx.ellipse(10, 0, 135 / 2.2, 100 / 2.2, 0, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.stroke(); // Antennae ctx.lineWidth = 8.5; ctx.fillStyle = "#333333"; ctx.strokeStyle = "#333333"; ctx.beginPath(); ctx.moveTo(70, 15.3); ctx.lineTo(115, (48.6 * time) / 20); ctx.quadraticCurveTo(85, 8.6, 50, 15.3); ctx.stroke(); ctx.fill(); ctx.beginPath(); ctx.moveTo(70, -15.3); ctx.lineTo(115, (-48.6 * time) / 20); ctx.quadraticCurveTo(85, -8.6, 50, -15.3); ctx.stroke(); ctx.fill(); break; case "Wasp Missile": ctx.lineCap = "round"; // Missile ctx.strokeStyle = "#333333"; ctx.fillStyle = "#333333"; ctx.lineWidth = 2.2; ctx.moveTo(-48, 25); ctx.beginPath(); ctx.lineTo(-48, -25); ctx.lineTo(-100, -2); ctx.lineTo(-100, 2); ctx.lineTo(-48, 25); ctx.stroke(); ctx.fill(); ctx.closePath(); break; case "Mecha Wasp": ctx.lineCap = "round"; // Missile ctx.strokeStyle = "#333333"; ctx.fillStyle = "#333333"; ctx.lineWidth = 2.2; ctx.moveTo(-48, 25); ctx.beginPath(); ctx.lineTo(-48, -25); ctx.lineTo(-100, -2); ctx.lineTo(-100, 2); ctx.lineTo(-48, 25); ctx.stroke(); ctx.fill(); ctx.closePath(); // Body ctx.lineWidth = 2.2; ctx.strokeStyle = "rgba(0, 0, 0, 0)"; ctx.fillStyle = "#9b9b9b"; // Body color (Baby Ant) ctx.beginPath(); ctx.ellipse(10, 0, 135 / 2.2, 100 / 2.2, 0, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); // Stripes ctx.fillStyle = "#333333"; ctx.beginPath(); ctx.rect(0, -100 / 2.2, 19.5, (100 / 2.2) * 2); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.rect(40, -(100 / 2.2) * 0.75, 19.5, (100 / 2.2) * 2 * 0.75); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.rect(-40, -(100 / 2.2) * 0.75, 19.5, (100 / 2.2) * 2 * 0.75); ctx.closePath(); ctx.fill(); // Outline ctx.lineWidth = 10.5; ctx.strokeStyle = "#8a8a8a"; // Outline color for body ctx.fillStyle = "rgba(0, 0, 0, 0)"; ctx.beginPath(); ctx.ellipse(10, 0, 135 / 2.2, 100 / 2.2, 0, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.stroke(); // Antennae ctx.lineWidth = 8.5; ctx.fillStyle = "#333333"; ctx.strokeStyle = "#333333"; ctx.beginPath(); ctx.moveTo(70, 15.3); ctx.lineTo(115, (48.6 * time) / 20); ctx.quadraticCurveTo(85, 8.6, 50, 15.3); ctx.stroke(); ctx.fill(); ctx.beginPath(); ctx.moveTo(70, -15.3); ctx.lineTo(115, (-48.6 * time) / 20); ctx.quadraticCurveTo(85, -8.6, 50, -15.3); ctx.stroke(); ctx.fill(); default: ctx.beginPath(); ctx.arc(0, 0, 50, 0, Math.PI * 2); // Radius of 50 is the base size ctx.fillStyle = "green"; // Default color for other mobs break; } ctx.fill(); ctx.stroke(); //ctx.fill(); //ctx.stroke(); ctx.save(); // Apply zoom to mob's position //ctx.setTransform(zoomLevel, 0, 0, zoomLevel, mobScreenX, mobScreenY); ctx.rotate(-mob.angle); ctx.scale(1/scaledMobSize,1/scaledMobSize); // Draw the mob's GUI elements (name, HP, rarity) if (mob.renderhp !== false) { // Adjust GUI elements like name, health bar, and rarity for zoom drawMobGUI(mob); } else { } if (showHitbox) { ctx.lineWidth = 5; ctx.strokeStyle = "#ff0000"; ctx.fillStyle = 'rgba(255, 255, 255, 0)'; ctx.beginPath(); ctx.arc(0, 0, mob.hitbox_size, 0, Math.PI * 2); // Draw the circle only if the "6" key is held down ctx.stroke(); // Stroke the circle ctx.fill(); // Optionally fill the circle (if desired)*/ } // Restore the canvas to the previous state ctx.restore(); ctx.restore(); } let hue = 0; // Starting hue function getRainbowColor() { hue = (hue + 1) % 360; // Cycle through hues (0-360) return `hsl(${hue}, 100%, 50%)`; // Full saturation and brightness } const rarityStuff = { 0: { name: "Common", color: "#7EEF6D" }, // Dynamic getter 1: { name: "Uncommon", color: "#FFE65D" }, 2: { name: "Rare", color: "#4C56DB" }, 3: { name: "Epic", color: "#861FDE" }, 4: { name: "Legendary", color: "#DE1F1F" }, 5: { name: "Mythical", color: "#1FDBDE" }, 6: { name: "Ultra", color: "#FF2B75" }, 7: { name: "Super", color: "#2BFFA3" }, 8: { name: "Unique", color: "#C643FF" }, 9: { name: "Mega", color: "#FF9B11" }, 10: { name: "Sublime", get color() { return getRainbowColor(); } } }; let zoomLevel = 1; // Default zoom level const minZoom = 1 / 5; // Minimum zoom (0.33x) const maxZoom = 3; // Maximum zoom (30x); // Function to draw the GUI (name, health bar, rarity) function drawMobGUI(mob) { // Draw the mob's name above the mob ctx.font = "bold 10px Ubuntu"; ctx.textAlign = "center"; //ctx.fillText(mob.name, 0, mob.radius + 8); textRender(mob.name, 0, mob.radius + 8, "#ffffff"); // Draw health bar below the mob const healthBarWidth = (40 * mob.radius) / 30; const healthBarHeight = 5; const maxHealth = mob.hp; const currentHealth = mob.curhp; // Calculate health percentage properly const healthPercentage = currentHealth / maxHealth; const barWidth = healthBarWidth * healthPercentage; // Draw the health bar background ctx.strokeStyle = "#101010"; ctx.lineWidth = 5; ctx.beginPath(); ctx.moveTo(-healthBarWidth / 2, mob.radius + 10); ctx.lineTo(healthBarWidth / 2, mob.radius + 10); ctx.closePath(); ctx.stroke(); // Draw the health bar foreground ctx.lineWidth = 3; ctx.strokeStyle = "green"; ctx.beginPath(); ctx.moveTo(-healthBarWidth / 2, mob.radius + 10); ctx.lineTo((-healthBarWidth / 2) + (barWidth), mob.radius + 10); // Adjust to reflect proper health percentage ctx.closePath(); ctx.stroke(); // Draw the rarity text below the health bar let rarityText = rarityStuff[mob.rarity] ? rarityStuff[mob.rarity].name : "Too high! 😱"; let rarityColor = rarityStuff[mob.rarity] ? rarityStuff[mob.rarity].color : "#ffffff"; ctx.fillStyle = rarityColor; ctx.textAlign = "center"; textRender(rarityText, 0, mob.radius + 23, rarityColor); } let map = []; // Declare map variable const TILE_SIZE = 1024; // Assuming each tile is 32x32 pixels // tiles: // https://i.ibb.co/v6WSsvjf/dirt-2.png // https://i.ibb.co/2YjSTVnj/factory.png const wallImages = { dirt: new Image(), factory: new Image(), cavern: new Image() }; wallImages.dirt.src = "https://i.ibb.co/v6WSsvjf/dirt-2.png"; // Dirt wall image wallImages.factory.src = "https://i.ibb.co/YTwCDXYN/factory2.png"; // Factory wall image wallImages.cavern.src = "https://i.ibb.co/5Xtft0vv/caverns.png"; function drawWall(x, y, type) { let img = wallImages[type] || wallImages.dirt; // Default to dirt if type is invalid ctx.drawImage(img, x - cameraOffsetX, y - cameraOffsetY, TILE_SIZE, TILE_SIZE); } function updateChat(messages = []) { chatMessages.innerHTML = ""; // Clear previous messages messages.forEach((message) => { const messageElem = document.createElement("div"); messageElem.textContent = message; chatMessages.appendChild(messageElem); }); } // Create Zoom UI buttons const zoomContainer = document.createElement("div"); zoomContainer.style.position = "absolute"; zoomContainer.style.top = "10px"; zoomContainer.style.left = "10px"; zoomContainer.style.display = "flex"; zoomContainer.style.gap = "5px"; const zoomInButton = document.createElement("button"); zoomInButton.innerText = "+"; zoomInButton.style.width = "30px"; zoomInButton.style.height = "30px"; zoomInButton.style.fontSize = "20px"; zoomInButton.onclick = () => { zoomLevel = Math.min(zoomLevel * 1.1, maxZoom); }; const zoomOutButton = document.createElement("button"); zoomOutButton.innerText = "-"; zoomOutButton.style.width = "30px"; zoomOutButton.style.height = "30px"; zoomOutButton.style.fontSize = "20px"; zoomOutButton.onclick = () => { zoomLevel = Math.max(zoomLevel / 1.1, minZoom); }; const hitboxButton = document.createElement("button"); hitboxButton.innerText = "HB"; hitboxButton.style.width = "30px"; hitboxButton.style.height = "30px"; hitboxButton.style.fontSize = "20px"; hitboxButton.onclick = () => { showHitbox = showHitbox === true ? false : true; }; // Append buttons to the page zoomContainer.appendChild(zoomInButton); zoomContainer.appendChild(zoomOutButton); zoomContainer.appendChild(hitboxButton); document.body.appendChild(zoomContainer); canvas.addEventListener("wheel", (event) => { event.preventDefault(); if (event.deltaY < 0) { zoomLevel = Math.min(zoomLevel * 1.1, maxZoom); } else { zoomLevel = Math.max(zoomLevel / 1.1, minZoom); } }); function drawBossHPBar(mob) { const healthBarWidth = 400; // Width of the boss HP bar (larger than the regular mob HP bar) const healthBarHeight = 5; // Height of the boss HP bar (same as the normal mob health bar) ctx.lineJoin = "round"; ctx.lineCap = "round"; let offsetHPBAR = 50; // Ensure that mob.hp and mob.curhp are valid numbers and positive const maxHealth = mob.hp > 0 ? mob.hp : 1; // Prevent division by zero const currentHealth = mob.curhp > 0 ? mob.curhp : 0; // Prevent negative health // Calculate health percentage properly const healthPercentage = currentHealth / maxHealth; const barWidth = healthBarWidth * healthPercentage; // Draw the health bar background (empty black bar) ctx.strokeStyle = "#101010"; ctx.lineWidth = 40; ctx.beginPath(); ctx.moveTo(canvas.width / 2 - healthBarWidth / 2, 20 + offsetHPBAR); // Position at the top of the screen ctx.lineTo(canvas.width / 2 + healthBarWidth / 2, 20 + offsetHPBAR); // Position at the top of the screen ctx.closePath(); ctx.stroke(); // Draw the health bar foreground (filled green bar) ctx.lineWidth = 25; ctx.strokeStyle = "#05fe12"; // Boss health bar color ctx.beginPath(); ctx.moveTo(canvas.width / 2 - healthBarWidth / 2, 20 + offsetHPBAR); // Position at the top of the screen ctx.lineTo(canvas.width / 2 - healthBarWidth / 2 + barWidth, 20 + offsetHPBAR); // Adjust to reflect proper health percentage ctx.closePath(); ctx.stroke(); // Display the mob's name in white at the top of the bar (centered) const nameWidth = ctx.measureText(mob.name).width; // Measure text width for mob name // Scale the text for mob name (3x larger) const scaledNameWidth = nameWidth * 3; // Calculate scaled width ctx.save(); // Save the current state ctx.transform(3, 0, 0, 3, canvas.width / 2 - scaledNameWidth / 2, 20 + offsetHPBAR - 10); // Scale and center the name textRender(mob.name, 0, 0, "#ffffff"); // Apply the transform for scaling ctx.restore(); // Restore the state to prevent further transformations // Display the rarity with its appropriate color (centered) const rarityName = mob.rarity; // Ensure rarity is a name, not a number const rarityTextWidth = ctx.measureText(rarityStuff[rarityName].name).width; // Measure text width for rarity // Scale the text for rarity name (2.5x larger) const scaledRarityWidth = rarityTextWidth * 1.5; // Calculate scaled width let rarityColor = rarityStuff[rarityName] ? rarityStuff[rarityName].color : "#ffffff"; // Default to white if not found ctx.save(); // Save the current state ctx.transform(1.5, 0, 0, 1.5, canvas.width / 2 - scaledRarityWidth / 2, 20 + offsetHPBAR + 20); // Scale and center the rarity textRender(rarityStuff[rarityName].name, 0, 0, rarityColor); // Apply the transform for scaling ctx.restore(); // Restore the state to prevent further transformations } function gameLoop() { ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the entire canvas // Set background ctx.fillStyle = "black"; ctx.fillRect(0, 0, canvas.width, canvas.height); let foundBosses = []; // Store all boss mobs (rarity > 7) if (myPlayerId) { const player = players[myPlayerId]; if (player) { ctx.save(); // Save the original state // Move the canvas center to follow the player BEFORE scaling ctx.translate(canvas.width / 2, canvas.height / 2); // Apply scaling ctx.scale(zoomLevel, zoomLevel); // Move the world to align with the player ctx.translate(-player.x, -player.y); // Define visibility range based on zoomLevel const visibilityRange = 1200 / zoomLevel; // Calculate the player's position in tile coordinates const playerTileX = Math.floor(player.x / TILE_SIZE); const playerTileY = Math.floor(player.y / TILE_SIZE); // Draw walls within visibility range for (let y = Math.max(0, playerTileY - 10); y < Math.min(map.length, playerTileY + 10); y++) { for (let x = Math.max(0, playerTileX - 10); x < Math.min(map[y].length, playerTileX + 10); x++) { if (map[y][x] === "w") { drawWall(x * TILE_SIZE, y * TILE_SIZE, "dirt"); } else if (!isNaN(map[y][x]) || map[y][x] === "s") { // Check if the tile is a number drawWall(x * TILE_SIZE, y * TILE_SIZE, "factory"); } } } // Draw all mobs and check for any mobs with rarity > 7 Object.values(mobs).forEach((mob) => { const dx = mob.x - player.x; const dy = mob.y - player.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance <= visibilityRange) { // Draw mob if within visibility range drawMob(mob); // If the mob's rarity is above 7, add it to the foundBosses array if (distance <= visibilityRange/2 && mob.rarity > 7) { foundBosses.push(mob); } } }); // Draw all players Object.values(players).forEach((otherPlayer) => { drawPlayer(otherPlayer); }); ctx.restore(); // Restore to the original state } } // After restore, draw all found boss HP bars, moved downward to prevent stacking if (foundBosses.length > 0) { let verticalOffset = 20; // Starting position for the first boss HP bar foundBosses.forEach((bossMob) => { // Only render if renderhp is not false if (bossMob.renderhp === false) return; ctx.save(); // Save the current state for each boss ctx.translate(0, verticalOffset); // Move down for each new boss HP bar drawBossHPBar(bossMob); // Draw each boss HP bar ctx.restore(); // Restore the state after drawing each boss HP bar verticalOffset += 80; // Increase the offset only after actually rendering }); } requestAnimationFrame(gameLoop); // Loop the game } gameLoop(); function spawnMob(mobName, x, y, angle, rarity) { // Emit the spawnMobRequest event with the provided mob data socket.emit('spawnMobRequest', { mobName: mobName, x: x, y: y, angle: angle, rarity: rarity, }); // Listen for the response from the server socket.on('spawnMobResponse', (response) => { // Handle the response (success or error) console.log(response.message); // Log the success or error message }); } document.addEventListener("keydown", (e) => { if (e.key === "ArrowUp" || e.key === "w") socket.emit("move", "up"); if (e.key === "ArrowDown" || e.key === "s") socket.emit("move", "down"); if (e.key === "ArrowLeft" || e.key === "a") socket.emit("move", "left"); if (e.key === "ArrowRight" || e.key === "d") socket.emit("move", "right"); }); canvas.addEventListener("mousedown", (e) => { if (e.button === 0) { socket.emit("mouseButtonState", true); } }); canvas.addEventListener("mouseup", (e) => { if (e.button === 0) { socket.emit("mouseButtonState", false); } }); chatInput.addEventListener("keydown", (e) => { if (e.key === "Enter" && chatInput.value.trim()) { socket.emit("chat", chatInput.value); chatInput.value = ""; } }); // Handle initial chat messages from the server socket.on("init", (data) => { map = data.map; players = data.players; mobs = data.mobs; myPlayerId = socket.id; // Ensure chatMessages is an array before updating if (Array.isArray(data.chatMessages)) { updateChat(data.chatMessages); } }); // Handle new messages in real-time socket.on("newMessage", (message) => { const messageElem = document.createElement("div"); messageElem.textContent = message; chatMessages.appendChild(messageElem); }); socket.on("update", (updatedData) => { players = updatedData.players; mobs = updatedData.mobs; // Update mobs data }); socket.on("chat", (messages) => { updateChat(messages); });