Rocketer Utilities

Adds a lot new settings to the game https://rocketer.glitch.me/

// ==UserScript==
// @name         Rocketer Utilities
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Adds a lot new settings to the game https://rocketer.glitch.me/
// @author       DB423 (Impsaccrain)
// @match        http*://rocketer.glitch.me/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// @license      DISTRIBUTION
// ==/UserScript==

(function() {
    'use strict';

    localStorage.RocketerUtilities = localStorage.RocketerUtilities || JSON.stringify({ Options: { AuraState: 1, AutoRespawn: 0, Theme: 0 }, version: 1.4 });

    if (JSON.parse(localStorage.RocketerUtilities).version != 1.4) {
        localStorage.RocketerUtilities = JSON.stringify({ Options: { AuraState: 1, AutoRespawn: 0, Theme: 0 }, version: 1.4 });
    };

    const Utils = JSON.parse(localStorage.RocketerUtilities);
    const Options = Utils.Options;

    var settings = document.getElementById('settingsPopup');
    var closesettingspopup = document.getElementById('closeSettingsPopup');
    var themes = document.getElementById('theme');
    changetheme = function (selectObject) {
        colortheme = selectObject.value;
        let newutils = JSON.parse(localStorage.RocketerUtilities);
        newutils.Options.Theme = document.getElementById('theme').selectedIndex;
        newutils = JSON.stringify(newutils);
        localStorage.RocketerUtilities = newutils;
    }
    var playershape = 'circle';

    var whitetheme = document.createElement('option');
    whitetheme.value = 'whitetheme';
    whitetheme.textContent = 'White';
    themes.appendChild(whitetheme);

    var simplistic = document.createElement('option');
    simplistic.value = 'simplistic';
    simplistic.textContent = 'Simplistic';
    themes.appendChild(simplistic);

    for (let i = 3; i < 15; i++) {
        shapecolors[i].whitetheme = {
            color: "#FFFFFF",
            outline: "#FFFFFF",
            hitcolor: "#FFFFFF",
            hitoutline: "#FFFFFF",
        };
        shapecolors[i].simplistic = {
            color: shapecolors[i].default.color,
            outline: shapecolors[i].default.color,
            hitcolor: shapecolors[i].default.hitcolor,
            hitoutline: shapecolors[i].default.hitcolor,
        };
    };

    var chatstate = true;
    function toggleChat() {
        let chatinput = document.getElementById('chat');
        chatstate = !chatstate;
        if (!chatstate) {
            chatinput.style.display = 'none';
        } else {
            chatinput.style.display = 'block';
        };
    };

    var aurastate = Options.AuraState == 1 ? true : false;
    function toggleAura() {
        aurastate = !aurastate
        Options.AuraState = aurastate ? 1 : 0;
        localStorage.RocketerUtilities = JSON.stringify(Utils);
    };
    var autorespawn = Options.AutoRespawn == 1 ? true : false;
    function toggleAutoRespawn() {
        autorespawn = !autorespawn
        Options.AutoRespawn = autorespawn ? 1 : 0;
        localStorage.RocketerUtilities = JSON.stringify(Utils);
    };

    var chattoggle = document.createElement('label');
    chattoggle.className = 'switch';
    var cti = document.createElement('input');
    cti.type = 'checkbox';
    cti.checked = true;
    cti.onclick = toggleChat;
    var cts = document.createElement('span');
    cts.className = 'slider round';
    chattoggle.appendChild(cti);
    chattoggle.appendChild(cts);

    var auratoggle = document.createElement('label');
    auratoggle.className = 'switch';
    var ati = document.createElement('input');
    ati.type = 'checkbox';
    ati.checked = Options.AuraState == 1 ? false : true;
    ati.onclick = toggleAura;
    var ats = document.createElement('span');
    ats.className = 'slider round';
    auratoggle.appendChild(ati);
    auratoggle.appendChild(ats);

    var respawntoggle = document.createElement('label');
    respawntoggle.className = 'switch';
    var rti = document.createElement('input');
    rti.type = 'checkbox';
    rti.checked = Options.AutoRespawn == 1 ? true : false;
    rti.onclick = toggleAutoRespawn;
    var rts = document.createElement('span');
    rts.className = 'slider round';
    respawntoggle.appendChild(rti);
    respawntoggle.appendChild(rts);

    var utilities = document.createElement('span');
    utilities.style = 'font-weight: 700; font-size: 25px;';
    utilities.textContent = 'Rocketer Utilities';

    settings.appendChild(document.createElement('br'));
    settings.appendChild(document.createElement('br'));
    settings.appendChild(utilities);
    settings.appendChild(document.createElement('hr'));
    settings.appendChild(document.createTextNode("Chat "));
    settings.appendChild(chattoggle);
    settings.appendChild(document.createElement('br'));
    settings.appendChild(document.createElement('br'));
    settings.appendChild(document.createTextNode("Invisible auras "));
    settings.appendChild(auratoggle);
    settings.appendChild(document.createElement('br'));
    settings.appendChild(document.createElement('br'));
    settings.appendChild(document.createTextNode("Auto-respawn "));
    settings.appendChild(respawntoggle);

    var playershapediv = document.createElement('div');
    playershapediv.appendChild(document.createElement('br'));
    playershapediv.appendChild(document.createTextNode('Player shape: '));
    var playershapeselect = document.createElement('select');
    playershapeselect.id = 'player-shape-select'
    playershapeselect.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
    playershapeselect.style.color = 'white';
    playershapeselect.style.borderRadius = '7px';
    playershapeselect.style.width = '100px';
    playershapeselect.style.height = '30px';

    var playershapecircle = document.createElement('option');
    playershapecircle.value = 'circle';
    playershapecircle.textContent = 'Circle';
    playershapeselect.appendChild(playershapecircle);

    var playershapesquare = document.createElement('option');
    playershapesquare.value = 'square';
    playershapesquare.textContent = 'Square';
    playershapeselect.appendChild(playershapesquare);

    var playershapetriangle = document.createElement('option');
    playershapetriangle.value = 'triangle';
    playershapetriangle.textContent = 'Triangle';
    playershapeselect.appendChild(playershapetriangle);

    playershapediv.appendChild(playershapeselect);

    setTimeout(function() {

    let changelogDisplayElement = document.getElementById('changelogDisplay');
    changelogDisplayElement.appendChild(document.createElement('br'));
    changelogDisplayElement.appendChild(document.createElement('br'));
    let rucspan = document.createElement('span');
    rucspan.id = 'rocketer-utils-changelog';
    let ruc = document.createTextNode('ROCKETER UTILITIES CHANGELOG - 1.4 - 6 December 2023');
    rucspan.style.color = 'orange';
    rucspan.appendChild(ruc);
    let rucp = document.createElement('p');
    function cct(text, br) {
        rucp.appendChild(document.createTextNode(text));
        if (br) {
            rucp.appendChild(document.createElement('br'));
        };
    };
    cct('- BUGFIX: Fixed the chat doubling itself', true);
    cct('- FEATURE: Added the smooth chat transitions (they didn\'t appear)', false)
    rucspan.appendChild(rucp);
    changelogDisplayElement.appendChild(rucspan);
    themes.selectedIndex = Options.Theme;
    changetheme(themes);

    }, 500);

    function newdrawplayer(canvas, object, fov, spawnProtect, playercolor, playeroutline, eternal, objectangle){//only barrels and body (no heath bars, names, and chats)
    const CRTP = document.getElementById('theme').value;
    //objectangle refers to angle rotated before triggering this function
    //fov is clientFovMultiplier for ctx, hctx is 1
      canvas.lineJoin = "round"; //make nice round corners
      //draw assets below body, e.g. rammer body base
      for (let assetID in object.assets){
        var asset = object.assets[assetID];
        if (asset.type == "under") {
          if (('angle' in asset) && asset.angle != 0) {
            canvas.rotate(asset.angle * Math.PI / 180);
          }
          canvas.translate(
            (object.width / fov) * asset.x,
            (object.width / fov) * asset.y
          );
          canvas.fillStyle = asset.color;
          canvas.strokeStyle = asset.outline;
          canvas.lineWidth = asset.outlineThickness / fov;
          if (asset.sides == 0) {
            canvas.beginPath();
            canvas.arc(0, 0, (object.width / fov) * asset.size, 0, 2 * Math.PI);
            canvas.fill();
            if (CRTP != 'simplistic') {
            canvas.stroke();
            };
          } else {
            canvas.beginPath();
            let baseSides = asset.sides;
            canvas.moveTo((object.width / fov) * asset.size, 0);
            for (let i = 1; i <= baseSides; i++) {
              canvas.lineTo((object.width / fov) * asset.size * Math.cos((i * 2 * Math.PI) / baseSides), (object.width / fov) * asset.size * Math.sin((i * 2 * Math.PI) / baseSides));
            }
            canvas.fill();
            if (CRTP != 'simplistic') {
            canvas.stroke();
            };
          }
          canvas.translate(
            (-object.width / fov) * asset.x,
            (-object.width / fov) * asset.y
          );
          if (('angle' in asset) && asset.angle != 0) {
            canvas.rotate(-asset.angle * Math.PI / 180);
          }
        }
      }

      //draw barrel
      canvas.lineWidth = 4 / fov;
      //weapon barrels
      for (let barrel in object.barrels){
        let thisBarrel = object.barrels[barrel];
        canvas.rotate((thisBarrel.additionalAngle * Math.PI) / 180); //rotate to barrel angle
        canvas.fillStyle = bodyColors.barrel.col;
        canvas.strokeStyle = bodyColors.barrel.outline;
        if (spawnProtect == "yes") {
          //if have spawn protection
          canvas.fillStyle = bodyColors.barrel.hitCol;
          canvas.strokeStyle = bodyColors.barrel.hitOutline;
        }
        //bullet barrel
        //note: barrelHeightChange refers to reduction in barrel height for barrel animation when shooting
        if (thisBarrel.barrelType == "bullet") {
          drawBulletBarrel(canvas,thisBarrel.x,thisBarrel.barrelWidth,thisBarrel.barrelHeight,thisBarrel.barrelHeightChange,fov)
        }
        //drone barrel
        else if (thisBarrel.barrelType == "drone") {
          drawDroneBarrel(canvas,thisBarrel.x,thisBarrel.barrelWidth,thisBarrel.barrelHeight,thisBarrel.barrelHeightChange,fov)
        }
        //trap barrel
        else if (thisBarrel.barrelType == "trap") {
          drawTrapBarrel(canvas,thisBarrel.x,thisBarrel.barrelWidth,thisBarrel.barrelHeight,thisBarrel.barrelHeightChange,fov)
        }
        //mine barrel
        else if (thisBarrel.barrelType == "mine") {
          drawMineBarrel(canvas,thisBarrel.x,thisBarrel.barrelWidth,thisBarrel.barrelHeight,thisBarrel.barrelHeightChange,fov)
        }
        //minion barrel
        else if (thisBarrel.barrelType == "minion") {
          drawMinionBarrel(canvas,thisBarrel.x,thisBarrel.barrelWidth,thisBarrel.barrelHeight,thisBarrel.barrelHeightChange,fov)
        }
        canvas.rotate((-thisBarrel.additionalAngle * Math.PI) / 180); //rotate back
      }

      //draw player body
      canvas.fillStyle = playercolor;
      canvas.strokeStyle = playeroutline;
      if (eternal == "no") {
        //not a tier 6 tank
        canvas.beginPath();
        canvas.arc(0, 0, object.width / fov, 0, 2 * Math.PI);
        canvas.fill();
        if (CRTP != 'simplistic') {
            canvas.stroke();
        };
      } else {
        //if a tier 6 tank
        canvas.beginPath();
        let baseSides = 6;
        canvas.moveTo((object.width / fov), 0);
        for (var i = 1; i <= baseSides; i++) {
          canvas.lineTo((object.width / fov) * Math.cos((i * 2 * Math.PI) / baseSides), (object.width / fov) * Math.sin((i * 2 * Math.PI) / baseSides));
        }
        canvas.fill();
        if (CRTP != 'simplistic') {
            canvas.stroke();
        };
      }

      //barrels in body upgrade
      for (let barrel in object.bodybarrels){
        let thisBarrel = object.bodybarrels[barrel];
        canvas.rotate(thisBarrel.additionalAngle - objectangle); //rotate to barrel angle
        canvas.fillStyle = bodyColors.barrel.col;
        canvas.strokeStyle = bodyColors.barrel.outline;
        if (spawnProtect == "yes") {
          //if have spawn protection
          canvas.fillStyle = bodyColors.barrel.hitCol;
          canvas.strokeStyle = bodyColors.barrel.hitOutline;
        }
        //bullet barrel
        if (thisBarrel.barrelType == "bullet") {
          drawBulletBarrel(canvas,thisBarrel.x,thisBarrel.barrelWidth,thisBarrel.barrelHeight,thisBarrel.barrelHeightChange,fov)
        }
        //drone barrel
        else if (thisBarrel.barrelType == "drone") {
          drawDroneBarrel(canvas,thisBarrel.x,thisBarrel.barrelWidth,thisBarrel.barrelHeight,thisBarrel.barrelHeightChange,fov)
        }
        //trap barrel (doesnt exist atm)
        else if (thisBarrel.barrelType == "trap") {
          drawTrapBarrel(canvas,thisBarrel.x,thisBarrel.barrelWidth,thisBarrel.barrelHeight,thisBarrel.barrelHeightChange,fov)
        }
        //mine barrel (doesnt exist atm)
        else if (thisBarrel.barrelType == "mine") {
          drawMineBarrel(canvas,thisBarrel.x,thisBarrel.barrelWidth,thisBarrel.barrelHeight,thisBarrel.barrelHeightChange,fov)
        }
        //minion barrel (doesnt exist atm)
        else if (thisBarrel.barrelType == "minion") {
          drawMinionBarrel(canvas,thisBarrel.x,thisBarrel.barrelWidth,thisBarrel.barrelHeight,thisBarrel.barrelHeightChange,fov)
        }
        canvas.rotate(-thisBarrel.additionalAngle + objectangle); //rotate back
      }
      //draw turret base
      if ('turretBaseSize' in object){
        canvas.fillStyle = bodyColors.barrel.col;
        canvas.strokeStyle = bodyColors.barrel.outline;
        canvas.beginPath();
        canvas.arc(0, 0, (object.width / clientFovMultiplier) * object.turretBaseSize, 0, 2 * Math.PI);
        canvas.fill();
        if (CRTP != 'simplistic') {
            canvas.stroke();
        };
      }

      //draw assets above body, e.g. aura assets
      for (let assetID in object.assets){
        var asset = object.assets[assetID];
        if (asset.type == "above") {
          if (('angle' in asset) && asset.angle != 0) {
            canvas.rotate(asset.angle * Math.PI / 180);
          }
          canvas.translate(
            (object.width / fov) * asset.x,
            (object.width / fov) * asset.y
          );
          canvas.fillStyle = asset.color;
          canvas.strokeStyle = asset.outline;
          canvas.lineWidth = asset.outlineThickness / fov;
          if (asset.sides == 0) {
            canvas.beginPath();
            canvas.arc(0, 0, (object.width / fov) * asset.size, 0, 2 * Math.PI);
            canvas.fill();
            if (CRTP != 'simplistic') {
                canvas.stroke();
            };
          } else {
            canvas.beginPath();
            let baseSides = asset.sides;
            canvas.moveTo((object.width / fov) * asset.size, 0);
            for (var i = 1; i <= baseSides; i++) {
              canvas.lineTo((object.width / fov) * asset.size * Math.cos((i * 2 * Math.PI) / baseSides), (object.width / fov) * asset.size * Math.sin((i * 2 * Math.PI) / baseSides));
            }
            canvas.fill();
            if (CRTP != 'simplistic') {
                canvas.stroke();
            };
          }
          canvas.translate(
            (-object.width / fov) * asset.x,
            (-object.width / fov) * asset.y
          );
          if (('angle' in asset) && asset.angle != 0) {
            canvas.rotate(-asset.angle * Math.PI / 180);
          }
        }
      }

      canvas.lineJoin = "miter"; //change back
  };
    function newdraw(object, id, playerstring, auraWidth) {
    const CRTP = document.getElementById('theme').value;
    //function for drawing objects on the canvas. need to provide aura width because this fuction cannot access variables outside
    var drawingX =
      (object.x - px) / clientFovMultiplier + canvas.width / 2; //calculate the location on canvas to draw object
    var drawingY =
      (object.y - py) / clientFovMultiplier + canvas.height / 2;

    if (object.type == "bullet") {
      //draw bullet
      if (object.hasOwnProperty("deadOpacity")) {
        //if this is an animation of a dead object
        ctx.globalAlpha = object.deadOpacity;
      }
      var chooseflash = 3;
      if (object.hit > 0 && object.bulletType != "aura") {
        //if shape is hit AND bullet is not aura, choose whether it's color is white or original color to create flashing effect
        chooseflash = Math.floor(Math.random() * 3); //random number 0, 1 or 2
      }
      if (chooseflash == 0) {
        ctx.fillStyle = "white";
      } else if (chooseflash == 1) {
        ctx.fillStyle = "pink";
      } else {
        if (object.ownsIt == "yes" || object.bulletType == "aura") {
          //if it's an aura or client's tank owns the bullet
          ctx.fillStyle = object.color;
        } else {
          ctx.fillStyle = "#f04f54"; //bullet color is red
        }
      }
      if (object.bulletType == "aura") {
        var choosing;
        if (aurastate && CRTP != 'simplistic') {
        choosing = Math.floor(Math.random() * 2); //choose if particle spawn
        } else {
        choosing = 0;
        };
        if (choosing == 1) {
          //spawn a particle
          var angleDegrees = Math.floor(Math.random() * 360); //choose angle in degrees
          var angleRadians = (angleDegrees * Math.PI) / 180; //convert to radians
          var randomDistFromCenter =
            Math.floor(Math.random() * object.width * 2) - object.width;
          radparticles[particleID] = {
            angle: angleRadians,
            x: object.x + randomDistFromCenter * Math.cos(angleRadians),
            y: object.y + randomDistFromCenter * Math.sin(angleRadians),
            width: 5,
            height: 5,
            speed: 1,
            timer: 30,
            maxtimer: 50,
            color: object.color,
            outline: object.outline,
            type: "particle",
          };
          particleID++;
        }
      }

      if (object.ownsIt == "yes" || object.bulletType == "aura") {
        //if it's an aura or client's tank owns the bullet
        ctx.strokeStyle = object.outline;
      } else {
        ctx.strokeStyle = "#b33b3f"; //bullet is red
      }


      //bullet is purple even if bullet belongs to enemy
      if (object.color == "#934c93") {
        ctx.fillStyle = object.color;
      }
      if (object.outline == "#660066") {
        ctx.strokeStyle = object.outline;
      }

      //team colors
      if (object.team == "blue" || object.team == "green" || object.team == "red" || object.team == "purple" || object.team == "eternal" || object.team == "magenta" || object.team == "fallen" || object.team == "celestial") {
        ctx.fillStyle = bodyColors[object.team].col;
        ctx.strokeStyle = bodyColors[object.team].outline;
      }

      if (object.bulletType == "aura"){
        //color is aura color, regardless of team
        ctx.fillStyle = object.color;
        ctx.strokeStyle = object.outline;
      }

      if (object.passive == "yes") {
        if (object.bulletType == "aura") {
          ctx.strokeStyle = "rgba(128,128,128,.2)";
          ctx.fillStyle = "rgba(128,128,128,.2)";
        } else {
          ctx.strokeStyle = "dimgrey";
          ctx.fillStyle = "grey";
        }
      }

      if (object.team=="mob"){
        //dune mob's bullets is the colo of mob
        ctx.fillStyle = botcolors[object.ownerName].color;
        ctx.strokeStyle = botcolors[object.ownerName].outline;
      }

      ctx.lineWidth = 4 / clientFovMultiplier;
      if (object.bulletType == "bullet" || object.bulletType == "aura") {
        if (!object.color.includes('rgba(56,183,100')){//not a heal aura
          ctx.beginPath();
          ctx.arc(
            drawingX,
            drawingY,
            object.width / clientFovMultiplier,
            0,
            2 * Math.PI
          );
          if (aurastate || object.bulletType != "aura") {
          ctx.fill();
          };
          if (object.bulletType == 'aura') {
          ctx.stroke();
          } else if (CRTP != 'simplistic') {
          ctx.stroke();
          };
        }
        else{//8 sides for healing aura
          ctx.beginPath();
          ctx.moveTo((object.width / clientFovMultiplier) + drawingX, drawingY);
          for (var i = 1; i <= 8 + 1; i += 1) {
            ctx.lineTo(
              (object.width / clientFovMultiplier) *
                  Math.cos((i * 2 * Math.PI) / 8) + drawingX,
              (object.width / clientFovMultiplier) *
                  Math.sin((i * 2 * Math.PI) / 8) + drawingY
            );
          }
           if (aurastate || object.bulletType != "aura") {
          ctx.fill();
           };
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
        }
      } else if (object.bulletType == "trap") {
        //width is the radius, so need to times two to get total width
        //note: x and y of object are the center of object, but when drawing rectangles, the x and y coordinates given need to be the top left corner of the rectangle, so need to minus the width and height
        ctx.fillRect(
          drawingX - object.width / clientFovMultiplier,
          drawingY - object.width / clientFovMultiplier,
          (object.width * 2) / clientFovMultiplier,
          (object.width * 2) / clientFovMultiplier
        );
          if (CRTP != 'simplistic') {
        ctx.strokeRect(
          drawingX - object.width / clientFovMultiplier,
          drawingY - object.width / clientFovMultiplier,
          (object.width * 2) / clientFovMultiplier,
          (object.width * 2) / clientFovMultiplier
        );
          };
      } else if (object.bulletType == "drone") {
        ctx.save();
        ctx.translate(drawingX, drawingY);
        ctx.rotate(object.moveAngle);
        //ctx.rotate((object.moveAngle*180/Math.PI - 90) *Math.PI/180);//cannot straightaway use the angle, must add 90 degrees to it, because 0 degrees is pointing right, but we are drawing the triangle upwards
        ctx.beginPath();
        ctx.moveTo(
          (object.width / clientFovMultiplier) * Math.cos(0),
          (object.width / clientFovMultiplier) * Math.sin(0)
        );
        for (var i = 1; i <= 3; i += 1) {
          ctx.lineTo(
            (object.width / clientFovMultiplier) *
              Math.cos((i * 2 * Math.PI) / 3),
            (object.width / clientFovMultiplier) *
              Math.sin((i * 2 * Math.PI) / 3)
          );
        }
        ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
        ctx.restore();
      } else if (object.bulletType == "mine" || object.bulletType == "minion") {
        //console.log(object.moveAngle/Math.PI*180)
        //mine is trap with barrel, minion is bullet with barrel
        ctx.save();
        ctx.translate(drawingX, drawingY);
        ctx.rotate(object.moveAngle);
        //ctx.rotate((object.moveAngle*180/Math.PI - 90) *Math.PI/180);//cannot straightaway use the angle, must add 90 degrees to it, because 0 degrees is pointing right, but we are drawing the triangle upwards

        if (object.bulletType == "minion"){
          //draw barrels underneath
          var prevfill = ctx.fillStyle;
          var prevstroke = ctx.strokeStyle;//store previous bullet color so can change back later
          ctx.fillStyle = bodyColors.barrel.col;
          ctx.strokeStyle = bodyColors.barrel.outline;
          Object.keys(object.barrels).forEach((barrel) => {
          let thisBarrel = object.barrels[barrel];
          ctx.rotate(thisBarrel.additionalAngle); //rotate to barrel angle
          if (thisBarrel.barrelType == "bullet") {
            ctx.fillRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            if (CRTP != 'simplistic') {
            ctx.strokeRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            };
          }
          //drone barrel
          else if (thisBarrel.barrelType == "drone") {
            ctx.beginPath();
            ctx.moveTo(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              0
            );
            ctx.lineTo(
              -thisBarrel.barrelWidth / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / clientFovMultiplier +
                (thisBarrel.x * 2) / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                (thisBarrel.x * 2) / clientFovMultiplier,
              0
            );
            ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
          }
          //trap barrel
          else if (thisBarrel.barrelType == "trap") {
            ctx.fillRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(
                thisBarrel.barrelHeight - thisBarrel.barrelHeightChange
              ) /
                3) *
                2) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (((thisBarrel.barrelHeight -
                thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier
            );
            if (CRTP != 'simplistic') {
            ctx.strokeRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(
                thisBarrel.barrelHeight - thisBarrel.barrelHeightChange
              ) /
                3) *
                2) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (((thisBarrel.barrelHeight -
                thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier
            );
            };
            ctx.beginPath();
            ctx.moveTo(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(
                thisBarrel.barrelHeight - thisBarrel.barrelHeightChange
              ) /
                3) *
                2) /
                clientFovMultiplier
            );
            ctx.lineTo(
              -thisBarrel.barrelWidth / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(
                thisBarrel.barrelHeight - thisBarrel.barrelHeightChange
              ) /
                3) *
                2) /
                clientFovMultiplier
            );
            ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
          }
          //mine barrel
          else if (thisBarrel.barrelType == "mine") {
            ctx.fillRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(
                thisBarrel.barrelHeight - thisBarrel.barrelHeightChange
              ) /
                3) *
                2) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (((thisBarrel.barrelHeight -
                thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier
            );
            if (CRTP != 'simplistic') {
            ctx.strokeRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(
                thisBarrel.barrelHeight - thisBarrel.barrelHeightChange
              ) /
                3) *
                2) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (((thisBarrel.barrelHeight -
                thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier
            );
            };
            ctx.beginPath();
            ctx.moveTo(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(
                thisBarrel.barrelHeight - thisBarrel.barrelHeightChange
              ) /
                3) *
                2) /
                clientFovMultiplier
            );
            ctx.lineTo(
              -thisBarrel.barrelWidth / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(
                thisBarrel.barrelHeight - thisBarrel.barrelHeightChange
              ) /
                3) *
                2) /
                clientFovMultiplier
            );
            ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
          }
        //minion barrel
        else if (thisBarrel.barrelType == "minion") {
          ctx.fillRect(
            -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
              clientFovMultiplier,
            thisBarrel.barrelWidth / clientFovMultiplier,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
              clientFovMultiplier
          );
          if (CRTP != 'simplistic') {
          ctx.strokeRect(
            -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
              clientFovMultiplier,
            thisBarrel.barrelWidth / clientFovMultiplier,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
              clientFovMultiplier
          );
          };
          ctx.fillRect(
            (-thisBarrel.barrelWidth * 1.5) / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) / 1.5 / clientFovMultiplier,
            (thisBarrel.barrelWidth / clientFovMultiplier) * 1.5,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) / 1.5 / clientFovMultiplier
          );
           if (CRTP != 'simplistic') {
          ctx.strokeRect(
            (-thisBarrel.barrelWidth * 1.5) / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) / 1.5 / clientFovMultiplier,
            (thisBarrel.barrelWidth / clientFovMultiplier) * 1.5,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) / 1.5 / clientFovMultiplier
          );
           };
          ctx.fillRect(
            (-thisBarrel.barrelWidth * 1.5) / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange)  / clientFovMultiplier,
            (thisBarrel.barrelWidth / clientFovMultiplier) * 1.5,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /5/ clientFovMultiplier
          );
          if (CRTP != 'simplistic') {
          ctx.strokeRect(
            (-thisBarrel.barrelWidth * 1.5) / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange)  / clientFovMultiplier,
            (thisBarrel.barrelWidth / clientFovMultiplier) * 1.5,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /5 /clientFovMultiplier
          );
          };
        }
          })
          ctx.fillStyle = prevfill;
          ctx.strokeStyle = prevstroke;
        }
        ctx.beginPath();
        if (object.bulletType == "mine"){//mine
          ctx.moveTo(
            (object.width / clientFovMultiplier) * Math.cos(0),
            (object.width / clientFovMultiplier) * Math.sin(0)
          );
          for (var i = 1; i <= 3; i += 1) {
            ctx.lineTo(
              (object.width / clientFovMultiplier) *
                Math.cos((i * 2 * Math.PI) / 3),
              (object.width / clientFovMultiplier) *
                Math.sin((i * 2 * Math.PI) / 3)
            );
          }
        }
        else{//minion
          ctx.arc(0, 0, object.width / clientFovMultiplier, 0, 2 * Math.PI);
        }
        ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
        ctx.rotate(-object.moveAngle); //rotate back
        //BARREL FOR THE MINE TRAP
        if (object.bulletType == "mine"){
        Object.keys(object.barrels).forEach((barrel) => {
          let thisBarrel = object.barrels[barrel];
          ctx.rotate(thisBarrel.additionalAngle); //rotate to barrel angle
          ctx.fillStyle = "grey";
          ctx.strokeStyle = "#5e5e5e";
          if (thisBarrel.barrelType == "bullet") {
            ctx.fillRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            if (CRTP != 'simplistic') {
            ctx.strokeRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            };
          }
          //drone barrel
          else if (thisBarrel.barrelType == "drone") {
            ctx.beginPath();
            ctx.moveTo(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              0
            );
            ctx.lineTo(
              -thisBarrel.barrelWidth / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / clientFovMultiplier +
                (thisBarrel.x * 2) / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                (thisBarrel.x * 2) / clientFovMultiplier,
              0
            );
            ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
          }
          //trap barrel
          else if (thisBarrel.barrelType == "trap") {
            ctx.fillRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(
                thisBarrel.barrelHeight - thisBarrel.barrelHeightChange
              ) /
                3) *
                2) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (((thisBarrel.barrelHeight -
                thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier
            );
            if (CRTP != 'simplistic') {
            ctx.strokeRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(
                thisBarrel.barrelHeight - thisBarrel.barrelHeightChange
              ) /
                3) *
                2) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (((thisBarrel.barrelHeight -
                thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier
            );
            };
            ctx.beginPath();
            ctx.moveTo(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(
                thisBarrel.barrelHeight - thisBarrel.barrelHeightChange
              ) /
                3) *
                2) /
                clientFovMultiplier
            );
            ctx.lineTo(
              -thisBarrel.barrelWidth / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(
                thisBarrel.barrelHeight - thisBarrel.barrelHeightChange
              ) /
                3) *
                2) /
                clientFovMultiplier
            );
            ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
          }
          //mine barrel
          else if (thisBarrel.barrelType == "mine") {
            ctx.fillRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(
                thisBarrel.barrelHeight - thisBarrel.barrelHeightChange
              ) /
                3) *
                2) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (((thisBarrel.barrelHeight -
                thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier
            );
            if (CRTP != 'simplistic') {
            ctx.strokeRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(
                thisBarrel.barrelHeight - thisBarrel.barrelHeightChange
              ) /
                3) *
                2) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (((thisBarrel.barrelHeight -
                thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier
            );
            };
            ctx.beginPath();
            ctx.moveTo(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(
                thisBarrel.barrelHeight - thisBarrel.barrelHeightChange
              ) /
                3) *
                2) /
                clientFovMultiplier
            );
            ctx.lineTo(
              -thisBarrel.barrelWidth / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(
                thisBarrel.barrelHeight - thisBarrel.barrelHeightChange
              ) /
                3) *
                2) /
                clientFovMultiplier
            );
            ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
          }
        //minion barrel
        else if (thisBarrel.barrelType == "minion") {
          ctx.fillRect(
            -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
              clientFovMultiplier,
            thisBarrel.barrelWidth / clientFovMultiplier,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
              clientFovMultiplier
          );
          if (CRTP != 'simplistic') {
          ctx.strokeRect(
            -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
              clientFovMultiplier,
            thisBarrel.barrelWidth / clientFovMultiplier,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
              clientFovMultiplier
          );
          };
          ctx.fillRect(
            (-thisBarrel.barrelWidth * 1.5) / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) / 1.5 / clientFovMultiplier,
            (thisBarrel.barrelWidth / clientFovMultiplier) * 1.5,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) / 1.5 / clientFovMultiplier
          );
          if (CRTP != 'simplistic') {
          ctx.strokeRect(
            (-thisBarrel.barrelWidth * 1.5) / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) / 1.5 / clientFovMultiplier,
            (thisBarrel.barrelWidth / clientFovMultiplier) * 1.5,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) / 1.5 / clientFovMultiplier
          );
          };
          ctx.fillRect(
            (-thisBarrel.barrelWidth * 1.5) / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange)  / clientFovMultiplier,
            (thisBarrel.barrelWidth / clientFovMultiplier) * 1.5,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /5/ clientFovMultiplier
          );
          if (CRTP != 'simplistic') {
          ctx.strokeRect(
            (-thisBarrel.barrelWidth * 1.5) / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange)  / clientFovMultiplier,
            (thisBarrel.barrelWidth / clientFovMultiplier) * 1.5,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /5 /clientFovMultiplier
          );
          };
        }
          ctx.beginPath();
          ctx.arc(
            0,
            0,
            thisBarrel.barrelWidth / clientFovMultiplier,
            0,
            2 * Math.PI
          );
          ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
          ctx.rotate(-thisBarrel.additionalAngle); //rotate back
        });
      }

        ctx.restore();
      }
      if (object.hasOwnProperty("deadOpacity")) {
        //if this is an animation of a dead object
        ctx.globalAlpha = 1.0; //reset opacity
      }
      if (showHitBox == "yes") {
        //draw hitbox
        ctx.strokeStyle = "blue";
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.arc(
          drawingX,
          drawingY,
          object.width / clientFovMultiplier,
          0,
          2 * Math.PI
        );
        ctx.stroke();
      }
    } else if (object.type == "bot") {
      //draw bot
      if (object.hasOwnProperty("deadOpacity")) {
        //if this is an animation of a dead object
        ctx.globalAlpha = object.deadOpacity;
      }
      ctx.lineWidth = 4 / clientFovMultiplier;
      ctx.lineJoin = "round"; //prevent spikes above the capital letter "M"
      ctx.save();
      ctx.translate(drawingX, drawingY);
      ctx.rotate(object.angle);
      //draw barrels
      if (object.name!="Pillbox"){//pillbox's barrel is visually a turret
        Object.keys(object.barrels).forEach((barrel) => {
          let thisBarrel = object.barrels[barrel];
          ctx.rotate(((thisBarrel.additionalAngle + 90) * Math.PI) / 180); //rotate to barrel angle
          ctx.fillStyle = bodyColors.barrel.col;
          ctx.strokeStyle = bodyColors.barrel.outline;
          if (thisBarrel.barrelType == "bullet") {
            ctx.fillRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            if (CRTP != 'simplistic') {
            ctx.strokeRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            };
          }
          //drone barrel
          else if (thisBarrel.barrelType == "drone") {
            ctx.beginPath();
            ctx.moveTo(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              0
            );
            ctx.lineTo(
              -thisBarrel.barrelWidth / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / clientFovMultiplier +
                (thisBarrel.x * 2) / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                (thisBarrel.x * 2) / clientFovMultiplier,
              0
            );
            ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
          }
          //trap barrel
          else if (thisBarrel.barrelType == "trap") {
            ctx.fillRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (((thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier
            );
            if (CRTP != 'simplistic') {
            ctx.strokeRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (((thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier
            );
            };
            ctx.beginPath();
            ctx.moveTo(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier
            );
            ctx.lineTo(
              -thisBarrel.barrelWidth / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier
            );
            ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
          }
          //mine barrel
          else if (thisBarrel.barrelType == "mine") {
            ctx.fillRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (((thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier
            );
            if (CRTP != 'simplistic') {
            ctx.strokeRect(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier,
              thisBarrel.barrelWidth / clientFovMultiplier,
              (((thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier
            );
            };
            ctx.beginPath();
            ctx.moveTo(
              -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier
            );
            ctx.lineTo(
              -thisBarrel.barrelWidth / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                clientFovMultiplier
            );
            ctx.lineTo(
              thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                thisBarrel.x / clientFovMultiplier,
              ((-(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                3) *
                2) /
                clientFovMultiplier
            );
            ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
          }
        //minion barrel
        else if (thisBarrel.barrelType == "minion") {
          ctx.fillRect(
            -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
              clientFovMultiplier,
            thisBarrel.barrelWidth / clientFovMultiplier,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
              clientFovMultiplier
          );
          if (CRTP != 'simplistic') {
          ctx.strokeRect(
            -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
              clientFovMultiplier,
            thisBarrel.barrelWidth / clientFovMultiplier,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
              clientFovMultiplier
          );
          };
          ctx.fillRect(
            (-thisBarrel.barrelWidth * 1.5) / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) / 1.5 / clientFovMultiplier,
            (thisBarrel.barrelWidth / clientFovMultiplier) * 1.5,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) / 1.5 / clientFovMultiplier
          );
          if (CRTP != 'simplistic') {
          ctx.strokeRect(
            (-thisBarrel.barrelWidth * 1.5) / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) / 1.5 / clientFovMultiplier,
            (thisBarrel.barrelWidth / clientFovMultiplier) * 1.5,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) / 1.5 / clientFovMultiplier
          );
          };
          ctx.fillRect(
            (-thisBarrel.barrelWidth * 1.5) / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange)  / clientFovMultiplier,
            (thisBarrel.barrelWidth / clientFovMultiplier) * 1.5,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /5/ clientFovMultiplier
          );
          if (CRTP != 'simplistic') {
          ctx.strokeRect(
            (-thisBarrel.barrelWidth * 1.5) / 2 / clientFovMultiplier +
              thisBarrel.x / clientFovMultiplier,
            -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange)  / clientFovMultiplier,
            (thisBarrel.barrelWidth / clientFovMultiplier) * 1.5,
            (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /5 /clientFovMultiplier
          );
          };
        }
          ctx.rotate((-(thisBarrel.additionalAngle + 90) * Math.PI) / 180); //rotate back
        });
      }
      if (object.name=="Cluster"){
        //draw the spawning barrels
        let barrelwidth = object.width*0.7;
        let barrelheight = object.width*1.2;
        ctx.fillStyle = bodyColors.barrel.col;
        ctx.strokeStyle = bodyColors.barrel.outline;
        ctx.save();
        ctx.rotate(90 * Math.PI / 180);
        for (let i = 0; i < 5; i++){
          if (i!=0){
            ctx.rotate(72 * Math.PI / 180); //rotate 72 for each barrel
          }
          ctx.beginPath();
          ctx.moveTo(
            -barrelwidth / 5 / clientFovMultiplier,
            0
          );
          ctx.lineTo(
            -barrelwidth / clientFovMultiplier,
            -barrelheight / clientFovMultiplier
          );
          ctx.lineTo(
            barrelwidth / clientFovMultiplier,
            -barrelheight / clientFovMultiplier
          );
          ctx.lineTo(
            barrelwidth / 5 / clientFovMultiplier,
            0
          );
          ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
        }
        ctx.restore();
      }
      else if (object.name=="Infestor"){
        //draw the spawning barrels
        let barrelwidth = object.width*0.7;
        let barrelheight = object.width*1.2;
        ctx.fillStyle = bodyColors.barrel.col;
        ctx.strokeStyle = bodyColors.barrel.outline;
        ctx.save();
        for (let i = 0; i < 4; i++){//normal barrels
          if (i!=0){
            ctx.rotate(90 * Math.PI / 180);
          }
          ctx.fillRect(
            -barrelwidth / 2 / clientFovMultiplier,
            -barrelheight / clientFovMultiplier,
            barrelwidth / clientFovMultiplier,
            barrelheight / clientFovMultiplier
          );
          if (CRTP != 'simplistic') {
          ctx.strokeRect(
            -barrelwidth / 2 / clientFovMultiplier,
            -barrelheight / clientFovMultiplier,
            barrelwidth / clientFovMultiplier,
            barrelheight / clientFovMultiplier
          );
          };
        }
        ctx.restore();
        ctx.save();
        ctx.rotate(45 * Math.PI / 180);
        barrelwidth = object.width*0.6;
        barrelheight = object.width*2;
        for (let i = 0; i < 4; i++){//traplike barrels
          if (i!=0){
            ctx.rotate(90 * Math.PI / 180);
          }
          ctx.fillRect(
            -barrelwidth / 2 / clientFovMultiplier,
            -barrelheight * 0.55 / clientFovMultiplier,
            barrelwidth / clientFovMultiplier,
            barrelheight * 0.5 / clientFovMultiplier
          );
          if (CRTP != 'simplistic') {
          ctx.strokeRect(
            -barrelwidth / 2 / clientFovMultiplier,
            -barrelheight * 0.55 / clientFovMultiplier,
            barrelwidth / clientFovMultiplier,
            barrelheight * 0.5 / clientFovMultiplier
          );
          };
          ctx.beginPath();
          ctx.moveTo(
            -barrelwidth / 2 / clientFovMultiplier,
            -barrelheight * 0.55 / clientFovMultiplier
          );
          ctx.lineTo(
            -barrelwidth/1.7 / clientFovMultiplier,
            -barrelheight * 0.65 / clientFovMultiplier
          );
          ctx.lineTo(
            barrelwidth/1.7 / clientFovMultiplier,
            -barrelheight * 0.65 / clientFovMultiplier
          );
          ctx.lineTo(
            barrelwidth / 2 / clientFovMultiplier,
            -barrelheight * 0.55 / clientFovMultiplier
          );
          ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
        }
        ctx.restore();
      }
      else if (object.name=="Champion"){
        //draw spikes
        var numberOfSpikes = 5;
        var outerRadius = object.width / clientFovMultiplier * 1.3;
        var innerRadius = object.width / clientFovMultiplier /1.3;
        var rot = (Math.PI / 2) * 3;//dont change this, or else will have strange extra lines
        var x = 0;
        var y = 0;
        ctx.fillStyle = bodyColors.barrel.col;
        ctx.strokeStyle = bodyColors.barrel.outline;
        ctx.save();
        ctx.rotate(90 * Math.PI / 180);
        ctx.beginPath();
        ctx.moveTo(0, 0 - outerRadius);
        for (i = 0; i < numberOfSpikes; i++) {
          x = 0 + Math.cos(rot) * outerRadius;
          y = 0 + Math.sin(rot) * outerRadius;
          ctx.lineTo(x, y);
          rot += Math.PI / numberOfSpikes;
          x = 0 + Math.cos(rot) * innerRadius;
          y = 0 + Math.sin(rot) * innerRadius;
          ctx.lineTo(x, y);
          rot += Math.PI / numberOfSpikes;
        }
        ctx.lineTo(0, 0 - outerRadius);
        ctx.closePath();
        ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
        ctx.restore();
      }
      var chooseflash = 3;
      if (object.hit > 0) {
        //if shape is hit, choose whether it's color is white or original color to create flashing effect
        chooseflash = Math.floor(Math.random() * 3); //random number 0, 1 or 2
      }
      if (chooseflash == 0) {
        ctx.fillStyle = "white";
      } else if (chooseflash == 1) {
        ctx.fillStyle = "pink";
      } else {
        ctx.fillStyle = botcolors[object.name].color;
      }
      ctx.strokeStyle = botcolors[object.name].outline;
      //draw body
      if (object.side==0) {
        //draw circle
        ctx.beginPath();
        ctx.arc(0, 0, object.width / clientFovMultiplier, 0, 2 * Math.PI);
        ctx.fill();
        ctx.stroke();
      } else if (object.side>=0) {
        if (object.hasOwnProperty('randomPointsArrayX')){
          //draw for rock and boulder
          //POLYGON WITH IRREGULAR SIDES
          ctx.rotate(-object.angle); //rotate back so that rock wont rotate to face you
          var rockSides = object.side;
          ctx.beginPath();
          ctx.moveTo(
            0 + (object.width / clientFovMultiplier) * Math.cos(0),
            0 + (object.width / clientFovMultiplier) * Math.sin(0)
          );
          for (var i = 1; i <= rockSides; i += 1) {
            var XRandom = object.randomPointsArrayX[i - 1];
            var YRandom = object.randomPointsArrayY[i - 1];
            ctx.lineTo(XRandom + (object.width / clientFovMultiplier) * Math.cos((i * 2 * Math.PI) / rockSides),
              YRandom + (object.width / clientFovMultiplier) * Math.sin((i * 2 * Math.PI) / rockSides)
            );
          }
          ctx.fill();
          ctx.stroke();
        }
        else{//normal spawner
          if (object.name=="Cluster"||object.name=="Pursuer"||object.name=="Champion"||object.name=="Infestor"){
            //need to rotate 72/2 degrees so that pentagon not facing vertex towards player
            ctx.rotate(Math.PI/object.side);//2 PI / sides / 2
          }
          ctx.beginPath();
          ctx.moveTo((object.width / clientFovMultiplier), 0);
          for (var i = 1; i <= object.side + 1; i += 1) {
            ctx.lineTo(
              (object.width / clientFovMultiplier) *
                  Math.cos((i * 2 * Math.PI) / object.side),
              (object.width / clientFovMultiplier) *
                  Math.sin((i * 2 * Math.PI) / object.side)
            );
          }
          ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
          if (object.name=="Cluster"||object.name=="Pursuer"){
            ctx.rotate(-Math.PI/object.side);//rotate back
            //draw circle on top
            ctx.fillStyle = bodyColors.barrel.col;//light grey
            ctx.strokeStyle = bodyColors.barrel.outline;
            ctx.beginPath();
            ctx.arc(0, 0, object.width/2 / clientFovMultiplier, 0, 2 * Math.PI);
            ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
          }
          else if (object.name=="Champion"){
            ctx.rotate(-Math.PI/object.side);//rotate back
            //draw circle on top
            ctx.fillStyle = "grey";//darker grey
            ctx.strokeStyle = "#5e5e5e";
            ctx.beginPath();
            ctx.arc(0, 0, object.width/2.5 / clientFovMultiplier, 0, 2 * Math.PI);
            ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
          }
          else if (object.name=="Infestor"){
            ctx.rotate(-Math.PI/object.side);//rotate back
            //draw circle on top
            ctx.fillStyle = bodyColors.barrel.col;//light grey
            ctx.strokeStyle = bodyColors.barrel.outline;
            ctx.beginPath();
            ctx.arc(0, 0, object.width/5 / clientFovMultiplier, 0, 2 * Math.PI);
            ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
          }
          else if (object.name=="Leech"){
            //draw circle on top
            ctx.fillStyle = bodyColors.barrel.col;//light grey
            ctx.strokeStyle = bodyColors.barrel.outline;
            ctx.beginPath();
            ctx.arc(0, 0, object.width/2 / clientFovMultiplier, 0, 2 * Math.PI);
            ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
          }
          else if (object.name=="Pillbox"){//pillbox's barrel is visually a turret
            ctx.lineJoin = "round"; //make nice round corners
            ctx.rotate(90 * Math.PI / 180);
            Object.keys(object.barrels).forEach((barrel) => {
              //note that you must use [barrel] instead of .barrel, if not there will be an error
              let thisBarrel = object.barrels[barrel];
              ctx.fillStyle = bodyColors.barrel.col;
              ctx.strokeStyle = bodyColors.barrel.outline;
              ctx.fillRect(
                -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                  thisBarrel.x,
                -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                  clientFovMultiplier,
                thisBarrel.barrelWidth / clientFovMultiplier,
                (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                  clientFovMultiplier
              );
              if (CRTP != 'simplistic') {
              ctx.strokeRect(
                -thisBarrel.barrelWidth / 2 / clientFovMultiplier +
                  thisBarrel.x,
                -(thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                  clientFovMultiplier,
                thisBarrel.barrelWidth / clientFovMultiplier,
                (thisBarrel.barrelHeight - thisBarrel.barrelHeightChange) /
                  clientFovMultiplier
              );
              };
            });
            ctx.rotate(-90 * Math.PI / 180);
            //draw turret base
            ctx.beginPath();
            ctx.arc(
              0,
              0,
              (object.width / clientFovMultiplier) * 0.6,
              0,
              2 * Math.PI
            );
            ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
            ctx.lineJoin = "miter"; //change back
          }
        }
      } else{//negative sides, draw a star! (cactus)
        var numberOfSpikes = -object.side;
        var outerRadius = object.width / clientFovMultiplier * 1.5;
        var innerRadius = object.width / clientFovMultiplier;

        var rot = (Math.PI / 2) * 3;//dont change this, or else will have strange extra lines
        var x = 0;
        var y = 0;
        ctx.rotate(-object.angle); //rotate back so that rock wont rotate to face you
        ctx.beginPath();
        ctx.moveTo(0, 0 - outerRadius);
        for (i = 0; i < numberOfSpikes; i++) {
          x = 0 + Math.cos(rot) * outerRadius;
          y = 0 + Math.sin(rot) * outerRadius;
          ctx.lineTo(x, y);
          rot += Math.PI / numberOfSpikes;
          x = 0 + Math.cos(rot) * innerRadius;
          y = 0 + Math.sin(rot) * innerRadius;
          ctx.lineTo(x, y);
          rot += Math.PI / numberOfSpikes;
        }
        ctx.lineTo(0, 0 - outerRadius);
        ctx.closePath();
        ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
      }
      ctx.restore();
      if (object.health < object.maxhealth) {
        //draw health bar background
        var w = (object.width * 2) / clientFovMultiplier;
        var h = 7 / clientFovMultiplier;
        var r = h / 2;
        var x = drawingX - object.width / clientFovMultiplier;
        var y = drawingY + object.width / clientFovMultiplier + 10;
        ctx.fillStyle = "black";
        ctx.strokeStyle = "black";
        ctx.lineWidth = 2.5 / clientFovMultiplier;
        ctx.beginPath();
        ctx.moveTo(x + r, y);
        ctx.arcTo(x + w, y, x + w, y + h, r);
        ctx.arcTo(x + w, y + h, x, y + h, r);
        ctx.arcTo(x, y + h, x, y, r);
        ctx.arcTo(x, y, x + w, y, r);
        ctx.closePath();
        ctx.fill();
        ctx.stroke();
        //draw health bar
        if (object.health > 0) {
          w = (w / object.maxhealth) * object.health;
          if (r * 2 > w) {
            //prevent weird shape when radius more than width
            r = w / 2;
            y += (h - w) / 2; //move health bar so that it is centered vertically in black bar
            h = w;
          }
          ctx.fillStyle = botcolors[object.name].color;
          ctx.beginPath();
          ctx.moveTo(x + r, y);
          ctx.arcTo(x + w, y, x + w, y + h, r);
          ctx.arcTo(x + w, y + h, x, y + h, r);
          ctx.arcTo(x, y + h, x, y, r);
          ctx.arcTo(x, y, x + w, y, r);
          ctx.closePath();
          ctx.fill();
          ctx.stroke();
        }
      }
      ctx.fillStyle = "white";
      ctx.strokeStyle = "black";
      ctx.lineWidth = 5 / clientFovMultiplier;
      ctx.font = "700 " + 20 / clientFovMultiplier + "px Roboto";
      ctx.textAlign = "center";
      ctx.lineJoin = "round"; //prevent spikes above the capital letter "M"
      //note: if you stroke then fill, the words will be thicker and nicer. If you fill then stroke, the words are thinner.
      if ((showStaticMobName == "yes"||botcolors[object.name].static=="no") && (showMinionMobName == "yes"||botcolors[object.name].minion=="no")){//settings for showing static and minion names
        if (botcolors[object.name].specialty != "") {
          var specialtyText = " (" + botcolors[object.name].specialty + ")";
        } else {
          var specialtyText = "";
        }
        ctx.strokeText(
          object.name + specialtyText,
          drawingX,
          drawingY - object.width / clientFovMultiplier - 10
        );
        ctx.fillText(
          object.name + specialtyText,
          drawingX,
          drawingY - object.width / clientFovMultiplier - 10
        );
      }
      ctx.lineJoin = "miter"; //prevent spikes above the capital letter "M"
      if (object.hasOwnProperty("deadOpacity")) {
        //if this is an animation of a dead object
        ctx.globalAlpha = 1.0; //reset opacity
      }
      if (showHitBox == "yes") {
        //draw hitbox
        ctx.strokeStyle = "blue";
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.arc(
          drawingX,
          drawingY,
          object.width / clientFovMultiplier,
          0,
          2 * Math.PI
        );
        ctx.stroke();
      }
    } else if (object.type == "shape") {
      if (object.hasOwnProperty("deadOpacity")) {
        //if this is an animation of a dead object
        ctx.globalAlpha = object.deadOpacity;
      }
      var radiantAuraSize =
        document.getElementById("sizevalue").innerHTML * auraWidth; //aura size determined by settings, but default is 5
      //draw shape
      ctx.save();
      ctx.translate(drawingX, drawingY);
      ctx.rotate((object.angle * Math.PI) / 180);
      if (object.hasOwnProperty("radtier")) {
        //radiant shape
        if (!radiantShapes.hasOwnProperty(id)) {
          var randomstate = Math.floor(Math.random() * 3); //randomly choose a color state for the radiant shape to start (if not when you spawn in cavern, all shapes same color)
          var randomtype = Math.floor(Math.random() * 2) + 1; //choose animation color type (1 or 2)
          if (randomtype == 1) {
            if (randomstate == 0) {
              radiantShapes[id] = {
                red: 255,
                blue: 0,
                green: 0,
                rgbstate: 1,
                radtype: randomtype,
              }; //keep track of radiant shape colors (done in client code)
            } else if (randomstate == 1) {
              radiantShapes[id] = {
                red: 199,
                blue: 0,
                green: 150,
                rgbstate: 2,
                radtype: randomtype,
              };
            } else if (randomstate == 2) {
              radiantShapes[id] = {
                red: -1,
                blue: 200,
                green: 0,
                rgbstate: 3,
                radtype: randomtype,
              };
            }
          } else {
            if (randomstate == 0) {
              radiantShapes[id] = {
                red: 118,
                blue: 168,
                green: 151,
                rgbstate: 1,
                radtype: randomtype,
              };
            } else if (randomstate == 1) {
              radiantShapes[id] = {
                red: 209,
                blue: 230,
                green: 222,
                rgbstate: 2,
                radtype: randomtype,
              };
            } else if (randomstate == 2) {
              radiantShapes[id] = {
                red: 234,
                blue: 240,
                green: 180,
                rgbstate: 3,
                radtype: randomtype,
              };
            }
          }
        }
        object.red = radiantShapes[id].red;
        object.blue = radiantShapes[id].blue;
        object.green = radiantShapes[id].green;
      }
      if (object.hasOwnProperty("red")) {
        //calculate color of spikes, which would be 20 higher than actual rgb value
        if (object.red + 150 <= 255) {
          var spikeRed = object.red + 150;
        } else {
          var spikeRed = 255;
        }
        if (object.blue + 150 <= 255) {
          var spikeBlue = object.blue + 150;
        } else {
          var spikeBlue = 255;
        }
        if (object.green + 150 <= 255) {
          var spikeGreen = object.green + 150;
        } else {
          var spikeGreen = 255;
        }
        if (object.radtier == 3) {
          //for high rarity radiant shapes, draw spikes
          ctx.rotate((extraSpikeRotate * Math.PI) / 180);
          ctx.fillStyle =
            "rgba(" +
            spikeRed +
            ", " +
            spikeGreen +
            ", " +
            spikeBlue +
            ", 0.7)";
          ctx.strokeStyle =
            "rgba(" +
            spikeRed +
            ", " +
            spikeGreen +
            ", " +
            spikeBlue +
            ", 0.3)";
          var numberOfSpikes = 6;
          var outerRadius =
            ((object.width * radiantAuraSize * 3) / clientFovMultiplier) *
            0.75;
          var innerRadius = (object.width / clientFovMultiplier) * 0.75;

          var rot = (Math.PI / 2) * 3;
          var x = 0;
          var y = 0;

          ctx.beginPath();
          ctx.moveTo(0, 0 - outerRadius);
          for (i = 0; i < numberOfSpikes; i++) {
            x = 0 + Math.cos(rot) * outerRadius;
            y = 0 + Math.sin(rot) * outerRadius;
            ctx.lineTo(x, y);
            rot += Math.PI / numberOfSpikes;
            x = 0 + Math.cos(rot) * innerRadius;
            y = 0 + Math.sin(rot) * innerRadius;
            ctx.lineTo(x, y);
            rot += Math.PI / numberOfSpikes;
          }
          ctx.lineTo(0, 0 - outerRadius);
          ctx.closePath();
          ctx.lineWidth = 3 / clientFovMultiplier;
          ctx.fill();
          ctx.stroke();
          ctx.rotate((-extraSpikeRotate * Math.PI) / 180);
        } else if (object.radtier == 4) {
          //for high rarity radiant shapes, draw spikes
          ctx.rotate((extraSpikeRotate1 * Math.PI) / 180);
          ctx.fillStyle =
            "rgba(" +
            spikeRed +
            ", " +
            spikeGreen +
            ", " +
            spikeBlue +
            ", 0.7)";
          ctx.strokeStyle =
            "rgba(" +
            spikeRed +
            ", " +
            spikeGreen +
            ", " +
            spikeBlue +
            ", 0.3)";
          var numberOfSpikes = 3;
          var outerRadius =
            (object.width * radiantAuraSize * 3) / clientFovMultiplier;
          var innerRadius = (object.width / clientFovMultiplier) * 0.5;
          var rot = (Math.PI / 2) * 3;
          var x = 0;
          var y = 0;
          ctx.beginPath();
          ctx.moveTo(0, 0 - outerRadius);
          for (i = 0; i < numberOfSpikes; i++) {
            x = 0 + Math.cos(rot) * outerRadius;
            y = 0 + Math.sin(rot) * outerRadius;
            ctx.lineTo(x, y);
            rot += Math.PI / numberOfSpikes;
            x = 0 + Math.cos(rot) * innerRadius;
            y = 0 + Math.sin(rot) * innerRadius;
            ctx.lineTo(x, y);
            rot += Math.PI / numberOfSpikes;
          }
          ctx.lineTo(0, 0 - outerRadius);
          ctx.closePath();
          ctx.lineWidth = 3 / clientFovMultiplier;
          ctx.fill();
          ctx.stroke();
          ctx.rotate((-extraSpikeRotate1 * Math.PI) / 180);
          ctx.rotate((extraSpikeRotate2 * Math.PI) / 180);
          var numberOfSpikes = 6;
          var outerRadius =
            ((object.width * radiantAuraSize * 3) / clientFovMultiplier) *
            0.5;
          var innerRadius = (object.width / clientFovMultiplier) * 0.5;
          var rot = (Math.PI / 2) * 3;
          var x = 0;
          var y = 0;
          ctx.beginPath();
          ctx.moveTo(0, 0 - outerRadius);
          for (i = 0; i < numberOfSpikes; i++) {
            x = 0 + Math.cos(rot) * outerRadius;
            y = 0 + Math.sin(rot) * outerRadius;
            ctx.lineTo(x, y);
            rot += Math.PI / numberOfSpikes;
            x = 0 + Math.cos(rot) * innerRadius;
            y = 0 + Math.sin(rot) * innerRadius;
            ctx.lineTo(x, y);
            rot += Math.PI / numberOfSpikes;
          }
          ctx.lineTo(0, 0 - outerRadius);
          ctx.closePath();
          ctx.lineWidth = 3 / clientFovMultiplier;
          ctx.fill();
          ctx.stroke();
          ctx.rotate((-extraSpikeRotate2 * Math.PI) / 180);
        } else if (object.radtier == 5) {
          //for high rarity radiant shapes, draw spikes
          ctx.rotate((extraSpikeRotate1 * Math.PI) / 180);
          ctx.fillStyle =
            "rgba(" +
            spikeRed +
            ", " +
            spikeGreen +
            ", " +
            spikeBlue +
            ", 0.7)";
          ctx.strokeStyle =
            "rgba(" +
            spikeRed +
            ", " +
            spikeGreen +
            ", " +
            spikeBlue +
            ", 0.3)";
          var numberOfSpikes = 3;
          var outerRadius =
            ((object.width * radiantAuraSize * 3) / clientFovMultiplier) *
            1.5;
          var innerRadius = (object.width / clientFovMultiplier) * 0.5;
          var rot = (Math.PI / 2) * 3;
          var x = 0;
          var y = 0;
          ctx.beginPath();
          ctx.moveTo(0, 0 - outerRadius);
          for (i = 0; i < numberOfSpikes; i++) {
            x = 0 + Math.cos(rot) * outerRadius;
            y = 0 + Math.sin(rot) * outerRadius;
            ctx.lineTo(x, y);
            rot += Math.PI / numberOfSpikes;
            x = 0 + Math.cos(rot) * innerRadius;
            y = 0 + Math.sin(rot) * innerRadius;
            ctx.lineTo(x, y);
            rot += Math.PI / numberOfSpikes;
          }
          ctx.lineTo(0, 0 - outerRadius);
          ctx.closePath();
          ctx.lineWidth = 3 / clientFovMultiplier;
          ctx.fill();
          ctx.stroke();
          ctx.rotate((-extraSpikeRotate1 * Math.PI) / 180);
          ctx.rotate((extraSpikeRotate2 * Math.PI) / 180);
          var numberOfSpikes = 3;
          var outerRadius =
            ((object.width * radiantAuraSize * 3) / clientFovMultiplier) *
            0.5;
          var innerRadius = (object.width / clientFovMultiplier) * 0.5;
          var rot = (Math.PI / 2) * 3;
          var x = 0;
          var y = 0;
          ctx.beginPath();
          ctx.moveTo(0, 0 - outerRadius);
          for (i = 0; i < numberOfSpikes; i++) {
            x = 0 + Math.cos(rot) * outerRadius;
            y = 0 + Math.sin(rot) * outerRadius;
            ctx.lineTo(x, y);
            rot += Math.PI / numberOfSpikes;
            x = 0 + Math.cos(rot) * innerRadius;
            y = 0 + Math.sin(rot) * innerRadius;
            ctx.lineTo(x, y);
            rot += Math.PI / numberOfSpikes;
          }
          ctx.lineTo(0, 0 - outerRadius);
          ctx.closePath();
          ctx.lineWidth = 3 / clientFovMultiplier;
          ctx.fill();
          ctx.stroke();
          ctx.rotate((-extraSpikeRotate2 * Math.PI) / 180);
        }
        //if shape is radiant
        //draw aura

        //old code where aura was a gradient
        /*
            const gradient = ctx.createRadialGradient(0, 0, object.width/clientFovMultiplier, 0, 0, object.width/clientFovMultiplier*radiantAuraSize);
            gradient.addColorStop(0, 'rgba(' + object.red + ', ' + object.green + ', ' + object.blue + ', 0.3)');
            gradient.addColorStop(0.5, 'rgba(' + object.red + ', ' + object.green + ', ' + object.blue + ', 0.1)');
            gradient.addColorStop(1, 'rgba(' + object.red + ', ' + object.green + ', ' + object.blue + ', 0.0)');
            ctx.fillStyle = gradient;
            ctx.beginPath();
            */

        //old code where aura have shape
        ctx.fillStyle =
          "rgba(" +
          object.red +
          ", " +
          object.green +
          ", " +
          object.blue +
          ", 0.3)";
        ctx.strokeStyle =
          "rgba(" +
          object.red +
          ", " +
          object.green +
          ", " +
          object.blue +
          ", 0.3)";
        ctx.lineWidth = 3 / clientFovMultiplier;
        ctx.beginPath();

        var shapeaurasize = object.radtier;
        if (shapeaurasize > 3) {
          shapeaurasize = 3; //prevent huge auras
        }
        ctx.moveTo(
          0 +
            ((object.width * radiantAuraSize * shapeaurasize) /
              clientFovMultiplier) *
              Math.cos(0),
          0 +
            ((object.width * radiantAuraSize * shapeaurasize) /
              clientFovMultiplier) *
              Math.sin(0)
        );
        for (var i = 1; i <= object.sides + 1; i += 1) {
          ctx.lineTo(
            0 +
              ((object.width * radiantAuraSize * shapeaurasize) /
                clientFovMultiplier) *
                Math.cos((i * 2 * Math.PI) / object.sides),
            0 +
              ((object.width * radiantAuraSize * shapeaurasize) /
                clientFovMultiplier) *
                Math.sin((i * 2 * Math.PI) / object.sides)
          );
        }

        //ctx.arc(0, 0, object.width/clientFovMultiplier*radiantAuraSize, 0, 2 * Math.PI);
        ctx.fill();
        ctx.stroke();
        var shadeFactor = 3 / 4; //smaller the value, darker the shade
        ctx.strokeStyle =
          "rgb(" +
          object.red * shadeFactor +
          ", " +
          object.green * shadeFactor +
          ", " +
          object.blue * shadeFactor +
          ")";
        ctx.fillStyle =
          "rgb(" +
          object.red +
          ", " +
          object.green +
          ", " +
          object.blue +
          ")";
        if (object.hit > 0) {
          //if shape is hit
          ctx.strokeStyle =
            "rgb(" +
            (object.red * shadeFactor + 20) +
            ", " +
            (object.green * shadeFactor + 20) +
            ", " +
            (object.blue * shadeFactor + 20) +
            ")";
          ctx.fillStyle =
            "rgb(" +
            (object.red + 20) +
            ", " +
            (object.green + 20) +
            ", " +
            (object.blue + 20) +
            ")";
        }

        //choose whether a particle would spawn
        //particle spawn chance based on number of sides the shape has, so square has less particles
        if (spawnradparticle == "yes"){
          var chooseValue = 20 - object.sides * 2; //lower the number means more particles spawned
          if (chooseValue < 5) {
            //5 refers to mimimum particle spawn chance
            chooseValue = 5;
          }
          var choosing = Math.floor(Math.random() * chooseValue); //choose if particle spawn
          if (choosing == 1) {
            //spawn a particle
            var angleDegrees = Math.floor(Math.random() * 360); //choose angle in degrees
            var angleRadians = (angleDegrees * Math.PI) / 180; //convert to radians
            var randomDistFromCenter =
              Math.floor(Math.random() * object.width * 2) - object.width;
            radparticles[particleID] = {
              angle: angleRadians,
              x: object.x + randomDistFromCenter * Math.cos(angleRadians),
              y: object.y + randomDistFromCenter * Math.sin(angleRadians),
              width: 5,
              height: 5,
              speed: 1,
              timer: 50,
              maxtimer: 50,
              color:
                "rgba(" +
                object.red +
                "," +
                object.green +
                "," +
                object.blue +
                ",.5)",
              outline:
                "rgba(" +
                (object.red* shadeFactor + 20) +
                "," +
                (object.green* shadeFactor + 20) +
                "," +
                (object.blue* shadeFactor + 20) +
                ",.5)",
              type: "particle",
            };
            particleID++;
          }
        }
      } else {
        //if not radiant
        //get shape colors in client code based on theme
        ctx.fillStyle = shapecolors[object.sides][colortheme].color;
        ctx.strokeStyle = shapecolors[object.sides][colortheme].outline;
        if (object.hit > 0) {
          //if shape is hit
          ctx.fillStyle = shapecolors[object.sides][colortheme].hitcolor;
          ctx.strokeStyle =
            shapecolors[object.sides][colortheme].hitoutline;
        }
      }
      ctx.lineJoin = "round"; //make corners of shape round
      if (object.sides == "star") {
        //draw a star

        var numberOfSpikes = 5;
        var outerRadius = object.width / clientFovMultiplier;
        var innerRadius = (object.width / clientFovMultiplier / 3) * 2;

        var rot = (Math.PI / 2) * 3;
        var x = 0;
        var y = 0;

        ctx.beginPath();
        ctx.moveTo(0, 0 - outerRadius);
        for (i = 0; i < numberOfSpikes; i++) {
          x = 0 + Math.cos(rot) * outerRadius;
          y = 0 + Math.sin(rot) * outerRadius;
          ctx.lineTo(x, y);
          rot += Math.PI / numberOfSpikes;
          x = 0 + Math.cos(rot) * innerRadius;
          y = 0 + Math.sin(rot) * innerRadius;
          ctx.lineTo(x, y);
          rot += Math.PI / numberOfSpikes;
        }
        ctx.lineTo(0, 0 - outerRadius);
        ctx.closePath();
        ctx.lineWidth = 4 / clientFovMultiplier;
        ctx.fill();
        ctx.stroke();
      } else {
        ctx.lineWidth = 4 / clientFovMultiplier;
        ctx.beginPath();
        ctx.moveTo(
          0 + (object.width / clientFovMultiplier) * Math.cos(0),
          0 + (object.width / clientFovMultiplier) * Math.sin(0)
        );
        for (var i = 1; i <= object.sides + 1; i += 1) {
          ctx.lineTo(
            0 +
              (object.width / clientFovMultiplier) *
                Math.cos((i * 2 * Math.PI) / object.sides),
            0 +
              (object.width / clientFovMultiplier) *
                Math.sin((i * 2 * Math.PI) / object.sides)
          );
        }
        ctx.fill();
        ctx.stroke();
      }
      ctx.lineJoin = "miter"; //change back to default
      ctx.restore(); //must restore to reset angle rotation so health bar wont be rotated sideways
      //draw shape's health bar
      if (object.health < object.maxhealth) {
        //draw health bar background
        var w = (object.width / clientFovMultiplier) * 2;
        var h = 7 / clientFovMultiplier;
        var r = h / 2;
        var x = drawingX - object.width / clientFovMultiplier;
        var y = drawingY + object.width / clientFovMultiplier + 10;
        ctx.fillStyle = "black";
        ctx.strokeStyle = "black";
        ctx.lineWidth = 2.5 / clientFovMultiplier;//determines with of black area
        ctx.beginPath();
        ctx.moveTo(x + r, y);
        ctx.arcTo(x + w, y, x + w, y + h, r);
        ctx.arcTo(x + w, y + h, x, y + h, r);
        ctx.arcTo(x, y + h, x, y, r);
        ctx.arcTo(x, y, x + w, y, r);
        ctx.closePath();
        ctx.fill();
        ctx.stroke();
        //draw health bar
        if (object.health > 0) {
          //dont draw health bar if negative health
          w = (w / object.maxhealth) * object.health;
          if (r * 2 > w) {
            //prevent weird shape when radius more than width
            r = w / 2;
            y += (h - w) / 2; //move health bar so that it is centered vertically in black bar
            h = w;
          }
          if (object.hasOwnProperty("red")) {
            //if shape is radiant
            ctx.fillStyle =
              "rgb(" +
              object.red +
              ", " +
              object.green +
              ", " +
              object.blue +
              ")";
          } else {
            ctx.fillStyle = shapecolors[object.sides][colortheme].color;
            if (object.sides==10||object.sides==11||object.sides==14){//these shapes are very dark, cannot see health bar
              ctx.fillStyle = shapecolors[12][colortheme].color;//use ddecagon's grey color for health bar
            }
          }
          ctx.beginPath();
          ctx.moveTo(x + r, y);
          ctx.arcTo(x + w, y, x + w, y + h, r);
          ctx.arcTo(x + w, y + h, x, y + h, r);
          ctx.arcTo(x, y + h, x, y, r);
          ctx.arcTo(x, y, x + w, y, r);
          ctx.closePath();
          ctx.fill();
          ctx.stroke();
        }
      }
      if (object.hasOwnProperty("deadOpacity")) {
        //if this is an animation of a dead object
        ctx.globalAlpha = 1.0; //reset opacity
      }
      if (showHitBox == "yes") {
        //draw hitbox
        ctx.strokeStyle = "blue";
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.arc(
          drawingX,
          drawingY,
          object.width / clientFovMultiplier,
          0,
          2 * Math.PI
        );
        ctx.stroke();
      }
    } else if (object.type == "spawner") {
      //spawner in sanctuary
      ctx.save();
      ctx.translate(drawingX, drawingY);
      ctx.rotate(object.angle);
      ctx.lineJoin = "round"; //make corners of shape round

      //actual body
      ctx.fillStyle = object.baseColor;
      ctx.strokeStyle = object.baseOutline;
      ctx.beginPath();
      ctx.moveTo(
        0 + (object.basewidth6 / clientFovMultiplier) * Math.cos(0),
        0 + (object.basewidth6 / clientFovMultiplier) * Math.sin(0)
      );
      for (var i = 1; i <= object.sides + 1; i += 1) {
        ctx.lineTo(
          0 +
            (object.basewidth6 / clientFovMultiplier) *
              Math.cos((i * 2 * Math.PI) / object.sides),
          0 +
            (object.basewidth6 / clientFovMultiplier) *
              Math.sin((i * 2 * Math.PI) / object.sides)
        );
      }
      ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
      ctx.fillStyle = object.color;
      ctx.strokeStyle = object.outline;
      ctx.beginPath();
      ctx.moveTo(
        0 + (object.width / clientFovMultiplier) * Math.cos(0),
        0 + (object.width / clientFovMultiplier) * Math.sin(0)
      );
      for (var i = 1; i <= object.sides + 1; i += 1) {
        ctx.lineTo(
          0 +
            (object.width / clientFovMultiplier) *
              Math.cos((i * 2 * Math.PI) / object.sides),
          0 +
            (object.width / clientFovMultiplier) *
              Math.sin((i * 2 * Math.PI) / object.sides)
        );
      }
      ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
      ctx.fillStyle = object.baseColor;
      ctx.strokeStyle = object.baseOutline;
      ctx.beginPath();
      ctx.moveTo(
        0 + (object.basewidth4 / clientFovMultiplier) * Math.cos(0),
        0 + (object.basewidth4 / clientFovMultiplier) * Math.sin(0)
      );
      for (var i = 1; i <= object.sides + 1; i += 1) {
        ctx.lineTo(
          0 +
            (object.basewidth4 / clientFovMultiplier) *
              Math.cos((i * 2 * Math.PI) / object.sides),
          0 +
            (object.basewidth4 / clientFovMultiplier) *
              Math.sin((i * 2 * Math.PI) / object.sides)
        );
      }
      ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
      ctx.fillStyle = object.color;
      ctx.strokeStyle = object.outline;
      ctx.lineWidth = 4 / clientFovMultiplier;
      ctx.beginPath();
      ctx.moveTo(
        0 + (object.basewidth5 / clientFovMultiplier) * Math.cos(0),
        0 + (object.basewidth5 / clientFovMultiplier) * Math.sin(0)
      );
      for (var i = 1; i <= object.sides + 1; i += 1) {
        ctx.lineTo(
          0 +
            (object.basewidth5 / clientFovMultiplier) *
              Math.cos((i * 2 * Math.PI) / object.sides),
          0 +
            (object.basewidth5 / clientFovMultiplier) *
              Math.sin((i * 2 * Math.PI) / object.sides)
        );
      }
      ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
      ctx.fillStyle = object.baseColor;
      ctx.strokeStyle = object.baseOutline;
      ctx.beginPath();
      ctx.moveTo(
        0 + (object.basewidth1 / clientFovMultiplier) * Math.cos(0),
        0 + (object.basewidth1 / clientFovMultiplier) * Math.sin(0)
      );
      for (var i = 1; i <= object.sides + 1; i += 1) {
        ctx.lineTo(
          0 +
            (object.basewidth1 / clientFovMultiplier) *
              Math.cos((i * 2 * Math.PI) / object.sides),
          0 +
            (object.basewidth1 / clientFovMultiplier) *
              Math.sin((i * 2 * Math.PI) / object.sides)
        );
      }
      ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
      ctx.fillStyle = object.barrelColor;
      ctx.strokeStyle = object.barrelOutline;
      ctx.beginPath();
      ctx.moveTo(
        0 + (object.basewidth2 / clientFovMultiplier) * Math.cos(0),
        0 + (object.basewidth2 / clientFovMultiplier) * Math.sin(0)
      );
      for (var i = 1; i <= object.sides + 1; i += 1) {
        ctx.lineTo(
          0 +
            (object.basewidth2 / clientFovMultiplier) *
              Math.cos((i * 2 * Math.PI) / object.sides),
          0 +
            (object.basewidth2 / clientFovMultiplier) *
              Math.sin((i * 2 * Math.PI) / object.sides)
        );
      }
      ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
      ctx.fillStyle = object.color;
      ctx.strokeStyle = object.outline;
      ctx.lineWidth = 4 / clientFovMultiplier;
      ctx.beginPath();
      ctx.moveTo(
        0 + (object.basewidth3 / clientFovMultiplier) * Math.cos(0),
        0 + (object.basewidth3 / clientFovMultiplier) * Math.sin(0)
      );
      for (var i = 1; i <= object.sides + 1; i += 1) {
        ctx.lineTo(
          0 +
            (object.basewidth3 / clientFovMultiplier) *
              Math.cos((i * 2 * Math.PI) / object.sides),
          0 +
            (object.basewidth3 / clientFovMultiplier) *
              Math.sin((i * 2 * Math.PI) / object.sides)
        );
      }
      ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
      //draw barrels
      ctx.fillStyle = object.barrelColor;
      ctx.strokeStyle = object.barrelOutline;
      //trapezoid at the tip
      var barrelwidth = 140;
      var barrelheight = 28;
      //rectangle
      var barrelwidth2 = 180;
      var barrelheight2 = 28;
      //base trapezoid
      var barrelwidth3 = 140;
      var barrelheight3 = 80;
      //note that trapezoids and rectangles are drawn differently

      var barrelDistanceFromCenter = (object.width * (Math.cos(Math.PI/object.sides)));//width of middle of polygon (less than width of circle)

      function drawSancBarrel(barNum){
        var barAngle = 360/object.sides*(barNum+0.5);//half of a side, cuz barrel is in between sides
        var barrelX = Math.cos((barAngle * Math.PI) / 180) * (barrelDistanceFromCenter+ barrelheight+ barrelheight2+ barrelheight3);//object.width * 0.9
        var barrelY = Math.sin((barAngle * Math.PI) / 180) * (barrelDistanceFromCenter+ barrelheight+ barrelheight2+ barrelheight3);
        var barrelX2 = Math.cos((barAngle * Math.PI) / 180) * (barrelDistanceFromCenter + barrelheight2 + barrelheight3); //move rectangle barrel downwards
        var barrelY2 = Math.sin((barAngle * Math.PI) / 180) * (barrelDistanceFromCenter + barrelheight2 + barrelheight3);
        var barrelX3 = Math.cos((barAngle * Math.PI) / 180) * (barrelDistanceFromCenter + barrelheight3); //move base trapezoid barrel downwards
        var barrelY3 = Math.sin((barAngle * Math.PI) / 180) * (barrelDistanceFromCenter + barrelheight3);
        //base trapezoid
        ctx.save();
        ctx.translate(
          barrelX3 / clientFovMultiplier,
          barrelY3 / clientFovMultiplier
        );
        ctx.rotate(((barAngle - 90) * Math.PI) / 180);
        ctx.beginPath();
        ctx.moveTo(
          ((-barrelwidth3 / 3) * 2) / clientFovMultiplier,
          -barrelheight3 / clientFovMultiplier
        );
        ctx.lineTo(-barrelwidth3 / clientFovMultiplier, 0);
        ctx.lineTo(barrelwidth3 / clientFovMultiplier, 0);
        ctx.lineTo(
          ((barrelwidth3 / 3) * 2) / clientFovMultiplier,
          -barrelheight3 / clientFovMultiplier
        );
        ctx.lineTo(
          ((-barrelwidth3 / 3) * 2) / clientFovMultiplier,
          -barrelheight3 / clientFovMultiplier
        );
        ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
        ctx.restore();
        //rectangle
        ctx.save();
        ctx.translate(
          barrelX2 / clientFovMultiplier,
          barrelY2 / clientFovMultiplier
        );
        ctx.rotate(((barAngle - 90) * Math.PI) / 180);
        ctx.fillRect(
          -barrelwidth2 / 2 / clientFovMultiplier,
          -barrelheight2 / clientFovMultiplier,
          barrelwidth2 / clientFovMultiplier,
          barrelheight2 / clientFovMultiplier
        );
        if (CRTP != 'simplistic') {
        ctx.strokeRect(
          -barrelwidth2 / 2 / clientFovMultiplier,
          -barrelheight2 / clientFovMultiplier,
          barrelwidth2 / clientFovMultiplier,
          barrelheight2 / clientFovMultiplier
        );
        };
        ctx.restore();
        //trapezium at the tip
        ctx.save();
        ctx.translate(
          barrelX / clientFovMultiplier,
          barrelY / clientFovMultiplier
        );
        ctx.rotate(((barAngle - 90) * Math.PI) / 180);
        ctx.beginPath();
        ctx.moveTo(-barrelwidth / 2 / clientFovMultiplier, 0);
        ctx.lineTo(
          -barrelwidth / clientFovMultiplier,
          -barrelheight / clientFovMultiplier
        );
        ctx.lineTo(
          barrelwidth / clientFovMultiplier,
          -barrelheight / clientFovMultiplier
        );
        ctx.lineTo(barrelwidth / 2 / clientFovMultiplier, 0);
        ctx.lineTo(-barrelwidth / 2 / clientFovMultiplier, 0);
        ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
        ctx.restore();
      }

      for (let i = 0; i < object.sides; i++) {
        drawSancBarrel(i);
      }
      //draw aura
      ctx.fillStyle = object.auraColor;
      ctx.lineWidth = 4 / clientFovMultiplier;
      ctx.beginPath();
      ctx.moveTo(
        0 + (object.auraWidth / clientFovMultiplier) * Math.cos(0),
        0 + (object.auraWidth / clientFovMultiplier) * Math.sin(0)
      );
      for (var i = 1; i <= object.sides + 1; i += 1) {
        ctx.lineTo(
          0 +
            (object.auraWidth / clientFovMultiplier) *
              Math.cos((i * 2 * Math.PI) / object.sides),
          0 +
            (object.auraWidth / clientFovMultiplier) *
              Math.sin((i * 2 * Math.PI) / object.sides)
        );
      }
      ctx.fill();
      ctx.lineJoin = "miter"; //change back
      ctx.restore();
      if (showHitBox == "yes") {
        //draw hitbox
        ctx.strokeStyle = "blue";
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.arc(
          drawingX,
          drawingY,
          object.width / clientFovMultiplier,
          0,
          2 * Math.PI
        );
        ctx.stroke();
      }
    } else if (object.type == "player") {
      var spawnProtectionFlashDuration = 3; //higher number indicates longer duration between flashes.
      if (object.hasOwnProperty("deadOpacity")) {
        //if this is an animation of a dead object
        ctx.globalAlpha = object.deadOpacity;
      }
      //draw players
      ctx.save(); //save so later can restore
      //translate canvas to location of player so that the player is at 0,0 coordinates, allowing rotation around the center of player's body
      ctx.translate(drawingX, drawingY);

      let objectangle = object.angle;
      if (
        id == playerstring &&
        object.autorotate != "yes" &&
        object.fastautorotate != "yes"
      ) {
        //if this player is the tank that the client is controlling
        objectangle = clientAngle;
        ctx.rotate(clientAngle); //instead of using client's actual tank angle, use the angle to the mouse. this reduces lag effect
      } else {
        ctx.rotate(object.angle);
      }

      let spawnProtect = "no";
      if (object.spawnProtection < object.spawnProtectionDuration && object.spawnProtection % spawnProtectionFlashDuration == 0) {
        spawnProtect = "yes";
      }

      let playercolor = "undefined";
      let playeroutline = "undefined";
      let eternal = "no";
      if (object.team == "none") {
        if (id == playerstring) {
          playercolor = bodyColors.blue.col;
          playeroutline = bodyColors.blue.outline;
          if (object.hit > 0 || spawnProtect == "yes") {
            playercolor = bodyColors.blue.hitCol
            playeroutline = bodyColors.blue.hitOutline
          }
        }
        else{
          playercolor = bodyColors.red.col;
          playeroutline = bodyColors.red.outline;
          if (object.hit > 0 || spawnProtect == "yes") {
            playercolor = bodyColors.red.hitCol
            playeroutline = bodyColors.red.hitOutline
          }
        }
      } else if (object.team == "blue" || object.team == "green" || object.team == "red" || object.team == "purple" || object.team == "eternal" || object.team == "magenta" || object.team == "fallen" || object.team == "celestial") {
          playercolor = bodyColors[object.team].col;
          playeroutline = bodyColors[object.team].outline;
          if (object.hit > 0 || spawnProtect == "yes") {
            playercolor = bodyColors[object.team].hitCol;
            playeroutline = bodyColors[object.team].hitOutline;
          }
          if (object.team == "eternal"){
            eternal = "yes";
          }
      }
      if (object.developer == "yes") {
        //if a developer
        playercolor = object.color;
        playeroutline = object.outline;
      }

      //store player color for upgrade buttons
      if (id == playerstring){
        playerBodyCol = playercolor;
        playerBodyOutline = playeroutline;
      }

      drawPlayer(ctx, object, clientFovMultiplier, spawnProtect, playercolor, playeroutline, eternal, objectangle)//draw barrel and body
      ctx.restore(); //restore coordinates to saved

      //write player name if not the client's tank
      if (id != playerstring) {
        ctx.fillStyle = "white";
        ctx.strokeStyle = "black";
        ctx.lineWidth = 8 / clientFovMultiplier;
        ctx.font = "700 " + 35 / clientFovMultiplier + "px Roboto";
        ctx.textAlign = "center";
        ctx.miterLimit = 2;//prevent text spikes, alternative to linejoin round
        //ctx.lineJoin = "round"; //prevent spikes above the capital letter "M"
        //note: if you stroke then fill, the words will be thicker and nicer. If you fill then stroke, the words are thinner.
        if (object.name == "unnamed"){
          //this guy is unnamed, add a 3 digit identifier
          let thisID = id.substr(id.length - 3);//last 3 digits of ID
          object.name += (" #" + thisID);
        }
        ctx.strokeText(
          object.name,
          drawingX,
          drawingY - (object.width + 40) / clientFovMultiplier
        );
        ctx.fillText(
          object.name,
          drawingX,
          drawingY - (object.width + 40) / clientFovMultiplier
        );
        //write player level
        ctx.font = "700 " + 18 / clientFovMultiplier + "px Roboto";
        ctx.strokeText(
          "Lvl " +
            object.level +
            " " +
            object.tankType +
            "-" +
            object.bodyType,
          drawingX,
          drawingY - (object.width + 10) / clientFovMultiplier
        );
        ctx.fillText(
          "Lvl " +
            object.level +
            " " +
            object.tankType +
            "-" +
            object.bodyType,
          drawingX,
          drawingY - (object.width + 10) / clientFovMultiplier
        );
        ctx.lineJoin = "miter"; //change it back
      }
      //draw player health
      if (object.health < object.maxhealth) {
        //draw health bar background
        var w = (object.width / clientFovMultiplier) * 2;
        var h = 7 / clientFovMultiplier;
        var r = h / 2;
        var x = drawingX - object.width / clientFovMultiplier;
        var y = drawingY + object.width / clientFovMultiplier + 10;
        ctx.fillStyle = "black";
        ctx.strokeStyle = "black";
        ctx.lineWidth = 2.5 / clientFovMultiplier;
        ctx.beginPath();
        ctx.moveTo(x + r, y);
        ctx.arcTo(x + w, y, x + w, y + h, r);
        ctx.arcTo(x + w, y + h, x, y + h, r);
        ctx.arcTo(x, y + h, x, y, r);
        ctx.arcTo(x, y, x + w, y, r);
        ctx.closePath();
        ctx.fill();
        ctx.stroke();
        //draw health bar
        if (object.health > 0) {
          w = (w / object.maxhealth) * object.health;
          //if (id == playerstring) {
            //if this player is the tank that the client is controlling
            if (object.team == "none") {
              if (id == playerstring) {
                ctx.fillStyle = bodyColors.blue.col;
              }
              else{
                ctx.fillStyle = bodyColors.red.col;
              }
            } else if (object.team == "blue" || object.team == "red" || object.team == "purple" || object.team == "green" || object.team == "eternal" || object.team == "magenta" || object.team == "fallen" || object.team == "celestial") {
              ctx.fillStyle = bodyColors[object.team].col;
            }
          if (r * 2 > w) {
            //prevent weird shape when radius more than width
            r = w / 2;
            y += (h - w) / 2; //move health bar so that it is centered vertically in black bar
            h = w;
          }
          ctx.beginPath();
          ctx.moveTo(x + r, y);
          ctx.arcTo(x + w, y, x + w, y + h, r);
          ctx.arcTo(x + w, y + h, x, y + h, r);
          ctx.arcTo(x, y + h, x, y, r);
          ctx.arcTo(x, y, x + w, y, r);
          ctx.closePath();
          ctx.fill();
          ctx.stroke();
        }
      }

      //write chats
      if (chatstate) {
//write chats
      if (id != playerstring) {
        var firstChatY = object.width / clientFovMultiplier /5*4 + 55 / clientFovMultiplier;
      }
      else{
        var firstChatY = object.width / clientFovMultiplier /5*4;//chat nearer to player body if no need to display name
      }
      ctx.font = "700 25px Roboto";
      ctx.textAlign = "center";
      ctx.lineJoin = "round"; //prevent spikes above the capital letter "M"
      var xpadding = 15;
      var ypadding = 10;
      var lineheight = 30;

      var timeWhenChatRemove = 200;//when change on server code, remember to change here too

      if (!(chatlist[id])){//used for animating chat positions
        chatlist[id] = JSON.parse(JSON.stringify(object.chats));
      }
      else{
        let tempArray = [];
        let messages = {};//prevent bug when multiple chats have same message
        object.chats.forEach(function (item, index) {
          let occurence = 0;//prevent bug when multiple chats have same message
          let foundit = 0;
          for (var i = 0; i < chatlist[id].length; i++) {//check if oldchats hae this message, to preserve the position for animation
            if (chatlist[id][i].chat == item.chat){
              if (messages[item.chat]){//saw a chat with the exact same message before!
                if (messages[item.chat] <= occurence){//this is a different chat
                  let k = JSON.parse(JSON.stringify(chatlist[id][i]));
                  k.time = item.time;
                  tempArray.push(k);
                  messages[chatlist[id][i].chat]++;
                  foundit = 1;
                  break
                }
                else{//this is the same chat that you saw before, continue hunting for the chat
                  occurence++;
                }
              }
              else{
                let k = JSON.parse(JSON.stringify(chatlist[id][i]));
                k.time = item.time;
                tempArray.push(k);
                messages[chatlist[id][i].chat] = 1;
                foundit = 1;
                break
              }
            }
          }
          if (foundit == 0){//new chat message
            let k = JSON.parse(JSON.stringify(item));
            k.opacity = 0;
            tempArray.push(k);
          }
        });
        chatlist[id] = tempArray;
      }

      object.chats.slice().reverse().forEach((chatObj, index) => {//slice and reverse to loop though array backwards (so older messages are above)
        ctx.fillStyle = "rgba(69,69,69,.7)";

        var longestLine = 0;

        //multiline chat
        const wrapText = function(ctx, text, x, y, maxWidth, lineHeight) {
          // First, start by splitting all of our text into words, but splitting it into an array split by spaces
          let words = text.split(' ');
          let line = ''; // This will store the text of the current line
          let testLine = ''; // This will store the text when we add a word, to test if it's too long
          let lineArray = []; // This is an array of lines, which the function will return

          // Lets iterate over each word
          for(var n = 0; n < words.length; n++) {
              // Create a test line, and measure it..
              testLine += `${words[n]} `;
              let metrics = ctx.measureText(testLine);
              let testWidth = metrics.width;
              // If the width of this test line is more than the max width
              if (testWidth > maxWidth && n > 0) {
                  // Then the line is finished, push the current line into "lineArray"
                  line = line.slice(0, -1);//remove space at the end of the line
                  lineArray.push([line, x, y]);
                  let thislinewidth = ctx.measureText(line).width;
                  if (thislinewidth > longestLine){
                    longestLine = thislinewidth;
                  }
                  // Increase the line height, so a new line is started
                  y += lineHeight;
                  // Update line and test line to use this word as the first word on the next line
                  line = `${words[n]} `;
                  testLine = `${words[n]} `;
              }
              else {
                  // If the test line is still less than the max width, then add the word to the current line
                  line += `${words[n]} `;
              }
              // If we never reach the full max width, then there is only one line.. so push it into the lineArray so we return something
              if(n === words.length - 1) {
                  line = line.slice(0, -1);//remove space at the end of the line
                  lineArray.push([line, x, y]);
                  let thislinewidth = ctx.measureText(line).width;
                  if (thislinewidth > longestLine){
                    longestLine = thislinewidth;
                  }
              }
          }
          // Return the line array
          return lineArray;
        }

        let wrappedText = wrapText(ctx, chatObj.chat, drawingX, drawingY - firstChatY, 900, lineheight);//split message into multiline text
        //draw rect
        var w = longestLine + xpadding * 2;
        var h = lineheight * wrappedText.length + ypadding * 2;
        if (wrappedText.length == 1){//remove spacing between text for single-line text
          h = 25 + ypadding * 2;
        }
        var r = 15;
        var x = drawingX - longestLine / 2 - xpadding;
        var y = drawingY - firstChatY - ypadding - h - 20;//the actual y location of this chat message
        //aniamte towards this y position
        //remember that the loop is reversed, so indexes are reversed here too
        let thischat = chatlist[id][chatlist[id].length - 1 - index];
        let diffpos = 0;
        if (!thischat.y){
          thischat.y = y;
        }
        else{
          if (y > thischat.y){
            thischat.y+=(y - thischat.y)/2*deltaTime;
            if (y < thischat.y){
              thischat.y = y;
            }
          }
          else if (y < thischat.y){
            thischat.y-=(thischat.y - y)/2*deltaTime;
            if (y > thischat.y){
              thischat.y = y;
            }
          }
          if (Math.abs(y - thischat.y)<0.1){//small difference between current position and actual position
            thischat.y = y;
          }
          diffpos = y - thischat.y;
          y = thischat.y;
        }
        if (thischat.opacity < 1){
          thischat.opacity+=0.1;
        }
        ctx.globalAlpha = thischat.opacity;
        ctx.beginPath();
        ctx.moveTo(x + r, y);
        ctx.arcTo(x + w, y, x + w, y + h, r);
        ctx.arcTo(x + w, y + h, x, y + h, r);
        ctx.arcTo(x, y + h, x, y, r);
        ctx.arcTo(x, y, x + w, y, r);
        ctx.closePath();
        ctx.fill();
        if (index == 0){
          //if this is first chat message, draw triangle
          let trianglewidth = 20;
          let triangleheight = 10;
          ctx.beginPath();
          ctx.moveTo(x + w/2 - trianglewidth/2, y + h);
          ctx.lineTo(x + w/2 + trianglewidth/2, y + h);
          ctx.lineTo(x + w/2, y + h + triangleheight);
          ctx.fill();
        }
        //write words
        ctx.fillStyle = "white";
        wrappedText.forEach(function(item) {
            ctx.fillText(item[0], item[1], item[2]-h-diffpos);//write text
        })
        ctx.globalAlpha = 1.0;
        firstChatY += (h + 10); //height of chat plus space between chats
      });
      ctx.lineJoin = "miter"; //change it back
      }
      if (object.hasOwnProperty("deadOpacity")) {
        //if this is an animation of a dead object
        ctx.globalAlpha = 1.0; //reset opacity
      }
      if (showHitBox == "yes") {
        //draw hitbox
        ctx.strokeStyle = "blue";
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.arc(
          drawingX,
          drawingY,
          object.width / clientFovMultiplier,
          0,
          2 * Math.PI
        );
        ctx.stroke();
      }
    } else if (object.type == "portal") {
      //draw the aura below the portal
      var auraSpeed = 75; //higher number means slower speed
      var auraWidth = 4; //reative to portal size
      var portalAuraSize = object.timer % auraSpeed;
      var portalwidth = portalwidths[id]; //use this for portal width. it keeps track size changes when players touch portal
      var portalsizeincrease = portalwidths[id] / object.width; //increase in width when someone touch it (needed for the spikes)
      //first aura
      var opacityCalculation =
        1 - ((auraWidth / auraSpeed) * portalAuraSize) / auraWidth; //goes from 0 to 0.3
      if (opacityCalculation > 0.3) {
        //max opacity for portal aura
        opacityCalculation = 0.3;
      }
      if (object.hasOwnProperty("red")) {
        //if portal is radiant
        ctx.fillStyle =
          "rgba(" +
          object.red +
          ", " +
          object.green +
          ", " +
          object.blue +
          "," +
          opacityCalculation +
          ")";
      } else {
        ctx.fillStyle =
          "rgba(" + object.color + "," + opacityCalculation + ")";
      }
      ctx.beginPath();
      ctx.arc(
        drawingX,
        drawingY,
        (portalwidth * ((auraWidth / auraSpeed) * portalAuraSize)) /
          clientFovMultiplier,
        0,
        2 * Math.PI
      );
      ctx.fill();
      //second smaller aura
      portalAuraSize = (object.timer - auraSpeed / 2) % auraSpeed;
      if (portalAuraSize > 0) {
        var opacityCalculation =
          1 - ((auraWidth / auraSpeed) * portalAuraSize) / auraWidth;
        if (opacityCalculation > 0.3) {
          //max opacity for portal aura
          opacityCalculation = 0.3;
        }
        if (object.hasOwnProperty("red")) {
          //if portal is radiant
          ctx.fillStyle =
            "rgba(" +
            object.red +
            ", " +
            object.green +
            ", " +
            object.blue +
            "," +
            opacityCalculation +
            ")";
        } else {
          ctx.fillStyle =
            "rgba(" + object.color + "," + opacityCalculation + ")";
        }
        ctx.beginPath();
        ctx.arc(
          drawingX,
          drawingY,
          (portalwidth * ((auraWidth / auraSpeed) * portalAuraSize)) /
            clientFovMultiplier,
          0,
          2 * Math.PI
        );
        ctx.fill();
      }

      if (object.hasOwnProperty("deadOpacity")) {
        //if this is an animation of a dead object
        ctx.globalAlpha = object.deadOpacity;
      }
      //drawing portals
      //create gradient
      //const gradient = ctx.createRadialGradient(drawingX, drawingY, object.width/3/clientFovMultiplier, drawingX, drawingY, object.width/clientFovMultiplier);

      // Add two color stops
      //caluclate color of outline of portal based on time until it die
      var portalColorCalc = object.timer / object.maxtimer;
      var portalColor = 255 - portalColorCalc * 255;
      var portalRGB =
        "rgb(" +
        portalColor +
        "," +
        portalColor +
        "," +
        portalColor +
        ")";
      var portalRGBoutline =
        "rgb(" +
        (portalColor - 20) +
        "," +
        (portalColor - 20) +
        "," +
        (portalColor - 20) +
        ")";
      if (object.ruptured == 1) {
        //portal is ruptured!
        //draw the stars
        ctx.save(); //save so later can restore
        ctx.translate(drawingX, drawingY);
        ctx.fillStyle = "white";
        ctx.strokeStyle = "lightgrey";
        ctx.lineWidth = 3 / clientFovMultiplier;
        ctx.lineJoin = "round";
        //first star: 3 spikes
        ctx.rotate((extraSpikeRotate * Math.PI) / 180);
        var numberOfSpikes = 3;
        var outerRadius =
          ((object.width * 3) / clientFovMultiplier) * portalsizeincrease;
        var innerRadius =
          (object.width / 3 / clientFovMultiplier) * portalsizeincrease;
        var rot = (Math.PI / 2) * 3;
        var x = 0;
        var y = 0;
        ctx.beginPath();
        ctx.moveTo(0, 0 - outerRadius);
        for (i = 0; i < numberOfSpikes; i++) {
          x = 0 + Math.cos(rot) * outerRadius;
          y = 0 + Math.sin(rot) * outerRadius;
          ctx.lineTo(x, y);
          rot += Math.PI / numberOfSpikes;
          x = 0 + Math.cos(rot) * innerRadius;
          y = 0 + Math.sin(rot) * innerRadius;
          ctx.lineTo(x, y);
          rot += Math.PI / numberOfSpikes;
        }
        ctx.lineTo(0, 0 - outerRadius);
        ctx.closePath();
        ctx.fill();
        ctx.stroke();
        ctx.rotate((-extraSpikeRotate * Math.PI) / 180);
        //second star: 6 spikes in opposite direction
        ctx.rotate(((360 - extraSpikeRotate) * 2 * Math.PI) / 180);
        var numberOfSpikes = 6;
        var outerRadius =
          ((object.width * 1.5) / clientFovMultiplier) *
          portalsizeincrease;
        var innerRadius =
          (object.width / 1.2 / clientFovMultiplier) * portalsizeincrease;
        var rot = (Math.PI / 2) * 3;
        var x = 0;
        var y = 0;
        ctx.beginPath();
        ctx.moveTo(0, 0 - outerRadius);
        for (i = 0; i < numberOfSpikes; i++) {
          x = 0 + Math.cos(rot) * outerRadius;
          y = 0 + Math.sin(rot) * outerRadius;
          ctx.lineTo(x, y);
          rot += Math.PI / numberOfSpikes;
          x = 0 + Math.cos(rot) * innerRadius;
          y = 0 + Math.sin(rot) * innerRadius;
          ctx.lineTo(x, y);
          rot += Math.PI / numberOfSpikes;
        }
        ctx.lineTo(0, 0 - outerRadius);
        ctx.closePath();
        ctx.fill();
        ctx.stroke();
        ctx.rotate((-(360 - extraSpikeRotate) * 2 * Math.PI) / 180);
        //third star: 6 spikes
        ctx.rotate((extraSpikeRotate * 2 * Math.PI) / 180);
        var numberOfSpikes = 6;
        var outerRadius =
          ((object.width * 1.5) / clientFovMultiplier) *
          portalsizeincrease;
        var innerRadius =
          (object.width / 1.2 / clientFovMultiplier) * portalsizeincrease;
        var rot = (Math.PI / 2) * 3;
        var x = 0;
        var y = 0;
        ctx.beginPath();
        ctx.moveTo(0, 0 - outerRadius);
        for (i = 0; i < numberOfSpikes; i++) {
          x = 0 + Math.cos(rot) * outerRadius;
          y = 0 + Math.sin(rot) * outerRadius;
          ctx.lineTo(x, y);
          rot += Math.PI / numberOfSpikes;
          x = 0 + Math.cos(rot) * innerRadius;
          y = 0 + Math.sin(rot) * innerRadius;
          ctx.lineTo(x, y);
          rot += Math.PI / numberOfSpikes;
        }
        ctx.lineTo(0, 0 - outerRadius);
        ctx.closePath();
        ctx.fill();
        ctx.stroke();
        ctx.rotate((-extraSpikeRotate * 2 * Math.PI) / 180);
        //fourth star: 6 dark spikes in opposite direction
        ctx.fillStyle = portalRGB;
        ctx.strokeStyle = portalRGBoutline;
        ctx.rotate(((360 - extraSpikeRotate) * 3 * Math.PI) / 180); //times 2 to make it faster
        var numberOfSpikes = 6;
        var outerRadius =
          ((object.width * 1.5) / clientFovMultiplier) *
          portalsizeincrease;
        var innerRadius =
          (object.width / 2 / clientFovMultiplier) * portalsizeincrease;
        var rot = (Math.PI / 2) * 3;
        var x = 0;
        var y = 0;
        ctx.beginPath();
        ctx.moveTo(0, 0 - outerRadius);
        for (i = 0; i < numberOfSpikes; i++) {
          x = 0 + Math.cos(rot) * outerRadius;
          y = 0 + Math.sin(rot) * outerRadius;
          ctx.lineTo(x, y);
          rot += Math.PI / numberOfSpikes;
          x = 0 + Math.cos(rot) * innerRadius;
          y = 0 + Math.sin(rot) * innerRadius;
          ctx.lineTo(x, y);
          rot += Math.PI / numberOfSpikes;
        }
        ctx.lineTo(0, 0 - outerRadius);
        ctx.closePath();
        ctx.fill();
        ctx.stroke();
        ctx.rotate((-(360 - extraSpikeRotate) * 3 * Math.PI) / 180);
        //fifth star: tiny black spikes
        ctx.rotate((extraSpikeRotate * 3 * Math.PI) / 180); //times 2 to make it faster
        var numberOfSpikes = 6;
        var outerRadius =
          ((object.width * 1.25) / clientFovMultiplier) *
          portalsizeincrease;
        var innerRadius =
          (object.width / 4 / clientFovMultiplier) * portalsizeincrease;
        var rot = (Math.PI / 2) * 3;
        var x = 0;
        var y = 0;
        ctx.beginPath();
        ctx.moveTo(0, 0 - outerRadius);
        for (i = 0; i < numberOfSpikes; i++) {
          x = 0 + Math.cos(rot) * outerRadius;
          y = 0 + Math.sin(rot) * outerRadius;
          ctx.lineTo(x, y);
          rot += Math.PI / numberOfSpikes;
          x = 0 + Math.cos(rot) * innerRadius;
          y = 0 + Math.sin(rot) * innerRadius;
          ctx.lineTo(x, y);
          rot += Math.PI / numberOfSpikes;
        }
        ctx.lineTo(0, 0 - outerRadius);
        ctx.closePath();
        ctx.fill();
        ctx.stroke();
        ctx.rotate((-extraSpikeRotate * 3 * Math.PI) / 180);
        ctx.restore();
        ctx.lineJoin = "miter";
      }
      ctx.fillStyle = portalRGB;
      ctx.strokeStyle = portalRGBoutline;
      ctx.lineWidth = 3 / clientFovMultiplier;
      ctx.beginPath();
      ctx.arc(
        drawingX,
        drawingY,
        portalwidth / clientFovMultiplier,
        0,
        2 * Math.PI
      );
      ctx.fill();
      ctx.stroke();
      if (object.hasOwnProperty("deadOpacity")) {
        //if this is an animation of a dead object
        ctx.globalAlpha = 1.0; //reset opacity
      }

      //spawn particles
      var choosing = Math.floor(Math.random() * 3); //choose if particle spawn. Lower number means more particles
      if (choosing == 1) {
        var angleDegrees = Math.floor(Math.random() * 360); //choose angle in degrees
        var angleRadians = (angleDegrees * Math.PI) / 180; //convert to radians
        portalparticles[particleID] = {
          angle: angleRadians,
          x: object.x,
          y: object.y,
          width: 50,
          height: 50,
          speed: 10,
          timer: 30,
          maxtimer: 15, //difference between timer and maxtimer is the opacity change of the particle. Larger difference means more or less transparent
          color: "white",
          outline: "lightgrey",
          type: "particle",
        };
        particleID++;
      }

      if (showHitBox == "yes") {
        //draw hitbox
        ctx.strokeStyle = "blue";
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.arc(
          drawingX,
          drawingY,
          object.width / clientFovMultiplier,
          0,
          2 * Math.PI
        );
        ctx.stroke();
      }
    } else if (object.type == "Fixedportal") {
      //drawing rectangular fixed portals, e.g. the portal at top left corner of dune
      ctx.save(); //save so later can restore
      ctx.translate(drawingX, drawingY); //translate so white portal is at 0,0 coordinates so can rotate around center of portal
      ctx.rotate((object.angleDegrees * Math.PI) / 180); //rotate portal
      ctx.fillStyle = object.color;
      ctx.strokeStyle = object.outline;
      ctx.fillRect(
        -object.width / 2 / clientFovMultiplier,
        -object.height / 2 / clientFovMultiplier,
        object.width / clientFovMultiplier,
        object.height / clientFovMultiplier
      );
      ctx.strokeRect(
        -object.width / 2 / clientFovMultiplier,
        -object.height / 2 / clientFovMultiplier,
        object.width / clientFovMultiplier,
        object.height / clientFovMultiplier
      );
      ctx.globalAlpha = 0.7; //transparency
      ctx.fillStyle = object.color2;
      ctx.fillRect(
        -object.width / clientFovMultiplier,
        -object.height / clientFovMultiplier,
        (object.width * 2) / clientFovMultiplier,
        (object.height * 2) / clientFovMultiplier
      );
      ctx.strokeRect(
        -object.width / clientFovMultiplier,
        -object.height / clientFovMultiplier,
        (object.width * 2) / clientFovMultiplier,
        (object.height * 2) / clientFovMultiplier
      );
      ctx.globalAlpha = 1.0; //reset transparency
      ctx.restore(); //restore after translating
      if (showHitBox == "yes") {
        //draw hitbox
        ctx.strokeStyle = "blue";
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.arc(
          drawingX,
          drawingY,
          object.width / 2 / clientFovMultiplier,
          0,
          2 * Math.PI
        );
        ctx.stroke();
      }
    } else if (object.type == "particle") {
      //draw particles
      if (object.timer <= 10){
        ctx.globalAlpha = object.timer / 10;
      }
      //ctx.globalAlpha = object.timer / object.maxtimer;
      ctx.fillStyle = object.color;
      ctx.strokeStyle = object.outline;
      ctx.lineWidth = 3 / clientFovMultiplier;
      ctx.beginPath();
      ctx.arc(
        drawingX,
        drawingY,
        object.width / clientFovMultiplier,
        0,
        2 * Math.PI
      );
      ctx.fill();
      ctx.stroke();
      ctx.globalAlpha = 1.0;
    } else if (object.type == "wall") {
      //ctx.fillStyle = "#232323";
      ctx.fillStyle = "rgba(15, 15, 15, .5)";
      ctx.fillRect(
        drawingX,
        drawingY,
        object.w / clientFovMultiplier,
        object.h / clientFovMultiplier
      );
      if (showHitBox == "yes") {
        //draw hitbox
        ctx.strokeStyle = "blue";
        ctx.lineWidth = 3;
        ctx.strokeRect(
          drawingX,
          drawingY,
          object.w / clientFovMultiplier,
          object.h / clientFovMultiplier
        );
      }
    } else if (object.type == "gate") {
      //ctx.fillStyle = "#232323";
      ctx.save();
      ctx.translate(drawingX, drawingY);
      ctx.rotate(object.angle/180*Math.PI);
      //draw white rectangle below
      ctx.fillStyle = "rgba(255,255,255,.7)";
      ctx.strokeStyle = "white";
      //FIRST WHITE RECTANGLE
      ctx.globalAlpha = 1.0 * (endGate - gateTimer) / (endGate - 1 - startGate);//gateTimer increases from 0.5 to 9, this equation makes the opacity decrease from 1 to 0
      ctx.fillRect(
        -(object.height / clientFovMultiplier * gateTimer)/2 + object.height / clientFovMultiplier/2,
         -object.width/2/clientFovMultiplier,
        object.height / clientFovMultiplier * gateTimer,
        object.width / clientFovMultiplier
      );
      ctx.strokeRect(
        -(object.height / clientFovMultiplier * gateTimer)/2 + object.height / clientFovMultiplier/2,
         -object.width/2/clientFovMultiplier,
        object.height / clientFovMultiplier * gateTimer,
        object.width / clientFovMultiplier
      );
      ctx.globalAlpha = 1.0;
      //SECOND WHITE RECTANGLE
      let gateTimer2 = gateTimer - endGate/2;
      if (gateTimer2 < startGate){
        gateTimer2 = endGate - (startGate - gateTimer2)
      }
      ctx.globalAlpha = 1.0 * (endGate - gateTimer2) / (endGate - 1 - startGate);//gateTimer increases from 1 to 7, this equation makes the opacity decrease from 1 to 0
      ctx.fillRect(
        -(object.height / clientFovMultiplier * gateTimer2)/2 + object.height / clientFovMultiplier/2,
         -object.width/2/clientFovMultiplier,
        object.height / clientFovMultiplier * gateTimer2,
        object.width / clientFovMultiplier
      );
      ctx.strokeRect(
        -(object.height / clientFovMultiplier * gateTimer2)/2 + object.height / clientFovMultiplier/2,
         -object.width/2/clientFovMultiplier,
        object.height / clientFovMultiplier * gateTimer2,
        object.width / clientFovMultiplier
      );
      ctx.globalAlpha = 1.0;
      //draw actual black gate
      ctx.fillStyle = "black";
      ctx.fillRect(0,
         -object.width/2/clientFovMultiplier,
        object.height / clientFovMultiplier,
        object.width / clientFovMultiplier
      );
      if (showHitBox == "yes") {
        //draw hitbox
        ctx.strokeStyle = "blue";
        ctx.lineWidth = 3;
        ctx.strokeRect(0,
         -object.width/2/clientFovMultiplier,
          object.height / clientFovMultiplier,
          object.width / clientFovMultiplier
        );
      }
      ctx.restore();
      //spawn particles
      var choosing = Math.floor(Math.random() * 3); //choose if particle spawn. Lower number means more particles
      if (choosing == 1) {
        var dir = Math.floor(Math.random() * 2); //choose angle in degrees
        if (dir == 0){
          var angleRadians = (object.angle) * Math.PI / 180; //convert to radians
        }
        else{
          var angleRadians = (object.angle - 180) * Math.PI / 180;
        }
        let randX = 0;
        let randY = 0;
        //code currently does not support particles for gates that are tilted
        //i dont see a need to add that in the near future
        if (object.angle == 0 || object.angle == 180 || object.angle == 360){
          randY = Math.floor(Math.random() * object.width) - object.width/2;
        }
        else if (object.angle == 90 || object.angle == 270){
          randX = Math.floor(Math.random() * object.width) - object.width/2;
        }
        portalparticles[particleID] = {
          angle: angleRadians,
          x: object.x + randX,
          y: object.y + randY,
          width: 50,
          height: 50,
          speed: 10,
          timer: 30,
          maxtimer: 15, //difference between timer and maxtimer is the opacity change of the particle. Larger difference means more or less transparent
          color: "white",
          outline: "lightgrey",
          type: "particle",
        };
        particleID++;
      }
    } else if (object.type == "def") {
      //base defender in 2tdm
      ctx.save();
      ctx.translate(drawingX, drawingY);
      ctx.rotate(object.angle);
      ctx.lineJoin = "round"; //make corners of shape round
      ctx.lineWidth = 4 / clientFovMultiplier;

      //draw octagon base
      var octagonWidth = object.width/5*6;
      ctx.fillStyle = bodyColors.asset.col;
      ctx.strokeStyle = bodyColors.asset.outline;
      ctx.beginPath();
      ctx.moveTo(
        0 + (octagonWidth / clientFovMultiplier) * Math.cos(0),
        0 + (octagonWidth / clientFovMultiplier) * Math.sin(0)
      );
      for (var i = 1; i <= 8 + 1; i += 1) {
        ctx.lineTo(
          0 +
            (octagonWidth / clientFovMultiplier) *
              Math.cos((i * 2 * Math.PI) / 8),
          0 +
            (octagonWidth / clientFovMultiplier) *
              Math.sin((i * 2 * Math.PI) / 8)
        );
      }
      ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };


      //draw barrels
      ctx.fillStyle = bodyColors.barrel.col;
      ctx.strokeStyle = bodyColors.barrel.outline;
      //trapezoid at the tip
      var barrelwidth = 70;
      var barrelheight = 20;
      //rectangle
      var barrelwidth2 = 90;
      var barrelheight2 = 20;
      //base trapezoid
      var barrelwidth3 = 70;
      var barrelheight3 = 60;
      //note that trapezoids and rectangles are drawn differently

      for (let i = 0; i < 4; i++) {//draw 4 barrels
        var barrelAngle = 360/4*i;
        var barrelX = Math.cos((barrelAngle * Math.PI) / 180) * object.width * 1.4;
        var barrelY = Math.sin((barrelAngle * Math.PI) / 180) * object.width * 1.4;
        var barrelX2 =
          Math.cos((barrelAngle * Math.PI) / 180) *
          (object.width * 1.4 - barrelheight); //move rectangle barrel downwards
        var barrelY2 =
          Math.sin((barrelAngle * Math.PI) / 180) *
          (object.width * 1.4 - barrelheight);
        var barrelX3 =
          Math.cos((barrelAngle * Math.PI) / 180) *
          (object.width * 1.4 - barrelheight - barrelheight2); //move base trapezoid barrel downwards
        var barrelY3 =
          Math.sin((barrelAngle * Math.PI) / 180) *
          (object.width * 1.4 - barrelheight - barrelheight2);
        //base trapezoid
        ctx.save();
        ctx.translate(
          barrelX3 / clientFovMultiplier,
          barrelY3 / clientFovMultiplier
        );
        ctx.rotate(((barrelAngle - 90) * Math.PI) / 180);
        ctx.beginPath();
        ctx.moveTo(
          ((-barrelwidth3 / 3) * 2) / clientFovMultiplier,
          -barrelheight3 / clientFovMultiplier
        );
        ctx.lineTo(-barrelwidth3 / clientFovMultiplier, 0);
        ctx.lineTo(barrelwidth3 / clientFovMultiplier, 0);
        ctx.lineTo(
          ((barrelwidth3 / 3) * 2) / clientFovMultiplier,
          -barrelheight3 / clientFovMultiplier
        );
        ctx.lineTo(
          ((-barrelwidth3 / 3) * 2) / clientFovMultiplier,
          -barrelheight3 / clientFovMultiplier
        );
        ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
        ctx.restore();
        //rectangle
        ctx.save();
        ctx.translate(
          barrelX2 / clientFovMultiplier,
          barrelY2 / clientFovMultiplier
        );
        ctx.rotate(((barrelAngle - 90) * Math.PI) / 180);
        ctx.fillRect(
          -barrelwidth2 / 2 / clientFovMultiplier,
          -barrelheight2 / clientFovMultiplier,
          barrelwidth2 / clientFovMultiplier,
          barrelheight2 / clientFovMultiplier
        );
        if (CRTP != 'simplistic') {
        ctx.strokeRect(
          -barrelwidth2 / 2 / clientFovMultiplier,
          -barrelheight2 / clientFovMultiplier,
          barrelwidth2 / clientFovMultiplier,
          barrelheight2 / clientFovMultiplier
        );
        };
        ctx.restore();
        //trapezium at the tip
        ctx.save();
        ctx.translate(
          barrelX / clientFovMultiplier,
          barrelY / clientFovMultiplier
        );
        ctx.rotate(((barrelAngle - 90) * Math.PI) / 180);
        ctx.beginPath();
        ctx.moveTo(-barrelwidth / 2 / clientFovMultiplier, 0);
        ctx.lineTo(
          -barrelwidth / clientFovMultiplier,
          -barrelheight / clientFovMultiplier
        );
        ctx.lineTo(
          barrelwidth / clientFovMultiplier,
          -barrelheight / clientFovMultiplier
        );
        ctx.lineTo(barrelwidth / 2 / clientFovMultiplier, 0);
        ctx.lineTo(-barrelwidth / 2 / clientFovMultiplier, 0);
        ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
        ctx.restore();
      }

      //draw body
      ctx.fillStyle = object.color;
      ctx.strokeStyle = object.outline;
      ctx.beginPath();
      ctx.arc(
        0,
        0,
        object.width / clientFovMultiplier,
        0,
        2 * Math.PI
      );
      ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
      var octagonWidth = object.width/5*4;
      ctx.fillStyle = bodyColors.asset.col;
      ctx.strokeStyle = bodyColors.asset.outline;
      ctx.beginPath();
      ctx.moveTo(
        0 + (octagonWidth / clientFovMultiplier) * Math.cos(0),
        0 + (octagonWidth / clientFovMultiplier) * Math.sin(0)
      );
      for (var i = 1; i <= 8 + 1; i += 1) {
        ctx.lineTo(
          0 +
            (octagonWidth / clientFovMultiplier) *
              Math.cos((i * 2 * Math.PI) / 8),
          0 +
            (octagonWidth / clientFovMultiplier) *
              Math.sin((i * 2 * Math.PI) / 8)
        );
      }
      ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
      ctx.fillStyle = object.color;
      ctx.strokeStyle = object.outline;
      ctx.beginPath();
      ctx.arc(
        0,
        0,
        object.width/2 / clientFovMultiplier,
        0,
        2 * Math.PI
      );
      ctx.fill();
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };

      ctx.lineJoin = "miter"; //change back
      ctx.restore();
      if (showHitBox == "yes") {
        //draw hitbox
        ctx.strokeStyle = "blue";
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.arc(
          drawingX,
          drawingY,
          object.width / clientFovMultiplier,
          0,
          2 * Math.PI
        );
          if (CRTP != 'simplistic') {
          ctx.stroke();
          };
      }
    }
  };

    function newbulletbarrel(canvas, x,width,height,shootChange, fov){//shootchange is change in barrel height when shooting
    canvas.fillRect(
      (x - width / 2) / fov,
      -(height - shootChange) / fov,
      width / fov,
      (height - shootChange) / fov
    );
    if (document.getElementById('theme').value != 'simplistic') {
    canvas.strokeRect(
      (x - width / 2) / fov,
      -(height - shootChange) / fov,
      width / fov,
      (height - shootChange) / fov
    );
    };
  };
    function newdronebarrel(canvas, x,width,height,shootChange, fov){
    canvas.beginPath();
    canvas.moveTo(
      -width / 2 / fov +
        x / fov,
      0
    );
    canvas.lineTo(
      -width / fov +
        x / fov,
      -(height - shootChange) / fov
    );
    canvas.lineTo(
      width / fov +
        (x * 2) / fov,
      -(height - shootChange) / fov
    );
    canvas.lineTo(
      width / 2 / fov +
        (x * 2) / fov,
      0
    );
    canvas.fill();
        if (document.getElementById('theme').value != 'simplistic') {
    canvas.stroke();
        };
  };
    function newtrapbarrel(canvas, x,width,height,shootChange, fov){
    canvas.fillRect(
      (x - width / 2) / fov,
      -(height - shootChange) * 0.67 / fov,
      width / fov,
      (height - shootChange) * 0.67 / fov
    );
            if (document.getElementById('theme').value != 'simplistic') {
    canvas.strokeRect(
      (x - width / 2) / fov,
      -(height - shootChange) * 0.67 / fov,
      width / fov,
      (height - shootChange) * 0.67 / fov
    );
            };
    canvas.beginPath();
    canvas.moveTo(
      (x - width / 2) / fov,
      -(height - shootChange) * 0.67 / fov
    );
    canvas.lineTo(
      (x - width) / fov,
      -(height - shootChange) / fov
    );
    canvas.lineTo(
      (x + width) / fov,
      -(height - shootChange) / fov
    );
    canvas.lineTo(
      (x + width / 2) / fov,
      -(height - shootChange) * 0.67 / fov
    );
    canvas.fill();
        if (document.getElementById('theme').value != 'simplistic') {
    canvas.stroke();
        };
  };
    function newminebarrel(canvas, x,width,height,shootChange, fov){
    canvas.fillRect(
      (x - width / 2) / fov,
      -(height - shootChange) / fov,
      width / fov,
      (height - shootChange) / fov
    );
    if (document.getElementById('theme').value != 'simplistic') {
    canvas.strokeRect(
      (x - width / 2) / fov,
      -(height - shootChange) / fov,
      width / fov,
      (height - shootChange) / fov
    );
    };
    canvas.fillRect(
      (-width * 1.5) / 2 / fov + x / fov,
      -(height - shootChange) * 0.67 / fov,
      (width / fov) * 1.5,
      (height - shootChange) * 0.67 / fov
    );
     if (document.getElementById('theme').value != 'simplistic') {
    canvas.strokeRect(
      (-width * 1.5) / 2 / fov + x / fov,
      -(height - shootChange) * 0.67 / fov,
      (width / fov) * 1.5,
      (height - shootChange) * 0.67 / fov
    );
     };
  };
    function newminionbarrel(canvas, x,width,height,shootChange, fov){
    canvas.fillRect(
      (x - width / 2) / fov,
      -(height - shootChange) / fov,
      width / fov,
      (height - shootChange) / fov
    );
    if (document.getElementById('theme').value != 'simplistic') {
    canvas.strokeRect(
      (x - width / 2) / fov,
      -(height - shootChange) / fov,
      width / fov,
      (height - shootChange) / fov
    );
    };
    canvas.fillRect(
      (x - width * 0.75) / fov,
      -(height - shootChange) / 1.5 / fov,
      (width / fov) * 1.5,
      (height - shootChange) / 1.5 / fov
    );
    if (document.getElementById('theme').value != 'simplistic') {
    canvas.strokeRect(
      (x - width * 0.75) / fov,
      -(height - shootChange) / 1.5 / fov,
      (width / fov) * 1.5,
      (height - shootChange) / 1.5 / fov
    );
    };
    canvas.fillRect(
      (x - width * 0.75) / fov,
      -(height - shootChange) / fov,
      (width / fov) * 1.5,
      (height - shootChange) / 5 / fov
    );
    if (document.getElementById('theme').value != 'simplistic') {
    canvas.strokeRect(
      (x - width * 0.75) / fov,
      -(height - shootChange) / fov,
      (width / fov) * 1.5,
      (height - shootChange) / 5 /fov
    );
    };
  };

    drawobjects = newdraw;
    drawPlayer = newdrawplayer;
    drawBulletBarrel = newbulletbarrel; drawDroneBarrel = newdronebarrel; drawTrapBarrel = newtrapbarrel; drawMineBarrel = newminebarrel; drawMinionBarrel = newminionbarrel;

    let pd = false;
    const editloop = () => {
        if (player.health <= 1 && !pd && gameStart == -1) {
            if (autorespawn) {
                document.getElementById('continue').click();
                document.getElementById('play').click();
            };
            pd = true;
        } else if (player.health > 1) {
            pd = false;
        };
        requestAnimationFrame(editloop);
    };
    editloop();
})();