`
- }
- for (let i = 0, len = b.guns.length; i < len; i++) {
- text += `
${b.guns[i].name} ${b.guns[i].description}
`
- }
- for (let i = 0, len = b.mods.length; i < len; i++) {
- text += `
${b.mods[i].name} ${b.mods[i].description}
`
- }
- el.innerHTML = text
- el.style.display = "grid"
- build.isShowingBuilds = true
- document.body.style.overflowY = "scroll";
- document.body.style.overflowX = "hidden";
- document.getElementById("controls").style.display = 'none'
- document.getElementById("settings").style.display = 'none'
- }
-});
-
-//set up canvas
-var canvas = document.getElementById("canvas");
-//using "const" causes problems in safari when an ID shares the same name.
-const ctx = canvas.getContext("2d");
-document.body.style.backgroundColor = "#fff";
-
-//disable pop up menu on right click
-document.oncontextmenu = function () {
- return false;
-}
-
-function setupCanvas() {
- canvas.width = window.innerWidth;
- canvas.height = window.innerHeight;
- canvas.width2 = canvas.width / 2; //precalculated because I use this often (in mouse look)
- canvas.height2 = canvas.height / 2;
- canvas.diagonal = Math.sqrt(canvas.width2 * canvas.width2 + canvas.height2 * canvas.height2);
- ctx.font = "15px Arial";
- ctx.lineJoin = "round";
- ctx.lineCap = "round";
- // ctx.lineCap='square';
- game.setZoom();
-}
-setupCanvas();
-window.onresize = () => {
- setupCanvas();
-};
-
-//mouse move input
-document.body.addEventListener("mousemove", (e) => {
- game.mouse.x = e.clientX;
- game.mouse.y = e.clientY;
-});
-
-document.body.addEventListener("mouseup", (e) => {
- // game.buildingUp(e); //uncomment when building levels
- // game.mouseDown = false;
- // console.log(e)
- if (e.which === 3) {
- game.mouseDownRight = false;
- } else {
- game.mouseDown = false;
- }
-});
-
-document.body.addEventListener("mousedown", (e) => {
- if (e.which === 3) {
- game.mouseDownRight = true;
- } else {
- game.mouseDown = true;
- }
-});
-
-//keyboard input
-const keys = [];
-document.body.addEventListener("keydown", (e) => {
- keys[e.keyCode] = true;
- game.keyPress();
-});
-
-document.body.addEventListener("keyup", (e) => {
- keys[e.keyCode] = false;
-});
-
-document.body.addEventListener("wheel", (e) => {
- if (e.deltaY > 0) {
- game.nextGun();
- } else {
- game.previousGun();
- }
-}, {
- passive: true
-});
-
-document.getElementById("fps-select").addEventListener("input", () => {
- let value = document.getElementById("fps-select").value
- if (value === 'max') {
- game.fpsCapDefault = 999999999;
- } else if (value === '72') {
- game.fpsCapDefault = 72
- } else if (value === '60') {
- game.fpsCapDefault = 60
- } else if (value === '45') {
- game.fpsCapDefault = 45
- } else if (value === '30') {
- game.fpsCapDefault = 30
- } else if (value === '15') {
- game.fpsCapDefault = 15
- }
-});
-
-document.getElementById("body-damage").addEventListener("input", () => {
- game.isBodyDamage = document.getElementById("body-damage").checked
-});
-
-// function playSound(id) {
-// //play sound
-// if (false) {
-// //sounds are turned off for now
-// // if (document.getElementById(id)) {
-// var sound = document.getElementById(id); //setup audio
-// sound.currentTime = 0; //reset position of playback to zero //sound.load();
-// sound.play();
-// }
-// }
-
-function shuffle(array) {
- var currentIndex = array.length,
- temporaryValue,
- randomIndex;
- // While there remain elements to shuffle...
- while (0 !== currentIndex) {
- // Pick a remaining element...
- randomIndex = Math.floor(Math.random() * currentIndex);
- currentIndex -= 1;
- // And swap it with the current element.
- temporaryValue = array[currentIndex];
- array[currentIndex] = array[randomIndex];
- array[randomIndex] = temporaryValue;
- }
- return array;
-}
-
-
-
-//main loop ************************************************************
-//**********************************************************************
-function cycle() {
- if (!game.paused) requestAnimationFrame(cycle);
- const now = Date.now();
- const elapsed = now - game.then; // calc elapsed time since last loop
- if (elapsed > game.fpsInterval) { // if enough time has elapsed, draw the next frame
- game.then = now - (elapsed % game.fpsInterval); // Get ready for next frame by setting then=now. Also, adjust for fpsInterval not being multiple of 16.67
- game.loop();
- }
+"use strict";
+/* TODO: *******************************************
+*****************************************************
+
+add builds with combinations of gun, field and mobs
+ use the pull down menu
+
+dynamically generate html about fields, guns and mods
+
+add grid check to improve queries over large body arrays
+ something about broad phase
+ having trouble with this, might give up
+
+gun: like drones, but fast moving and short lived
+ dies after doing damage
+
+gun: Spirit Bomb (singularity)
+ use charge up like rail gun
+ electricity graphics like plasma torch
+ suck in nearby mobs, power ups?, blocks?
+ sucked in stuff increase size
+ uses energy
+
+mod: auto pick up guns, heals, ammo
+ use the same rule for drones
+ maybe give some other bonus too?
+
+rework junk bot
+ it's behavior is too unpredictable
+ range is unclear
+ having the bullets last long after doing dmg isn't fun
+ we want a fun gun that acts like a melee weapon
+
+atmosphere levels
+ large rotating fan that the player has to move through
+ give the user a rest, between combat
+ low combat
+ nonaggressive mobs
+ one mob attacking the passive mobs
+ more graphics
+
+Boss levels
+ boss grows and spilt, if you don't kill it fast
+ sensor that locks you in after you enter the boss room
+ boss that eats other mobs and gains stats from them
+ chance to spawn on any level (past level 5)
+ boss that knows how to shoot (player) bullets that collide with player
+ overwrite custom engine collision bullet mob function.
+
+add a key that player picks up and needs to set on the exit door to open it
+
+make power ups keep moving to player if the pickup field is turned off before they get picked up
+ not sure how to do this without adding a constant check
+
+animate new level spawn by having the map aspects randomly fly into place
+
+new map with repeating endlessness
+ get ideas from Manifold Garden game
+ if falling, get teleported above the map
+ I tried it, but had trouble getting the camera to adjust to the teleportation
+ this can apply to blocks mobs, and power ups as well
+
+field power up effects
+ field allows player to hold and throw living mobs
+
+mod power ups ideas
+ double jump
+ bullet on mob damage effects
+ add to the array mob.do new mob behaviors
+ add a damage over time
+ add a freeze
+
+give mobs more animal-like behaviors
+ like rain world
+ give mobs something to do when they don't see player
+ explore map
+ eat power ups
+ drop power up (if killed after eating one)
+ mobs some times aren't aggressive
+ when low on life or after taking a large hit
+ mobs can fight each other
+ this might be hard to code
+ isolated mobs try to group up.
+
+game mechanics
+ mechanics that support the physics engine
+ add rope/constraint
+ get ideas from game: limbo / inside
+ environmental hazards
+ laser
+ lava
+ button / switch
+ door
+ fizzler
+ moving platform
+ map zones
+ water
+ low friction ground
+ bouncy ground
+
+
+// collision info:
+ category mask
+powerUp: 0x100000 0x100001
+body: 0x010000 0x011111
+player: 0x001000 0x010011
+bullet: 0x000100 0x010011
+mob: 0x000010 0x011111
+mobBullet: 0x000010 0x011101
+mobShield: 0x000010 0x001100
+map: 0x000001 0x111111
+
+
+
+
+*/
+
+//build build grid display
+const build = {
+ isShowingBuilds: false,
+ list: [],
+ choosePowerUp(who, index, type) {
+ //check if matching a current power up
+ for (let i = 0; i < build.list.length; i++) {
+ if (build.list[i].index === index && build.list[i].type === type) { //if already click, toggle off
+ build.list.splice(i, 1);
+ who.style.backgroundColor = "#fff"
+ return
+ }
+ }
+
+ //check if trying to get a second field
+ if (type === "field") {
+ for (let i = 0; i < build.list.length; i++) {
+ if (build.list[i].type === "field") { //if already click, toggle off
+ build.list[i].who.style.backgroundColor = "#fff"
+ build.list.splice(i, 1);
+ }
+ }
+ }
+
+ if (build.list.length < 5) { //add to build array
+ // who.style.border = "2px solid #333"
+ who.style.backgroundColor = "#919ba8" //"#868f9a"
+ build.list[build.list.length] = {
+ who: who,
+ index: index,
+ type: type
+ }
+ }
+ },
+ startBuildRun() {
+ spawn.setSpawnList();
+ spawn.setSpawnList();
+ game.startGame();
+ game.difficulty = 6;
+ level.isBuildRun = true;
+ for (let i = 0; i < build.list.length; i++) {
+ if (build.list[i].type === "field") {
+ mech.fieldUpgrades[build.list[i].index].effect();
+ } else if (build.list[i].type === "gun") {
+ b.giveGuns(build.list[i].index)
+ } else if (build.list[i].type === "mod") {
+ b.giveMod(build.list[i].index)
+ }
+ }
+ }
+}
+
+document.getElementById("build-button").addEventListener("click", () => {
+ document.getElementById("build-button").style.display = "none";
+ const el = document.getElementById("build-grid")
+ if (build.isShowingBuilds) {
+ el.style.display = "none"
+ build.isShowingBuilds = false
+ document.body.style.overflow = "hidden"
+ document.getElementById("controls").style.display = 'inline'
+ document.getElementById("settings").style.display = 'inline'
+ } else {
+ build.list = []
+ // let text = '
choose up to 5 powers
'
+ let text =
+ `
+
+
+
+ Choose up to five power ups. Once you start, only health and ammo will drop, so pick carefully.
+
`
+ for (let i = 1, len = mech.fieldUpgrades.length; i < len; i++) {
+ text += `
`, 300);
+ // if (game.difficulty === 0) text = "";
+ // text = "Level " + (game.difficulty + 1) + ": " + spawn.pickList[0] + "s + " + spawn.pickList[1] + "s";
+
+ // text = text + " with population: ";
+ // for (let i = 0, len = spawn.pickList.length; i < len; ++i) {
+ // if (spawn.pickList[i] != spawn.pickList[i - 1]) {
+ // text += spawn.pickList[i] + ", ";
+ // }
+ // }
+ // this.speech(text);
+ // game.makeTextLog(text, 360);
+ },
+ addToWorld(mapName) {
+ //needs to be run to put bodies into the world
+ for (let i = 0; i < body.length; i++) {
+ //body[i].collisionFilter.group = 0;
+ body[i].collisionFilter.category = 0x010000;
+ body[i].collisionFilter.mask = 0x111111;
+ body[i].classType = "body";
+ World.add(engine.world, body[i]); //add to world
+ }
+ for (let i = 0; i < map.length; i++) {
+ //map[i].collisionFilter.group = 0;
+ map[i].collisionFilter.category = 0x000001;
+ map[i].collisionFilter.mask = 0x111111;
+ Matter.Body.setStatic(map[i], true); //make static
+ World.add(engine.world, map[i]); //add to world
+ }
+ for (let i = 0; i < cons.length; i++) {
+ World.add(engine.world, cons[i]);
+ }
+ for (let i = 0; i < consBB.length; i++) {
+ World.add(engine.world, consBB[i]);
+ }
+ }
};
\ No newline at end of file
diff --git a/js/mobs.js b/js/mobs.js
index 3b4c630..e464407 100644
--- a/js/mobs.js
+++ b/js/mobs.js
@@ -1,1011 +1,1011 @@
-//create array of mobs
-let mob = [];
-//method to populate the array above
-const mobs = {
- loop() {
- let i = mob.length;
- while (i--) {
- if (mob[i].alive) {
- mob[i].do();
- } else {
- mob[i].replace(i); //removing mob and replace with body, this is done here to avoid an array index bug with drawing I think
- }
- }
- },
- draw() {
- ctx.lineWidth = 2;
- let i = mob.length;
- while (i--) {
- ctx.beginPath();
- const vertices = mob[i].vertices;
- ctx.moveTo(vertices[0].x, vertices[0].y);
- for (let j = 1, len = vertices.length; j < len; ++j) {
- ctx.lineTo(vertices[j].x, vertices[j].y);
- }
- ctx.lineTo(vertices[0].x, vertices[0].y);
- ctx.fillStyle = mob[i].fill;
- ctx.strokeStyle = mob[i].stroke;
- ctx.fill();
- ctx.stroke();
- }
- },
- // alert(range) {
- // range = range * range;
- // for (let i = 0; i < mob.length; i++) {
- // if (mob[i].distanceToPlayer2() < range) mob[i].locatePlayer();
- // }
- // },
- // startle(amount) {
- // for (let i = 0; i < mob.length; i++) {
- // if (!mob[i].seePlayer.yes) {
- // mob[i].force.x += amount * mob[i].mass * (Math.random() - 0.5);
- // mob[i].force.y += amount * mob[i].mass * (Math.random() - 0.5);
- // }
- // }
- // },
- //**********************************************************************************************
- //**********************************************************************************************
- spawn(xPos, yPos, sides, radius, color) {
- let i = mob.length;
- mob[i] = Matter.Bodies.polygon(xPos, yPos, sides, radius, {
- //inertia: Infinity, //prevents rotation
- mob: true,
- density: 0.001,
- //friction: 0,
- frictionAir: 0.005,
- //frictionStatic: 0,
- restitution: 0.5,
- collisionFilter: {
- group: 0,
- category: 0x000010,
- mask: 0x011111
- },
- onHit: undefined,
- alive: true,
- index: i,
- health: 1,
- accelMag: 0.001,
- cd: 0, //game cycle when cooldown will be over
- delay: 60, //static: time between cooldowns
- fill: color,
- stroke: "#000",
- seePlayer: {
- yes: false,
- recall: 0,
- position: {
- x: xPos,
- y: yPos
- }
- },
- radius: radius,
- spawnPos: {
- x: xPos,
- y: yPos
- },
- seeAtDistance2: 4000000, //sqrt(4000000) = 2000 = max seeing range
- distanceToPlayer() {
- const dx = this.position.x - player.position.x;
- const dy = this.position.y - player.position.y;
- return Math.sqrt(dx * dx + dy * dy);
- },
- distanceToPlayer2() {
- const dx = this.position.x - player.position.x;
- const dy = this.position.y - player.position.y;
- return dx * dx + dy * dy;
- },
- gravity() {
- this.force.y += this.mass * this.g;
- },
- seePlayerFreq: Math.round((30 + 30 * Math.random()) * game.lookFreqScale), //how often NPC checks to see where player is, lower numbers have better vision
- foundPlayer() {
- this.locatePlayer();
- if (!this.seePlayer.yes) {
- this.alertNearByMobs();
- this.seePlayer.yes = true;
- }
- },
- lostPlayer() {
- this.seePlayer.yes = false;
- this.seePlayer.recall -= this.seePlayerFreq;
- if (this.seePlayer.recall < 0) this.seePlayer.recall = 0;
- },
- memory: 120, //default time to remember player's location
- locatePlayer() {
- if (!mech.isStealth) {
- // updates mob's memory of player location
- this.seePlayer.recall = this.memory + Math.round(this.memory * Math.random()); //seconds before mob falls a sleep
- this.seePlayer.position.x = player.position.x;
- this.seePlayer.position.y = player.position.y;
- }
- },
- // locatePlayerByDist() {
- // if (this.distanceToPlayer2() < this.locateRange) {
- // this.locatePlayer();
- // }
- // },
- seePlayerCheck() {
- if (!(game.cycle % this.seePlayerFreq)) {
- if (
- this.distanceToPlayer2() < this.seeAtDistance2 &&
- Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 &&
- Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0
- ) {
- this.foundPlayer();
- } else if (this.seePlayer.recall) {
- this.lostPlayer();
- }
- }
- },
- seePlayerCheckByDistance() {
- if (!(game.cycle % this.seePlayerFreq)) {
- if (this.distanceToPlayer2() < this.seeAtDistance2) {
- this.foundPlayer();
- } else if (this.seePlayer.recall) {
- this.lostPlayer();
- }
- }
- },
- seePlayerByDistOrLOS() {
- if (!(game.cycle % this.seePlayerFreq)) {
- if (
- this.distanceToPlayer2() < this.seeAtDistance2 ||
- (Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0)
- ) {
- this.foundPlayer();
- } else if (this.seePlayer.recall) {
- this.lostPlayer();
- }
- }
- },
- seePlayerByDistAndLOS() {
- if (!(game.cycle % this.seePlayerFreq)) {
- if (
- this.distanceToPlayer2() < this.seeAtDistance2 &&
- (Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0)
- ) {
- this.foundPlayer();
- } else if (this.seePlayer.recall) {
- this.lostPlayer();
- }
- }
- },
- isLookingAtPlayer(threshold) {
- const diff = Matter.Vector.normalise(Matter.Vector.sub(player.position, this.position));
- //make a vector for the mob's direction of length 1
- const dir = {
- x: Math.cos(this.angle),
- y: Math.sin(this.angle)
- };
- //the dot product of diff and dir will return how much over lap between the vectors
- const dot = Matter.Vector.dot(dir, diff);
- // console.log(Math.cos(dot)*180/Math.PI)
- if (dot > threshold) {
- return true;
- } else {
- return false;
- }
- },
- lookRange: 0.2 + Math.random() * 0.2,
- lookTorque: 0.0000004 * (Math.random() > 0.5 ? -1 : 1),
- seePlayerByLookingAt() {
- if (!(game.cycle % this.seePlayerFreq) && (this.seePlayer.recall || this.isLookingAtPlayer(this.lookRange))) {
- if (
- this.distanceToPlayer2() < this.seeAtDistance2 &&
- Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 &&
- Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0
- ) {
- this.foundPlayer();
- } else if (this.seePlayer.recall) {
- this.lostPlayer();
- }
- }
- //if you don't recall player location rotate and draw to show where you are looking
- if (!this.seePlayer.recall) {
- this.torque = this.lookTorque * this.inertia;
- //draw
- const range = Math.PI * this.lookRange;
- ctx.beginPath();
- ctx.arc(this.position.x, this.position.y, this.radius * 2.5, this.angle - range, this.angle + range);
- ctx.arc(this.position.x, this.position.y, this.radius * 1.4, this.angle + range, this.angle - range, true);
- ctx.fillStyle = "rgba(0,0,0,0.07)";
- ctx.fill();
- }
- },
- mechPosRange() {
- return {
- x: player.position.x, // + (Math.random() - 0.5) * 50,
- y: player.position.y + (Math.random() - 0.5) * 110
- };
- //mob vision for testing
- // ctx.beginPath();
- // ctx.lineWidth = "5";
- // ctx.strokeStyle = "#ff0";
- // ctx.moveTo(this.position.x, this.position.y);
- // ctx.lineTo(targetPos.x, targetPos.y);
- // ctx.stroke();
- // return targetPos;
- },
- laserBeam() {
- if (game.cycle % 7 && this.seePlayer.yes) {
- ctx.setLineDash([125 * Math.random(), 125 * Math.random()]);
- // ctx.lineDashOffset = 6*(game.cycle % 215);
- if (this.distanceToPlayer() < this.laserRange) {
- //if (Math.random()>0.2 && this.seePlayer.yes && this.distanceToPlayer2()<800000) {
- if (b.isModTempResist) {
- mech.damage(0.00006 * game.dmgScale);
- } else {
- mech.damage(0.0003 * game.dmgScale);
- }
- if (mech.fieldMeter > 0.1) mech.fieldMeter -= 0.004
- ctx.beginPath();
- ctx.moveTo(this.position.x, this.position.y);
- ctx.lineTo(mech.pos.x, mech.pos.y);
- ctx.lineTo(mech.pos.x + (Math.random() - 0.5) * 3000, mech.pos.y + (Math.random() - 0.5) * 3000);
- ctx.lineWidth = 2;
- ctx.strokeStyle = "rgb(255,0,170)";
- ctx.stroke();
-
- ctx.beginPath();
- ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI);
- ctx.fillStyle = "rgba(255,0,170,0.15)";
- ctx.fill();
-
- }
- ctx.beginPath();
- ctx.arc(this.position.x, this.position.y, this.laserRange * 0.9, 0, 2 * Math.PI);
- ctx.strokeStyle = "rgba(255,0,170,0.5)";
- ctx.lineWidth = 1;
- ctx.stroke();
- ctx.setLineDash([]);
- ctx.fillStyle = "rgba(255,0,170,0.03)";
- ctx.fill();
- }
- },
- laser() {
- const vertexCollision = function (v1, v1End, domain) {
- for (let i = 0; i < domain.length; ++i) {
- let vertices = domain[i].vertices;
- const len = vertices.length - 1;
- for (let j = 0; j < len; j++) {
- results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]);
- if (results.onLine1 && results.onLine2) {
- const dx = v1.x - results.x;
- const dy = v1.y - results.y;
- const dist2 = dx * dx + dy * dy;
- if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) {
- best = {
- x: results.x,
- y: results.y,
- dist2: dist2,
- who: domain[i],
- v1: vertices[j],
- v2: vertices[j + 1]
- };
- }
- }
- }
- results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]);
- if (results.onLine1 && results.onLine2) {
- const dx = v1.x - results.x;
- const dy = v1.y - results.y;
- const dist2 = dx * dx + dy * dy;
- if (dist2 < best.dist2) {
- best = {
- x: results.x,
- y: results.y,
- dist2: dist2,
- who: domain[i],
- v1: vertices[0],
- v2: vertices[len]
- };
- }
- }
- }
- };
- if (this.seePlayer.recall) {
- this.torque = this.lookTorque * this.inertia * 2;
-
- const seeRange = 2500;
- best = {
- x: null,
- y: null,
- dist2: Infinity,
- who: null,
- v1: null,
- v2: null
- };
- const look = {
- x: this.position.x + seeRange * Math.cos(this.angle),
- y: this.position.y + seeRange * Math.sin(this.angle)
- };
- vertexCollision(this.position, look, map);
- vertexCollision(this.position, look, body);
- vertexCollision(this.position, look, [player]);
- // hitting player
- if (best.who === player) {
- if (b.isModTempResist) {
- dmg = 0.0008 * game.dmgScale;
- } else {
- dmg = 0.004 * game.dmgScale;
- }
-
- mech.damage(dmg);
- //draw damage
- ctx.fillStyle = color;
- ctx.beginPath();
- ctx.arc(best.x, best.y, dmg * 2000, 0, 2 * Math.PI);
- ctx.fill();
- }
- //draw beam
- if (best.dist2 === Infinity) {
- best = look;
- }
- ctx.beginPath();
- ctx.moveTo(this.position.x, this.position.y);
- ctx.lineTo(best.x, best.y);
- ctx.strokeStyle = "#f00"; // Purple path
- ctx.lineWidth = 1;
- ctx.setLineDash([50 + 120 * Math.random(), 50 * Math.random()]);
- ctx.stroke(); // Draw it
- ctx.setLineDash([0, 0]);
- }
- },
- searchSpring() {
- ctx.beginPath();
- ctx.arc(this.cons.pointA.x, this.cons.pointA.y, 6, 0, 2 * Math.PI);
- ctx.arc(this.cons2.pointA.x, this.cons2.pointA.y, 6, 0, 2 * Math.PI);
- // ctx.arc(this.cons.bodyB.position.x, this.cons.bodyB.position.y,6,0,2*Math.PI);
- ctx.fillStyle = "#222";
- ctx.fill();
-
- if (!(game.cycle % this.seePlayerFreq)) {
- if (
- (this.seePlayer.recall || this.isLookingAtPlayer(this.lookRange)) &&
- this.distanceToPlayer2() < this.seeAtDistance2 &&
- Matter.Query.ray(map, this.position, player.position).length === 0 &&
- Matter.Query.ray(body, this.position, player.position).length === 0
- ) {
- this.foundPlayer();
- if (!(game.cycle % (this.seePlayerFreq * 2))) {
- this.springTarget.x = this.seePlayer.position.x;
- this.springTarget.y = this.seePlayer.position.y;
- this.cons.length = -200;
- this.cons2.length = 100 + 1.5 * this.radius;
- } else {
- this.springTarget2.x = this.seePlayer.position.x;
- this.springTarget2.y = this.seePlayer.position.y;
- this.cons.length = 100 + 1.5 * this.radius;
- this.cons2.length = -200;
- }
- } else if (this.seePlayer.recall) {
- this.lostPlayer();
- }
- }
- //if you don't recall player location rotate and draw to show where you are looking
- if (!this.seePlayer.recall) {
- this.torque = this.lookTorque * this.inertia;
- //draw
- const range = Math.PI * this.lookRange;
- ctx.beginPath();
- ctx.arc(this.position.x, this.position.y, this.radius * 2.5, this.angle - range, this.angle + range);
- ctx.arc(this.position.x, this.position.y, this.radius * 1.4, this.angle + range, this.angle - range, true);
- ctx.fillStyle = "rgba(0,0,0,0.07)";
- ctx.fill();
- //spring to random place on map
- const vertexCollision = function (v1, v1End, domain) {
- for (let i = 0; i < domain.length; ++i) {
- let vertices = domain[i].vertices;
- const len = vertices.length - 1;
- for (let j = 0; j < len; j++) {
- results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]);
- if (results.onLine1 && results.onLine2) {
- const dx = v1.x - results.x;
- const dy = v1.y - results.y;
- const dist2 = dx * dx + dy * dy;
- if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) {
- best = {
- x: results.x,
- y: results.y,
- dist2: dist2,
- who: domain[i],
- v1: vertices[j],
- v2: vertices[j + 1]
- };
- }
- }
- }
- results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]);
- if (results.onLine1 && results.onLine2) {
- const dx = v1.x - results.x;
- const dy = v1.y - results.y;
- const dist2 = dx * dx + dy * dy;
- if (dist2 < best.dist2) {
- best = {
- x: results.x,
- y: results.y,
- dist2: dist2,
- who: domain[i],
- v1: vertices[0],
- v2: vertices[len]
- };
- }
- }
- }
- };
- const seeRange = 3000;
- if (!(game.cycle % (this.seePlayerFreq * 10))) {
- best = {
- x: null,
- y: null,
- dist2: Infinity,
- who: null,
- v1: null,
- v2: null
- };
- const look = {
- x: this.position.x + seeRange * Math.cos(this.angle),
- y: this.position.y + seeRange * Math.sin(this.angle)
- };
- vertexCollision(this.position, look, map);
- if (best.dist2 != Infinity) {
- this.springTarget.x = best.x;
- this.springTarget.y = best.y;
- this.cons.length = 100 + 1.5 * this.radius;
- this.cons2.length = 100 + 1.5 * this.radius;
- }
- }
- if (!((game.cycle + this.seePlayerFreq * 5) % (this.seePlayerFreq * 10))) {
- best = {
- x: null,
- y: null,
- dist2: Infinity,
- who: null,
- v1: null,
- v2: null
- };
- const look = {
- x: this.position.x + seeRange * Math.cos(this.angle),
- y: this.position.y + seeRange * Math.sin(this.angle)
- };
- vertexCollision(this.position, look, map);
- if (best.dist2 != Infinity) {
- this.springTarget2.x = best.x;
- this.springTarget2.y = best.y;
- this.cons.length = 100 + 1.5 * this.radius;
- this.cons2.length = 100 + 1.5 * this.radius;
- }
- }
- }
- },
- alertNearByMobs() {
- //this.alertRange2 is set at the very bottom of this mobs, after mob is made
- for (let i = 0; i < mob.length; i++) {
- if (!mob[i].seePlayer.recall && Matter.Vector.magnitudeSquared(Matter.Vector.sub(this.position, mob[i].position)) < this.alertRange2) {
- mob[i].locatePlayer();
- }
- }
- //add alert to draw queue
- // game.drawList.push({
- // x: this.position.x,
- // y: this.position.y,
- // radius: Math.sqrt(this.alertRange2),
- // color: "rgba(0,0,0,0.02)",
- // time: game.drawTime
- // });
- },
- zoom() {
- this.zoomMode--;
- if (this.zoomMode > 150) {
- this.drawTrail();
- if (this.seePlayer.recall) {
- //attraction to player
- const forceMag = this.accelMag * this.mass;
- const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x);
- this.force.x += forceMag * Math.cos(angle);
- this.force.y += forceMag * Math.sin(angle);
- }
- } else if (this.zoomMode < 0) {
- this.zoomMode = 300;
- this.setupTrail();
- }
- },
- setupTrail() {
- this.trail = [];
- for (let i = 0; i < this.trailLength; ++i) {
- this.trail.push({
- x: this.position.x,
- y: this.position.y
- });
- }
- },
- drawTrail() {
- //dont' forget to run setupTrail() after mob spawn
- const t = this.trail;
- const len = t.length;
- t.pop();
- t.unshift({
- x: this.position.x,
- y: this.position.y
- });
- //draw
- ctx.strokeStyle = this.trailFill;
- ctx.beginPath();
- // ctx.moveTo(t[0].x, t[0].y);
- // ctx.lineTo(t[0].x, t[0].y);
- // ctx.globalAlpha = 0.2;
- // ctx.lineWidth = this.radius * 3;
- // ctx.stroke();
- ctx.globalAlpha = 0.5 / len;
- ctx.lineWidth = this.radius * 1.95;
- for (let i = 0; i < len; ++i) {
- // ctx.lineWidth *= 0.96;
- ctx.lineTo(t[i].x, t[i].y);
- ctx.stroke();
- }
- ctx.globalAlpha = 1;
- },
- curl(range = 1000, mag = -10) {
- //cause all mobs, and bodies to rotate in a circle
- applyCurl = function (center, array) {
- for (let i = 0; i < array.length; ++i) {
- const sub = Matter.Vector.sub(center, array[i].position)
- const radius2 = Matter.Vector.magnitudeSquared(sub);
-
- //if too close, like center mob or shield, don't curl // if too far don't curl
- if (radius2 < range * range && radius2 > 10000) {
- const curlVector = Matter.Vector.mult(Matter.Vector.perp(Matter.Vector.normalise(sub)), mag)
- //apply curl force
- Matter.Body.setVelocity(array[i], {
- x: array[i].velocity.x * 0.94 + curlVector.x * 0.06,
- y: array[i].velocity.y * 0.94 + curlVector.y * 0.06
- })
- // //draw curl
- // ctx.beginPath();
- // ctx.moveTo(array[i].position.x, array[i].position.y);
- // ctx.lineTo(array[i].position.x + curlVector.x * 10, array[i].position.y + curlVector.y * 10);
- // ctx.lineWidth = 2;
- // ctx.strokeStyle = "#000";
- // ctx.stroke();
- }
- }
- }
- applyCurl(this.position, mob);
- applyCurl(this.position, body);
- applyCurl(this.position, powerUp);
- // applyCurl(this.position, bullet); // too powerful, just stops all bullets need to write a curl function just for bullets
- // applyCurl(this.position, [player]);
-
- //draw limit
- // ctx.beginPath();
- // ctx.arc(this.position.x, this.position.y, range, 0, 2 * Math.PI);
- // ctx.fillStyle = "rgba(55,255,255, 0.1)";
- // ctx.fill();
- },
- pullPlayer() {
- if (this.seePlayer.yes && Matter.Vector.magnitudeSquared(Matter.Vector.sub(this.position, player.position)) < 1000000) {
- const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x);
- player.force.x -= game.accelScale * 1.13 * Math.cos(angle) * (mech.onGround ? 2 * player.mass * game.g : player.mass * game.g);
- player.force.y -= game.accelScale * 0.84 * player.mass * game.g * Math.sin(angle);
-
- ctx.beginPath();
- ctx.moveTo(this.position.x, this.position.y);
- ctx.lineTo(mech.pos.x, mech.pos.y);
- ctx.lineWidth = Math.min(60, this.radius * 2);
- ctx.strokeStyle = "rgba(0,0,0,0.5)";
- ctx.stroke();
- ctx.beginPath();
- ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI);
- ctx.fillStyle = "rgba(0,0,0,0.3)";
- ctx.fill();
- }
- },
- repelBullets() {
- if (this.seePlayer.yes) {
- ctx.lineWidth = "8";
- ctx.strokeStyle = this.fill;
- ctx.beginPath();
- for (let i = 0, len = bullet.length; i < len; ++i) {
- const dx = bullet[i].position.x - this.position.x;
- const dy = bullet[i].position.y - this.position.y;
- const dist = Math.sqrt(dx * dx + dy * dy);
- if (dist < 500) {
- ctx.moveTo(this.position.x, this.position.y);
- ctx.lineTo(bullet[i].position.x, bullet[i].position.y);
- const angle = Math.atan2(dy, dx);
- const mag = (1500 * bullet[i].mass * game.g) / dist;
- bullet[i].force.x += mag * Math.cos(angle);
- bullet[i].force.y += mag * Math.sin(angle);
- }
- }
- ctx.stroke();
- }
- },
- attraction() {
- //accelerate towards the player
- if (this.seePlayer.recall) {
- // && dx * dx + dy * dy < 2000000) {
- const forceMag = this.accelMag * this.mass;
- const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x);
- this.force.x += forceMag * Math.cos(angle);
- this.force.y += forceMag * Math.sin(angle);
- }
- },
- repulsionRange: 500000,
- repulsion() {
- //accelerate towards the player
- if (this.seePlayer.recall && this.distanceToPlayer2() < this.repulsionRange) {
- // && dx * dx + dy * dy < 2000000) {
- const forceMag = this.accelMag * this.mass;
- const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x);
- this.force.x -= 2 * forceMag * Math.cos(angle);
- this.force.y -= 2 * forceMag * Math.sin(angle); // - 0.0007 * this.mass; //antigravity
- }
- },
- hop() {
- //accelerate towards the player after a delay
- if (this.cd < game.cycle && this.seePlayer.recall && this.speed < 1) {
- this.cd = game.cycle + this.delay;
- const forceMag = (this.accelMag + this.accelMag * Math.random()) * this.mass;
- const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x);
- this.force.x += forceMag * Math.cos(angle);
- this.force.y += forceMag * Math.sin(angle) - 0.04 * this.mass; //antigravity
- }
- },
- hoverOverPlayer() {
- if (this.seePlayer.recall) {
- // vertical positioning
- const rangeY = 250;
- if (this.position.y > this.seePlayer.position.y - this.hoverElevation + rangeY) {
- this.force.y -= this.accelMag * this.mass;
- } else if (this.position.y < this.seePlayer.position.y - this.hoverElevation - rangeY) {
- this.force.y += this.accelMag * this.mass;
- }
- // horizontal positioning
- const rangeX = 150;
- if (this.position.x > this.seePlayer.position.x + this.hoverXOff + rangeX) {
- this.force.x -= this.accelMag * this.mass;
- } else if (this.position.x < this.seePlayer.position.x + this.hoverXOff - rangeX) {
- this.force.x += this.accelMag * this.mass;
- }
- }
- // else {
- // this.gravity();
- // }
- },
- grow() {
- if (!mech.isBodiesAsleep) {
- if (this.seePlayer.recall) {
- if (this.radius < 80) {
- const scale = 1.01;
- Matter.Body.scale(this, scale, scale);
- this.radius *= scale;
- // this.torque = -0.00002 * this.inertia;
- this.fill = `hsl(144, ${this.radius}%, 50%)`;
- }
- } else {
- if (this.radius > 15) {
- const scale = 0.99;
- Matter.Body.scale(this, scale, scale);
- this.radius *= scale;
- this.fill = `hsl(144, ${this.radius}%, 50%)`;
- }
- }
- }
- },
- search() {
- //be sure to declare searchTarget in mob spawn
- //accelerate towards the searchTarget
- if (!this.seePlayer.recall) {
- const newTarget = function (that) {
- if (Math.random() < 0.025) {
- that.searchTarget = player.position; //chance to target player
- } else {
- //target random body
- that.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position;
- }
- };
-
- const sub = Matter.Vector.sub(this.searchTarget, this.position);
- if (Matter.Vector.magnitude(sub) > this.radius * 2) {
- // ctx.beginPath();
- // ctx.strokeStyle = "#aaa";
- // ctx.moveTo(this.position.x, this.position.y);
- // ctx.lineTo(this.searchTarget.x,this.searchTarget.y);
- // ctx.stroke();
- //accelerate at 0.1 of normal acceleration
- this.force = Matter.Vector.mult(Matter.Vector.normalise(sub), this.accelMag * this.mass * 0.2);
- } else {
- //after reaching random target switch to new target
- newTarget(this);
- }
- //switch to a new target after a while
- if (!(game.cycle % (this.seePlayerFreq * 15))) {
- newTarget(this);
- }
- }
- },
- strike() {
- //teleport to player when close enough on CD
- if (this.seePlayer.recall && this.cd < game.cycle) {
- const dist = Matter.Vector.sub(this.seePlayer.position, this.position);
- const distMag = Matter.Vector.magnitude(dist);
- if (distMag < 400) {
- this.cd = game.cycle + this.delay;
- ctx.beginPath();
- ctx.moveTo(this.position.x, this.position.y);
- Matter.Body.translate(this, Matter.Vector.mult(Matter.Vector.normalise(dist), distMag - 20 - radius));
- ctx.lineTo(this.position.x, this.position.y);
- ctx.lineWidth = radius * 2;
- ctx.strokeStyle = this.fill; //"rgba(0,0,0,0.5)"; //'#000'
- ctx.stroke();
- }
- }
- },
- blink() {
- //teleport towards player as a way to move
- if (this.seePlayer.recall && !(game.cycle % this.blinkRate)) {
- ctx.beginPath();
- ctx.moveTo(this.position.x, this.position.y);
- const dist = Matter.Vector.sub(this.seePlayer.position, this.position);
- const distMag = Matter.Vector.magnitude(dist);
- const unitVector = Matter.Vector.normalise(dist);
- const rando = (Math.random() - 0.5) * 50;
- if (distMag < this.blinkLength) {
- Matter.Body.translate(this, Matter.Vector.mult(unitVector, distMag + rando));
- } else {
- Matter.Body.translate(this, Matter.Vector.mult(unitVector, this.blinkLength + rando));
- }
- ctx.lineTo(this.position.x, this.position.y);
- ctx.lineWidth = radius * 2;
- ctx.strokeStyle = this.stroke; //"rgba(0,0,0,0.5)"; //'#000'
- ctx.stroke();
- }
- },
- drift() {
- //teleport towards player as a way to move
- if (this.seePlayer.recall && !(game.cycle % this.blinkRate)) {
- // && !mech.lookingAtMob(this,0.5)){
- ctx.beginPath();
- ctx.moveTo(this.position.x, this.position.y);
- const dist = Matter.Vector.sub(this.seePlayer.position, this.position);
- const distMag = Matter.Vector.magnitude(dist);
- const vector = Matter.Vector.mult(Matter.Vector.normalise(dist), this.blinkLength);
- if (distMag < this.blinkLength) {
- Matter.Body.setPosition(this, this.seePlayer.position);
- Matter.Body.translate(this, {
- x: (Math.random() - 0.5) * 50,
- y: (Math.random() - 0.5) * 50
- });
- } else {
- vector.x += (Math.random() - 0.5) * 200;
- vector.y += (Math.random() - 0.5) * 200;
- Matter.Body.translate(this, vector);
- }
- ctx.lineTo(this.position.x, this.position.y);
- ctx.lineWidth = radius * 2;
- ctx.strokeStyle = this.stroke;
- ctx.stroke();
- }
- },
- bomb() {
- //throw a mob/bullet at player
- if (
- !(game.cycle % this.fireFreq) &&
- Math.abs(this.position.x - this.seePlayer.position.x) < 400 && //above player
- Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && //see player
- Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0
- ) {
- spawn.bullet(this.position.x, this.position.y + this.radius * 0.5, 10 + Math.ceil(this.radius / 15), 5);
- //add spin and speed
- Matter.Body.setAngularVelocity(mob[mob.length - 1], (Math.random() - 0.5) * 0.5);
- Matter.Body.setVelocity(mob[mob.length - 1], {
- x: this.velocity.x,
- y: this.velocity.y
- });
- //spin for mob as well
- Matter.Body.setAngularVelocity(this, (Math.random() - 0.5) * 0.25);
- }
- },
- fire() {
- if (!mech.isBodiesAsleep) {
- const setNoseShape = () => {
- const mag = this.radius + this.radius * this.noseLength;
- this.vertices[1].x = this.position.x + Math.cos(this.angle) * mag;
- this.vertices[1].y = this.position.y + Math.sin(this.angle) * mag;
- };
- //throw a mob/bullet at player
- if (this.seePlayer.recall) {
- //set direction to turn to fire
- if (!(game.cycle % this.seePlayerFreq)) {
- this.fireDir = Matter.Vector.normalise(Matter.Vector.sub(this.seePlayer.position, this.position));
- this.fireDir.y -= Math.abs(this.seePlayer.position.x - this.position.x) / 1600; //gives the bullet an arc
- this.fireAngle = Math.atan2(this.fireDir.y, this.fireDir.x);
- }
- //rotate towards fireAngle
- const angle = this.angle + Math.PI / 2;
- c = Math.cos(angle) * this.fireDir.x + Math.sin(angle) * this.fireDir.y;
- const threshold = 0.1;
- if (c > threshold) {
- this.torque += 0.000004 * this.inertia;
- } else if (c < -threshold) {
- this.torque -= 0.000004 * this.inertia;
- } else if (this.noseLength > 1.5) {
- //fire
- spawn.bullet(this.vertices[1].x, this.vertices[1].y, 5 + Math.ceil(this.radius / 15), 5);
- const v = 15;
- Matter.Body.setVelocity(mob[mob.length - 1], {
- x: this.velocity.x + this.fireDir.x * v + Math.random(),
- y: this.velocity.y + this.fireDir.y * v + Math.random()
- });
- this.noseLength = 0;
- // recoil
- this.force.x -= 0.005 * this.fireDir.x * this.mass;
- this.force.y -= 0.005 * this.fireDir.y * this.mass;
- }
- if (this.noseLength < 1.5) this.noseLength += this.fireFreq;
- setNoseShape();
- } else if (this.noseLength > 0.1) {
- this.noseLength -= this.fireFreq / 2;
- setNoseShape();
- }
- // else if (this.noseLength < -0.1) {
- // this.noseLength += this.fireFreq / 4;
- // setNoseShape();
- // }
- }
- },
- turnToFacePlayer() {
- //turn to face player
- const dx = player.position.x - this.position.x;
- const dy = -player.position.y + this.position.y;
- const dist = this.distanceToPlayer();
- const angle = this.angle + Math.PI / 2;
- c = Math.cos(angle) * dx - Math.sin(angle) * dy;
- // if (c > 0.04) {
- // Matter.Body.rotate(this, 0.01);
- // } else if (c < 0.04) {
- // Matter.Body.rotate(this, -0.01);
- // }
- if (c > 0.04 * dist) {
- this.torque += 0.002 * this.mass;
- } else if (c < 0.04) {
- this.torque -= 0.002 * this.mass;
- }
- },
- facePlayer() {
- const unitVector = Matter.Vector.normalise(Matter.Vector.sub(this.seePlayer.position, this.position));
- const angle = Math.atan2(unitVector.y, unitVector.x);
- Matter.Body.setAngle(this, angle - Math.PI);
- },
- explode() {
- mech.damage(Math.min(Math.max(0.02 * Math.sqrt(this.mass), 0.01), 0.35) * game.dmgScale);
- this.dropPowerUp = false;
- this.death(); //death with no power up or body
- },
- timeLimit() {
- if (!mech.isBodiesAsleep) {
- this.timeLeft--;
- if (this.timeLeft < 0) {
- this.dropPowerUp = false;
- this.death(); //death with no power up
- }
- }
- },
- healthBar() {
- //draw health bar
- if (this.seePlayer.recall) {
- // && this.health < 1
- const h = this.radius * 0.3;
- const w = this.radius * 2;
- const x = this.position.x - w / 2;
- const y = this.position.y - w * 0.7;
- ctx.fillStyle = "rgba(100, 100, 100, 0.3)";
- ctx.fillRect(x, y, w, h);
- ctx.fillStyle = "rgba(255,0,0,0.7)";
- ctx.fillRect(x, y, w * this.health, h);
- }
- },
- damage(dmg) {
- dmg /= Math.sqrt(this.mass)
- if (b.isModLowHealthDmg) dmg *= (3 / (2 + mech.health)) //up to 50% dmg at zero player health
- if (b.isModFarAwayDmg) dmg *= 1 + Math.sqrt(Math.max(1000, Math.min(3500, this.distanceToPlayer())) - 1000) * 0.01 //up to 50% dmg at max range of 3500
- if (dmg !== Infinity) {
- if (b.modEnergySiphon) mech.fieldMeter += Math.min(this.health, dmg) * b.modEnergySiphon
- if (b.modHealthDrain) mech.addHealth(Math.min(this.health, dmg) * b.modHealthDrain)
- }
- this.health -= dmg
- //this.fill = this.color + this.health + ')';
- if (this.health < 0.05) this.death();
- this.onDamage(this); //custom damage effects
- },
- onDamage() {
- // a placeholder for custom effects on mob damage
- //to use declare custom method in mob spawn
- },
- onDeath() {
- // a placeholder for custom effects on mob death
- // to use declare custom method in mob spawn
- },
- leaveBody: true,
- dropPowerUp: true,
- death() {
- this.onDeath(this); //custom death effects
- this.removeConsBB();
- this.alive = false;
- if (this.dropPowerUp) {
- powerUps.spawnRandomPowerUp(this.position.x, this.position.y, this.mass, radius);
- if (Math.random() < b.modSpores) {
- for (let i = 0, len = Math.floor(3 + this.mass * Math.random()); i < len; i++) {
- b.spore(this) //spawn drone
- }
- }
- }
-
- },
- removeConsBB() {
- for (let i = 0, len = consBB.length; i < len; ++i) {
- if (consBB[i].bodyA === this) {
- if (consBB[i].bodyB.shield) {
- consBB[i].bodyB.do = function () {
- this.death();
- };
- }
- consBB[i].bodyA = consBB[i].bodyB;
- consBB.splice(i, 1);
- this.removeConsBB();
- break;
- } else if (consBB[i].bodyB === this) {
- if (consBB[i].bodyA.shield) {
- consBB[i].bodyA.do = function () {
- this.death();
- };
- }
- consBB[i].bodyB = consBB[i].bodyA;
- consBB.splice(i, 1);
- this.removeConsBB();
- break;
- }
- }
- },
- removeCons() {
- for (let i = 0, len = cons.length; i < len; ++i) {
- if (cons[i].bodyA === this) {
- cons[i].bodyA = cons[i].bodyB;
- cons.splice(i, 1);
- this.removeCons();
- break;
- } else if (cons[i].bodyB === this) {
- cons[i].bodyB = cons[i].bodyA;
- cons.splice(i, 1);
- this.removeCons();
- break;
- }
- }
- },
- //replace dead mob with a regular body
- replace(i) {
- if (this.leaveBody) {
- const len = body.length;
- body[len] = Matter.Bodies.fromVertices(this.position.x, this.position.y, this.vertices);
- Matter.Body.setVelocity(body[len], this.velocity);
- Matter.Body.setAngularVelocity(body[len], this.angularVelocity);
- body[len].collisionFilter.category = 0x010000;
- body[len].collisionFilter.mask = 0x011111;
- // body[len].collisionFilter.category = body[len].collisionFilter.category //0x000001;
- // body[len].collisionFilter.mask = body[len].collisionFilter.mask //0x011111;
-
- //large mobs or too many bodies go intangible and fall until removed from game to help performance
- if (body[len].mass > 10 || 40 + 30 * Math.random() < body.length) {
- body[len].collisionFilter.mask = 0x001100;
- }
- body[len].classType = "body";
- World.add(engine.world, body[len]); //add to world
- }
- Matter.World.remove(engine.world, this);
- mob.splice(i, 1);
- }
- });
- mob[i].alertRange2 = Math.pow(mob[i].radius * 3.5 + 550, 2);
- World.add(engine.world, mob[i]); //add to world
- }
+//create array of mobs
+let mob = [];
+//method to populate the array above
+const mobs = {
+ loop() {
+ let i = mob.length;
+ while (i--) {
+ if (mob[i].alive) {
+ mob[i].do();
+ } else {
+ mob[i].replace(i); //removing mob and replace with body, this is done here to avoid an array index bug with drawing I think
+ }
+ }
+ },
+ draw() {
+ ctx.lineWidth = 2;
+ let i = mob.length;
+ while (i--) {
+ ctx.beginPath();
+ const vertices = mob[i].vertices;
+ ctx.moveTo(vertices[0].x, vertices[0].y);
+ for (let j = 1, len = vertices.length; j < len; ++j) {
+ ctx.lineTo(vertices[j].x, vertices[j].y);
+ }
+ ctx.lineTo(vertices[0].x, vertices[0].y);
+ ctx.fillStyle = mob[i].fill;
+ ctx.strokeStyle = mob[i].stroke;
+ ctx.fill();
+ ctx.stroke();
+ }
+ },
+ // alert(range) {
+ // range = range * range;
+ // for (let i = 0; i < mob.length; i++) {
+ // if (mob[i].distanceToPlayer2() < range) mob[i].locatePlayer();
+ // }
+ // },
+ // startle(amount) {
+ // for (let i = 0; i < mob.length; i++) {
+ // if (!mob[i].seePlayer.yes) {
+ // mob[i].force.x += amount * mob[i].mass * (Math.random() - 0.5);
+ // mob[i].force.y += amount * mob[i].mass * (Math.random() - 0.5);
+ // }
+ // }
+ // },
+ //**********************************************************************************************
+ //**********************************************************************************************
+ spawn(xPos, yPos, sides, radius, color) {
+ let i = mob.length;
+ mob[i] = Matter.Bodies.polygon(xPos, yPos, sides, radius, {
+ //inertia: Infinity, //prevents rotation
+ mob: true,
+ density: 0.001,
+ //friction: 0,
+ frictionAir: 0.005,
+ //frictionStatic: 0,
+ restitution: 0.5,
+ collisionFilter: {
+ group: 0,
+ category: 0x000010,
+ mask: 0x011111
+ },
+ onHit: undefined,
+ alive: true,
+ index: i,
+ health: 1,
+ accelMag: 0.001,
+ cd: 0, //game cycle when cooldown will be over
+ delay: 60, //static: time between cooldowns
+ fill: color,
+ stroke: "#000",
+ seePlayer: {
+ yes: false,
+ recall: 0,
+ position: {
+ x: xPos,
+ y: yPos
+ }
+ },
+ radius: radius,
+ spawnPos: {
+ x: xPos,
+ y: yPos
+ },
+ seeAtDistance2: 4000000, //sqrt(4000000) = 2000 = max seeing range
+ distanceToPlayer() {
+ const dx = this.position.x - player.position.x;
+ const dy = this.position.y - player.position.y;
+ return Math.sqrt(dx * dx + dy * dy);
+ },
+ distanceToPlayer2() {
+ const dx = this.position.x - player.position.x;
+ const dy = this.position.y - player.position.y;
+ return dx * dx + dy * dy;
+ },
+ gravity() {
+ this.force.y += this.mass * this.g;
+ },
+ seePlayerFreq: Math.round((30 + 30 * Math.random()) * game.lookFreqScale), //how often NPC checks to see where player is, lower numbers have better vision
+ foundPlayer() {
+ this.locatePlayer();
+ if (!this.seePlayer.yes) {
+ this.alertNearByMobs();
+ this.seePlayer.yes = true;
+ }
+ },
+ lostPlayer() {
+ this.seePlayer.yes = false;
+ this.seePlayer.recall -= this.seePlayerFreq;
+ if (this.seePlayer.recall < 0) this.seePlayer.recall = 0;
+ },
+ memory: 120, //default time to remember player's location
+ locatePlayer() {
+ if (!mech.isStealth) {
+ // updates mob's memory of player location
+ this.seePlayer.recall = this.memory + Math.round(this.memory * Math.random()); //seconds before mob falls a sleep
+ this.seePlayer.position.x = player.position.x;
+ this.seePlayer.position.y = player.position.y;
+ }
+ },
+ // locatePlayerByDist() {
+ // if (this.distanceToPlayer2() < this.locateRange) {
+ // this.locatePlayer();
+ // }
+ // },
+ seePlayerCheck() {
+ if (!(game.cycle % this.seePlayerFreq)) {
+ if (
+ this.distanceToPlayer2() < this.seeAtDistance2 &&
+ Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 &&
+ Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0
+ ) {
+ this.foundPlayer();
+ } else if (this.seePlayer.recall) {
+ this.lostPlayer();
+ }
+ }
+ },
+ seePlayerCheckByDistance() {
+ if (!(game.cycle % this.seePlayerFreq)) {
+ if (this.distanceToPlayer2() < this.seeAtDistance2) {
+ this.foundPlayer();
+ } else if (this.seePlayer.recall) {
+ this.lostPlayer();
+ }
+ }
+ },
+ seePlayerByDistOrLOS() {
+ if (!(game.cycle % this.seePlayerFreq)) {
+ if (
+ this.distanceToPlayer2() < this.seeAtDistance2 ||
+ (Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0)
+ ) {
+ this.foundPlayer();
+ } else if (this.seePlayer.recall) {
+ this.lostPlayer();
+ }
+ }
+ },
+ seePlayerByDistAndLOS() {
+ if (!(game.cycle % this.seePlayerFreq)) {
+ if (
+ this.distanceToPlayer2() < this.seeAtDistance2 &&
+ (Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0)
+ ) {
+ this.foundPlayer();
+ } else if (this.seePlayer.recall) {
+ this.lostPlayer();
+ }
+ }
+ },
+ isLookingAtPlayer(threshold) {
+ const diff = Matter.Vector.normalise(Matter.Vector.sub(player.position, this.position));
+ //make a vector for the mob's direction of length 1
+ const dir = {
+ x: Math.cos(this.angle),
+ y: Math.sin(this.angle)
+ };
+ //the dot product of diff and dir will return how much over lap between the vectors
+ const dot = Matter.Vector.dot(dir, diff);
+ // console.log(Math.cos(dot)*180/Math.PI)
+ if (dot > threshold) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+ lookRange: 0.2 + Math.random() * 0.2,
+ lookTorque: 0.0000004 * (Math.random() > 0.5 ? -1 : 1),
+ seePlayerByLookingAt() {
+ if (!(game.cycle % this.seePlayerFreq) && (this.seePlayer.recall || this.isLookingAtPlayer(this.lookRange))) {
+ if (
+ this.distanceToPlayer2() < this.seeAtDistance2 &&
+ Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 &&
+ Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0
+ ) {
+ this.foundPlayer();
+ } else if (this.seePlayer.recall) {
+ this.lostPlayer();
+ }
+ }
+ //if you don't recall player location rotate and draw to show where you are looking
+ if (!this.seePlayer.recall) {
+ this.torque = this.lookTorque * this.inertia;
+ //draw
+ const range = Math.PI * this.lookRange;
+ ctx.beginPath();
+ ctx.arc(this.position.x, this.position.y, this.radius * 2.5, this.angle - range, this.angle + range);
+ ctx.arc(this.position.x, this.position.y, this.radius * 1.4, this.angle + range, this.angle - range, true);
+ ctx.fillStyle = "rgba(0,0,0,0.07)";
+ ctx.fill();
+ }
+ },
+ mechPosRange() {
+ return {
+ x: player.position.x, // + (Math.random() - 0.5) * 50,
+ y: player.position.y + (Math.random() - 0.5) * 110
+ };
+ //mob vision for testing
+ // ctx.beginPath();
+ // ctx.lineWidth = "5";
+ // ctx.strokeStyle = "#ff0";
+ // ctx.moveTo(this.position.x, this.position.y);
+ // ctx.lineTo(targetPos.x, targetPos.y);
+ // ctx.stroke();
+ // return targetPos;
+ },
+ laserBeam() {
+ if (game.cycle % 7 && this.seePlayer.yes) {
+ ctx.setLineDash([125 * Math.random(), 125 * Math.random()]);
+ // ctx.lineDashOffset = 6*(game.cycle % 215);
+ if (this.distanceToPlayer() < this.laserRange) {
+ //if (Math.random()>0.2 && this.seePlayer.yes && this.distanceToPlayer2()<800000) {
+ if (b.isModTempResist) {
+ mech.damage(0.00006 * game.dmgScale);
+ } else {
+ mech.damage(0.0003 * game.dmgScale);
+ }
+ if (mech.fieldMeter > 0.1) mech.fieldMeter -= 0.004
+ ctx.beginPath();
+ ctx.moveTo(this.position.x, this.position.y);
+ ctx.lineTo(mech.pos.x, mech.pos.y);
+ ctx.lineTo(mech.pos.x + (Math.random() - 0.5) * 3000, mech.pos.y + (Math.random() - 0.5) * 3000);
+ ctx.lineWidth = 2;
+ ctx.strokeStyle = "rgb(255,0,170)";
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI);
+ ctx.fillStyle = "rgba(255,0,170,0.15)";
+ ctx.fill();
+
+ }
+ ctx.beginPath();
+ ctx.arc(this.position.x, this.position.y, this.laserRange * 0.9, 0, 2 * Math.PI);
+ ctx.strokeStyle = "rgba(255,0,170,0.5)";
+ ctx.lineWidth = 1;
+ ctx.stroke();
+ ctx.setLineDash([]);
+ ctx.fillStyle = "rgba(255,0,170,0.03)";
+ ctx.fill();
+ }
+ },
+ laser() {
+ const vertexCollision = function (v1, v1End, domain) {
+ for (let i = 0; i < domain.length; ++i) {
+ let vertices = domain[i].vertices;
+ const len = vertices.length - 1;
+ for (let j = 0; j < len; j++) {
+ results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]);
+ if (results.onLine1 && results.onLine2) {
+ const dx = v1.x - results.x;
+ const dy = v1.y - results.y;
+ const dist2 = dx * dx + dy * dy;
+ if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) {
+ best = {
+ x: results.x,
+ y: results.y,
+ dist2: dist2,
+ who: domain[i],
+ v1: vertices[j],
+ v2: vertices[j + 1]
+ };
+ }
+ }
+ }
+ results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]);
+ if (results.onLine1 && results.onLine2) {
+ const dx = v1.x - results.x;
+ const dy = v1.y - results.y;
+ const dist2 = dx * dx + dy * dy;
+ if (dist2 < best.dist2) {
+ best = {
+ x: results.x,
+ y: results.y,
+ dist2: dist2,
+ who: domain[i],
+ v1: vertices[0],
+ v2: vertices[len]
+ };
+ }
+ }
+ }
+ };
+ if (this.seePlayer.recall) {
+ this.torque = this.lookTorque * this.inertia * 2;
+
+ const seeRange = 2500;
+ best = {
+ x: null,
+ y: null,
+ dist2: Infinity,
+ who: null,
+ v1: null,
+ v2: null
+ };
+ const look = {
+ x: this.position.x + seeRange * Math.cos(this.angle),
+ y: this.position.y + seeRange * Math.sin(this.angle)
+ };
+ vertexCollision(this.position, look, map);
+ vertexCollision(this.position, look, body);
+ vertexCollision(this.position, look, [player]);
+ // hitting player
+ if (best.who === player) {
+ if (b.isModTempResist) {
+ dmg = 0.0008 * game.dmgScale;
+ } else {
+ dmg = 0.004 * game.dmgScale;
+ }
+
+ mech.damage(dmg);
+ //draw damage
+ ctx.fillStyle = color;
+ ctx.beginPath();
+ ctx.arc(best.x, best.y, dmg * 2000, 0, 2 * Math.PI);
+ ctx.fill();
+ }
+ //draw beam
+ if (best.dist2 === Infinity) {
+ best = look;
+ }
+ ctx.beginPath();
+ ctx.moveTo(this.position.x, this.position.y);
+ ctx.lineTo(best.x, best.y);
+ ctx.strokeStyle = "#f00"; // Purple path
+ ctx.lineWidth = 1;
+ ctx.setLineDash([50 + 120 * Math.random(), 50 * Math.random()]);
+ ctx.stroke(); // Draw it
+ ctx.setLineDash([0, 0]);
+ }
+ },
+ searchSpring() {
+ ctx.beginPath();
+ ctx.arc(this.cons.pointA.x, this.cons.pointA.y, 6, 0, 2 * Math.PI);
+ ctx.arc(this.cons2.pointA.x, this.cons2.pointA.y, 6, 0, 2 * Math.PI);
+ // ctx.arc(this.cons.bodyB.position.x, this.cons.bodyB.position.y,6,0,2*Math.PI);
+ ctx.fillStyle = "#222";
+ ctx.fill();
+
+ if (!(game.cycle % this.seePlayerFreq)) {
+ if (
+ (this.seePlayer.recall || this.isLookingAtPlayer(this.lookRange)) &&
+ this.distanceToPlayer2() < this.seeAtDistance2 &&
+ Matter.Query.ray(map, this.position, player.position).length === 0 &&
+ Matter.Query.ray(body, this.position, player.position).length === 0
+ ) {
+ this.foundPlayer();
+ if (!(game.cycle % (this.seePlayerFreq * 2))) {
+ this.springTarget.x = this.seePlayer.position.x;
+ this.springTarget.y = this.seePlayer.position.y;
+ this.cons.length = -200;
+ this.cons2.length = 100 + 1.5 * this.radius;
+ } else {
+ this.springTarget2.x = this.seePlayer.position.x;
+ this.springTarget2.y = this.seePlayer.position.y;
+ this.cons.length = 100 + 1.5 * this.radius;
+ this.cons2.length = -200;
+ }
+ } else if (this.seePlayer.recall) {
+ this.lostPlayer();
+ }
+ }
+ //if you don't recall player location rotate and draw to show where you are looking
+ if (!this.seePlayer.recall) {
+ this.torque = this.lookTorque * this.inertia;
+ //draw
+ const range = Math.PI * this.lookRange;
+ ctx.beginPath();
+ ctx.arc(this.position.x, this.position.y, this.radius * 2.5, this.angle - range, this.angle + range);
+ ctx.arc(this.position.x, this.position.y, this.radius * 1.4, this.angle + range, this.angle - range, true);
+ ctx.fillStyle = "rgba(0,0,0,0.07)";
+ ctx.fill();
+ //spring to random place on map
+ const vertexCollision = function (v1, v1End, domain) {
+ for (let i = 0; i < domain.length; ++i) {
+ let vertices = domain[i].vertices;
+ const len = vertices.length - 1;
+ for (let j = 0; j < len; j++) {
+ results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]);
+ if (results.onLine1 && results.onLine2) {
+ const dx = v1.x - results.x;
+ const dy = v1.y - results.y;
+ const dist2 = dx * dx + dy * dy;
+ if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) {
+ best = {
+ x: results.x,
+ y: results.y,
+ dist2: dist2,
+ who: domain[i],
+ v1: vertices[j],
+ v2: vertices[j + 1]
+ };
+ }
+ }
+ }
+ results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]);
+ if (results.onLine1 && results.onLine2) {
+ const dx = v1.x - results.x;
+ const dy = v1.y - results.y;
+ const dist2 = dx * dx + dy * dy;
+ if (dist2 < best.dist2) {
+ best = {
+ x: results.x,
+ y: results.y,
+ dist2: dist2,
+ who: domain[i],
+ v1: vertices[0],
+ v2: vertices[len]
+ };
+ }
+ }
+ }
+ };
+ const seeRange = 3000;
+ if (!(game.cycle % (this.seePlayerFreq * 10))) {
+ best = {
+ x: null,
+ y: null,
+ dist2: Infinity,
+ who: null,
+ v1: null,
+ v2: null
+ };
+ const look = {
+ x: this.position.x + seeRange * Math.cos(this.angle),
+ y: this.position.y + seeRange * Math.sin(this.angle)
+ };
+ vertexCollision(this.position, look, map);
+ if (best.dist2 != Infinity) {
+ this.springTarget.x = best.x;
+ this.springTarget.y = best.y;
+ this.cons.length = 100 + 1.5 * this.radius;
+ this.cons2.length = 100 + 1.5 * this.radius;
+ }
+ }
+ if (!((game.cycle + this.seePlayerFreq * 5) % (this.seePlayerFreq * 10))) {
+ best = {
+ x: null,
+ y: null,
+ dist2: Infinity,
+ who: null,
+ v1: null,
+ v2: null
+ };
+ const look = {
+ x: this.position.x + seeRange * Math.cos(this.angle),
+ y: this.position.y + seeRange * Math.sin(this.angle)
+ };
+ vertexCollision(this.position, look, map);
+ if (best.dist2 != Infinity) {
+ this.springTarget2.x = best.x;
+ this.springTarget2.y = best.y;
+ this.cons.length = 100 + 1.5 * this.radius;
+ this.cons2.length = 100 + 1.5 * this.radius;
+ }
+ }
+ }
+ },
+ alertNearByMobs() {
+ //this.alertRange2 is set at the very bottom of this mobs, after mob is made
+ for (let i = 0; i < mob.length; i++) {
+ if (!mob[i].seePlayer.recall && Matter.Vector.magnitudeSquared(Matter.Vector.sub(this.position, mob[i].position)) < this.alertRange2) {
+ mob[i].locatePlayer();
+ }
+ }
+ //add alert to draw queue
+ // game.drawList.push({
+ // x: this.position.x,
+ // y: this.position.y,
+ // radius: Math.sqrt(this.alertRange2),
+ // color: "rgba(0,0,0,0.02)",
+ // time: game.drawTime
+ // });
+ },
+ zoom() {
+ this.zoomMode--;
+ if (this.zoomMode > 150) {
+ this.drawTrail();
+ if (this.seePlayer.recall) {
+ //attraction to player
+ const forceMag = this.accelMag * this.mass;
+ const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x);
+ this.force.x += forceMag * Math.cos(angle);
+ this.force.y += forceMag * Math.sin(angle);
+ }
+ } else if (this.zoomMode < 0) {
+ this.zoomMode = 300;
+ this.setupTrail();
+ }
+ },
+ setupTrail() {
+ this.trail = [];
+ for (let i = 0; i < this.trailLength; ++i) {
+ this.trail.push({
+ x: this.position.x,
+ y: this.position.y
+ });
+ }
+ },
+ drawTrail() {
+ //dont' forget to run setupTrail() after mob spawn
+ const t = this.trail;
+ const len = t.length;
+ t.pop();
+ t.unshift({
+ x: this.position.x,
+ y: this.position.y
+ });
+ //draw
+ ctx.strokeStyle = this.trailFill;
+ ctx.beginPath();
+ // ctx.moveTo(t[0].x, t[0].y);
+ // ctx.lineTo(t[0].x, t[0].y);
+ // ctx.globalAlpha = 0.2;
+ // ctx.lineWidth = this.radius * 3;
+ // ctx.stroke();
+ ctx.globalAlpha = 0.5 / len;
+ ctx.lineWidth = this.radius * 1.95;
+ for (let i = 0; i < len; ++i) {
+ // ctx.lineWidth *= 0.96;
+ ctx.lineTo(t[i].x, t[i].y);
+ ctx.stroke();
+ }
+ ctx.globalAlpha = 1;
+ },
+ curl(range = 1000, mag = -10) {
+ //cause all mobs, and bodies to rotate in a circle
+ applyCurl = function (center, array) {
+ for (let i = 0; i < array.length; ++i) {
+ const sub = Matter.Vector.sub(center, array[i].position)
+ const radius2 = Matter.Vector.magnitudeSquared(sub);
+
+ //if too close, like center mob or shield, don't curl // if too far don't curl
+ if (radius2 < range * range && radius2 > 10000) {
+ const curlVector = Matter.Vector.mult(Matter.Vector.perp(Matter.Vector.normalise(sub)), mag)
+ //apply curl force
+ Matter.Body.setVelocity(array[i], {
+ x: array[i].velocity.x * 0.94 + curlVector.x * 0.06,
+ y: array[i].velocity.y * 0.94 + curlVector.y * 0.06
+ })
+ // //draw curl
+ // ctx.beginPath();
+ // ctx.moveTo(array[i].position.x, array[i].position.y);
+ // ctx.lineTo(array[i].position.x + curlVector.x * 10, array[i].position.y + curlVector.y * 10);
+ // ctx.lineWidth = 2;
+ // ctx.strokeStyle = "#000";
+ // ctx.stroke();
+ }
+ }
+ }
+ applyCurl(this.position, mob);
+ applyCurl(this.position, body);
+ applyCurl(this.position, powerUp);
+ // applyCurl(this.position, bullet); // too powerful, just stops all bullets need to write a curl function just for bullets
+ // applyCurl(this.position, [player]);
+
+ //draw limit
+ // ctx.beginPath();
+ // ctx.arc(this.position.x, this.position.y, range, 0, 2 * Math.PI);
+ // ctx.fillStyle = "rgba(55,255,255, 0.1)";
+ // ctx.fill();
+ },
+ pullPlayer() {
+ if (this.seePlayer.yes && Matter.Vector.magnitudeSquared(Matter.Vector.sub(this.position, player.position)) < 1000000) {
+ const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x);
+ player.force.x -= game.accelScale * 1.13 * Math.cos(angle) * (mech.onGround ? 2 * player.mass * game.g : player.mass * game.g);
+ player.force.y -= game.accelScale * 0.84 * player.mass * game.g * Math.sin(angle);
+
+ ctx.beginPath();
+ ctx.moveTo(this.position.x, this.position.y);
+ ctx.lineTo(mech.pos.x, mech.pos.y);
+ ctx.lineWidth = Math.min(60, this.radius * 2);
+ ctx.strokeStyle = "rgba(0,0,0,0.5)";
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI);
+ ctx.fillStyle = "rgba(0,0,0,0.3)";
+ ctx.fill();
+ }
+ },
+ repelBullets() {
+ if (this.seePlayer.yes) {
+ ctx.lineWidth = "8";
+ ctx.strokeStyle = this.fill;
+ ctx.beginPath();
+ for (let i = 0, len = bullet.length; i < len; ++i) {
+ const dx = bullet[i].position.x - this.position.x;
+ const dy = bullet[i].position.y - this.position.y;
+ const dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist < 500) {
+ ctx.moveTo(this.position.x, this.position.y);
+ ctx.lineTo(bullet[i].position.x, bullet[i].position.y);
+ const angle = Math.atan2(dy, dx);
+ const mag = (1500 * bullet[i].mass * game.g) / dist;
+ bullet[i].force.x += mag * Math.cos(angle);
+ bullet[i].force.y += mag * Math.sin(angle);
+ }
+ }
+ ctx.stroke();
+ }
+ },
+ attraction() {
+ //accelerate towards the player
+ if (this.seePlayer.recall) {
+ // && dx * dx + dy * dy < 2000000) {
+ const forceMag = this.accelMag * this.mass;
+ const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x);
+ this.force.x += forceMag * Math.cos(angle);
+ this.force.y += forceMag * Math.sin(angle);
+ }
+ },
+ repulsionRange: 500000,
+ repulsion() {
+ //accelerate towards the player
+ if (this.seePlayer.recall && this.distanceToPlayer2() < this.repulsionRange) {
+ // && dx * dx + dy * dy < 2000000) {
+ const forceMag = this.accelMag * this.mass;
+ const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x);
+ this.force.x -= 2 * forceMag * Math.cos(angle);
+ this.force.y -= 2 * forceMag * Math.sin(angle); // - 0.0007 * this.mass; //antigravity
+ }
+ },
+ hop() {
+ //accelerate towards the player after a delay
+ if (this.cd < game.cycle && this.seePlayer.recall && this.speed < 1) {
+ this.cd = game.cycle + this.delay;
+ const forceMag = (this.accelMag + this.accelMag * Math.random()) * this.mass;
+ const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x);
+ this.force.x += forceMag * Math.cos(angle);
+ this.force.y += forceMag * Math.sin(angle) - 0.04 * this.mass; //antigravity
+ }
+ },
+ hoverOverPlayer() {
+ if (this.seePlayer.recall) {
+ // vertical positioning
+ const rangeY = 250;
+ if (this.position.y > this.seePlayer.position.y - this.hoverElevation + rangeY) {
+ this.force.y -= this.accelMag * this.mass;
+ } else if (this.position.y < this.seePlayer.position.y - this.hoverElevation - rangeY) {
+ this.force.y += this.accelMag * this.mass;
+ }
+ // horizontal positioning
+ const rangeX = 150;
+ if (this.position.x > this.seePlayer.position.x + this.hoverXOff + rangeX) {
+ this.force.x -= this.accelMag * this.mass;
+ } else if (this.position.x < this.seePlayer.position.x + this.hoverXOff - rangeX) {
+ this.force.x += this.accelMag * this.mass;
+ }
+ }
+ // else {
+ // this.gravity();
+ // }
+ },
+ grow() {
+ if (!mech.isBodiesAsleep) {
+ if (this.seePlayer.recall) {
+ if (this.radius < 80) {
+ const scale = 1.01;
+ Matter.Body.scale(this, scale, scale);
+ this.radius *= scale;
+ // this.torque = -0.00002 * this.inertia;
+ this.fill = `hsl(144, ${this.radius}%, 50%)`;
+ }
+ } else {
+ if (this.radius > 15) {
+ const scale = 0.99;
+ Matter.Body.scale(this, scale, scale);
+ this.radius *= scale;
+ this.fill = `hsl(144, ${this.radius}%, 50%)`;
+ }
+ }
+ }
+ },
+ search() {
+ //be sure to declare searchTarget in mob spawn
+ //accelerate towards the searchTarget
+ if (!this.seePlayer.recall) {
+ const newTarget = function (that) {
+ if (Math.random() < 0.025) {
+ that.searchTarget = player.position; //chance to target player
+ } else {
+ //target random body
+ that.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position;
+ }
+ };
+
+ const sub = Matter.Vector.sub(this.searchTarget, this.position);
+ if (Matter.Vector.magnitude(sub) > this.radius * 2) {
+ // ctx.beginPath();
+ // ctx.strokeStyle = "#aaa";
+ // ctx.moveTo(this.position.x, this.position.y);
+ // ctx.lineTo(this.searchTarget.x,this.searchTarget.y);
+ // ctx.stroke();
+ //accelerate at 0.1 of normal acceleration
+ this.force = Matter.Vector.mult(Matter.Vector.normalise(sub), this.accelMag * this.mass * 0.2);
+ } else {
+ //after reaching random target switch to new target
+ newTarget(this);
+ }
+ //switch to a new target after a while
+ if (!(game.cycle % (this.seePlayerFreq * 15))) {
+ newTarget(this);
+ }
+ }
+ },
+ strike() {
+ //teleport to player when close enough on CD
+ if (this.seePlayer.recall && this.cd < game.cycle) {
+ const dist = Matter.Vector.sub(this.seePlayer.position, this.position);
+ const distMag = Matter.Vector.magnitude(dist);
+ if (distMag < 400) {
+ this.cd = game.cycle + this.delay;
+ ctx.beginPath();
+ ctx.moveTo(this.position.x, this.position.y);
+ Matter.Body.translate(this, Matter.Vector.mult(Matter.Vector.normalise(dist), distMag - 20 - radius));
+ ctx.lineTo(this.position.x, this.position.y);
+ ctx.lineWidth = radius * 2;
+ ctx.strokeStyle = this.fill; //"rgba(0,0,0,0.5)"; //'#000'
+ ctx.stroke();
+ }
+ }
+ },
+ blink() {
+ //teleport towards player as a way to move
+ if (this.seePlayer.recall && !(game.cycle % this.blinkRate)) {
+ ctx.beginPath();
+ ctx.moveTo(this.position.x, this.position.y);
+ const dist = Matter.Vector.sub(this.seePlayer.position, this.position);
+ const distMag = Matter.Vector.magnitude(dist);
+ const unitVector = Matter.Vector.normalise(dist);
+ const rando = (Math.random() - 0.5) * 50;
+ if (distMag < this.blinkLength) {
+ Matter.Body.translate(this, Matter.Vector.mult(unitVector, distMag + rando));
+ } else {
+ Matter.Body.translate(this, Matter.Vector.mult(unitVector, this.blinkLength + rando));
+ }
+ ctx.lineTo(this.position.x, this.position.y);
+ ctx.lineWidth = radius * 2;
+ ctx.strokeStyle = this.stroke; //"rgba(0,0,0,0.5)"; //'#000'
+ ctx.stroke();
+ }
+ },
+ drift() {
+ //teleport towards player as a way to move
+ if (this.seePlayer.recall && !(game.cycle % this.blinkRate)) {
+ // && !mech.lookingAtMob(this,0.5)){
+ ctx.beginPath();
+ ctx.moveTo(this.position.x, this.position.y);
+ const dist = Matter.Vector.sub(this.seePlayer.position, this.position);
+ const distMag = Matter.Vector.magnitude(dist);
+ const vector = Matter.Vector.mult(Matter.Vector.normalise(dist), this.blinkLength);
+ if (distMag < this.blinkLength) {
+ Matter.Body.setPosition(this, this.seePlayer.position);
+ Matter.Body.translate(this, {
+ x: (Math.random() - 0.5) * 50,
+ y: (Math.random() - 0.5) * 50
+ });
+ } else {
+ vector.x += (Math.random() - 0.5) * 200;
+ vector.y += (Math.random() - 0.5) * 200;
+ Matter.Body.translate(this, vector);
+ }
+ ctx.lineTo(this.position.x, this.position.y);
+ ctx.lineWidth = radius * 2;
+ ctx.strokeStyle = this.stroke;
+ ctx.stroke();
+ }
+ },
+ bomb() {
+ //throw a mob/bullet at player
+ if (
+ !(game.cycle % this.fireFreq) &&
+ Math.abs(this.position.x - this.seePlayer.position.x) < 400 && //above player
+ Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && //see player
+ Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0
+ ) {
+ spawn.bullet(this.position.x, this.position.y + this.radius * 0.5, 10 + Math.ceil(this.radius / 15), 5);
+ //add spin and speed
+ Matter.Body.setAngularVelocity(mob[mob.length - 1], (Math.random() - 0.5) * 0.5);
+ Matter.Body.setVelocity(mob[mob.length - 1], {
+ x: this.velocity.x,
+ y: this.velocity.y
+ });
+ //spin for mob as well
+ Matter.Body.setAngularVelocity(this, (Math.random() - 0.5) * 0.25);
+ }
+ },
+ fire() {
+ if (!mech.isBodiesAsleep) {
+ const setNoseShape = () => {
+ const mag = this.radius + this.radius * this.noseLength;
+ this.vertices[1].x = this.position.x + Math.cos(this.angle) * mag;
+ this.vertices[1].y = this.position.y + Math.sin(this.angle) * mag;
+ };
+ //throw a mob/bullet at player
+ if (this.seePlayer.recall) {
+ //set direction to turn to fire
+ if (!(game.cycle % this.seePlayerFreq)) {
+ this.fireDir = Matter.Vector.normalise(Matter.Vector.sub(this.seePlayer.position, this.position));
+ this.fireDir.y -= Math.abs(this.seePlayer.position.x - this.position.x) / 1600; //gives the bullet an arc
+ this.fireAngle = Math.atan2(this.fireDir.y, this.fireDir.x);
+ }
+ //rotate towards fireAngle
+ const angle = this.angle + Math.PI / 2;
+ c = Math.cos(angle) * this.fireDir.x + Math.sin(angle) * this.fireDir.y;
+ const threshold = 0.1;
+ if (c > threshold) {
+ this.torque += 0.000004 * this.inertia;
+ } else if (c < -threshold) {
+ this.torque -= 0.000004 * this.inertia;
+ } else if (this.noseLength > 1.5) {
+ //fire
+ spawn.bullet(this.vertices[1].x, this.vertices[1].y, 5 + Math.ceil(this.radius / 15), 5);
+ const v = 15;
+ Matter.Body.setVelocity(mob[mob.length - 1], {
+ x: this.velocity.x + this.fireDir.x * v + Math.random(),
+ y: this.velocity.y + this.fireDir.y * v + Math.random()
+ });
+ this.noseLength = 0;
+ // recoil
+ this.force.x -= 0.005 * this.fireDir.x * this.mass;
+ this.force.y -= 0.005 * this.fireDir.y * this.mass;
+ }
+ if (this.noseLength < 1.5) this.noseLength += this.fireFreq;
+ setNoseShape();
+ } else if (this.noseLength > 0.1) {
+ this.noseLength -= this.fireFreq / 2;
+ setNoseShape();
+ }
+ // else if (this.noseLength < -0.1) {
+ // this.noseLength += this.fireFreq / 4;
+ // setNoseShape();
+ // }
+ }
+ },
+ turnToFacePlayer() {
+ //turn to face player
+ const dx = player.position.x - this.position.x;
+ const dy = -player.position.y + this.position.y;
+ const dist = this.distanceToPlayer();
+ const angle = this.angle + Math.PI / 2;
+ c = Math.cos(angle) * dx - Math.sin(angle) * dy;
+ // if (c > 0.04) {
+ // Matter.Body.rotate(this, 0.01);
+ // } else if (c < 0.04) {
+ // Matter.Body.rotate(this, -0.01);
+ // }
+ if (c > 0.04 * dist) {
+ this.torque += 0.002 * this.mass;
+ } else if (c < 0.04) {
+ this.torque -= 0.002 * this.mass;
+ }
+ },
+ facePlayer() {
+ const unitVector = Matter.Vector.normalise(Matter.Vector.sub(this.seePlayer.position, this.position));
+ const angle = Math.atan2(unitVector.y, unitVector.x);
+ Matter.Body.setAngle(this, angle - Math.PI);
+ },
+ explode() {
+ mech.damage(Math.min(Math.max(0.02 * Math.sqrt(this.mass), 0.01), 0.35) * game.dmgScale);
+ this.dropPowerUp = false;
+ this.death(); //death with no power up or body
+ },
+ timeLimit() {
+ if (!mech.isBodiesAsleep) {
+ this.timeLeft--;
+ if (this.timeLeft < 0) {
+ this.dropPowerUp = false;
+ this.death(); //death with no power up
+ }
+ }
+ },
+ healthBar() {
+ //draw health bar
+ if (this.seePlayer.recall) {
+ // && this.health < 1
+ const h = this.radius * 0.3;
+ const w = this.radius * 2;
+ const x = this.position.x - w / 2;
+ const y = this.position.y - w * 0.7;
+ ctx.fillStyle = "rgba(100, 100, 100, 0.3)";
+ ctx.fillRect(x, y, w, h);
+ ctx.fillStyle = "rgba(255,0,0,0.7)";
+ ctx.fillRect(x, y, w * this.health, h);
+ }
+ },
+ damage(dmg) {
+ dmg /= Math.sqrt(this.mass)
+ if (b.isModLowHealthDmg) dmg *= (3 / (2 + mech.health)) //up to 50% dmg at zero player health
+ if (b.isModFarAwayDmg) dmg *= 1 + Math.sqrt(Math.max(1000, Math.min(3500, this.distanceToPlayer())) - 1000) * 0.01 //up to 50% dmg at max range of 3500
+ if (dmg !== Infinity) {
+ if (b.modEnergySiphon) mech.fieldMeter += Math.min(this.health, dmg) * b.modEnergySiphon
+ if (b.modHealthDrain) mech.addHealth(Math.min(this.health, dmg) * b.modHealthDrain)
+ }
+ this.health -= dmg
+ //this.fill = this.color + this.health + ')';
+ if (this.health < 0.05) this.death();
+ this.onDamage(this); //custom damage effects
+ },
+ onDamage() {
+ // a placeholder for custom effects on mob damage
+ //to use declare custom method in mob spawn
+ },
+ onDeath() {
+ // a placeholder for custom effects on mob death
+ // to use declare custom method in mob spawn
+ },
+ leaveBody: true,
+ dropPowerUp: true,
+ death() {
+ this.onDeath(this); //custom death effects
+ this.removeConsBB();
+ this.alive = false;
+ if (this.dropPowerUp) {
+ powerUps.spawnRandomPowerUp(this.position.x, this.position.y, this.mass, radius);
+ if (Math.random() < b.modSpores) {
+ for (let i = 0, len = Math.floor(3 + this.mass * Math.random()); i < len; i++) {
+ b.spore(this) //spawn drone
+ }
+ }
+ }
+
+ },
+ removeConsBB() {
+ for (let i = 0, len = consBB.length; i < len; ++i) {
+ if (consBB[i].bodyA === this) {
+ if (consBB[i].bodyB.shield) {
+ consBB[i].bodyB.do = function () {
+ this.death();
+ };
+ }
+ consBB[i].bodyA = consBB[i].bodyB;
+ consBB.splice(i, 1);
+ this.removeConsBB();
+ break;
+ } else if (consBB[i].bodyB === this) {
+ if (consBB[i].bodyA.shield) {
+ consBB[i].bodyA.do = function () {
+ this.death();
+ };
+ }
+ consBB[i].bodyB = consBB[i].bodyA;
+ consBB.splice(i, 1);
+ this.removeConsBB();
+ break;
+ }
+ }
+ },
+ removeCons() {
+ for (let i = 0, len = cons.length; i < len; ++i) {
+ if (cons[i].bodyA === this) {
+ cons[i].bodyA = cons[i].bodyB;
+ cons.splice(i, 1);
+ this.removeCons();
+ break;
+ } else if (cons[i].bodyB === this) {
+ cons[i].bodyB = cons[i].bodyA;
+ cons.splice(i, 1);
+ this.removeCons();
+ break;
+ }
+ }
+ },
+ //replace dead mob with a regular body
+ replace(i) {
+ if (this.leaveBody) {
+ const len = body.length;
+ body[len] = Matter.Bodies.fromVertices(this.position.x, this.position.y, this.vertices);
+ Matter.Body.setVelocity(body[len], this.velocity);
+ Matter.Body.setAngularVelocity(body[len], this.angularVelocity);
+ body[len].collisionFilter.category = 0x010000;
+ body[len].collisionFilter.mask = 0x011111;
+ // body[len].collisionFilter.category = body[len].collisionFilter.category //0x000001;
+ // body[len].collisionFilter.mask = body[len].collisionFilter.mask //0x011111;
+
+ //large mobs or too many bodies go intangible and fall until removed from game to help performance
+ if (body[len].mass > 10 || 40 + 30 * Math.random() < body.length) {
+ body[len].collisionFilter.mask = 0x001100;
+ }
+ body[len].classType = "body";
+ World.add(engine.world, body[len]); //add to world
+ }
+ Matter.World.remove(engine.world, this);
+ mob.splice(i, 1);
+ }
+ });
+ mob[i].alertRange2 = Math.pow(mob[i].radius * 3.5 + 550, 2);
+ World.add(engine.world, mob[i]); //add to world
+ }
};
\ No newline at end of file
diff --git a/js/player.js b/js/player.js
index 11b80b6..a6bf5ae 100644
--- a/js/player.js
+++ b/js/player.js
@@ -1,1516 +1,1516 @@
-//global player variables for use in matter.js physics
-let player, jumpSensor, playerBody, playerHead, headSensor;
-
-// player Object Prototype *********************************************
-const mech = {
- spawn() {
- //load player in matter.js physic engine
- // let vector = Vertices.fromPath("0 40 50 40 50 115 0 115 30 130 20 130"); //player as a series of vertices
- let vertices = Vertices.fromPath("0,40, 50,40, 50,115, 30,130, 20,130, 0,115, 0,40"); //player as a series of vertices
- playerBody = Matter.Bodies.fromVertices(0, 0, vertices);
- jumpSensor = Bodies.rectangle(0, 46, 36, 6, {
- //this sensor check if the player is on the ground to enable jumping
- sleepThreshold: 99999999999,
- isSensor: true
- });
- vertices = Vertices.fromPath("16 -82 2 -66 2 -37 43 -37 43 -66 30 -82");
- playerHead = Matter.Bodies.fromVertices(0, -55, vertices); //this part of the player lowers on crouch
- headSensor = Bodies.rectangle(0, -57, 48, 45, {
- //senses if the player's head is empty and can return after crouching
- sleepThreshold: 99999999999,
- isSensor: true
- });
- player = Body.create({
- //combine jumpSensor and playerBody
- parts: [playerBody, playerHead, jumpSensor, headSensor],
- inertia: Infinity, //prevents player rotation
- friction: 0.002,
- frictionAir: 0.001,
- //frictionStatic: 0.5,
- restitution: 0,
- sleepThreshold: Infinity,
- collisionFilter: {
- group: 0,
- category: 0x001000,
- mask: 0x010011
- },
- death() {
- mech.death();
- }
- });
- Matter.Body.setMass(player, mech.mass);
- World.add(engine.world, [player]);
-
- mech.holdConstraint = Constraint.create({
- //holding body constraint
- pointA: {
- x: 0,
- y: 0
- },
- bodyB: jumpSensor, //setting constraint to jump sensor because it has to be on something until the player picks up things
- stiffness: 0.4
- });
- World.add(engine.world, mech.holdConstraint);
- },
- cycle: 0,
- width: 50,
- radius: 30,
- fillColor: "#fff",
- fillColorDark: "#ccc",
- height: 42,
- yOffWhen: {
- crouch: 22,
- stand: 49,
- jump: 70
- },
- defaultMass: 5,
- mass: 5,
- FxNotHolding: 0.015,
- Fx: 0.015, //run Force on ground //this is reset in b.setModDefaults()
- FxAir: 0.015, //run Force in Air
- yOff: 70,
- yOffGoal: 70,
- onGround: false, //checks if on ground or in air
- standingOn: undefined,
- numTouching: 0,
- crouch: false,
- isHeadClear: true,
- spawnPos: {
- x: 0,
- y: 0
- },
- spawnVel: {
- x: 0,
- y: 0
- },
- pos: {
- x: 0,
- y: 0
- },
- setPosToSpawn(xPos, yPos) {
- this.spawnPos.x = this.pos.x = xPos;
- this.spawnPos.y = this.pos.y = yPos;
- this.transX = this.transSmoothX = canvas.width2 - this.pos.x;
- this.transY = this.transSmoothY = canvas.height2 - this.pos.y;
- this.Vx = this.spawnVel.x;
- this.Vy = this.spawnVel.y;
- player.force.x = 0;
- player.force.y = 0;
- Matter.Body.setPosition(player, this.spawnPos);
- Matter.Body.setVelocity(player, this.spawnVel);
- },
- Sy: 0, //adds a smoothing effect to vertical only
- Vx: 0,
- Vy: 0,
- jumpForce: 0.38, //this is reset in b.setModDefaults()
- gravity: 0.0019,
- friction: {
- ground: 0.01,
- air: 0.0025
- },
- angle: 0,
- walk_cycle: 0,
- stepSize: 0,
- flipLegs: -1,
- hip: {
- x: 12,
- y: 24
- },
- knee: {
- x: 0,
- y: 0,
- x2: 0,
- y2: 0
- },
- foot: {
- x: 0,
- y: 0
- },
- legLength1: 55,
- legLength2: 45,
- transX: 0,
- transY: 0,
- move() {
- this.pos.x = player.position.x;
- this.pos.y = playerBody.position.y - this.yOff;
- this.Vx = player.velocity.x;
- this.Vy = player.velocity.y;
- },
- transSmoothX: 0,
- transSmoothY: 0,
- lastGroundedPositionY: 0,
- // mouseZoom: 0,
- look() {
- //always on mouse look
- this.angle = Math.atan2(
- game.mouseInGame.y - this.pos.y,
- game.mouseInGame.x - this.pos.x
- );
- //smoothed mouse look translations
- const scale = 0.8;
- this.transSmoothX = canvas.width2 - this.pos.x - (game.mouse.x - canvas.width2) * scale;
- this.transSmoothY = canvas.height2 - this.pos.y - (game.mouse.y - canvas.height2) * scale;
-
- this.transX += (this.transSmoothX - this.transX) * 0.07;
- this.transY += (this.transSmoothY - this.transY) * 0.07;
- },
- doCrouch() {
- if (!this.crouch) {
- this.crouch = true;
- this.yOffGoal = this.yOffWhen.crouch;
- Matter.Body.translate(playerHead, {
- x: 0,
- y: 40
- });
- }
- },
- undoCrouch() {
- if (this.crouch) {
- this.crouch = false;
- this.yOffGoal = this.yOffWhen.stand;
- Matter.Body.translate(playerHead, {
- x: 0,
- y: -40
- });
- }
- },
- hardLandCD: 0,
- enterAir() {
- //triggered in engine.js on collision
- this.onGround = false;
- this.hardLandCD = 0 // disable hard landing
- if (this.isHeadClear) {
- if (this.crouch) {
- this.undoCrouch();
- }
- this.yOffGoal = this.yOffWhen.jump;
- }
- },
- //triggered in engine.js on collision
- enterLand() {
- this.onGround = true;
- if (this.crouch) {
- if (this.isHeadClear) {
- this.undoCrouch();
- } else {
- this.yOffGoal = this.yOffWhen.crouch;
- }
- } else {
- //sets a hard land where player stays in a crouch for a bit and can't jump
- //crouch is forced in keyMove() on ground section below
- const momentum = player.velocity.y * player.mass //player mass is 5 so this triggers at 20 down velocity, unless the player is holding something
- if (momentum > 100) {
- this.doCrouch();
- this.yOff = this.yOffWhen.jump;
- this.hardLandCD = mech.cycle + Math.min(momentum / 6 - 6, 40)
-
- if (game.isBodyDamage && player.velocity.y > 26 && momentum > 165) { //falling damage
- mech.damageImmune = mech.cycle + 30; //player is immune to collision damage for 30 cycles
- let dmg = Math.sqrt(momentum - 165) * 0.01
- dmg = Math.min(Math.max(dmg, 0.02), 0.20);
- mech.damage(dmg);
- }
- } else {
- this.yOffGoal = this.yOffWhen.stand;
- }
- }
- },
- buttonCD_jump: 0, //cool down for player buttons
- keyMove() {
- if (this.onGround) { //on ground **********************
- if (this.crouch) {
- if (!(keys[83] || keys[40]) && this.isHeadClear && this.hardLandCD < mech.cycle) this.undoCrouch();
- } else if (keys[83] || keys[40] || this.hardLandCD > mech.cycle) {
- this.doCrouch(); //on ground && not crouched and pressing s or down
- } else if ((keys[87] || keys[38]) && this.buttonCD_jump + 20 < mech.cycle && this.yOffWhen.stand > 23) {
- this.buttonCD_jump = mech.cycle; //can't jump again until 20 cycles pass
-
- //apply a fraction of the jump force to the body the player is jumping off of
- Matter.Body.applyForce(mech.standingOn, mech.pos, {
- x: 0,
- y: this.jumpForce * 0.12 * Math.min(mech.standingOn.mass, 5)
- });
-
- player.force.y = -this.jumpForce; //player jump force
- Matter.Body.setVelocity(player, { //zero player y-velocity for consistent jumps
- x: player.velocity.x,
- y: 0
- });
- }
-
- //horizontal move on ground
- //apply a force to move
- if (keys[65] || keys[37]) { //left / a
- if (player.velocity.x > -2) {
- player.force.x -= this.Fx * 1.5
- } else {
- player.force.x -= this.Fx
- }
- } else if (keys[68] || keys[39]) { //right / d
- if (player.velocity.x < 2) {
- player.force.x += this.Fx * 1.5
- } else {
- player.force.x += this.Fx
- }
- } else {
- const stoppingFriction = 0.92;
- Matter.Body.setVelocity(player, {
- x: player.velocity.x * stoppingFriction,
- y: player.velocity.y * stoppingFriction
- });
- }
- //come to a stop if fast or if no move key is pressed
- if (player.speed > 4) {
- const stoppingFriction = (this.crouch) ? 0.65 : 0.89; // this controls speed when crouched
- Matter.Body.setVelocity(player, {
- x: player.velocity.x * stoppingFriction,
- y: player.velocity.y * stoppingFriction
- });
- }
-
- } else { // in air **********************************
- //check for short jumps
- if (
- this.buttonCD_jump + 60 > mech.cycle && //just pressed jump
- !(keys[87] || keys[38]) && //but not pressing jump key
- this.Vy < 0 //moving up
- ) {
- Matter.Body.setVelocity(player, {
- //reduce player y-velocity every cycle
- x: player.velocity.x,
- y: player.velocity.y * 0.94
- });
- }
- const limit = 125 / player.mass / player.mass
- if (keys[65] || keys[37]) {
- if (player.velocity.x > -limit) player.force.x -= this.FxAir; // move player left / a
- } else if (keys[68] || keys[39]) {
- if (player.velocity.x < limit) player.force.x += this.FxAir; //move player right / d
- }
- }
-
- //smoothly move leg height towards height goal
- this.yOff = this.yOff * 0.85 + this.yOffGoal * 0.15;
- },
- alive: true,
- death() {
- if (b.modIsImmortal) { //if player has the immortality buff, spawn on the same level with randomized stats
- spawn.setSpawnList(); //new mob types
- game.clearNow = true; //triggers a map reset
-
- //count mods
- let totalMods = -2; //lose the immortality mod and one more, so -2
- for (let i = 0; i < b.mods.length; i++) {
- if (b.mods[i].have) totalMods++
- }
-
- function randomizeMods() {
- b.setModDefaults(); //remove all mods
- for (let i = 0; i < totalMods; i++) {
- //find what mods I don't have
- let options = [];
- for (let i = 0; i < b.mods.length; i++) {
- //can't get quantum immortality again
- if (i !== 7 && !b.mods[i].have) options.push(i);
- }
- //add a new mod
- if (options.length > 0) {
- const choose = Math.floor(Math.random() * options.length)
- let newMod = options[choose]
- b.giveMod(newMod)
- options.splice(choose, 1);
- }
- }
- game.updateModHUD();
- }
-
- function randomizeField() {
- if (game.difficulty * (Math.random() + 0.27) > 2) {
- mech.fieldUpgrades[Math.floor(Math.random() * (mech.fieldUpgrades.length))].effect();
- } else {
- mech.fieldUpgrades[0].effect();
- }
- }
-
- function randomizeHealth() {
- mech.health = 0.5 + Math.random()
- if (mech.health > 1) mech.health = 1;
- mech.displayHealth();
- }
-
- function randomizeGuns() {
- const length = Math.round(b.inventory.length * (1 + 0.4 * (Math.random() - 0.5)))
- //removes guns and ammo
- b.inventory = [];
- b.activeGun = null;
- b.inventoryGun = 0;
- for (let i = 0, len = b.guns.length; i < len; ++i) {
- b.guns[i].have = false;
- if (b.guns[i].ammo !== Infinity) b.guns[i].ammo = 0;
- }
- for (let i = 0; i < length; i++) {
- powerUps.gun.effect();
- }
-
- //randomize ammo
- for (let i = 0, len = b.inventory.length; i < len; i++) {
- if (b.guns[b.inventory[i]].ammo !== Infinity) {
- b.guns[b.inventory[i]].ammo = Math.max(0, Math.floor(6 * b.guns[b.inventory[i]].ammo * (Math.random() - 0.3)))
- }
- }
- game.makeGunHUD(); //update gun HUD
- }
-
- game.wipe = function () { //set wipe to have trails
- ctx.fillStyle = "rgba(255,255,255,0)";
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- }
- randomizeMods()
- randomizeGuns()
- randomizeField()
- randomizeHealth()
- for (let i = 0, len = 7; i < len; i++) {
- setTimeout(function () {
- randomizeMods()
- randomizeGuns()
- randomizeField()
- randomizeHealth()
- game.replaceTextLog = true;
- game.makeTextLog(`probability amplitude will synchronize in ${len-i-1} seconds`, 1000);
- game.wipe = function () { //set wipe to have trails
- ctx.fillStyle = `rgba(255,255,255,${(i+1)*(i+1)*0.006})`;
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- }
- }, (i + 1) * 1000);
- }
-
- setTimeout(function () {
- game.wipe = function () { //set wipe to normal
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- }
- game.replaceTextLog = true;
- game.makeTextLog("your quantum probability has stabilized", 1000);
- document.title = "n-gon: L" + (game.difficulty) + " " + level.levels[level.onLevel];
- }, 8000);
-
- } else if (this.alive) { //normal death code here
- this.alive = false;
- game.paused = true;
- this.health = 0;
- this.displayHealth();
- document.getElementById("text-log").style.opacity = 0; //fade out any active text logs
- document.getElementById("fade-out").style.opacity = 1; //slowly fades out
- setTimeout(function () {
- game.splashReturn();
- }, 3000);
- }
- },
- health: 0,
- drawHealth() {
- if (this.health < 1) {
- ctx.fillStyle = "rgba(100, 100, 100, 0.5)";
- ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, 60, 10);
- ctx.fillStyle = "#f00";
- ctx.fillRect(
- this.pos.x - this.radius,
- this.pos.y - 50,
- 60 * this.health,
- 10
- );
- }
- },
- displayHealth() {
- id = document.getElementById("health");
- id.style.width = Math.floor(300 * this.health) + "px";
- //css animation blink if health is low
- if (this.health < 0.3) {
- id.classList.add("low-health");
- } else {
- id.classList.remove("low-health");
- }
- },
- addHealth(heal) {
- this.health += heal;
- if (this.health > 1 || b.isModFullHeal) this.health = 1;
- this.displayHealth();
- },
- defaultFPSCycle: 0, //tracks when to return to normal fps
- damage(dmg) {
- if (b.isModMonogamy && b.inventory[0] === b.activeGun) {
- for (let i = 0, len = b.inventory.length; i < len; i++) {
- dmg *= 0.93
- }
- }
- this.health -= dmg;
- if (this.health < 0) {
- this.health = 0;
- this.death();
- return;
- }
- this.displayHealth();
- document.getElementById("dmg").style.transition = "opacity 0s";
- document.getElementById("dmg").style.opacity = 0.1 + Math.min(0.6, dmg * 4);
-
- //chance to build a drone on damage from mod
- if (b.isModDroneOnDamage) {
- const len = (dmg - 0.08 + 0.05 * Math.random()) / 0.05
- for (let i = 0; i < len; i++) {
- if (Math.random() < 0.6) b.guns[13].fire() //spawn drone
- }
- }
-
- // freeze game and display a full screen red color
- if (dmg > 0.05) {
- this.drop(); //drop block if holding
- game.fpsCap = 4 //40 - Math.min(25, 100 * dmg)
- game.fpsInterval = 1000 / game.fpsCap;
- } else {
- game.fpsCap = game.fpsCapDefault
- game.fpsInterval = 1000 / game.fpsCap;
- }
- mech.defaultFPSCycle = mech.cycle
-
- const normalFPS = function () {
- if (mech.defaultFPSCycle < mech.cycle) { //back to default values
- game.fpsCap = game.fpsCapDefault
- game.fpsInterval = 1000 / game.fpsCap;
- document.getElementById("dmg").style.transition = "opacity 1s";
- document.getElementById("dmg").style.opacity = "0";
- } else {
- requestAnimationFrame(normalFPS);
- }
- };
- requestAnimationFrame(normalFPS);
-
- // // freeze game and display a full screen red color
- // if (dmg > 0.05) {
- // if (dmg > 0.07) {
- // this.drop(); //drop block if holding
- // }
-
- // game.fpsCap = 4 //40 - Math.min(25, 100 * dmg)
- // game.fpsInterval = 1000 / game.fpsCap;
- // } else {
- // game.fpsCap = game.fpsCapDefault
- // game.fpsInterval = 1000 / game.fpsCap;
- // }
- // mech.defaultFPSCycle = mech.cycle
-
- // const normalFPS = function () {
- // if (mech.defaultFPSCycle < mech.cycle) { //back to default values
- // game.fpsCap = game.fpsCapDefault
- // game.fpsInterval = 1000 / game.fpsCap;
- // document.getElementById("dmg").style.transition = "opacity 1s";
- // document.getElementById("dmg").style.opacity = "0";
- // } else {
- // requestAnimationFrame(normalFPS);
- // }
- // };
- // requestAnimationFrame(normalFPS);
- },
- damageImmune: 0,
- hitMob(i, dmg) {
- //prevents damage happening too quick
- },
- buttonCD: 0, //cool down for player buttons
- usePowerUp(i) {
- powerUp[i].effect();
- Matter.World.remove(engine.world, powerUp[i]);
- powerUp.splice(i, 1);
- },
- drawLeg(stroke) {
- // if (game.mouseInGame.x > this.pos.x) {
- if (mech.angle > -Math.PI / 2 && mech.angle < Math.PI / 2) {
- this.flipLegs = 1;
- } else {
- this.flipLegs = -1;
- }
- ctx.save();
- ctx.scale(this.flipLegs, 1); //leg lines
- ctx.beginPath();
- ctx.moveTo(this.hip.x, this.hip.y);
- ctx.lineTo(this.knee.x, this.knee.y);
- ctx.lineTo(this.foot.x, this.foot.y);
- ctx.strokeStyle = stroke;
- ctx.lineWidth = 7;
- ctx.stroke();
-
- //toe lines
- ctx.beginPath();
- ctx.moveTo(this.foot.x, this.foot.y);
- ctx.lineTo(this.foot.x - 15, this.foot.y + 5);
- ctx.moveTo(this.foot.x, this.foot.y);
- ctx.lineTo(this.foot.x + 15, this.foot.y + 5);
- ctx.lineWidth = 4;
- ctx.stroke();
-
- //hip joint
- ctx.beginPath();
- ctx.arc(this.hip.x, this.hip.y, 11, 0, 2 * Math.PI);
- //knee joint
- ctx.moveTo(this.knee.x + 7, this.knee.y);
- ctx.arc(this.knee.x, this.knee.y, 7, 0, 2 * Math.PI);
- //foot joint
- ctx.moveTo(this.foot.x + 6, this.foot.y);
- ctx.arc(this.foot.x, this.foot.y, 6, 0, 2 * Math.PI);
- ctx.fillStyle = this.fillColor;
- ctx.fill();
- ctx.lineWidth = 2;
- ctx.stroke();
- ctx.restore();
- },
- calcLeg(cycle_offset, offset) {
- this.hip.x = 12 + offset;
- this.hip.y = 24 + offset;
- //stepSize goes to zero if Vx is zero or not on ground (make this transition cleaner)
- this.stepSize = 0.8 * this.stepSize + 0.2 * (7 * Math.sqrt(Math.min(9, Math.abs(this.Vx))) * this.onGround);
- //changes to stepsize are smoothed by adding only a percent of the new value each cycle
- const stepAngle = 0.034 * this.walk_cycle + cycle_offset;
- this.foot.x = 2.2 * this.stepSize * Math.cos(stepAngle) + offset;
- this.foot.y = offset + 1.2 * this.stepSize * Math.sin(stepAngle) + this.yOff + this.height;
- const Ymax = this.yOff + this.height;
- if (this.foot.y > Ymax) this.foot.y = Ymax;
-
- //calculate knee position as intersection of circle from hip and foot
- const d = Math.sqrt((this.hip.x - this.foot.x) * (this.hip.x - this.foot.x) + (this.hip.y - this.foot.y) * (this.hip.y - this.foot.y));
- const l = (this.legLength1 * this.legLength1 - this.legLength2 * this.legLength2 + d * d) / (2 * d);
- const h = Math.sqrt(this.legLength1 * this.legLength1 - l * l);
- this.knee.x = (l / d) * (this.foot.x - this.hip.x) - (h / d) * (this.foot.y - this.hip.y) + this.hip.x + offset;
- this.knee.y = (l / d) * (this.foot.y - this.hip.y) + (h / d) * (this.foot.x - this.hip.x) + this.hip.y;
- },
- draw() {
- ctx.fillStyle = this.fillColor;
- this.walk_cycle += this.flipLegs * this.Vx;
-
- //draw body
- ctx.save();
- ctx.translate(this.pos.x, this.pos.y);
- this.calcLeg(Math.PI, -3);
- this.drawLeg("#4a4a4a");
- this.calcLeg(0, 0);
- this.drawLeg("#333");
- ctx.rotate(this.angle);
-
- ctx.beginPath();
- ctx.arc(0, 0, 30, 0, 2 * Math.PI);
- let grd = ctx.createLinearGradient(-30, 0, 30, 0);
- grd.addColorStop(0, this.fillColorDark);
- grd.addColorStop(1, this.fillColor);
- ctx.fillStyle = grd;
- ctx.fill();
- ctx.arc(15, 0, 4, 0, 2 * Math.PI);
- ctx.strokeStyle = "#333";
- ctx.lineWidth = 2;
- ctx.stroke();
- // ctx.beginPath();
- // ctx.arc(15, 0, 3, 0, 2 * Math.PI);
- // ctx.fillStyle = '#9cf' //'#0cf';
- // ctx.fill()
- ctx.restore();
- },
- // *********************************************
- // **************** holding ********************
- // *********************************************
- closest: {
- dist: 1000,
- index: 0
- },
- isHolding: false,
- isStealth: false,
- throwCharge: 0,
- fireCDcycle: 0,
- fieldCDcycle: 0,
- fieldMode: 0, //basic field mode before upgrades
- // these values are set on reset by setHoldDefaults()
- fieldMeter: 0,
- fieldRegen: 0,
- fieldMode: 0,
- fieldFire: false,
- holdingMassScale: 0,
- throwChargeRate: 0,
- throwChargeMax: 0,
- fieldFireCD: 0,
- fieldShieldingScale: 0,
- grabRange: 0,
- fieldArc: 0,
- fieldThreshold: 0,
- calculateFieldThreshold() {
- this.fieldThreshold = Math.cos(this.fieldArc * Math.PI)
- },
- setHoldDefaults() {
- this.fieldMeter = 1;
- this.fieldRegen = 0.001;
- this.fieldFire = false;
- this.fieldCDcycle = 0;
- this.isStealth = false;
- player.collisionFilter.mask = 0x010011 //0x010011 is normal
- this.holdingMassScale = 0.5;
- this.fieldFireCD = 15;
- this.fieldShieldingScale = 1; //scale energy loss after collision with mob
- this.grabRange = 175;
- this.fieldArc = 0.2; //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob)
- this.calculateFieldThreshold(); //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob)
- mech.isBodiesAsleep = true;
- mech.wakeCheck();
- // this.phaseBlocks(0x011111)
- },
- drawFieldMeter(range = 60) {
- if (this.fieldMeter < 1) {
- mech.fieldMeter += mech.fieldRegen;
- ctx.fillStyle = "rgba(0, 0, 0, 0.4)";
- ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, range, 10);
- ctx.fillStyle = "#0cf";
- ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, range * this.fieldMeter, 10);
- } else {
- mech.fieldMeter = 1
- }
- },
- lookingAt(who) {
- //calculate a vector from body to player and make it length 1
- const diff = Matter.Vector.normalise(Matter.Vector.sub(who.position, mech.pos));
- //make a vector for the player's direction of length 1
- const dir = {
- x: Math.cos(mech.angle),
- y: Math.sin(mech.angle)
- };
- //the dot product of diff and dir will return how much over lap between the vectors
- // console.log(Matter.Vector.dot(dir, diff))
- if (Matter.Vector.dot(dir, diff) > this.fieldThreshold) {
- return true;
- }
- return false;
- },
- drop() {
- if (this.isHolding) {
- this.isHolding = false;
- this.definePlayerMass()
- this.holdingTarget.collisionFilter.category = 0x010000;
- this.holdingTarget.collisionFilter.mask = 0x011111;
- this.holdingTarget = null;
- this.throwCharge = 0;
- }
- },
- definePlayerMass(mass = mech.defaultMass) {
- Matter.Body.setMass(player, mass);
- //reduce air and ground move forces
- this.Fx = 0.075 / mass * b.modSquirrelFx
- this.FxAir = 0.375 / mass / mass
- //make player stand a bit lower when holding heavy masses
- this.yOffWhen.stand = Math.max(this.yOffWhen.crouch, Math.min(49, 49 - (mass - 5) * 6))
- if (this.onGround && !this.crouch) this.yOffGoal = this.yOffWhen.stand;
- },
- drawHold(target, stroke = true) {
- const eye = 15;
- const len = target.vertices.length - 1;
- ctx.fillStyle = "rgba(110,170,200," + (0.2 + 0.4 * Math.random()) + ")";
- ctx.lineWidth = 1;
- ctx.strokeStyle = "#000";
- ctx.beginPath();
- ctx.moveTo(
- mech.pos.x + eye * Math.cos(this.angle),
- mech.pos.y + eye * Math.sin(this.angle)
- );
- ctx.lineTo(target.vertices[len].x, target.vertices[len].y);
- ctx.lineTo(target.vertices[0].x, target.vertices[0].y);
- ctx.fill();
- if (stroke) ctx.stroke();
- for (let i = 0; i < len; i++) {
- ctx.beginPath();
- ctx.moveTo(
- mech.pos.x + eye * Math.cos(this.angle),
- mech.pos.y + eye * Math.sin(this.angle)
- );
- ctx.lineTo(target.vertices[i].x, target.vertices[i].y);
- ctx.lineTo(target.vertices[i + 1].x, target.vertices[i + 1].y);
- ctx.fill();
- if (stroke) ctx.stroke();
- }
- },
- holding() {
- this.fieldMeter -= this.fieldRegen;
- if (this.fieldMeter < 0) this.fieldMeter = 0;
- Matter.Body.setPosition(this.holdingTarget, {
- x: mech.pos.x + 70 * Math.cos(this.angle),
- y: mech.pos.y + 70 * Math.sin(this.angle)
- });
- Matter.Body.setVelocity(this.holdingTarget, player.velocity);
- Matter.Body.rotate(this.holdingTarget, 0.01 / this.holdingTarget.mass); //gently spin the block
- },
- throw () {
- if ((keys[32] || game.mouseDownRight)) {
- if (this.fieldMeter > 0.0008) {
- this.fieldMeter -= 0.0008;
- this.throwCharge += this.throwChargeRate;;
- //draw charge
- const x = mech.pos.x + 15 * Math.cos(this.angle);
- const y = mech.pos.y + 15 * Math.sin(this.angle);
- const len = this.holdingTarget.vertices.length - 1;
- const edge = this.throwCharge * this.throwCharge * 0.02;
- const grd = ctx.createRadialGradient(x, y, edge, x, y, edge + 5);
- grd.addColorStop(0, "rgba(255,50,150,0.3)");
- grd.addColorStop(1, "transparent");
- ctx.fillStyle = grd;
- ctx.beginPath();
- ctx.moveTo(x, y);
- ctx.lineTo(this.holdingTarget.vertices[len].x, this.holdingTarget.vertices[len].y);
- ctx.lineTo(this.holdingTarget.vertices[0].x, this.holdingTarget.vertices[0].y);
- ctx.fill();
- for (let i = 0; i < len; i++) {
- ctx.beginPath();
- ctx.moveTo(x, y);
- ctx.lineTo(this.holdingTarget.vertices[i].x, this.holdingTarget.vertices[i].y);
- ctx.lineTo(this.holdingTarget.vertices[i + 1].x, this.holdingTarget.vertices[i + 1].y);
- ctx.fill();
- }
- } else {
- this.drop()
- }
- } else if (this.throwCharge > 0) {
- //throw the body
- this.fireCDcycle = mech.cycle + this.fieldFireCD;
- this.isHolding = false;
- //bullet-like collisions
- this.holdingTarget.collisionFilter.category = 0x000100;
- this.holdingTarget.collisionFilter.mask = 0x110111;
- //check every second to see if player is away from thrown body, and make solid
- const solid = function (that) {
- const dx = that.position.x - player.position.x;
- const dy = that.position.y - player.position.y;
- if (dx * dx + dy * dy > 10000 && that.speed < 3 && that !== mech.holdingTarget) {
- that.collisionFilter.category = 0x010000; //make solid
- that.collisionFilter.mask = 0x011111;
- } else {
- setTimeout(solid, 50, that);
- }
- };
- setTimeout(solid, 200, this.holdingTarget);
- //throw speed scales a bit with mass
- const speed = Math.min(85, Math.min(54 / this.holdingTarget.mass + 5, 48) * Math.min(this.throwCharge, this.throwChargeMax) / 50);
-
- this.throwCharge = 0;
- Matter.Body.setVelocity(this.holdingTarget, {
- x: player.velocity.x + Math.cos(this.angle) * speed,
- y: player.velocity.y + Math.sin(this.angle) * speed
- });
- //player recoil //stronger in x-dir to prevent jump hacking
- Matter.Body.setVelocity(player, {
- x: player.velocity.x - Math.cos(this.angle) * speed / 20 * Math.sqrt(this.holdingTarget.mass),
- y: player.velocity.y - Math.sin(this.angle) * speed / 80 * Math.sqrt(this.holdingTarget.mass)
- });
- this.definePlayerMass() //return to normal player mass
- }
- },
- drawField() {
- if (mech.holdingTarget) {
- ctx.fillStyle = "rgba(110,170,200," + (mech.fieldMeter * (0.05 + 0.05 * Math.random())) + ")";
- ctx.strokeStyle = "rgba(110, 200, 235, " + (0.3 + 0.08 * Math.random()) + ")" //"#9bd" //"rgba(110, 200, 235, " + (0.5 + 0.1 * Math.random()) + ")"
- } else {
- ctx.fillStyle = "rgba(110,170,200," + (0.02 + mech.fieldMeter * (0.15 + 0.15 * Math.random())) + ")";
- ctx.strokeStyle = "rgba(110, 200, 235, " + (0.6 + 0.2 * Math.random()) + ")" //"#9bd" //"rgba(110, 200, 235, " + (0.5 + 0.1 * Math.random()) + ")"
- }
- // const off = 2 * Math.cos(game.cycle * 0.1)
- const range = this.grabRange - 20;
- ctx.beginPath();
- ctx.arc(mech.pos.x, mech.pos.y, range, mech.angle - Math.PI * mech.fieldArc, mech.angle + Math.PI * mech.fieldArc, false);
- ctx.lineWidth = 2;
- ctx.lineCap = "butt"
- ctx.stroke();
- let eye = 13;
- let aMag = 0.75 * Math.PI * mech.fieldArc
- let a = mech.angle + aMag
- let cp1x = mech.pos.x + 0.6 * range * Math.cos(a)
- let cp1y = mech.pos.y + 0.6 * range * Math.sin(a)
- ctx.quadraticCurveTo(cp1x, cp1y, mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle))
- a = mech.angle - aMag
- cp1x = mech.pos.x + 0.6 * range * Math.cos(a)
- cp1y = mech.pos.y + 0.6 * range * Math.sin(a)
- ctx.quadraticCurveTo(cp1x, cp1y, mech.pos.x + 1 * range * Math.cos(mech.angle - Math.PI * mech.fieldArc), mech.pos.y + 1 * range * Math.sin(mech.angle - Math.PI * mech.fieldArc))
- ctx.fill();
- // ctx.lineTo(mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle));
-
- //draw random lines in field for cool effect
- let offAngle = mech.angle + 1.7 * Math.PI * mech.fieldArc * (Math.random() - 0.5);
- ctx.beginPath();
- eye = 15;
- ctx.moveTo(mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle));
- ctx.lineTo(mech.pos.x + range * Math.cos(offAngle), mech.pos.y + range * Math.sin(offAngle));
- ctx.strokeStyle = "rgba(120,170,255,0.6)";
- ctx.lineWidth = 1;
- ctx.stroke();
- },
- grabPowerUp() { //look for power ups to grab with field
- if (mech.fieldCDcycle < mech.cycle) {
- const grabPowerUpRange2 = (this.grabRange + 220) * (this.grabRange + 220)
- for (let i = 0, len = powerUp.length; i < len; ++i) {
- const dxP = mech.pos.x - powerUp[i].position.x;
- const dyP = mech.pos.y - powerUp[i].position.y;
- const dist2 = dxP * dxP + dyP * dyP;
- // float towards player if looking at and in range or if very close to player
- if (dist2 < grabPowerUpRange2 && this.lookingAt(powerUp[i]) || dist2 < 16000) {
- if (dist2 < 5000) { //use power up if it is close enough
- Matter.Body.setVelocity(player, { //player knock back, after grabbing power up
- x: player.velocity.x + ((powerUp[i].velocity.x * powerUp[i].mass) / player.mass) * 0.3,
- y: player.velocity.y + ((powerUp[i].velocity.y * powerUp[i].mass) / player.mass) * 0.3
- });
- mech.usePowerUp(i);
- return;
- }
- this.fieldMeter -= this.fieldRegen * 0.5;
- powerUp[i].force.x += 7 * (dxP / dist2) * powerUp[i].mass;
- powerUp[i].force.y += 7 * (dyP / dist2) * powerUp[i].mass - powerUp[i].mass * game.g; //negate gravity
- //extra friction
- Matter.Body.setVelocity(powerUp[i], {
- x: powerUp[i].velocity.x * 0.11,
- y: powerUp[i].velocity.y * 0.11
- });
- }
- }
- }
- },
- pushMass(who) {
- const fieldBlockCost = Math.max(0.02, who.mass * 0.012) //0.012
- if (this.fieldMeter > fieldBlockCost) {
- this.fieldMeter -= fieldBlockCost * this.fieldShieldingScale;
- if (this.fieldMeter < 0) this.fieldMeter = 0;
- this.drawHold(who);
- //knock backs
- const angle = Math.atan2(player.position.y - who.position.y, player.position.x - who.position.x);
- const mass = Math.min(Math.sqrt(who.mass), 4);
- Matter.Body.setVelocity(who, {
- x: player.velocity.x - (15 * Math.cos(angle)) / mass,
- y: player.velocity.y - (15 * Math.sin(angle)) / mass
- });
- Matter.Body.setVelocity(player, {
- x: player.velocity.x + 5 * Math.cos(angle) * mass,
- y: player.velocity.y + 5 * Math.sin(angle) * mass
- });
- }
- },
- pushMobsFacing() { // find mobs in range and in direction looking
- for (let i = 0, len = mob.length; i < len; ++i) {
- if (
- Matter.Vector.magnitude(Matter.Vector.sub(mob[i].position, this.pos)) < this.grabRange &&
- this.lookingAt(mob[i]) &&
- Matter.Query.ray(map, mob[i].position, this.pos).length === 0
- ) {
- mob[i].locatePlayer();
- mech.pushMass(mob[i]);
- }
- }
- },
- pushMobs360(range = this.grabRange * 0.75) { // find mobs in range in any direction
- for (let i = 0, len = mob.length; i < len; ++i) {
- if (
- Matter.Vector.magnitude(Matter.Vector.sub(mob[i].position, this.pos)) < range &&
- Matter.Query.ray(map, mob[i].position, this.pos).length === 0
- ) {
- mob[i].locatePlayer();
- mech.pushMass(mob[i]);
- }
- }
- },
- pushBodyFacing() { // push all body in range and in direction looking
- for (let i = 0, len = body.length; i < len; ++i) {
- if (
- body[i].speed > 12 && body[i].mass > 2 &&
- Matter.Vector.magnitude(Matter.Vector.sub(body[i].position, this.pos)) < this.grabRange &&
- this.lookingAt(body[i]) &&
- Matter.Query.ray(map, body[i].position, this.pos).length === 0
- ) {
- mech.pushMass(body[i]);
- }
- }
- },
- pushBody360(range = this.grabRange * 0.75) { // push all body in range and in direction looking
- for (let i = 0, len = body.length; i < len; ++i) {
- if (
- body[i].speed > 12 && body[i].mass > 2 &&
- Matter.Vector.magnitude(Matter.Vector.sub(body[i].position, this.pos)) < range &&
- this.lookingAt(body[i]) &&
- Matter.Query.ray(map, body[i].position, this.pos).length === 0 &&
- body[i].collisionFilter.category === 0x010000
- ) {
- mech.pushMass(body[i]);
- }
- }
- },
- lookForPickUp(range = this.grabRange) { //find body to pickup
- this.fieldMeter -= this.fieldRegen;
- const grabbing = {
- targetIndex: null,
- targetRange: range,
- // lookingAt: false //false to pick up object in range, but not looking at
- };
- for (let i = 0, len = body.length; i < len; ++i) {
- if (Matter.Query.ray(map, body[i].position, this.pos).length === 0) {
- //is this next body a better target then my current best
- const dist = Matter.Vector.magnitude(Matter.Vector.sub(body[i].position, this.pos));
- const looking = this.lookingAt(body[i]);
- // if (dist < grabbing.targetRange && (looking || !grabbing.lookingAt) && !body[i].isNotHoldable) {
- if (dist < grabbing.targetRange && looking && !body[i].isNotHoldable) {
- grabbing.targetRange = dist;
- grabbing.targetIndex = i;
- // grabbing.lookingAt = looking;
- }
- }
- }
- // set pick up target for when mouse is released
- if (body[grabbing.targetIndex]) {
- this.holdingTarget = body[grabbing.targetIndex];
- //
- ctx.beginPath(); //draw on each valid body
- let vertices = this.holdingTarget.vertices;
- ctx.moveTo(vertices[0].x, vertices[0].y);
- for (let j = 1; j < vertices.length; j += 1) {
- ctx.lineTo(vertices[j].x, vertices[j].y);
- }
- ctx.lineTo(vertices[0].x, vertices[0].y);
- ctx.fillStyle = "rgba(190,215,230," + (0.3 + 0.7 * Math.random()) + ")";
- ctx.fill();
-
- ctx.globalAlpha = 0.2;
- this.drawHold(this.holdingTarget);
- ctx.globalAlpha = 1;
- } else {
- this.holdingTarget = null;
- }
- },
- pickUp() {
- //triggers when a hold target exits and field button is released
- this.isHolding = true;
- this.definePlayerMass(mech.defaultMass + this.holdingTarget.mass * this.holdingMassScale)
- //collide with nothing
- this.holdingTarget.collisionFilter.category = 0x000000;
- this.holdingTarget.collisionFilter.mask = 0x000000;
- // if (this.holdingTarget) {
- // this.holdingTarget.collisionFilter.category = 0x010000;
- // this.holdingTarget.collisionFilter.mask = 0x011111;
- // }
- // combine momentum // this doesn't feel right in game
- // const px = player.velocity.x * player.mass + this.holdingTarget.velocity.x * this.holdingTarget.mass;
- // const py = player.velocity.y * player.mass + this.holdingTarget.velocity.y * this.holdingTarget.mass;
- // Matter.Body.setVelocity(player, {
- // x: px / (player.mass + this.holdingTarget.mass),
- // y: py / (player.mass + this.holdingTarget.mass)
- // });
- },
- wakeCheck() {
- if (mech.isBodiesAsleep) {
- mech.isBodiesAsleep = false;
-
- function wake(who) {
- for (let i = 0, len = who.length; i < len; ++i) {
- Matter.Sleeping.set(who[i], false)
- if (who[i].storeVelocity) {
- Matter.Body.setVelocity(who[i], {
- x: who[i].storeVelocity.x,
- y: who[i].storeVelocity.y
- })
- Matter.Body.setAngularVelocity(who[i], who[i].storeAngularVelocity)
- }
- }
- }
- wake(mob);
- wake(body);
- wake(bullet);
- for (let i = 0, len = cons.length; i < len; i++) {
- if (cons[i].stiffness === 0) {
- cons[i].stiffness = cons[i].storeStiffness
- }
- }
- // wake(powerUp);
- }
- },
- hold() {},
- fieldText() {
- game.replaceTextLog = true;
- game.makeTextLog(`${game.SVGrightMouse} ${mech.fieldUpgrades[mech.fieldMode].name}
${mech.fieldUpgrades[mech.fieldMode].description}`, 1000);
- game.replaceTextLog = false;
- document.getElementById("field").innerHTML = mech.fieldUpgrades[mech.fieldMode].name //add field
- },
- fieldUpgrades: [{
- name: "field emitter",
- description: "use energy to shield yourself from damage lets you pick up and throw objects",
- effect: () => {
- mech.fieldMode = 0;
- mech.fieldText();
- game.replaceTextLog = true; //allow text over write
- // game.makeTextLog(" (right click or space bar)", 1200);
- mech.setHoldDefaults();
- mech.hold = function () {
- if (mech.isHolding) {
- mech.drawHold(mech.holdingTarget);
- mech.holding();
- mech.throw();
- } else if ((keys[32] || game.mouseDownRight && mech.fieldMeter > 0.1)) { //not hold but field button is pressed
- mech.drawField();
- mech.grabPowerUp();
- mech.pushMobsFacing();
- mech.pushBodyFacing();
- mech.lookForPickUp();
- } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //holding, but field button is released
- mech.pickUp();
- } else {
- mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists)
- }
- mech.drawFieldMeter()
- }
- }
- },
- {
- name: "time dilation field",
- description: "use energy to stop time can fire bullets while field is active",
- effect: () => {
- mech.fieldMode = 1;
- mech.fieldText();
- mech.setHoldDefaults();
- mech.fieldFire = true;
- mech.grabRange = 130
- mech.isBodiesAsleep = false;
- mech.hold = function () {
- if (mech.isHolding) {
- mech.wakeCheck();
- mech.drawHold(mech.holdingTarget);
- mech.holding();
- mech.throw();
- } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) {
- const DRAIN = 0.0027
- if (mech.fieldMeter > DRAIN) {
- mech.fieldMeter -= DRAIN;
-
- //draw field everywhere
-
- ctx.globalCompositeOperation = "saturation"
- // ctx.fillStyle = "rgba(100,200,230," + (0.25 + 0.06 * Math.random()) + ")";
- ctx.fillStyle = "#ccc";
- ctx.fillRect(-100000, -100000, 200000, 200000)
- ctx.globalCompositeOperation = "source-over"
- //stop time
- mech.isBodiesAsleep = true;
-
- function sleep(who) {
- for (let i = 0, len = who.length; i < len; ++i) {
- if (!who[i].isSleeping) {
- who[i].storeVelocity = who[i].velocity
- who[i].storeAngularVelocity = who[i].angularVelocity
- }
- Matter.Sleeping.set(who[i], true)
- }
- }
- sleep(mob);
- sleep(body);
- sleep(bullet);
- //doesn't really work, just slows down constraints
- for (let i = 0, len = cons.length; i < len; i++) {
- if (cons[i].stiffness !== 0) {
- cons[i].storeStiffness = cons[i].stiffness;
- cons[i].stiffness = 0;
- }
- }
- game.cycle--; //pause all functions that depend on game cycle increasing
-
- mech.grabPowerUp();
- mech.lookForPickUp(180);
- } else {
- mech.wakeCheck();
- mech.fieldCDcycle = mech.cycle + 120;
- }
- } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //holding, but field button is released
- mech.wakeCheck();
- mech.pickUp();
- } else {
- mech.wakeCheck();
- mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists)
- }
- mech.drawFieldMeter()
- if (mech.fieldMode !== 1) {
- //wake up if this is no longer the current field mode, like after a new power up
- mech.wakeCheck();
-
- }
- }
- }
- },
- {
- name: "plasma torch",
- description: "use energy to emit damaging plasma decreasedshield range and efficiency",
- effect: () => {
- mech.fieldMode = 2;
- mech.fieldText();
- mech.setHoldDefaults();
- // mech.fieldShieldingScale = 2;
- // mech.grabRange = 125;
- mech.fieldArc = 0.1 //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob)
- mech.calculateFieldThreshold(); //run after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob)
- mech.hold = function () {
- if (mech.isHolding) {
- mech.drawHold(mech.holdingTarget);
- mech.holding();
- mech.throw();
- } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) { //not hold but field button is pressed
- const DRAIN = 0.0006
- if (mech.fieldMeter > DRAIN) {
- mech.fieldMeter -= DRAIN;
-
- //calculate laser collision
- let best;
- let range = 80 + (mech.crouch ? 500 : 300) * Math.sqrt(Math.random()) //+ 100 * Math.sin(mech.cycle * 0.3);
- const dir = mech.angle // + 0.04 * (Math.random() - 0.5)
- const path = [{
- x: mech.pos.x + 20 * Math.cos(dir),
- y: mech.pos.y + 20 * Math.sin(dir)
- },
- {
- x: mech.pos.x + range * Math.cos(dir),
- y: mech.pos.y + range * Math.sin(dir)
- }
- ];
- const vertexCollision = function (v1, v1End, domain) {
- for (let i = 0; i < domain.length; ++i) {
- let vertices = domain[i].vertices;
- const len = vertices.length - 1;
- for (let j = 0; j < len; j++) {
- results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]);
- if (results.onLine1 && results.onLine2) {
- const dx = v1.x - results.x;
- const dy = v1.y - results.y;
- const dist2 = dx * dx + dy * dy;
- if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) {
- best = {
- x: results.x,
- y: results.y,
- dist2: dist2,
- who: domain[i],
- v1: vertices[j],
- v2: vertices[j + 1]
- };
- }
- }
- }
- results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]);
- if (results.onLine1 && results.onLine2) {
- const dx = v1.x - results.x;
- const dy = v1.y - results.y;
- const dist2 = dx * dx + dy * dy;
- if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) {
- best = {
- x: results.x,
- y: results.y,
- dist2: dist2,
- who: domain[i],
- v1: vertices[0],
- v2: vertices[len]
- };
- }
- }
- }
- };
-
- //check for collisions
- best = {
- x: null,
- y: null,
- dist2: Infinity,
- who: null,
- v1: null,
- v2: null
- };
- vertexCollision(path[0], path[1], mob);
- vertexCollision(path[0], path[1], map);
- vertexCollision(path[0], path[1], body);
- if (best.dist2 != Infinity) { //if hitting something
- path[path.length - 1] = {
- x: best.x,
- y: best.y
- };
- if (best.who.alive) {
- const dmg = 0.35 * b.dmgScale; //********** SCALE DAMAGE HERE *********************
- best.who.damage(dmg);
- best.who.locatePlayer();
-
- //push mobs away
- const force = Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(mech.pos, path[1])), -0.01 * Math.sqrt(best.who.mass))
- Matter.Body.applyForce(best.who, path[1], force)
- // const angle = Math.atan2(player.position.y - best.who.position.y, player.position.x - best.who.position.x);
- // const mass = Math.min(Math.sqrt(best.who.mass), 6);
- // Matter.Body.setVelocity(best.who, {
- // x: best.who.velocity.x * 0.85 - 3 * Math.cos(angle) / mass,
- // y: best.who.velocity.y * 0.85 - 3 * Math.sin(angle) / mass
- // });
-
- //draw mob damage circle
- game.drawList.push({
- x: path[1].x,
- y: path[1].y,
- radius: Math.sqrt(dmg) * 50,
- color: "rgba(255,0,255,0.2)",
- time: game.drawTime * 4
- });
- } else if (!best.who.isStatic) {
- //push blocks away
- const force = Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(mech.pos, path[1])), -0.006 * Math.sqrt(Math.sqrt(best.who.mass)))
- Matter.Body.applyForce(best.who, path[1], force)
- }
- }
-
- //draw blowtorch laser beam
- ctx.strokeStyle = "rgba(255,0,255,0.1)"
- ctx.lineWidth = 14
- ctx.beginPath();
- ctx.moveTo(path[0].x, path[0].y);
- ctx.lineTo(path[1].x, path[1].y);
- ctx.stroke();
- ctx.strokeStyle = "#f0f";
- ctx.lineWidth = 2
- ctx.beginPath();
- ctx.moveTo(path[0].x, path[0].y);
- ctx.lineTo(path[1].x, path[1].y);
- ctx.stroke();
-
- //draw electricity
- const Dx = Math.cos(mech.angle);
- const Dy = Math.sin(mech.angle);
- let x = mech.pos.x + 20 * Dx;
- let y = mech.pos.y + 20 * Dy;
- ctx.beginPath();
- ctx.moveTo(x, y);
- const step = range / 10
- for (let i = 0; i < 8; i++) {
- x += step * (Dx + 1.5 * (Math.random() - 0.5))
- y += step * (Dy + 1.5 * (Math.random() - 0.5))
- ctx.lineTo(x, y);
- }
- ctx.lineWidth = 2 * Math.random();
- ctx.stroke();
-
- mech.pushMobs360(110);
- // mech.pushBody360(100); //disabled because doesn't work at short range
- mech.grabPowerUp();
- mech.lookForPickUp();
- } else {
- mech.fieldCDcycle = mech.cycle + 120; //if out of energy
- }
- } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //holding, but field button is released
- mech.pickUp();
- } else {
- mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists)
- }
- mech.drawFieldMeter()
- }
- }
- },
- {
- name: "negative mass field",
- description: "use energy to nullify gravity can fire bullets while active",
- effect: () => {
- mech.fieldMode = 3;
- mech.fieldText();
- mech.setHoldDefaults();
- mech.fieldFire = true;
-
- mech.hold = function () {
- if (mech.isHolding) {
- mech.drawHold(mech.holdingTarget);
- mech.holding();
- mech.throw();
- } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) { //push away
- const DRAIN = 0.0004
- if (mech.fieldMeter > DRAIN) {
- mech.pushMobs360(170);
- mech.pushBody360(180);
- mech.grabPowerUp();
- mech.lookForPickUp(170);
- //look for nearby objects to make zero-g
- function zeroG(who, mag = 1.06) {
- for (let i = 0, len = who.length; i < len; ++i) {
- sub = Matter.Vector.sub(who[i].position, mech.pos);
- dist = Matter.Vector.magnitude(sub);
- if (dist < mech.grabRange) {
- who[i].force.y -= who[i].mass * (game.g * mag); //add a bit more then standard gravity
- }
- }
- }
- // zeroG(bullet); //works fine, but not that noticeable and maybe not worth the possible performance hit
- // zeroG(mob); //mobs are too irregular to make this work?
-
- Matter.Body.setVelocity(player, {
- x: player.velocity.x,
- y: player.velocity.y * 0.97
- });
-
- if (keys[83] || keys[40]) { //down
- player.force.y -= 0.8 * player.mass * mech.gravity;
- mech.grabRange = mech.grabRange * 0.97 + 400 * 0.03;
- zeroG(powerUp, 0.85);
- zeroG(body, 0.85);
- } else if (keys[87] || keys[38]) { //up
- mech.fieldMeter -= 5 * DRAIN;
- mech.grabRange = mech.grabRange * 0.97 + 750 * 0.03;
- player.force.y -= 1.2 * player.mass * mech.gravity;
- zeroG(powerUp, 1.13);
- zeroG(body, 1.13);
- } else {
- mech.fieldMeter -= DRAIN;
- mech.grabRange = mech.grabRange * 0.97 + 650 * 0.03;
- player.force.y -= 1.07 * player.mass * mech.gravity; // slow upward drift
- zeroG(powerUp);
- zeroG(body);
- }
-
- //add extra friction for horizontal motion
- if (keys[65] || keys[68] || keys[37] || keys[39]) {
- Matter.Body.setVelocity(player, {
- x: player.velocity.x * 0.85,
- y: player.velocity.y
- });
- }
-
- //draw zero-G range
- ctx.beginPath();
- ctx.arc(mech.pos.x, mech.pos.y, mech.grabRange, 0, 2 * Math.PI);
- ctx.fillStyle = "#f5f5ff";
- ctx.globalCompositeOperation = "difference";
- ctx.fill();
- ctx.globalCompositeOperation = "source-over";
- } else {
- //trigger cool down
- mech.fieldCDcycle = mech.cycle + 120;
- }
- } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //holding, but field button is released
- mech.pickUp();
- mech.grabRange = 0
- } else {
- mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists)
- mech.grabRange = 0
- }
- mech.drawFieldMeter()
- }
- }
- },
- {
- name: "standing wave harmonics",
- description: "oscillating shields surround you constantly decreasedenergy regeneration",
- effect: () => {
- mech.fieldMode = 4;
- mech.fieldText();
- mech.setHoldDefaults();
- mech.fieldRegen *= 0.3;
-
- mech.hold = function () {
- if (mech.isHolding) {
- mech.drawHold(mech.holdingTarget);
- mech.holding();
- mech.throw();
- } else if ((keys[32] || game.mouseDownRight && mech.fieldMeter > 0.1)) { //not hold but field button is pressed
- mech.grabPowerUp();
- mech.lookForPickUp(180);
- } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle) { //holding, but field button is released
- mech.pickUp();
- } else {
- mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists)
- }
- if (mech.fieldMeter > 0.1) {
- const grabRange1 = 90 + 60 * Math.sin(mech.cycle / 23)
- const grabRange2 = 85 + 70 * Math.sin(mech.cycle / 37)
- const grabRange3 = 80 + 80 * Math.sin(mech.cycle / 47)
- const netGrabRange = Math.max(grabRange1, grabRange2, grabRange3)
- ctx.fillStyle = "rgba(110,170,200," + (0.04 + mech.fieldMeter * (0.12 + 0.13 * Math.random())) + ")";
- ctx.beginPath();
- ctx.arc(mech.pos.x, mech.pos.y, grabRange1, 0, 2 * Math.PI);
- ctx.fill();
- ctx.beginPath();
- ctx.arc(mech.pos.x, mech.pos.y, grabRange2, 0, 2 * Math.PI);
- ctx.fill();
- ctx.beginPath();
- ctx.arc(mech.pos.x, mech.pos.y, grabRange3, 0, 2 * Math.PI);
- ctx.fill();
- mech.pushMobs360(netGrabRange);
- mech.pushBody360(netGrabRange);
- }
- mech.drawFieldMeter()
- }
- }
- },
- {
- name: "nano-scale manufacturing",
- description: "excess energy used to build drones 3xenergy regeneration",
- effect: () => {
- let gunIndex = 13 //Math.random() < 0.5 ? 13 : 14
- mech.fieldMode = 5;
- mech.fieldText();
- mech.setHoldDefaults();
- mech.fieldRegen *= 3;
- mech.hold = function () {
- if (mech.fieldMeter === 1) {
- mech.fieldMeter -= 0.43;
- b.guns[gunIndex].fire() //spawn drone
- mech.fireCDcycle = mech.cycle + 25; // set fire cool down to prevent +energy from making huge numbers of drones
- }
- if (mech.isHolding) {
- mech.drawHold(mech.holdingTarget);
- mech.holding();
- mech.throw();
- } else if ((keys[32] || game.mouseDownRight && mech.fieldMeter > 0.1)) { //not hold but field button is pressed
- mech.pushMobsFacing();
- mech.pushBodyFacing();
- mech.drawField();
- mech.grabPowerUp();
- mech.lookForPickUp();
- } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //holding, but field button is released
- mech.pickUp();
- } else {
- mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists)
- }
- mech.drawFieldMeter()
- }
- }
- },
- {
- name: "phase decoherence field",
- description: "use energy to to become intangible can't see or be seen outside field",
- effect: () => {
- mech.fieldMode = 6;
- mech.fieldText();
- mech.setHoldDefaults();
- // mech.grabRange = 230
- mech.hold = function () {
- mech.isStealth = false //isStealth is checked in mob foundPlayer()
- player.collisionFilter.mask = 0x010011 //0x010011 is normal
- if (mech.isHolding) {
- mech.drawHold(mech.holdingTarget);
- mech.holding();
- mech.throw();
- } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) {
- const DRAIN = 0.0015
- if (mech.fieldMeter > DRAIN) {
- mech.fieldMeter -= DRAIN;
-
- mech.isStealth = true //isStealth is checked in mob foundPlayer()
- player.collisionFilter.mask = 0x000001 //0x010011 is normals
-
- ctx.beginPath();
- ctx.arc(mech.pos.x, mech.pos.y, mech.grabRange, 0, 2 * Math.PI);
- ctx.globalCompositeOperation = "destination-in"; //in or atop
- ctx.fillStyle = `rgba(255,255,255,${mech.fieldMeter*0.5})`;
- ctx.fill();
- ctx.globalCompositeOperation = "source-over";
- ctx.strokeStyle = "#000"
- ctx.lineWidth = 2;
- ctx.stroke();
-
- mech.grabPowerUp();
- mech.lookForPickUp(110);
- } else {
- mech.fieldCDcycle = mech.cycle + 120;
- }
- } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //holding, but field button is released
- mech.pickUp();
- } else {
- mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists)
- }
- mech.drawFieldMeter()
- }
- }
- },
- ],
+//global player variables for use in matter.js physics
+let player, jumpSensor, playerBody, playerHead, headSensor;
+
+// player Object Prototype *********************************************
+const mech = {
+ spawn() {
+ //load player in matter.js physic engine
+ // let vector = Vertices.fromPath("0 40 50 40 50 115 0 115 30 130 20 130"); //player as a series of vertices
+ let vertices = Vertices.fromPath("0,40, 50,40, 50,115, 30,130, 20,130, 0,115, 0,40"); //player as a series of vertices
+ playerBody = Matter.Bodies.fromVertices(0, 0, vertices);
+ jumpSensor = Bodies.rectangle(0, 46, 36, 6, {
+ //this sensor check if the player is on the ground to enable jumping
+ sleepThreshold: 99999999999,
+ isSensor: true
+ });
+ vertices = Vertices.fromPath("16 -82 2 -66 2 -37 43 -37 43 -66 30 -82");
+ playerHead = Matter.Bodies.fromVertices(0, -55, vertices); //this part of the player lowers on crouch
+ headSensor = Bodies.rectangle(0, -57, 48, 45, {
+ //senses if the player's head is empty and can return after crouching
+ sleepThreshold: 99999999999,
+ isSensor: true
+ });
+ player = Body.create({
+ //combine jumpSensor and playerBody
+ parts: [playerBody, playerHead, jumpSensor, headSensor],
+ inertia: Infinity, //prevents player rotation
+ friction: 0.002,
+ frictionAir: 0.001,
+ //frictionStatic: 0.5,
+ restitution: 0,
+ sleepThreshold: Infinity,
+ collisionFilter: {
+ group: 0,
+ category: 0x001000,
+ mask: 0x010011
+ },
+ death() {
+ mech.death();
+ }
+ });
+ Matter.Body.setMass(player, mech.mass);
+ World.add(engine.world, [player]);
+
+ mech.holdConstraint = Constraint.create({
+ //holding body constraint
+ pointA: {
+ x: 0,
+ y: 0
+ },
+ bodyB: jumpSensor, //setting constraint to jump sensor because it has to be on something until the player picks up things
+ stiffness: 0.4
+ });
+ World.add(engine.world, mech.holdConstraint);
+ },
+ cycle: 0,
+ width: 50,
+ radius: 30,
+ fillColor: "#fff",
+ fillColorDark: "#ccc",
+ height: 42,
+ yOffWhen: {
+ crouch: 22,
+ stand: 49,
+ jump: 70
+ },
+ defaultMass: 5,
+ mass: 5,
+ FxNotHolding: 0.015,
+ Fx: 0.015, //run Force on ground //this is reset in b.setModDefaults()
+ FxAir: 0.015, //run Force in Air
+ yOff: 70,
+ yOffGoal: 70,
+ onGround: false, //checks if on ground or in air
+ standingOn: undefined,
+ numTouching: 0,
+ crouch: false,
+ isHeadClear: true,
+ spawnPos: {
+ x: 0,
+ y: 0
+ },
+ spawnVel: {
+ x: 0,
+ y: 0
+ },
+ pos: {
+ x: 0,
+ y: 0
+ },
+ setPosToSpawn(xPos, yPos) {
+ this.spawnPos.x = this.pos.x = xPos;
+ this.spawnPos.y = this.pos.y = yPos;
+ this.transX = this.transSmoothX = canvas.width2 - this.pos.x;
+ this.transY = this.transSmoothY = canvas.height2 - this.pos.y;
+ this.Vx = this.spawnVel.x;
+ this.Vy = this.spawnVel.y;
+ player.force.x = 0;
+ player.force.y = 0;
+ Matter.Body.setPosition(player, this.spawnPos);
+ Matter.Body.setVelocity(player, this.spawnVel);
+ },
+ Sy: 0, //adds a smoothing effect to vertical only
+ Vx: 0,
+ Vy: 0,
+ jumpForce: 0.38, //this is reset in b.setModDefaults()
+ gravity: 0.0019,
+ friction: {
+ ground: 0.01,
+ air: 0.0025
+ },
+ angle: 0,
+ walk_cycle: 0,
+ stepSize: 0,
+ flipLegs: -1,
+ hip: {
+ x: 12,
+ y: 24
+ },
+ knee: {
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 0
+ },
+ foot: {
+ x: 0,
+ y: 0
+ },
+ legLength1: 55,
+ legLength2: 45,
+ transX: 0,
+ transY: 0,
+ move() {
+ this.pos.x = player.position.x;
+ this.pos.y = playerBody.position.y - this.yOff;
+ this.Vx = player.velocity.x;
+ this.Vy = player.velocity.y;
+ },
+ transSmoothX: 0,
+ transSmoothY: 0,
+ lastGroundedPositionY: 0,
+ // mouseZoom: 0,
+ look() {
+ //always on mouse look
+ this.angle = Math.atan2(
+ game.mouseInGame.y - this.pos.y,
+ game.mouseInGame.x - this.pos.x
+ );
+ //smoothed mouse look translations
+ const scale = 0.8;
+ this.transSmoothX = canvas.width2 - this.pos.x - (game.mouse.x - canvas.width2) * scale;
+ this.transSmoothY = canvas.height2 - this.pos.y - (game.mouse.y - canvas.height2) * scale;
+
+ this.transX += (this.transSmoothX - this.transX) * 0.07;
+ this.transY += (this.transSmoothY - this.transY) * 0.07;
+ },
+ doCrouch() {
+ if (!this.crouch) {
+ this.crouch = true;
+ this.yOffGoal = this.yOffWhen.crouch;
+ Matter.Body.translate(playerHead, {
+ x: 0,
+ y: 40
+ });
+ }
+ },
+ undoCrouch() {
+ if (this.crouch) {
+ this.crouch = false;
+ this.yOffGoal = this.yOffWhen.stand;
+ Matter.Body.translate(playerHead, {
+ x: 0,
+ y: -40
+ });
+ }
+ },
+ hardLandCD: 0,
+ enterAir() {
+ //triggered in engine.js on collision
+ this.onGround = false;
+ this.hardLandCD = 0 // disable hard landing
+ if (this.isHeadClear) {
+ if (this.crouch) {
+ this.undoCrouch();
+ }
+ this.yOffGoal = this.yOffWhen.jump;
+ }
+ },
+ //triggered in engine.js on collision
+ enterLand() {
+ this.onGround = true;
+ if (this.crouch) {
+ if (this.isHeadClear) {
+ this.undoCrouch();
+ } else {
+ this.yOffGoal = this.yOffWhen.crouch;
+ }
+ } else {
+ //sets a hard land where player stays in a crouch for a bit and can't jump
+ //crouch is forced in keyMove() on ground section below
+ const momentum = player.velocity.y * player.mass //player mass is 5 so this triggers at 20 down velocity, unless the player is holding something
+ if (momentum > 100) {
+ this.doCrouch();
+ this.yOff = this.yOffWhen.jump;
+ this.hardLandCD = mech.cycle + Math.min(momentum / 6 - 6, 40)
+
+ if (game.isBodyDamage && player.velocity.y > 26 && momentum > 165) { //falling damage
+ mech.damageImmune = mech.cycle + 30; //player is immune to collision damage for 30 cycles
+ let dmg = Math.sqrt(momentum - 165) * 0.01
+ dmg = Math.min(Math.max(dmg, 0.02), 0.20);
+ mech.damage(dmg);
+ }
+ } else {
+ this.yOffGoal = this.yOffWhen.stand;
+ }
+ }
+ },
+ buttonCD_jump: 0, //cool down for player buttons
+ keyMove() {
+ if (this.onGround) { //on ground **********************
+ if (this.crouch) {
+ if (!(keys[83] || keys[40]) && this.isHeadClear && this.hardLandCD < mech.cycle) this.undoCrouch();
+ } else if (keys[83] || keys[40] || this.hardLandCD > mech.cycle) {
+ this.doCrouch(); //on ground && not crouched and pressing s or down
+ } else if ((keys[87] || keys[38]) && this.buttonCD_jump + 20 < mech.cycle && this.yOffWhen.stand > 23) {
+ this.buttonCD_jump = mech.cycle; //can't jump again until 20 cycles pass
+
+ //apply a fraction of the jump force to the body the player is jumping off of
+ Matter.Body.applyForce(mech.standingOn, mech.pos, {
+ x: 0,
+ y: this.jumpForce * 0.12 * Math.min(mech.standingOn.mass, 5)
+ });
+
+ player.force.y = -this.jumpForce; //player jump force
+ Matter.Body.setVelocity(player, { //zero player y-velocity for consistent jumps
+ x: player.velocity.x,
+ y: 0
+ });
+ }
+
+ //horizontal move on ground
+ //apply a force to move
+ if (keys[65] || keys[37]) { //left / a
+ if (player.velocity.x > -2) {
+ player.force.x -= this.Fx * 1.5
+ } else {
+ player.force.x -= this.Fx
+ }
+ } else if (keys[68] || keys[39]) { //right / d
+ if (player.velocity.x < 2) {
+ player.force.x += this.Fx * 1.5
+ } else {
+ player.force.x += this.Fx
+ }
+ } else {
+ const stoppingFriction = 0.92;
+ Matter.Body.setVelocity(player, {
+ x: player.velocity.x * stoppingFriction,
+ y: player.velocity.y * stoppingFriction
+ });
+ }
+ //come to a stop if fast or if no move key is pressed
+ if (player.speed > 4) {
+ const stoppingFriction = (this.crouch) ? 0.65 : 0.89; // this controls speed when crouched
+ Matter.Body.setVelocity(player, {
+ x: player.velocity.x * stoppingFriction,
+ y: player.velocity.y * stoppingFriction
+ });
+ }
+
+ } else { // in air **********************************
+ //check for short jumps
+ if (
+ this.buttonCD_jump + 60 > mech.cycle && //just pressed jump
+ !(keys[87] || keys[38]) && //but not pressing jump key
+ this.Vy < 0 //moving up
+ ) {
+ Matter.Body.setVelocity(player, {
+ //reduce player y-velocity every cycle
+ x: player.velocity.x,
+ y: player.velocity.y * 0.94
+ });
+ }
+ const limit = 125 / player.mass / player.mass
+ if (keys[65] || keys[37]) {
+ if (player.velocity.x > -limit) player.force.x -= this.FxAir; // move player left / a
+ } else if (keys[68] || keys[39]) {
+ if (player.velocity.x < limit) player.force.x += this.FxAir; //move player right / d
+ }
+ }
+
+ //smoothly move leg height towards height goal
+ this.yOff = this.yOff * 0.85 + this.yOffGoal * 0.15;
+ },
+ alive: true,
+ death() {
+ if (b.modIsImmortal) { //if player has the immortality buff, spawn on the same level with randomized stats
+ spawn.setSpawnList(); //new mob types
+ game.clearNow = true; //triggers a map reset
+
+ //count mods
+ let totalMods = -2; //lose the immortality mod and one more, so -2
+ for (let i = 0; i < b.mods.length; i++) {
+ if (b.mods[i].have) totalMods++
+ }
+
+ function randomizeMods() {
+ b.setModDefaults(); //remove all mods
+ for (let i = 0; i < totalMods; i++) {
+ //find what mods I don't have
+ let options = [];
+ for (let i = 0; i < b.mods.length; i++) {
+ //can't get quantum immortality again
+ if (i !== 7 && !b.mods[i].have) options.push(i);
+ }
+ //add a new mod
+ if (options.length > 0) {
+ const choose = Math.floor(Math.random() * options.length)
+ let newMod = options[choose]
+ b.giveMod(newMod)
+ options.splice(choose, 1);
+ }
+ }
+ game.updateModHUD();
+ }
+
+ function randomizeField() {
+ if (game.difficulty * (Math.random() + 0.27) > 2) {
+ mech.fieldUpgrades[Math.floor(Math.random() * (mech.fieldUpgrades.length))].effect();
+ } else {
+ mech.fieldUpgrades[0].effect();
+ }
+ }
+
+ function randomizeHealth() {
+ mech.health = 0.5 + Math.random()
+ if (mech.health > 1) mech.health = 1;
+ mech.displayHealth();
+ }
+
+ function randomizeGuns() {
+ const length = Math.round(b.inventory.length * (1 + 0.4 * (Math.random() - 0.5)))
+ //removes guns and ammo
+ b.inventory = [];
+ b.activeGun = null;
+ b.inventoryGun = 0;
+ for (let i = 0, len = b.guns.length; i < len; ++i) {
+ b.guns[i].have = false;
+ if (b.guns[i].ammo !== Infinity) b.guns[i].ammo = 0;
+ }
+ for (let i = 0; i < length; i++) {
+ powerUps.gun.effect();
+ }
+
+ //randomize ammo
+ for (let i = 0, len = b.inventory.length; i < len; i++) {
+ if (b.guns[b.inventory[i]].ammo !== Infinity) {
+ b.guns[b.inventory[i]].ammo = Math.max(0, Math.floor(6 * b.guns[b.inventory[i]].ammo * (Math.random() - 0.3)))
+ }
+ }
+ game.makeGunHUD(); //update gun HUD
+ }
+
+ game.wipe = function () { //set wipe to have trails
+ ctx.fillStyle = "rgba(255,255,255,0)";
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ }
+ randomizeMods()
+ randomizeGuns()
+ randomizeField()
+ randomizeHealth()
+ for (let i = 0, len = 7; i < len; i++) {
+ setTimeout(function () {
+ randomizeMods()
+ randomizeGuns()
+ randomizeField()
+ randomizeHealth()
+ game.replaceTextLog = true;
+ game.makeTextLog(`probability amplitude will synchronize in ${len-i-1} seconds`, 1000);
+ game.wipe = function () { //set wipe to have trails
+ ctx.fillStyle = `rgba(255,255,255,${(i+1)*(i+1)*0.006})`;
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ }
+ }, (i + 1) * 1000);
+ }
+
+ setTimeout(function () {
+ game.wipe = function () { //set wipe to normal
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ }
+ game.replaceTextLog = true;
+ game.makeTextLog("your quantum probability has stabilized", 1000);
+ document.title = "n-gon: L" + (game.difficulty) + " " + level.levels[level.onLevel];
+ }, 8000);
+
+ } else if (this.alive) { //normal death code here
+ this.alive = false;
+ game.paused = true;
+ this.health = 0;
+ this.displayHealth();
+ document.getElementById("text-log").style.opacity = 0; //fade out any active text logs
+ document.getElementById("fade-out").style.opacity = 1; //slowly fades out
+ setTimeout(function () {
+ game.splashReturn();
+ }, 3000);
+ }
+ },
+ health: 0,
+ drawHealth() {
+ if (this.health < 1) {
+ ctx.fillStyle = "rgba(100, 100, 100, 0.5)";
+ ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, 60, 10);
+ ctx.fillStyle = "#f00";
+ ctx.fillRect(
+ this.pos.x - this.radius,
+ this.pos.y - 50,
+ 60 * this.health,
+ 10
+ );
+ }
+ },
+ displayHealth() {
+ id = document.getElementById("health");
+ id.style.width = Math.floor(300 * this.health) + "px";
+ //css animation blink if health is low
+ if (this.health < 0.3) {
+ id.classList.add("low-health");
+ } else {
+ id.classList.remove("low-health");
+ }
+ },
+ addHealth(heal) {
+ this.health += heal;
+ if (this.health > 1 || b.isModFullHeal) this.health = 1;
+ this.displayHealth();
+ },
+ defaultFPSCycle: 0, //tracks when to return to normal fps
+ damage(dmg) {
+ if (b.isModMonogamy && b.inventory[0] === b.activeGun) {
+ for (let i = 0, len = b.inventory.length; i < len; i++) {
+ dmg *= 0.93
+ }
+ }
+ this.health -= dmg;
+ if (this.health < 0) {
+ this.health = 0;
+ this.death();
+ return;
+ }
+ this.displayHealth();
+ document.getElementById("dmg").style.transition = "opacity 0s";
+ document.getElementById("dmg").style.opacity = 0.1 + Math.min(0.6, dmg * 4);
+
+ //chance to build a drone on damage from mod
+ if (b.isModDroneOnDamage) {
+ const len = (dmg - 0.08 + 0.05 * Math.random()) / 0.05
+ for (let i = 0; i < len; i++) {
+ if (Math.random() < 0.6) b.guns[13].fire() //spawn drone
+ }
+ }
+
+ // freeze game and display a full screen red color
+ if (dmg > 0.05) {
+ this.drop(); //drop block if holding
+ game.fpsCap = 4 //40 - Math.min(25, 100 * dmg)
+ game.fpsInterval = 1000 / game.fpsCap;
+ } else {
+ game.fpsCap = game.fpsCapDefault
+ game.fpsInterval = 1000 / game.fpsCap;
+ }
+ mech.defaultFPSCycle = mech.cycle
+
+ const normalFPS = function () {
+ if (mech.defaultFPSCycle < mech.cycle) { //back to default values
+ game.fpsCap = game.fpsCapDefault
+ game.fpsInterval = 1000 / game.fpsCap;
+ document.getElementById("dmg").style.transition = "opacity 1s";
+ document.getElementById("dmg").style.opacity = "0";
+ } else {
+ requestAnimationFrame(normalFPS);
+ }
+ };
+ requestAnimationFrame(normalFPS);
+
+ // // freeze game and display a full screen red color
+ // if (dmg > 0.05) {
+ // if (dmg > 0.07) {
+ // this.drop(); //drop block if holding
+ // }
+
+ // game.fpsCap = 4 //40 - Math.min(25, 100 * dmg)
+ // game.fpsInterval = 1000 / game.fpsCap;
+ // } else {
+ // game.fpsCap = game.fpsCapDefault
+ // game.fpsInterval = 1000 / game.fpsCap;
+ // }
+ // mech.defaultFPSCycle = mech.cycle
+
+ // const normalFPS = function () {
+ // if (mech.defaultFPSCycle < mech.cycle) { //back to default values
+ // game.fpsCap = game.fpsCapDefault
+ // game.fpsInterval = 1000 / game.fpsCap;
+ // document.getElementById("dmg").style.transition = "opacity 1s";
+ // document.getElementById("dmg").style.opacity = "0";
+ // } else {
+ // requestAnimationFrame(normalFPS);
+ // }
+ // };
+ // requestAnimationFrame(normalFPS);
+ },
+ damageImmune: 0,
+ hitMob(i, dmg) {
+ //prevents damage happening too quick
+ },
+ buttonCD: 0, //cool down for player buttons
+ usePowerUp(i) {
+ powerUp[i].effect();
+ Matter.World.remove(engine.world, powerUp[i]);
+ powerUp.splice(i, 1);
+ },
+ drawLeg(stroke) {
+ // if (game.mouseInGame.x > this.pos.x) {
+ if (mech.angle > -Math.PI / 2 && mech.angle < Math.PI / 2) {
+ this.flipLegs = 1;
+ } else {
+ this.flipLegs = -1;
+ }
+ ctx.save();
+ ctx.scale(this.flipLegs, 1); //leg lines
+ ctx.beginPath();
+ ctx.moveTo(this.hip.x, this.hip.y);
+ ctx.lineTo(this.knee.x, this.knee.y);
+ ctx.lineTo(this.foot.x, this.foot.y);
+ ctx.strokeStyle = stroke;
+ ctx.lineWidth = 7;
+ ctx.stroke();
+
+ //toe lines
+ ctx.beginPath();
+ ctx.moveTo(this.foot.x, this.foot.y);
+ ctx.lineTo(this.foot.x - 15, this.foot.y + 5);
+ ctx.moveTo(this.foot.x, this.foot.y);
+ ctx.lineTo(this.foot.x + 15, this.foot.y + 5);
+ ctx.lineWidth = 4;
+ ctx.stroke();
+
+ //hip joint
+ ctx.beginPath();
+ ctx.arc(this.hip.x, this.hip.y, 11, 0, 2 * Math.PI);
+ //knee joint
+ ctx.moveTo(this.knee.x + 7, this.knee.y);
+ ctx.arc(this.knee.x, this.knee.y, 7, 0, 2 * Math.PI);
+ //foot joint
+ ctx.moveTo(this.foot.x + 6, this.foot.y);
+ ctx.arc(this.foot.x, this.foot.y, 6, 0, 2 * Math.PI);
+ ctx.fillStyle = this.fillColor;
+ ctx.fill();
+ ctx.lineWidth = 2;
+ ctx.stroke();
+ ctx.restore();
+ },
+ calcLeg(cycle_offset, offset) {
+ this.hip.x = 12 + offset;
+ this.hip.y = 24 + offset;
+ //stepSize goes to zero if Vx is zero or not on ground (make this transition cleaner)
+ this.stepSize = 0.8 * this.stepSize + 0.2 * (7 * Math.sqrt(Math.min(9, Math.abs(this.Vx))) * this.onGround);
+ //changes to stepsize are smoothed by adding only a percent of the new value each cycle
+ const stepAngle = 0.034 * this.walk_cycle + cycle_offset;
+ this.foot.x = 2.2 * this.stepSize * Math.cos(stepAngle) + offset;
+ this.foot.y = offset + 1.2 * this.stepSize * Math.sin(stepAngle) + this.yOff + this.height;
+ const Ymax = this.yOff + this.height;
+ if (this.foot.y > Ymax) this.foot.y = Ymax;
+
+ //calculate knee position as intersection of circle from hip and foot
+ const d = Math.sqrt((this.hip.x - this.foot.x) * (this.hip.x - this.foot.x) + (this.hip.y - this.foot.y) * (this.hip.y - this.foot.y));
+ const l = (this.legLength1 * this.legLength1 - this.legLength2 * this.legLength2 + d * d) / (2 * d);
+ const h = Math.sqrt(this.legLength1 * this.legLength1 - l * l);
+ this.knee.x = (l / d) * (this.foot.x - this.hip.x) - (h / d) * (this.foot.y - this.hip.y) + this.hip.x + offset;
+ this.knee.y = (l / d) * (this.foot.y - this.hip.y) + (h / d) * (this.foot.x - this.hip.x) + this.hip.y;
+ },
+ draw() {
+ ctx.fillStyle = this.fillColor;
+ this.walk_cycle += this.flipLegs * this.Vx;
+
+ //draw body
+ ctx.save();
+ ctx.translate(this.pos.x, this.pos.y);
+ this.calcLeg(Math.PI, -3);
+ this.drawLeg("#4a4a4a");
+ this.calcLeg(0, 0);
+ this.drawLeg("#333");
+ ctx.rotate(this.angle);
+
+ ctx.beginPath();
+ ctx.arc(0, 0, 30, 0, 2 * Math.PI);
+ let grd = ctx.createLinearGradient(-30, 0, 30, 0);
+ grd.addColorStop(0, this.fillColorDark);
+ grd.addColorStop(1, this.fillColor);
+ ctx.fillStyle = grd;
+ ctx.fill();
+ ctx.arc(15, 0, 4, 0, 2 * Math.PI);
+ ctx.strokeStyle = "#333";
+ ctx.lineWidth = 2;
+ ctx.stroke();
+ // ctx.beginPath();
+ // ctx.arc(15, 0, 3, 0, 2 * Math.PI);
+ // ctx.fillStyle = '#9cf' //'#0cf';
+ // ctx.fill()
+ ctx.restore();
+ },
+ // *********************************************
+ // **************** holding ********************
+ // *********************************************
+ closest: {
+ dist: 1000,
+ index: 0
+ },
+ isHolding: false,
+ isStealth: false,
+ throwCharge: 0,
+ fireCDcycle: 0,
+ fieldCDcycle: 0,
+ fieldMode: 0, //basic field mode before upgrades
+ // these values are set on reset by setHoldDefaults()
+ fieldMeter: 0,
+ fieldRegen: 0,
+ fieldMode: 0,
+ fieldFire: false,
+ holdingMassScale: 0,
+ throwChargeRate: 0,
+ throwChargeMax: 0,
+ fieldFireCD: 0,
+ fieldShieldingScale: 0,
+ grabRange: 0,
+ fieldArc: 0,
+ fieldThreshold: 0,
+ calculateFieldThreshold() {
+ this.fieldThreshold = Math.cos(this.fieldArc * Math.PI)
+ },
+ setHoldDefaults() {
+ this.fieldMeter = 1;
+ this.fieldRegen = 0.001;
+ this.fieldFire = false;
+ this.fieldCDcycle = 0;
+ this.isStealth = false;
+ player.collisionFilter.mask = 0x010011 //0x010011 is normal
+ this.holdingMassScale = 0.5;
+ this.fieldFireCD = 15;
+ this.fieldShieldingScale = 1; //scale energy loss after collision with mob
+ this.grabRange = 175;
+ this.fieldArc = 0.2; //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob)
+ this.calculateFieldThreshold(); //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob)
+ mech.isBodiesAsleep = true;
+ mech.wakeCheck();
+ // this.phaseBlocks(0x011111)
+ },
+ drawFieldMeter(range = 60) {
+ if (this.fieldMeter < 1) {
+ mech.fieldMeter += mech.fieldRegen;
+ ctx.fillStyle = "rgba(0, 0, 0, 0.4)";
+ ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, range, 10);
+ ctx.fillStyle = "#0cf";
+ ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, range * this.fieldMeter, 10);
+ } else {
+ mech.fieldMeter = 1
+ }
+ },
+ lookingAt(who) {
+ //calculate a vector from body to player and make it length 1
+ const diff = Matter.Vector.normalise(Matter.Vector.sub(who.position, mech.pos));
+ //make a vector for the player's direction of length 1
+ const dir = {
+ x: Math.cos(mech.angle),
+ y: Math.sin(mech.angle)
+ };
+ //the dot product of diff and dir will return how much over lap between the vectors
+ // console.log(Matter.Vector.dot(dir, diff))
+ if (Matter.Vector.dot(dir, diff) > this.fieldThreshold) {
+ return true;
+ }
+ return false;
+ },
+ drop() {
+ if (this.isHolding) {
+ this.isHolding = false;
+ this.definePlayerMass()
+ this.holdingTarget.collisionFilter.category = 0x010000;
+ this.holdingTarget.collisionFilter.mask = 0x011111;
+ this.holdingTarget = null;
+ this.throwCharge = 0;
+ }
+ },
+ definePlayerMass(mass = mech.defaultMass) {
+ Matter.Body.setMass(player, mass);
+ //reduce air and ground move forces
+ this.Fx = 0.075 / mass * b.modSquirrelFx
+ this.FxAir = 0.375 / mass / mass
+ //make player stand a bit lower when holding heavy masses
+ this.yOffWhen.stand = Math.max(this.yOffWhen.crouch, Math.min(49, 49 - (mass - 5) * 6))
+ if (this.onGround && !this.crouch) this.yOffGoal = this.yOffWhen.stand;
+ },
+ drawHold(target, stroke = true) {
+ const eye = 15;
+ const len = target.vertices.length - 1;
+ ctx.fillStyle = "rgba(110,170,200," + (0.2 + 0.4 * Math.random()) + ")";
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = "#000";
+ ctx.beginPath();
+ ctx.moveTo(
+ mech.pos.x + eye * Math.cos(this.angle),
+ mech.pos.y + eye * Math.sin(this.angle)
+ );
+ ctx.lineTo(target.vertices[len].x, target.vertices[len].y);
+ ctx.lineTo(target.vertices[0].x, target.vertices[0].y);
+ ctx.fill();
+ if (stroke) ctx.stroke();
+ for (let i = 0; i < len; i++) {
+ ctx.beginPath();
+ ctx.moveTo(
+ mech.pos.x + eye * Math.cos(this.angle),
+ mech.pos.y + eye * Math.sin(this.angle)
+ );
+ ctx.lineTo(target.vertices[i].x, target.vertices[i].y);
+ ctx.lineTo(target.vertices[i + 1].x, target.vertices[i + 1].y);
+ ctx.fill();
+ if (stroke) ctx.stroke();
+ }
+ },
+ holding() {
+ this.fieldMeter -= this.fieldRegen;
+ if (this.fieldMeter < 0) this.fieldMeter = 0;
+ Matter.Body.setPosition(this.holdingTarget, {
+ x: mech.pos.x + 70 * Math.cos(this.angle),
+ y: mech.pos.y + 70 * Math.sin(this.angle)
+ });
+ Matter.Body.setVelocity(this.holdingTarget, player.velocity);
+ Matter.Body.rotate(this.holdingTarget, 0.01 / this.holdingTarget.mass); //gently spin the block
+ },
+ throw () {
+ if ((keys[32] || game.mouseDownRight)) {
+ if (this.fieldMeter > 0.0007) {
+ this.fieldMeter -= 0.0007;
+ this.throwCharge += this.throwChargeRate;;
+ //draw charge
+ const x = mech.pos.x + 15 * Math.cos(this.angle);
+ const y = mech.pos.y + 15 * Math.sin(this.angle);
+ const len = this.holdingTarget.vertices.length - 1;
+ const edge = this.throwCharge * this.throwCharge * 0.02;
+ const grd = ctx.createRadialGradient(x, y, edge, x, y, edge + 5);
+ grd.addColorStop(0, "rgba(255,50,150,0.3)");
+ grd.addColorStop(1, "transparent");
+ ctx.fillStyle = grd;
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ ctx.lineTo(this.holdingTarget.vertices[len].x, this.holdingTarget.vertices[len].y);
+ ctx.lineTo(this.holdingTarget.vertices[0].x, this.holdingTarget.vertices[0].y);
+ ctx.fill();
+ for (let i = 0; i < len; i++) {
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ ctx.lineTo(this.holdingTarget.vertices[i].x, this.holdingTarget.vertices[i].y);
+ ctx.lineTo(this.holdingTarget.vertices[i + 1].x, this.holdingTarget.vertices[i + 1].y);
+ ctx.fill();
+ }
+ } else {
+ this.drop()
+ }
+ } else if (this.throwCharge > 0) {
+ //throw the body
+ this.fireCDcycle = mech.cycle + this.fieldFireCD;
+ this.isHolding = false;
+ //bullet-like collisions
+ this.holdingTarget.collisionFilter.category = 0x000100;
+ this.holdingTarget.collisionFilter.mask = 0x110111;
+ //check every second to see if player is away from thrown body, and make solid
+ const solid = function (that) {
+ const dx = that.position.x - player.position.x;
+ const dy = that.position.y - player.position.y;
+ if (dx * dx + dy * dy > 10000 && that.speed < 3 && that !== mech.holdingTarget) {
+ that.collisionFilter.category = 0x010000; //make solid
+ that.collisionFilter.mask = 0x011111;
+ } else {
+ setTimeout(solid, 50, that);
+ }
+ };
+ setTimeout(solid, 200, this.holdingTarget);
+ //throw speed scales a bit with mass
+ const speed = Math.min(85, Math.min(54 / this.holdingTarget.mass + 5, 48) * Math.min(this.throwCharge, this.throwChargeMax) / 50);
+
+ this.throwCharge = 0;
+ Matter.Body.setVelocity(this.holdingTarget, {
+ x: player.velocity.x + Math.cos(this.angle) * speed,
+ y: player.velocity.y + Math.sin(this.angle) * speed
+ });
+ //player recoil //stronger in x-dir to prevent jump hacking
+ Matter.Body.setVelocity(player, {
+ x: player.velocity.x - Math.cos(this.angle) * speed / 20 * Math.sqrt(this.holdingTarget.mass),
+ y: player.velocity.y - Math.sin(this.angle) * speed / 80 * Math.sqrt(this.holdingTarget.mass)
+ });
+ this.definePlayerMass() //return to normal player mass
+ }
+ },
+ drawField() {
+ if (mech.holdingTarget) {
+ ctx.fillStyle = "rgba(110,170,200," + (mech.fieldMeter * (0.05 + 0.05 * Math.random())) + ")";
+ ctx.strokeStyle = "rgba(110, 200, 235, " + (0.3 + 0.08 * Math.random()) + ")" //"#9bd" //"rgba(110, 200, 235, " + (0.5 + 0.1 * Math.random()) + ")"
+ } else {
+ ctx.fillStyle = "rgba(110,170,200," + (0.02 + mech.fieldMeter * (0.15 + 0.15 * Math.random())) + ")";
+ ctx.strokeStyle = "rgba(110, 200, 235, " + (0.6 + 0.2 * Math.random()) + ")" //"#9bd" //"rgba(110, 200, 235, " + (0.5 + 0.1 * Math.random()) + ")"
+ }
+ // const off = 2 * Math.cos(game.cycle * 0.1)
+ const range = this.grabRange - 20;
+ ctx.beginPath();
+ ctx.arc(mech.pos.x, mech.pos.y, range, mech.angle - Math.PI * mech.fieldArc, mech.angle + Math.PI * mech.fieldArc, false);
+ ctx.lineWidth = 2;
+ ctx.lineCap = "butt"
+ ctx.stroke();
+ let eye = 13;
+ let aMag = 0.75 * Math.PI * mech.fieldArc
+ let a = mech.angle + aMag
+ let cp1x = mech.pos.x + 0.6 * range * Math.cos(a)
+ let cp1y = mech.pos.y + 0.6 * range * Math.sin(a)
+ ctx.quadraticCurveTo(cp1x, cp1y, mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle))
+ a = mech.angle - aMag
+ cp1x = mech.pos.x + 0.6 * range * Math.cos(a)
+ cp1y = mech.pos.y + 0.6 * range * Math.sin(a)
+ ctx.quadraticCurveTo(cp1x, cp1y, mech.pos.x + 1 * range * Math.cos(mech.angle - Math.PI * mech.fieldArc), mech.pos.y + 1 * range * Math.sin(mech.angle - Math.PI * mech.fieldArc))
+ ctx.fill();
+ // ctx.lineTo(mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle));
+
+ //draw random lines in field for cool effect
+ let offAngle = mech.angle + 1.7 * Math.PI * mech.fieldArc * (Math.random() - 0.5);
+ ctx.beginPath();
+ eye = 15;
+ ctx.moveTo(mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle));
+ ctx.lineTo(mech.pos.x + range * Math.cos(offAngle), mech.pos.y + range * Math.sin(offAngle));
+ ctx.strokeStyle = "rgba(120,170,255,0.6)";
+ ctx.lineWidth = 1;
+ ctx.stroke();
+ },
+ grabPowerUp() { //look for power ups to grab with field
+ if (mech.fieldCDcycle < mech.cycle) {
+ const grabPowerUpRange2 = (this.grabRange + 220) * (this.grabRange + 220)
+ for (let i = 0, len = powerUp.length; i < len; ++i) {
+ const dxP = mech.pos.x - powerUp[i].position.x;
+ const dyP = mech.pos.y - powerUp[i].position.y;
+ const dist2 = dxP * dxP + dyP * dyP;
+ // float towards player if looking at and in range or if very close to player
+ if (dist2 < grabPowerUpRange2 && this.lookingAt(powerUp[i]) || dist2 < 16000) {
+ if (dist2 < 5000) { //use power up if it is close enough
+ Matter.Body.setVelocity(player, { //player knock back, after grabbing power up
+ x: player.velocity.x + ((powerUp[i].velocity.x * powerUp[i].mass) / player.mass) * 0.3,
+ y: player.velocity.y + ((powerUp[i].velocity.y * powerUp[i].mass) / player.mass) * 0.3
+ });
+ mech.usePowerUp(i);
+ return;
+ }
+ this.fieldMeter -= this.fieldRegen * 0.5;
+ powerUp[i].force.x += 7 * (dxP / dist2) * powerUp[i].mass;
+ powerUp[i].force.y += 7 * (dyP / dist2) * powerUp[i].mass - powerUp[i].mass * game.g; //negate gravity
+ //extra friction
+ Matter.Body.setVelocity(powerUp[i], {
+ x: powerUp[i].velocity.x * 0.11,
+ y: powerUp[i].velocity.y * 0.11
+ });
+ }
+ }
+ }
+ },
+ pushMass(who) {
+ const fieldBlockCost = Math.max(0.02, who.mass * 0.012) //0.012
+ if (this.fieldMeter > fieldBlockCost) {
+ this.fieldMeter -= fieldBlockCost * this.fieldShieldingScale;
+ if (this.fieldMeter < 0) this.fieldMeter = 0;
+ this.drawHold(who);
+ //knock backs
+ const angle = Math.atan2(player.position.y - who.position.y, player.position.x - who.position.x);
+ const mass = Math.min(Math.sqrt(who.mass), 4);
+ Matter.Body.setVelocity(who, {
+ x: player.velocity.x - (15 * Math.cos(angle)) / mass,
+ y: player.velocity.y - (15 * Math.sin(angle)) / mass
+ });
+ Matter.Body.setVelocity(player, {
+ x: player.velocity.x + 5 * Math.cos(angle) * mass,
+ y: player.velocity.y + 5 * Math.sin(angle) * mass
+ });
+ }
+ },
+ pushMobsFacing() { // find mobs in range and in direction looking
+ for (let i = 0, len = mob.length; i < len; ++i) {
+ if (
+ Matter.Vector.magnitude(Matter.Vector.sub(mob[i].position, this.pos)) < this.grabRange &&
+ this.lookingAt(mob[i]) &&
+ Matter.Query.ray(map, mob[i].position, this.pos).length === 0
+ ) {
+ mob[i].locatePlayer();
+ mech.pushMass(mob[i]);
+ }
+ }
+ },
+ pushMobs360(range = this.grabRange * 0.75) { // find mobs in range in any direction
+ for (let i = 0, len = mob.length; i < len; ++i) {
+ if (
+ Matter.Vector.magnitude(Matter.Vector.sub(mob[i].position, this.pos)) < range &&
+ Matter.Query.ray(map, mob[i].position, this.pos).length === 0
+ ) {
+ mob[i].locatePlayer();
+ mech.pushMass(mob[i]);
+ }
+ }
+ },
+ pushBodyFacing() { // push all body in range and in direction looking
+ for (let i = 0, len = body.length; i < len; ++i) {
+ if (
+ body[i].speed > 12 && body[i].mass > 2 &&
+ Matter.Vector.magnitude(Matter.Vector.sub(body[i].position, this.pos)) < this.grabRange &&
+ this.lookingAt(body[i]) &&
+ Matter.Query.ray(map, body[i].position, this.pos).length === 0
+ ) {
+ mech.pushMass(body[i]);
+ }
+ }
+ },
+ pushBody360(range = this.grabRange * 0.75) { // push all body in range and in direction looking
+ for (let i = 0, len = body.length; i < len; ++i) {
+ if (
+ body[i].speed > 12 && body[i].mass > 2 &&
+ Matter.Vector.magnitude(Matter.Vector.sub(body[i].position, this.pos)) < range &&
+ this.lookingAt(body[i]) &&
+ Matter.Query.ray(map, body[i].position, this.pos).length === 0 &&
+ body[i].collisionFilter.category === 0x010000
+ ) {
+ mech.pushMass(body[i]);
+ }
+ }
+ },
+ lookForPickUp(range = this.grabRange) { //find body to pickup
+ this.fieldMeter -= this.fieldRegen;
+ const grabbing = {
+ targetIndex: null,
+ targetRange: range,
+ // lookingAt: false //false to pick up object in range, but not looking at
+ };
+ for (let i = 0, len = body.length; i < len; ++i) {
+ if (Matter.Query.ray(map, body[i].position, this.pos).length === 0) {
+ //is this next body a better target then my current best
+ const dist = Matter.Vector.magnitude(Matter.Vector.sub(body[i].position, this.pos));
+ const looking = this.lookingAt(body[i]);
+ // if (dist < grabbing.targetRange && (looking || !grabbing.lookingAt) && !body[i].isNotHoldable) {
+ if (dist < grabbing.targetRange && looking && !body[i].isNotHoldable) {
+ grabbing.targetRange = dist;
+ grabbing.targetIndex = i;
+ // grabbing.lookingAt = looking;
+ }
+ }
+ }
+ // set pick up target for when mouse is released
+ if (body[grabbing.targetIndex]) {
+ this.holdingTarget = body[grabbing.targetIndex];
+ //
+ ctx.beginPath(); //draw on each valid body
+ let vertices = this.holdingTarget.vertices;
+ ctx.moveTo(vertices[0].x, vertices[0].y);
+ for (let j = 1; j < vertices.length; j += 1) {
+ ctx.lineTo(vertices[j].x, vertices[j].y);
+ }
+ ctx.lineTo(vertices[0].x, vertices[0].y);
+ ctx.fillStyle = "rgba(190,215,230," + (0.3 + 0.7 * Math.random()) + ")";
+ ctx.fill();
+
+ ctx.globalAlpha = 0.2;
+ this.drawHold(this.holdingTarget);
+ ctx.globalAlpha = 1;
+ } else {
+ this.holdingTarget = null;
+ }
+ },
+ pickUp() {
+ //triggers when a hold target exits and field button is released
+ this.isHolding = true;
+ this.definePlayerMass(mech.defaultMass + this.holdingTarget.mass * this.holdingMassScale)
+ //collide with nothing
+ this.holdingTarget.collisionFilter.category = 0x000000;
+ this.holdingTarget.collisionFilter.mask = 0x000000;
+ // if (this.holdingTarget) {
+ // this.holdingTarget.collisionFilter.category = 0x010000;
+ // this.holdingTarget.collisionFilter.mask = 0x011111;
+ // }
+ // combine momentum // this doesn't feel right in game
+ // const px = player.velocity.x * player.mass + this.holdingTarget.velocity.x * this.holdingTarget.mass;
+ // const py = player.velocity.y * player.mass + this.holdingTarget.velocity.y * this.holdingTarget.mass;
+ // Matter.Body.setVelocity(player, {
+ // x: px / (player.mass + this.holdingTarget.mass),
+ // y: py / (player.mass + this.holdingTarget.mass)
+ // });
+ },
+ wakeCheck() {
+ if (mech.isBodiesAsleep) {
+ mech.isBodiesAsleep = false;
+
+ function wake(who) {
+ for (let i = 0, len = who.length; i < len; ++i) {
+ Matter.Sleeping.set(who[i], false)
+ if (who[i].storeVelocity) {
+ Matter.Body.setVelocity(who[i], {
+ x: who[i].storeVelocity.x,
+ y: who[i].storeVelocity.y
+ })
+ Matter.Body.setAngularVelocity(who[i], who[i].storeAngularVelocity)
+ }
+ }
+ }
+ wake(mob);
+ wake(body);
+ wake(bullet);
+ for (let i = 0, len = cons.length; i < len; i++) {
+ if (cons[i].stiffness === 0) {
+ cons[i].stiffness = cons[i].storeStiffness
+ }
+ }
+ // wake(powerUp);
+ }
+ },
+ hold() {},
+ fieldText() {
+ game.replaceTextLog = true;
+ game.makeTextLog(`${game.SVGrightMouse} ${mech.fieldUpgrades[mech.fieldMode].name}
${mech.fieldUpgrades[mech.fieldMode].description}`, 1000);
+ game.replaceTextLog = false;
+ document.getElementById("field").innerHTML = mech.fieldUpgrades[mech.fieldMode].name //add field
+ },
+ fieldUpgrades: [{
+ name: "field emitter",
+ description: "use energy to shield yourself from damage lets you pick up and throw objects",
+ effect: () => {
+ mech.fieldMode = 0;
+ mech.fieldText();
+ game.replaceTextLog = true; //allow text over write
+ // game.makeTextLog(" (right click or space bar)", 1200);
+ mech.setHoldDefaults();
+ mech.hold = function () {
+ if (mech.isHolding) {
+ mech.drawHold(mech.holdingTarget);
+ mech.holding();
+ mech.throw();
+ } else if ((keys[32] || game.mouseDownRight && mech.fieldMeter > 0.1)) { //not hold but field button is pressed
+ mech.drawField();
+ mech.grabPowerUp();
+ mech.pushMobsFacing();
+ mech.pushBodyFacing();
+ mech.lookForPickUp();
+ } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //holding, but field button is released
+ mech.pickUp();
+ } else {
+ mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists)
+ }
+ mech.drawFieldMeter()
+ }
+ }
+ },
+ {
+ name: "time dilation field",
+ description: "use energy to stop time can fire bullets while field is active",
+ effect: () => {
+ mech.fieldMode = 1;
+ mech.fieldText();
+ mech.setHoldDefaults();
+ mech.fieldFire = true;
+ mech.grabRange = 130
+ mech.isBodiesAsleep = false;
+ mech.hold = function () {
+ if (mech.isHolding) {
+ mech.wakeCheck();
+ mech.drawHold(mech.holdingTarget);
+ mech.holding();
+ mech.throw();
+ } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) {
+ const DRAIN = 0.0027
+ if (mech.fieldMeter > DRAIN) {
+ mech.fieldMeter -= DRAIN;
+
+ //draw field everywhere
+
+ ctx.globalCompositeOperation = "saturation"
+ // ctx.fillStyle = "rgba(100,200,230," + (0.25 + 0.06 * Math.random()) + ")";
+ ctx.fillStyle = "#ccc";
+ ctx.fillRect(-100000, -100000, 200000, 200000)
+ ctx.globalCompositeOperation = "source-over"
+ //stop time
+ mech.isBodiesAsleep = true;
+
+ function sleep(who) {
+ for (let i = 0, len = who.length; i < len; ++i) {
+ if (!who[i].isSleeping) {
+ who[i].storeVelocity = who[i].velocity
+ who[i].storeAngularVelocity = who[i].angularVelocity
+ }
+ Matter.Sleeping.set(who[i], true)
+ }
+ }
+ sleep(mob);
+ sleep(body);
+ sleep(bullet);
+ //doesn't really work, just slows down constraints
+ for (let i = 0, len = cons.length; i < len; i++) {
+ if (cons[i].stiffness !== 0) {
+ cons[i].storeStiffness = cons[i].stiffness;
+ cons[i].stiffness = 0;
+ }
+ }
+ game.cycle--; //pause all functions that depend on game cycle increasing
+
+ mech.grabPowerUp();
+ mech.lookForPickUp(180);
+ } else {
+ mech.wakeCheck();
+ mech.fieldCDcycle = mech.cycle + 120;
+ }
+ } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //holding, but field button is released
+ mech.wakeCheck();
+ mech.pickUp();
+ } else {
+ mech.wakeCheck();
+ mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists)
+ }
+ mech.drawFieldMeter()
+ if (mech.fieldMode !== 1) {
+ //wake up if this is no longer the current field mode, like after a new power up
+ mech.wakeCheck();
+
+ }
+ }
+ }
+ },
+ {
+ name: "plasma torch",
+ description: "use energy to emit damaging plasma decreasedshield range and efficiency",
+ effect: () => {
+ mech.fieldMode = 2;
+ mech.fieldText();
+ mech.setHoldDefaults();
+ // mech.fieldShieldingScale = 2;
+ // mech.grabRange = 125;
+ mech.fieldArc = 0.1 //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob)
+ mech.calculateFieldThreshold(); //run after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob)
+ mech.hold = function () {
+ if (mech.isHolding) {
+ mech.drawHold(mech.holdingTarget);
+ mech.holding();
+ mech.throw();
+ } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) { //not hold but field button is pressed
+ const DRAIN = 0.0006
+ if (mech.fieldMeter > DRAIN) {
+ mech.fieldMeter -= DRAIN;
+
+ //calculate laser collision
+ let best;
+ let range = 80 + (mech.crouch ? 500 : 300) * Math.sqrt(Math.random()) //+ 100 * Math.sin(mech.cycle * 0.3);
+ const dir = mech.angle // + 0.04 * (Math.random() - 0.5)
+ const path = [{
+ x: mech.pos.x + 20 * Math.cos(dir),
+ y: mech.pos.y + 20 * Math.sin(dir)
+ },
+ {
+ x: mech.pos.x + range * Math.cos(dir),
+ y: mech.pos.y + range * Math.sin(dir)
+ }
+ ];
+ const vertexCollision = function (v1, v1End, domain) {
+ for (let i = 0; i < domain.length; ++i) {
+ let vertices = domain[i].vertices;
+ const len = vertices.length - 1;
+ for (let j = 0; j < len; j++) {
+ results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]);
+ if (results.onLine1 && results.onLine2) {
+ const dx = v1.x - results.x;
+ const dy = v1.y - results.y;
+ const dist2 = dx * dx + dy * dy;
+ if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) {
+ best = {
+ x: results.x,
+ y: results.y,
+ dist2: dist2,
+ who: domain[i],
+ v1: vertices[j],
+ v2: vertices[j + 1]
+ };
+ }
+ }
+ }
+ results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]);
+ if (results.onLine1 && results.onLine2) {
+ const dx = v1.x - results.x;
+ const dy = v1.y - results.y;
+ const dist2 = dx * dx + dy * dy;
+ if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) {
+ best = {
+ x: results.x,
+ y: results.y,
+ dist2: dist2,
+ who: domain[i],
+ v1: vertices[0],
+ v2: vertices[len]
+ };
+ }
+ }
+ }
+ };
+
+ //check for collisions
+ best = {
+ x: null,
+ y: null,
+ dist2: Infinity,
+ who: null,
+ v1: null,
+ v2: null
+ };
+ vertexCollision(path[0], path[1], mob);
+ vertexCollision(path[0], path[1], map);
+ vertexCollision(path[0], path[1], body);
+ if (best.dist2 != Infinity) { //if hitting something
+ path[path.length - 1] = {
+ x: best.x,
+ y: best.y
+ };
+ if (best.who.alive) {
+ const dmg = 0.35 * b.dmgScale; //********** SCALE DAMAGE HERE *********************
+ best.who.damage(dmg);
+ best.who.locatePlayer();
+
+ //push mobs away
+ const force = Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(mech.pos, path[1])), -0.01 * Math.sqrt(best.who.mass))
+ Matter.Body.applyForce(best.who, path[1], force)
+ // const angle = Math.atan2(player.position.y - best.who.position.y, player.position.x - best.who.position.x);
+ // const mass = Math.min(Math.sqrt(best.who.mass), 6);
+ // Matter.Body.setVelocity(best.who, {
+ // x: best.who.velocity.x * 0.85 - 3 * Math.cos(angle) / mass,
+ // y: best.who.velocity.y * 0.85 - 3 * Math.sin(angle) / mass
+ // });
+
+ //draw mob damage circle
+ game.drawList.push({
+ x: path[1].x,
+ y: path[1].y,
+ radius: Math.sqrt(dmg) * 50,
+ color: "rgba(255,0,255,0.2)",
+ time: game.drawTime * 4
+ });
+ } else if (!best.who.isStatic) {
+ //push blocks away
+ const force = Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(mech.pos, path[1])), -0.006 * Math.sqrt(Math.sqrt(best.who.mass)))
+ Matter.Body.applyForce(best.who, path[1], force)
+ }
+ }
+
+ //draw blowtorch laser beam
+ ctx.strokeStyle = "rgba(255,0,255,0.1)"
+ ctx.lineWidth = 14
+ ctx.beginPath();
+ ctx.moveTo(path[0].x, path[0].y);
+ ctx.lineTo(path[1].x, path[1].y);
+ ctx.stroke();
+ ctx.strokeStyle = "#f0f";
+ ctx.lineWidth = 2
+ ctx.beginPath();
+ ctx.moveTo(path[0].x, path[0].y);
+ ctx.lineTo(path[1].x, path[1].y);
+ ctx.stroke();
+
+ //draw electricity
+ const Dx = Math.cos(mech.angle);
+ const Dy = Math.sin(mech.angle);
+ let x = mech.pos.x + 20 * Dx;
+ let y = mech.pos.y + 20 * Dy;
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ const step = range / 10
+ for (let i = 0; i < 8; i++) {
+ x += step * (Dx + 1.5 * (Math.random() - 0.5))
+ y += step * (Dy + 1.5 * (Math.random() - 0.5))
+ ctx.lineTo(x, y);
+ }
+ ctx.lineWidth = 2 * Math.random();
+ ctx.stroke();
+
+ mech.pushMobs360(110);
+ // mech.pushBody360(100); //disabled because doesn't work at short range
+ mech.grabPowerUp();
+ mech.lookForPickUp();
+ } else {
+ mech.fieldCDcycle = mech.cycle + 120; //if out of energy
+ }
+ } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //holding, but field button is released
+ mech.pickUp();
+ } else {
+ mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists)
+ }
+ mech.drawFieldMeter()
+ }
+ }
+ },
+ {
+ name: "negative mass field",
+ description: "use energy to nullify gravity can fire bullets while active",
+ effect: () => {
+ mech.fieldMode = 3;
+ mech.fieldText();
+ mech.setHoldDefaults();
+ mech.fieldFire = true;
+
+ mech.hold = function () {
+ if (mech.isHolding) {
+ mech.drawHold(mech.holdingTarget);
+ mech.holding();
+ mech.throw();
+ } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) { //push away
+ const DRAIN = 0.0004
+ if (mech.fieldMeter > DRAIN) {
+ mech.pushMobs360(170);
+ mech.pushBody360(180);
+ mech.grabPowerUp();
+ mech.lookForPickUp(170);
+ //look for nearby objects to make zero-g
+ function zeroG(who, mag = 1.06) {
+ for (let i = 0, len = who.length; i < len; ++i) {
+ sub = Matter.Vector.sub(who[i].position, mech.pos);
+ dist = Matter.Vector.magnitude(sub);
+ if (dist < mech.grabRange) {
+ who[i].force.y -= who[i].mass * (game.g * mag); //add a bit more then standard gravity
+ }
+ }
+ }
+ // zeroG(bullet); //works fine, but not that noticeable and maybe not worth the possible performance hit
+ // zeroG(mob); //mobs are too irregular to make this work?
+
+ Matter.Body.setVelocity(player, {
+ x: player.velocity.x,
+ y: player.velocity.y * 0.97
+ });
+
+ if (keys[83] || keys[40]) { //down
+ player.force.y -= 0.8 * player.mass * mech.gravity;
+ mech.grabRange = mech.grabRange * 0.97 + 400 * 0.03;
+ zeroG(powerUp, 0.85);
+ zeroG(body, 0.85);
+ } else if (keys[87] || keys[38]) { //up
+ mech.fieldMeter -= 5 * DRAIN;
+ mech.grabRange = mech.grabRange * 0.97 + 750 * 0.03;
+ player.force.y -= 1.2 * player.mass * mech.gravity;
+ zeroG(powerUp, 1.13);
+ zeroG(body, 1.13);
+ } else {
+ mech.fieldMeter -= DRAIN;
+ mech.grabRange = mech.grabRange * 0.97 + 650 * 0.03;
+ player.force.y -= 1.07 * player.mass * mech.gravity; // slow upward drift
+ zeroG(powerUp);
+ zeroG(body);
+ }
+
+ //add extra friction for horizontal motion
+ if (keys[65] || keys[68] || keys[37] || keys[39]) {
+ Matter.Body.setVelocity(player, {
+ x: player.velocity.x * 0.85,
+ y: player.velocity.y
+ });
+ }
+
+ //draw zero-G range
+ ctx.beginPath();
+ ctx.arc(mech.pos.x, mech.pos.y, mech.grabRange, 0, 2 * Math.PI);
+ ctx.fillStyle = "#f5f5ff";
+ ctx.globalCompositeOperation = "difference";
+ ctx.fill();
+ ctx.globalCompositeOperation = "source-over";
+ } else {
+ //trigger cool down
+ mech.fieldCDcycle = mech.cycle + 120;
+ }
+ } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //holding, but field button is released
+ mech.pickUp();
+ mech.grabRange = 0
+ } else {
+ mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists)
+ mech.grabRange = 0
+ }
+ mech.drawFieldMeter()
+ }
+ }
+ },
+ {
+ name: "standing wave harmonics",
+ description: "oscillating shields surround you constantly decreasedenergy regeneration",
+ effect: () => {
+ mech.fieldMode = 4;
+ mech.fieldText();
+ mech.setHoldDefaults();
+ mech.fieldRegen *= 0.3;
+
+ mech.hold = function () {
+ if (mech.isHolding) {
+ mech.drawHold(mech.holdingTarget);
+ mech.holding();
+ mech.throw();
+ } else if ((keys[32] || game.mouseDownRight && mech.fieldMeter > 0.1)) { //not hold but field button is pressed
+ mech.grabPowerUp();
+ mech.lookForPickUp(180);
+ } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle) { //holding, but field button is released
+ mech.pickUp();
+ } else {
+ mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists)
+ }
+ if (mech.fieldMeter > 0.1) {
+ const grabRange1 = 90 + 60 * Math.sin(mech.cycle / 23)
+ const grabRange2 = 85 + 70 * Math.sin(mech.cycle / 37)
+ const grabRange3 = 80 + 80 * Math.sin(mech.cycle / 47)
+ const netGrabRange = Math.max(grabRange1, grabRange2, grabRange3)
+ ctx.fillStyle = "rgba(110,170,200," + (0.04 + mech.fieldMeter * (0.12 + 0.13 * Math.random())) + ")";
+ ctx.beginPath();
+ ctx.arc(mech.pos.x, mech.pos.y, grabRange1, 0, 2 * Math.PI);
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(mech.pos.x, mech.pos.y, grabRange2, 0, 2 * Math.PI);
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(mech.pos.x, mech.pos.y, grabRange3, 0, 2 * Math.PI);
+ ctx.fill();
+ mech.pushMobs360(netGrabRange);
+ mech.pushBody360(netGrabRange);
+ }
+ mech.drawFieldMeter()
+ }
+ }
+ },
+ {
+ name: "nano-scale manufacturing",
+ description: "excess energy used to build drones 3xenergy regeneration",
+ effect: () => {
+ let gunIndex = 13 //Math.random() < 0.5 ? 13 : 14
+ mech.fieldMode = 5;
+ mech.fieldText();
+ mech.setHoldDefaults();
+ mech.fieldRegen *= 3;
+ mech.hold = function () {
+ if (mech.fieldMeter === 1) {
+ mech.fieldMeter -= 0.43;
+ b.guns[gunIndex].fire() //spawn drone
+ mech.fireCDcycle = mech.cycle + 25; // set fire cool down to prevent +energy from making huge numbers of drones
+ }
+ if (mech.isHolding) {
+ mech.drawHold(mech.holdingTarget);
+ mech.holding();
+ mech.throw();
+ } else if ((keys[32] || game.mouseDownRight && mech.fieldMeter > 0.1)) { //not hold but field button is pressed
+ mech.pushMobsFacing();
+ mech.pushBodyFacing();
+ mech.drawField();
+ mech.grabPowerUp();
+ mech.lookForPickUp();
+ } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //holding, but field button is released
+ mech.pickUp();
+ } else {
+ mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists)
+ }
+ mech.drawFieldMeter()
+ }
+ }
+ },
+ {
+ name: "phase decoherence field",
+ description: "use energy to to become intangible can't see or be seen outside field",
+ effect: () => {
+ mech.fieldMode = 6;
+ mech.fieldText();
+ mech.setHoldDefaults();
+ // mech.grabRange = 230
+ mech.hold = function () {
+ mech.isStealth = false //isStealth is checked in mob foundPlayer()
+ player.collisionFilter.mask = 0x010011 //0x010011 is normal
+ if (mech.isHolding) {
+ mech.drawHold(mech.holdingTarget);
+ mech.holding();
+ mech.throw();
+ } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) {
+ const DRAIN = 0.0015
+ if (mech.fieldMeter > DRAIN) {
+ mech.fieldMeter -= DRAIN;
+
+ mech.isStealth = true //isStealth is checked in mob foundPlayer()
+ player.collisionFilter.mask = 0x000001 //0x010011 is normals
+
+ ctx.beginPath();
+ ctx.arc(mech.pos.x, mech.pos.y, mech.grabRange, 0, 2 * Math.PI);
+ ctx.globalCompositeOperation = "destination-in"; //in or atop
+ ctx.fillStyle = `rgba(255,255,255,${mech.fieldMeter*0.5})`;
+ ctx.fill();
+ ctx.globalCompositeOperation = "source-over";
+ ctx.strokeStyle = "#000"
+ ctx.lineWidth = 2;
+ ctx.stroke();
+
+ mech.grabPowerUp();
+ mech.lookForPickUp(110);
+ } else {
+ mech.fieldCDcycle = mech.cycle + 120;
+ }
+ } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //holding, but field button is released
+ mech.pickUp();
+ } else {
+ mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists)
+ }
+ mech.drawFieldMeter()
+ }
+ }
+ },
+ ],
};
\ No newline at end of file
diff --git a/js/powerups.js b/js/powerups.js
index 892acb9..d3cbed4 100644
--- a/js/powerups.js
+++ b/js/powerups.js
@@ -1,239 +1,239 @@
-let powerUp = [];
-
-const powerUps = {
- heal: {
- name: "heal",
- color: "#0fb",
- size() {
- return 40 * Math.sqrt(0.1 + Math.random() * 0.5);
- },
- effect() {
- let heal = (this.size / 40) ** 2
- if (b.fullHeal) heal = Infinity
- heal = Math.min(1 - mech.health, heal)
- mech.addHealth(heal);
- if (heal > 0) game.makeTextLog(" heal " + (heal * 100).toFixed(0) + "%", 300)
- }
- },
- ammo: {
- name: "ammo",
- color: "#467",
- size() {
- return 17;
- },
- effect() {
- //only get ammo for guns player has
- let target;
- // console.log(b.inventory.length)
- if (b.inventory.length > 0) {
- //add ammo to a gun in inventory
- target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]];
- //try 3 more times to give ammo to a gun with ammo, not Infinity
- if (target.ammo === Infinity) {
- target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]]
- if (target.ammo === Infinity) {
- target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]]
- if (target.ammo === Infinity) target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]]
- }
- }
- } else {
- //if you don't have any guns just add ammo to a random gun you don't have yet
- target = b.guns[Math.floor(Math.random() * b.guns.length)];
- }
- if (target.ammo === Infinity) {
- mech.fieldMeter = 1;
- if (!game.lastLogTime) game.makeTextLog("+energy", 300);
- } else {
- //ammo given scales as mobs take more hits to kill
- let ammo = Math.ceil((target.ammoPack * (0.45 + 0.06 * Math.random())) / Math.sqrt(b.dmgScale));
- if (level.isBuildRun) ammo = Math.floor(ammo * 1.2)
- target.ammo += ammo;
- game.updateGunHUD();
- game.makeTextLog(" +" + ammo + " ammo for " + target.name + "", 300);
- }
- }
- },
- field: {
- name: "field",
- color: "#0cf",
- size() {
- return 45;
- },
- effect() {
- const previousMode = mech.fieldMode
-
- if (!this.mode) { //this.mode is set if the power up has been ejected from player
- mode = mech.fieldMode
- while (mode === mech.fieldMode) {
- mode = Math.ceil(Math.random() * (mech.fieldUpgrades.length - 1))
- }
- mech.fieldUpgrades[mode].effect(); //choose random field upgrade that you don't already have
- } else {
- mech.fieldUpgrades[this.mode].effect(); //set a predetermined power up
- }
- //pop the old field out in case player wants to swap back
- if (previousMode !== 0) {
- mech.fieldCDcycle = mech.cycle + 40; //trigger fieldCD to stop power up grab automatic pick up of spawn
- powerUps.spawn(mech.pos.x, mech.pos.y - 15, "field", false, previousMode);
- }
- }
- },
- mod: {
- name: "mod",
- color: "#a8f",
- size() {
- return 42;
- },
- effect() {
- //find what mods I don't have
- let options = [];
- for (let i = 0; i < b.mods.length; i++) {
- if (!b.mods[i].have) options.push(i);
- }
- //give a random mod from the mods I don't have
- if (options.length > 0) {
- let newMod = options[Math.floor(Math.random() * options.length)]
- b.giveMod(newMod)
- game.replaceTextLog = true;
- game.makeTextLog(` ${b.mods[newMod].name}
${b.mods[newMod].description}`, 1000);
- game.replaceTextLog = false;
- }
- }
- },
- gun: {
- name: "gun",
- color: "#26a",
- size() {
- return 35;
- },
- effect() {
- //find what guns I don't have
- let options = [];
- if (b.activeGun === null && game.difficulty < 3) {
- //choose the first gun to be one that is good for the early game
- for (let i = 0; i < b.guns.length; ++i) {
- if (!b.guns[i].have && b.guns[i].isStarterGun) options.push(i);
- }
- } else {
- //choose a gun you don't have
- for (let i = 0; i < b.guns.length; ++i) {
- if (!b.guns[i].have) options.push(i);
- }
- }
- //give player a gun they don't already have if possible
- game.replaceTextLog = true;
- if (options.length > 0) {
- let newGun = options[Math.floor(Math.random() * options.length)];
- if (b.activeGun === null) b.activeGun = newGun //if no active gun switch to new gun
- game.makeTextLog(`${game.SVGleftMouse} ${b.guns[newGun].name}
${b.guns[newGun].description}`, 900);
- b.guns[newGun].have = true;
- b.inventory.push(newGun);
- b.guns[newGun].ammo += b.guns[newGun].ammoPack * 2;
- game.makeGunHUD();
- } else {
- //if you have all guns then get ammo
- const ammoTarget = Math.floor(Math.random() * (b.guns.length));
- const ammo = Math.ceil(b.guns[ammoTarget].ammoPack * 2);
- b.guns[ammoTarget].ammo += ammo;
- game.updateGunHUD();
- game.makeTextLog("+" + ammo + " ammo for " + b.guns[ammoTarget].name + "", 300);
- }
- game.replaceTextLog = false
- }
- },
- spawnRandomPowerUp(x, y) { //mostly used after mob dies
- if (Math.random() * Math.random() - 0.25 > Math.sqrt(mech.health) || Math.random() < 0.04) { //spawn heal chance is higher at low health
- powerUps.spawn(x, y, "heal");
- if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "heal");
- return;
- }
- if (Math.random() < 0.2 && b.inventory.length > 0) {
- powerUps.spawn(x, y, "ammo");
- if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "ammo");
- return;
- }
- if (Math.random() < 0.004 * (4 - b.inventory.length)) { //a new gun has a low chance for each not acquired gun to drop
- powerUps.spawn(x, y, "gun");
- if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "gun");
- return;
- }
- if (Math.random() < 0.004 * (7 - b.modCount)) {
- powerUps.spawn(x, y, "mod");
- if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "mod");
- return;
- }
- if (Math.random() < 0.005) {
- powerUps.spawn(x, y, "field");
- if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "field");
- return;
- }
- },
- spawnBossPowerUp(x, y) { //boss spawns field and gun mod upgrades
- if (mech.fieldMode === 0) {
- powerUps.spawn(x, y, "field")
- if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "field")
- } else if (Math.random() < 0.3) {
- powerUps.spawn(x, y, "mod")
- if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "mod")
- } else if (Math.random() < 0.3) {
- powerUps.spawn(x, y, "field");
- if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "field");
- } else if (Math.random() < 0.3) {
- powerUps.spawn(x, y, "gun")
- if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "gun")
- } else if (mech.health < 0.6) {
- powerUps.spawn(x, y, "heal");
- if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "heal");
- } else {
- powerUps.spawn(x, y, "ammo");
- if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "ammo");
- }
- },
- chooseRandomPowerUp(x, y) { //100% chance to drop a random power up //used in spawn.debris
- if (Math.random() < 0.5) {
- powerUps.spawn(x, y, "heal", false);
- } else {
- powerUps.spawn(x, y, "ammo", false);
- }
- },
- spawnStartingPowerUps(x, y) { //used for map specific power ups, mostly to give player a starting gun
- if (b.inventory.length < 2 || game.isEasyMode) {
- powerUps.spawn(x, y, "gun", false); //starting gun
- } else {
- powerUps.spawnRandomPowerUp(x, y);
- powerUps.spawnRandomPowerUp(x, y);
- powerUps.spawnRandomPowerUp(x, y);
- powerUps.spawnRandomPowerUp(x, y);
- }
- },
- spawn(x, y, target, moving = true, mode = null) {
- if (!level.isBuildRun || target === "heal" || target === "ammo") {
- let i = powerUp.length;
- target = powerUps[target];
- size = target.size();
- powerUp[i] = Matter.Bodies.polygon(x, y, 0, size, {
- density: 0.001,
- frictionAir: 0.01,
- restitution: 0.8,
- inertia: Infinity, //prevents rotation
- collisionFilter: {
- group: 0,
- category: 0x100000,
- mask: 0x100001
- },
- color: target.color,
- effect: target.effect,
- mode: mode,
- name: target.name,
- size: size
- });
- if (moving) {
- Matter.Body.setVelocity(powerUp[i], {
- x: (Math.random() - 0.5) * 15,
- y: Math.random() * -9 - 3
- });
- }
- World.add(engine.world, powerUp[i]); //add to world
- }
- },
+let powerUp = [];
+
+const powerUps = {
+ heal: {
+ name: "heal",
+ color: "#0fb",
+ size() {
+ return 40 * Math.sqrt(0.1 + Math.random() * 0.5);
+ },
+ effect() {
+ let heal = (this.size / 40) ** 2
+ if (b.fullHeal) heal = Infinity
+ heal = Math.min(1 - mech.health, heal)
+ mech.addHealth(heal);
+ if (heal > 0) game.makeTextLog(" heal " + (heal * 100).toFixed(0) + "%", 300)
+ }
+ },
+ ammo: {
+ name: "ammo",
+ color: "#467",
+ size() {
+ return 17;
+ },
+ effect() {
+ //only get ammo for guns player has
+ let target;
+ // console.log(b.inventory.length)
+ if (b.inventory.length > 0) {
+ //add ammo to a gun in inventory
+ target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]];
+ //try 3 more times to give ammo to a gun with ammo, not Infinity
+ if (target.ammo === Infinity) {
+ target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]]
+ if (target.ammo === Infinity) {
+ target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]]
+ if (target.ammo === Infinity) target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]]
+ }
+ }
+ } else {
+ //if you don't have any guns just add ammo to a random gun you don't have yet
+ target = b.guns[Math.floor(Math.random() * b.guns.length)];
+ }
+ if (target.ammo === Infinity) {
+ mech.fieldMeter = 1;
+ if (!game.lastLogTime) game.makeTextLog("+energy", 300);
+ } else {
+ //ammo given scales as mobs take more hits to kill
+ let ammo = Math.ceil((target.ammoPack * (0.45 + 0.06 * Math.random())) / Math.sqrt(b.dmgScale));
+ if (level.isBuildRun) ammo = Math.floor(ammo * 1.2)
+ target.ammo += ammo;
+ game.updateGunHUD();
+ game.makeTextLog(" +" + ammo + " ammo for " + target.name + "", 300);
+ }
+ }
+ },
+ field: {
+ name: "field",
+ color: "#0cf",
+ size() {
+ return 45;
+ },
+ effect() {
+ const previousMode = mech.fieldMode
+
+ if (!this.mode) { //this.mode is set if the power up has been ejected from player
+ mode = mech.fieldMode
+ while (mode === mech.fieldMode) {
+ mode = Math.ceil(Math.random() * (mech.fieldUpgrades.length - 1))
+ }
+ mech.fieldUpgrades[mode].effect(); //choose random field upgrade that you don't already have
+ } else {
+ mech.fieldUpgrades[this.mode].effect(); //set a predetermined power up
+ }
+ //pop the old field out in case player wants to swap back
+ if (previousMode !== 0) {
+ mech.fieldCDcycle = mech.cycle + 40; //trigger fieldCD to stop power up grab automatic pick up of spawn
+ powerUps.spawn(mech.pos.x, mech.pos.y - 15, "field", false, previousMode);
+ }
+ }
+ },
+ mod: {
+ name: "mod",
+ color: "#a8f",
+ size() {
+ return 42;
+ },
+ effect() {
+ //find what mods I don't have
+ let options = [];
+ for (let i = 0; i < b.mods.length; i++) {
+ if (!b.mods[i].have) options.push(i);
+ }
+ //give a random mod from the mods I don't have
+ if (options.length > 0) {
+ let newMod = options[Math.floor(Math.random() * options.length)]
+ b.giveMod(newMod)
+ game.replaceTextLog = true;
+ game.makeTextLog(` ${b.mods[newMod].name}
${b.mods[newMod].description}`, 1000);
+ game.replaceTextLog = false;
+ }
+ }
+ },
+ gun: {
+ name: "gun",
+ color: "#26a",
+ size() {
+ return 35;
+ },
+ effect() {
+ //find what guns I don't have
+ let options = [];
+ if (b.activeGun === null && game.difficulty < 3) {
+ //choose the first gun to be one that is good for the early game
+ for (let i = 0; i < b.guns.length; ++i) {
+ if (!b.guns[i].have && b.guns[i].isStarterGun) options.push(i);
+ }
+ } else {
+ //choose a gun you don't have
+ for (let i = 0; i < b.guns.length; ++i) {
+ if (!b.guns[i].have) options.push(i);
+ }
+ }
+ //give player a gun they don't already have if possible
+ game.replaceTextLog = true;
+ if (options.length > 0) {
+ let newGun = options[Math.floor(Math.random() * options.length)];
+ if (b.activeGun === null) b.activeGun = newGun //if no active gun switch to new gun
+ game.makeTextLog(`${game.SVGleftMouse} ${b.guns[newGun].name}
${b.guns[newGun].description}`, 900);
+ b.guns[newGun].have = true;
+ b.inventory.push(newGun);
+ b.guns[newGun].ammo += b.guns[newGun].ammoPack * 2;
+ game.makeGunHUD();
+ } else {
+ //if you have all guns then get ammo
+ const ammoTarget = Math.floor(Math.random() * (b.guns.length));
+ const ammo = Math.ceil(b.guns[ammoTarget].ammoPack * 2);
+ b.guns[ammoTarget].ammo += ammo;
+ game.updateGunHUD();
+ game.makeTextLog("+" + ammo + " ammo for " + b.guns[ammoTarget].name + "", 300);
+ }
+ game.replaceTextLog = false
+ }
+ },
+ spawnRandomPowerUp(x, y) { //mostly used after mob dies
+ if (Math.random() * Math.random() - 0.25 > Math.sqrt(mech.health) || Math.random() < 0.04) { //spawn heal chance is higher at low health
+ powerUps.spawn(x, y, "heal");
+ if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "heal");
+ return;
+ }
+ if (Math.random() < 0.2 && b.inventory.length > 0) {
+ powerUps.spawn(x, y, "ammo");
+ if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "ammo");
+ return;
+ }
+ if (Math.random() < 0.004 * (4 - b.inventory.length)) { //a new gun has a low chance for each not acquired gun to drop
+ powerUps.spawn(x, y, "gun");
+ if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "gun");
+ return;
+ }
+ if (Math.random() < 0.0035 * (7 - b.modCount)) {
+ powerUps.spawn(x, y, "mod");
+ if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "mod");
+ return;
+ }
+ if (Math.random() < 0.005) {
+ powerUps.spawn(x, y, "field");
+ if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "field");
+ return;
+ }
+ },
+ spawnBossPowerUp(x, y) { //boss spawns field and gun mod upgrades
+ if (mech.fieldMode === 0) {
+ powerUps.spawn(x, y, "field")
+ if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "field")
+ } else if (Math.random() < 0.27) {
+ powerUps.spawn(x, y, "mod")
+ if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "mod")
+ } else if (Math.random() < 0.27) {
+ powerUps.spawn(x, y, "field");
+ if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "field");
+ } else if (Math.random() < 0.27) {
+ powerUps.spawn(x, y, "gun")
+ if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "gun")
+ } else if (mech.health < 0.6) {
+ powerUps.spawn(x, y, "heal");
+ if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "heal");
+ } else {
+ powerUps.spawn(x, y, "ammo");
+ if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "ammo");
+ }
+ },
+ chooseRandomPowerUp(x, y) { //100% chance to drop a random power up //used in spawn.debris
+ if (Math.random() < 0.5) {
+ powerUps.spawn(x, y, "heal", false);
+ } else {
+ powerUps.spawn(x, y, "ammo", false);
+ }
+ },
+ spawnStartingPowerUps(x, y) { //used for map specific power ups, mostly to give player a starting gun
+ if (b.inventory.length < 2 || game.isEasyMode) {
+ powerUps.spawn(x, y, "gun", false); //starting gun
+ } else {
+ powerUps.spawnRandomPowerUp(x, y);
+ powerUps.spawnRandomPowerUp(x, y);
+ powerUps.spawnRandomPowerUp(x, y);
+ powerUps.spawnRandomPowerUp(x, y);
+ }
+ },
+ spawn(x, y, target, moving = true, mode = null) {
+ if (!level.isBuildRun || target === "heal" || target === "ammo") {
+ let i = powerUp.length;
+ target = powerUps[target];
+ size = target.size();
+ powerUp[i] = Matter.Bodies.polygon(x, y, 0, size, {
+ density: 0.001,
+ frictionAir: 0.01,
+ restitution: 0.8,
+ inertia: Infinity, //prevents rotation
+ collisionFilter: {
+ group: 0,
+ category: 0x100000,
+ mask: 0x100001
+ },
+ color: target.color,
+ effect: target.effect,
+ mode: mode,
+ name: target.name,
+ size: size
+ });
+ if (moving) {
+ Matter.Body.setVelocity(powerUp[i], {
+ x: (Math.random() - 0.5) * 15,
+ y: Math.random() * -9 - 3
+ });
+ }
+ World.add(engine.world, powerUp[i]); //add to world
+ }
+ },
};
\ No newline at end of file
diff --git a/js/spawn.js b/js/spawn.js
index c146b2e..39c666a 100644
--- a/js/spawn.js
+++ b/js/spawn.js
@@ -1,1693 +1,1693 @@
-//main object for spawning things in a level
-const spawn = {
- pickList: ["starter", "starter"],
- fullPickList: [
- "chaser", "chaser", "chaser",
- "striker", "striker",
- "spinner",
- "hopper", "hopper", "hopper", "hopper",
- "grower",
- "springer",
- "shooter", "shooter", "shooter", "shooter", "shooter",
- "beamer",
- "focuser",
- "laser", "laser",
- // "blinker", //make blinker a boss
- "sucker",
- "exploder", "exploder", "exploder",
- "spawner",
- "ghoster",
- "sneaker",
- ],
- allowedBossList: ["chaser", "spinner", "striker", "springer", "laser", "focuser", "beamer", "exploder", "spawner", "shooter"], //"zoomer",
- setSpawnList() {
- //this is run at the start of each new level to determine the possible mobs for the level
- //each level has 2 mobs: one new mob and one from the last level
- spawn.pickList.splice(0, 1);
- spawn.pickList.push(spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]);
- },
- randomMob(x, y, chance = 1) {
- if (Math.random() < chance + 0.09 * (game.difficulty - 1) && mob.length < 4 + game.difficulty * 1.7) {
- const pick = this.pickList[Math.floor(Math.random() * this.pickList.length)];
- this[pick](x, y);
- }
- },
- randomSmallMob(x, y,
- num = Math.max(Math.min(Math.round(Math.random() * (game.difficulty - 1) * 0.45 - 0.4), 4), 0),
- size = 16 + Math.ceil(Math.random() * 15),
- chance = 1) {
- if (Math.random() < chance + (game.difficulty - 1) * 0.03 && mob.length < 4 + game.difficulty * 1.7) {
- for (let i = 0; i < num; ++i) {
- const pick = this.pickList[Math.floor(Math.random() * this.pickList.length)];
- this[pick](x + Math.round((Math.random() - 0.5) * 20) + i * size * 2.5, y + Math.round((Math.random() - 0.5) * 20), size);
- }
- }
- },
- randomBoss(x, y, chance = 1) {
- if (Math.random() < chance + (game.difficulty - 1) * 0.14 && game.difficulty !== 1 && mob.length < 4 + game.difficulty * 2 || chance == Infinity) {
- //choose from the possible picklist
- let pick = this.pickList[Math.floor(Math.random() * this.pickList.length)];
- //is the pick able to be a boss?
- let canBeBoss = false;
- for (let i = 0, len = this.allowedBossList.length; i < len; ++i) {
- if (this.allowedBossList[i] === pick) {
- canBeBoss = true;
- break;
- }
- }
- if (canBeBoss) {
- if (Math.random() < 0.55) {
- this.nodeBoss(x, y, pick);
- } else {
- this.lineBoss(x, y, pick);
- }
- } else {
- if (Math.random() < 0.07) {
- this[pick](x, y, 90 + Math.random() * 40); //one extra large mob
- } else if (Math.random() < 0.35) {
- this.groupBoss(x, y) //hidden grouping blocks
- } else {
- pick = (Math.random() < 0.5) ? "randomList" : "random";
- if (Math.random() < 0.55) {
- this.nodeBoss(x, y, pick);
- } else {
- this.lineBoss(x, y, pick);
- }
- }
- }
- }
- },
-
- //mob templates *********************************************************************************************
- //***********************************************************************************************************
- groupBoss(x, y, num = 5 + Math.random() * 8) {
- for (let i = 0; i < num; i++) {
- const radius = 25 + Math.floor(Math.random() * 20)
- spawn.grouper(x + Math.random() * radius, y + Math.random() * radius, radius);
- }
- },
- grouper(x, y, radius = 27 + Math.floor(Math.random() * 10)) {
- mobs.spawn(x, y, 4, radius, "#777");
- let me = mob[mob.length - 1];
- me.g = 0.0002; //required if using 'gravity'
- me.accelMag = 0.0007 * game.accelScale;
- me.groupingRangeMax = 250000 + Math.random() * 100000;
- me.groupingRangeMin = (radius * 8) * (radius * 8);
- me.groupingStrength = 0.0005
- me.memory = 200;
-
- me.do = function () {
- this.gravity();
- if (this.seePlayer.recall) {
- this.seePlayerByDistAndLOS();
- this.healthBar();
- this.attraction();
- //tether to other blocks
- ctx.beginPath();
- for (let i = 0, len = mob.length; i < len; i++) {
- if (mob[i] != this && mob[i].dropPowerUp) { //don't tether to self, bullets, shields, ...
- const distance2 = Matter.Vector.magnitudeSquared(Matter.Vector.sub(this.position, mob[i].position))
- if (distance2 < this.groupingRangeMax) {
- if (!mob[i].seePlayer.recall) mob[i].seePlayerByDistAndLOS(); //wake up sleepy mobs
- if (distance2 > this.groupingRangeMin) {
- const angle = Math.atan2(mob[i].position.y - this.position.y, mob[i].position.x - this.position.x);
- const forceMag = this.groupingStrength * mob[i].mass;
- mob[i].force.x -= forceMag * Math.cos(angle);
- mob[i].force.y -= forceMag * Math.sin(angle);
- }
- ctx.moveTo(this.position.x, this.position.y);
- ctx.lineTo(mob[i].position.x, mob[i].position.y);
- }
- }
- }
- ctx.strokeStyle = "#000";
- ctx.lineWidth = 1;
- ctx.stroke();
- }
- }
- },
- starter(x, y, radius = 30) {
- //easy mob for on level 1
- mobs.spawn(x, y, 8, radius, "#9ccdc6");
- let me = mob[mob.length - 1];
- me.accelMag = 0.00055 * game.accelScale;
- me.memory = 60;
- Matter.Body.setDensity(me, 0.0005) // normal density is 0.001 // this reduces life by half and decreases knockback
-
- me.do = function () {
- this.healthBar();
- this.seePlayerByLookingAt();
- this.attraction();
- };
- },
- healer(x, y, radius = 20) {
- //easy mob for on level 1
- mobs.spawn(x, y, 3, radius, "rgba(50,255,200,0.4)");
- let me = mob[mob.length - 1];
- me.frictionAir = 0.02;
- me.accelMag = 0.0004 * game.accelScale;
- if (map.length) me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; //required for search
- me.lookFrequency = 160 + Math.floor(57 * Math.random())
- me.lockedOn = null;
- Matter.Body.setDensity(me, 0.003) // normal density is 0.001
-
- me.do = function () {
- this.healthBar();
-
- if (!(game.cycle % this.lookFrequency)) {
- //slow self heal
- this.health += 0.02;
- if (this.health > 1) this.health = 1;
-
- //target mobs with low health
- let closeDist = Infinity;
- for (let i = 0; i < mob.length; i++) {
- if (mob[i] != this && Matter.Query.ray(map, this.position, mob[i].position).length === 0) {
- const TARGET_VECTOR = Matter.Vector.sub(this.position, mob[i].position)
- const DIST = Matter.Vector.magnitude(TARGET_VECTOR) * mob[i].health * mob[i].health * mob[i].health; //distance is multiplied by mob health to prioritize low health mobs
- if (DIST < closeDist) {
- closeDist = DIST;
- this.lockedOn = mob[i]
- }
- }
- }
- }
-
- //move away from player if too close
- if (this.distanceToPlayer2() < 400000) {
- const TARGET_VECTOR = Matter.Vector.sub(this.position, player.position)
- this.force = Matter.Vector.mult(Matter.Vector.normalise(TARGET_VECTOR), this.mass * this.accelMag * 1.4)
- if (this.lockedOn) this.lockedOn = null
- } else if (this.lockedOn && this.lockedOn.alive) {
- //move towards and heal locked on target
- const TARGET_VECTOR = Matter.Vector.sub(this.position, this.lockedOn.position)
- const DIST = Matter.Vector.magnitude(TARGET_VECTOR);
- if (DIST > 250) {
- this.force = Matter.Vector.mult(Matter.Vector.normalise(TARGET_VECTOR), -this.mass * this.accelMag)
- } else {
- if (this.lockedOn.health < 1) {
- this.lockedOn.health += 0.002;
- if (this.lockedOn.health > 1) this.lockedOn.health = 1;
- //spin when healing
- this.torque = 0.000005 * this.inertia;
- //draw heal
- ctx.beginPath();
- ctx.moveTo(this.position.x, this.position.y);
- ctx.lineTo(this.lockedOn.position.x, this.lockedOn.position.y);
- ctx.lineWidth = 10
- ctx.strokeStyle = "rgba(50,255,200,0.4)"
- ctx.stroke();
- }
- }
- } else {
- //wander if no heal targets visible
- //be sure to declare searchTarget in mob spawn
- const newTarget = function (that) {
- that.searchTarget = mob[Math.floor(Math.random() * (mob.length - 1))].position;
- };
-
- const sub = Matter.Vector.sub(this.searchTarget, this.position);
- if (Matter.Vector.magnitude(sub) > this.radius * 2) {
- ctx.beginPath();
- ctx.strokeStyle = "#aaa";
- ctx.moveTo(this.position.x, this.position.y);
- ctx.lineTo(this.searchTarget.x, this.searchTarget.y);
- ctx.stroke();
- //accelerate at 0.6 of normal acceleration
- this.force = Matter.Vector.mult(Matter.Vector.normalise(sub), this.accelMag * this.mass * 0.6);
- } else {
- //after reaching random target switch to new target
- newTarget(this);
- }
- //switch to a new target after a while
- if (!(game.cycle % (this.lookFrequency * 15))) {
- newTarget(this);
- }
-
- }
- };
- },
- chaser(x, y, radius = 35 + Math.ceil(Math.random() * 40)) {
- mobs.spawn(x, y, 8, radius, "#2c9790");
- let me = mob[mob.length - 1];
- // Matter.Body.setDensity(me, 0.0007); //extra dense //normal is 0.001 //makes effective life much lower
- me.friction = 0;
- me.frictionAir = 0;
- me.accelMag = 0.001 * game.accelScale;;
- me.g = me.accelMag * 0.6; //required if using 'gravity'
- me.memory = 50;
- if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y);
- me.do = function () {
- this.healthBar();
- this.gravity();
- this.seePlayerCheck();
- this.attraction();
- };
- },
- grower(x, y, radius = 15) {
- mobs.spawn(x, y, 7, radius, "hsl(144, 15%, 50%)");
- let me = mob[mob.length - 1];
- me.big = false; //required for grow
- me.accelMag = 0.00045 * game.accelScale;
- me.do = function () {
- this.healthBar();
- this.seePlayerByLookingAt();
- this.attraction();
- this.grow();
- };
- },
- springer(x, y, radius = 20 + Math.ceil(Math.random() * 35)) {
- mobs.spawn(x, y, 8, radius, "#b386e8");
- let me = mob[mob.length - 1];
- me.friction = 0;
- me.frictionAir = 0.1;
- me.lookTorque = 0.000005;
- me.g = 0.0002; //required if using 'gravity'
- me.seePlayerFreq = Math.round((40 + 25 * Math.random()) * game.lookFreqScale);
- const springStiffness = 0.002;
- const springDampening = 0.1;
-
- me.springTarget = {
- x: me.position.x,
- y: me.position.y
- };
- const len = cons.length;
- cons[len] = Constraint.create({
- pointA: me.springTarget,
- bodyB: me,
- stiffness: springStiffness,
- damping: springDampening
- });
- cons[len].length = 100 + 1.5 * radius;
- me.cons = cons[len];
-
- me.springTarget2 = {
- x: me.position.x,
- y: me.position.y
- };
- const len2 = cons.length;
- cons[len2] = Constraint.create({
- pointA: me.springTarget2,
- bodyB: me,
- stiffness: springStiffness,
- damping: springDampening
- });
- cons[len2].length = 100 + 1.5 * radius;
- me.cons2 = cons[len2];
-
- me.onDeath = function () {
- this.removeCons();
- };
- if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y);
- me.do = function () {
- this.healthBar();
- this.gravity();
- this.searchSpring();
- };
- },
- zoomer(x, y, radius = 20 + Math.ceil(Math.random() * 30)) {
- mobs.spawn(x, y, 6, radius, "#ffe2fd");
- let me = mob[mob.length - 1];
- me.trailLength = 20; //required for trails
- me.setupTrail(); //fill trails array up with the current position of mob
- me.trailFill = "#ff00f0";
- me.g = 0.001; //required if using 'gravity'
- me.frictionAir = 0.02;
- me.accelMag = 0.004 * game.accelScale;
- me.memory = 30;
- me.zoomMode = 150;
- me.onHit = function () {
- this.zoomMode = 150;
- };
- me.do = function () {
- this.healthBar();
- this.seePlayerByDistAndLOS();
- this.zoom();
- this.gravity();
- };
- },
- hopper(x, y, radius = 30 + Math.ceil(Math.random() * 30)) {
- mobs.spawn(x, y, 5, radius, "rgb(0,200,180)");
- let me = mob[mob.length - 1];
- me.accelMag = 0.04 * game.accelScale;
- me.g = 0.0015; //required if using 'gravity'
- me.frictionAir = 0.018;
- me.restitution = 0;
- me.delay = 110;
- me.randomHopFrequency = 50 + Math.floor(Math.random() * 1000);
- me.randomHopCD = game.cycle + me.randomHopFrequency;
- me.do = function () {
- this.healthBar();
- this.gravity();
- this.seePlayerCheck();
- this.hop();
- //randomly hob if not aware of player
- if (this.randomHopCD < game.cycle && this.speed < 1 && !this.seePlayer.recall) {
- this.randomHopCD = game.cycle + this.randomHopFrequency;
- //slowly change randomHopFrequency after each hop
- this.randomHopFrequency = Math.max(100, this.randomHopFrequency + (0.5 - Math.random()) * 200);
- const forceMag = (this.accelMag + this.accelMag * Math.random()) * this.mass * (0.1 + Math.random() * 0.3);
- const angle = -Math.PI / 2 + (Math.random() - 0.5) * Math.PI;
- this.force.x += forceMag * Math.cos(angle);
- this.force.y += forceMag * Math.sin(angle) - 0.04 * this.mass; //antigravity
- }
- };
- },
- spinner(x, y, radius = 30 + Math.ceil(Math.random() * 35)) {
- mobs.spawn(x, y, 5, radius, "#000000");
- let me = mob[mob.length - 1];
- me.fill = "#28b";
- me.rememberFill = me.fill;
- me.cdBurst1 = 0; //must add for burstAttraction
- me.cdBurst2 = 0; //must add for burstAttraction
- me.delay = 0;
- me.burstDir = {
- x: 0,
- y: 0
- };
- me.accelMag = 0.16 * game.accelScale;
- me.frictionAir = 0.022;
- me.lookTorque = 0.0000014;
- me.restitution = 0;
- me.do = function () {
- this.healthBar();
- this.seePlayerByLookingAt();
- //accelerate towards the player after a delay
- if (this.seePlayer.recall) {
- if (this.cdBurst2 < game.cycle && this.angularSpeed < 0.01) {
- this.cdBurst2 = Infinity;
- this.cdBurst1 = game.cycle + 40;
- this.burstDir = Matter.Vector.normalise(Matter.Vector.sub(this.seePlayer.position, this.position));
- } else if (this.cdBurst1 < game.cycle) {
- this.cdBurst2 = game.cycle + this.delay;
- this.cdBurst1 = Infinity;
- this.force = Matter.Vector.mult(this.burstDir, this.mass * 0.25);
- this.fill = this.rememberFill;
- } else if (this.cdBurst1 != Infinity) {
- this.torque += 0.000035 * this.inertia;
- this.fill = randomColor({
- hue: "blue"
- });
- //draw attack vector
- const mag = this.radius * 2 + 200;
- const gradient = ctx.createRadialGradient(this.position.x, this.position.y, 0, this.position.x, this.position.y, mag);
- gradient.addColorStop(0, "rgba(0,0,0,0.2)");
- gradient.addColorStop(1, "transparent");
- ctx.strokeStyle = gradient;
- ctx.lineWidth = 5;
- ctx.setLineDash([10, 20]); //30
- const dir = Matter.Vector.add(this.position, Matter.Vector.mult(this.burstDir, mag));
- ctx.beginPath();
- ctx.moveTo(this.position.x, this.position.y);
- ctx.lineTo(dir.x, dir.y);
- ctx.stroke();
- ctx.setLineDash([]);
- } else {
- this.fill = this.rememberFill;
- }
- } else {
- this.cdBurst2 = 0;
- }
- };
- },
- sucker(x, y, radius = 30 + Math.ceil(Math.random() * 70)) {
- radius = 9 + radius / 8; //extra small
- mobs.spawn(x, y, 6, radius, "#000");
- let me = mob[mob.length - 1];
- me.stroke = "transparent"; //used for drawSneaker
- me.eventHorizon = radius * 23; //required for blackhole
- me.seeAtDistance2 = (me.eventHorizon + 500) * (me.eventHorizon + 500); //vision limit is event horizon
- me.accelMag = 0.00009 * game.accelScale;
- // me.frictionAir = 0.005;
- me.memory = 600;
- Matter.Body.setDensity(me, 0.004); //extra dense //normal is 0.001 //makes effective life much larger
- me.do = function () {
- //keep it slow, to stop issues from explosion knock backs
- if (this.speed > 5) {
- Matter.Body.setVelocity(this, {
- x: this.velocity.x * 0.99,
- y: this.velocity.y * 0.99
- });
- }
- this.seePlayerByDistOrLOS();
- if (this.seePlayer.recall) {
- //eventHorizon waves in and out
- eventHorizon = this.eventHorizon * (0.93 + 0.17 * Math.sin(game.cycle * 0.011))
-
- //accelerate towards the player
- const forceMag = this.accelMag * this.mass;
- const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x);
- this.force.x += forceMag * Math.cos(angle);
- this.force.y += forceMag * Math.sin(angle);
-
- //draw darkness
- ctx.beginPath();
- ctx.arc(this.position.x, this.position.y, eventHorizon * 0.25, 0, 2 * Math.PI);
- ctx.fillStyle = "rgba(0,0,0,0.9)";
- ctx.fill();
- ctx.beginPath();
- ctx.arc(this.position.x, this.position.y, eventHorizon * 0.55, 0, 2 * Math.PI);
- ctx.fillStyle = "rgba(0,0,0,0.5)";
- ctx.fill();
- ctx.beginPath();
- ctx.arc(this.position.x, this.position.y, eventHorizon, 0, 2 * Math.PI);
- ctx.fillStyle = "rgba(0,0,0,0.1)";
- ctx.fill();
-
- this.healthBar();
- //when player is inside event horizon
- if (Matter.Vector.magnitude(Matter.Vector.sub(this.position, player.position)) < eventHorizon) {
- mech.damage(0.00015 * game.dmgScale);
- if (mech.fieldMeter > 0.1) mech.fieldMeter -= 0.007
-
- const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x);
- player.force.x -= 1.25 * Math.cos(angle) * player.mass * game.g * (mech.onGround ? 1.8 : 1);
- player.force.y -= 0.96 * player.mass * game.g * Math.sin(angle);
- //draw line to player
- ctx.beginPath();
- ctx.moveTo(this.position.x, this.position.y);
- ctx.lineTo(mech.pos.x, mech.pos.y);
- ctx.lineWidth = Math.min(60, this.radius * 2);
- ctx.strokeStyle = "rgba(0,0,0,0.5)";
- ctx.stroke();
- ctx.beginPath();
- ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI);
- ctx.fillStyle = "rgba(0,0,0,0.3)";
- ctx.fill();
- }
- }
- }
- },
- suckerBoss(x, y, radius = 20) {
- mobs.spawn(x, y, 12, radius, "#000");
- let me = mob[mob.length - 1];
- me.stroke = "transparent"; //used for drawSneaker
- me.eventHorizon = 1100; //required for black hole
- me.seeAtDistance2 = (me.eventHorizon + 1000) * (me.eventHorizon + 1000); //vision limit is event horizon
- me.accelMag = 0.00003 * game.accelScale;
- me.collisionFilter.mask = 0x001100
- // me.frictionAir = 0.005;
- me.memory = 1600;
- Matter.Body.setDensity(me, 0.05); //extra dense //normal is 0.001 //makes effective life much larger
- me.onDeath = function () {
- //applying forces to player doesn't seem to work inside this method, not sure why
- powerUps.spawnBossPowerUp(this.position.x, this.position.y)
- if (game.difficulty > 5) {
- for (let i = 0; i < (game.difficulty - 3); ++i) {
- spawn.sucker(this.position.x + (Math.random() - 0.5) * radius * 2, this.position.y + (Math.random() - 0.5) * radius * 2, 70 * Math.random());
- Matter.Body.setVelocity(mob[mob.length - 1], {
- x: (Math.random() - 0.5) * 70,
- y: (Math.random() - 0.5) * 70
- });
- }
- }
- };
- me.do = function () {
- //keep it slow, to stop issues from explosion knock backs
- if (this.speed > 1) {
- Matter.Body.setVelocity(this, {
- x: this.velocity.x * 0.95,
- y: this.velocity.y * 0.95
- });
- }
- this.seePlayerByDistOrLOS();
- if (this.seePlayer.recall) {
- //accelerate towards the player
- const forceMag = this.accelMag * this.mass;
- const dx = this.seePlayer.position.x - this.position.x
- const dy = this.seePlayer.position.y - this.position.y
- const mag = Math.sqrt(dx * dx + dy * dy)
- this.force.x += forceMag * dx / mag;
- this.force.y += forceMag * dy / mag;
-
- //eventHorizon waves in and out
- eventHorizon = this.eventHorizon * (1 + 0.2 * Math.sin(game.cycle * 0.008))
- // zoom camera in and out with the event horizon
-
- //draw darkness
- ctx.beginPath();
- ctx.arc(this.position.x, this.position.y, eventHorizon * 0.2, 0, 2 * Math.PI);
- ctx.fillStyle = "rgba(0,20,40,0.6)";
- ctx.fill();
- ctx.beginPath();
- ctx.arc(this.position.x, this.position.y, eventHorizon * 0.4, 0, 2 * Math.PI);
- ctx.fillStyle = "rgba(0,20,40,0.4)";
- ctx.fill();
- ctx.beginPath();
- ctx.arc(this.position.x, this.position.y, eventHorizon * 0.6, 0, 2 * Math.PI);
- ctx.fillStyle = "rgba(0,20,40,0.3)";
- ctx.fill();
- ctx.beginPath();
- ctx.arc(this.position.x, this.position.y, eventHorizon * 0.8, 0, 2 * Math.PI);
- ctx.fillStyle = "rgba(0,20,40,0.2)";
- ctx.fill();
- ctx.beginPath();
- ctx.arc(this.position.x, this.position.y, eventHorizon, 0, 2 * Math.PI);
- ctx.fillStyle = "rgba(0,0,0,0.05)";
- ctx.fill();
- //when player is inside event horizon
- if (Matter.Vector.magnitude(Matter.Vector.sub(this.position, player.position)) < eventHorizon) {
- mech.damage(0.00015 * game.dmgScale);
- if (mech.fieldMeter > 0.1) mech.fieldMeter -= 0.007
- const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x);
- player.force.x -= 1.3 * Math.cos(angle) * player.mass * game.g * (mech.onGround ? 1.7 : 1);
- player.force.y -= 1.2 * Math.sin(angle) * player.mass * game.g;
- //draw line to player
- ctx.beginPath();
- ctx.moveTo(this.position.x, this.position.y);
- ctx.lineTo(mech.pos.x, mech.pos.y);
- ctx.lineWidth = Math.min(60, this.radius * 2);
- ctx.strokeStyle = "rgba(0,0,0,0.5)";
- ctx.stroke();
- ctx.beginPath();
- ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI);
- ctx.fillStyle = "rgba(0,0,0,0.3)";
- ctx.fill();
- }
- this.healthBar();
- this.curl(eventHorizon);
- }
- }
- },
- beamer(x, y, radius = 15 + Math.ceil(Math.random() * 15)) {
- mobs.spawn(x, y, 4, radius, "rgb(255,0,190)");
- let me = mob[mob.length - 1];
- me.repulsionRange = 73000; //squared
- me.laserRange = 370;
- me.accelMag = 0.0005 * game.accelScale;
- me.frictionStatic = 0;
- me.friction = 0;
- if (Math.random() < Math.min(0.2 + (game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y);
- me.do = function () {
- this.healthBar();
- this.seePlayerByLookingAt();
- this.attraction();
- this.repulsion();
- //laser beam
- this.laserBeam();
- };
- },
- focuser(x, y, radius = 30 + Math.ceil(Math.random() * 10)) {
- radius = Math.ceil(radius * 0.7);
- mobs.spawn(x, y, 4, radius, "rgb(0,0,255)");
- let me = mob[mob.length - 1];
- Matter.Body.setDensity(me, 0.003); //extra dense //normal is 0.001
- me.restitution = 0;
- me.laserPos = me.position; //required for laserTracking
- me.repulsionRange = 1200000; //squared
- me.accelMag = 0.0002 * game.accelScale;
- me.frictionStatic = 0;
- me.friction = 0;
- me.onDamage = function () {
- this.laserPos = this.position;
- };
- // if (Math.random() < Math.min(0.2 + game.difficulty * 0.1, 0.7)) spawn.shield(me, x, y);
- me.do = function () {
- this.healthBar();
- if (!mech.isBodiesAsleep) {
- this.seePlayerByLookingAt();
- const dist2 = this.distanceToPlayer2();
- //laser Tracking
- if (this.seePlayer.yes && dist2 < 4000000 && !mech.isStealth) {
- this.attraction();
- const rangeWidth = 2000; //this is sqrt of 4000000 from above if()
- //targeting laser will slowly move from the mob to the player's position
- this.laserPos = Matter.Vector.add(this.laserPos, Matter.Vector.mult(Matter.Vector.sub(player.position, this.laserPos), 0.1));
- let targetDist = Matter.Vector.magnitude(Matter.Vector.sub(this.laserPos, mech.pos));
- const r = 10;
-
- // ctx.setLineDash([15, 200]);
- // ctx.lineDashOffset = 20*(game.cycle % 215);
- ctx.beginPath();
- ctx.moveTo(this.position.x, this.position.y);
- if (targetDist < r + 15) {
- // || dist2 < 80000
- targetDist = r + 10;
- //charge at player
- const forceMag = this.accelMag * 40 * this.mass;
- const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x);
- this.force.x += forceMag * Math.cos(angle);
- this.force.y += forceMag * Math.sin(angle);
- } else {
- //high friction if can't lock onto player
- Matter.Body.setVelocity(this, {
- x: this.velocity.x * 0.96,
- y: this.velocity.y * 0.96
- });
- }
- if (dist2 > 80000) {
- const laserWidth = 0.002;
- let laserOffR = Matter.Vector.rotateAbout(this.laserPos, (targetDist - r) * laserWidth, this.position);
- let sub = Matter.Vector.normalise(Matter.Vector.sub(laserOffR, this.position));
- laserOffR = Matter.Vector.add(laserOffR, Matter.Vector.mult(sub, rangeWidth));
- ctx.lineTo(laserOffR.x, laserOffR.y);
-
- let laserOffL = Matter.Vector.rotateAbout(this.laserPos, (targetDist - r) * -laserWidth, this.position);
- sub = Matter.Vector.normalise(Matter.Vector.sub(laserOffL, this.position));
- laserOffL = Matter.Vector.add(laserOffL, Matter.Vector.mult(sub, rangeWidth));
- ctx.lineTo(laserOffL.x, laserOffL.y);
- // ctx.fillStyle = "rgba(0,0,255,0.15)";
- var gradient = ctx.createRadialGradient(this.position.x, this.position.y, 0, this.position.x, this.position.y, rangeWidth);
- gradient.addColorStop(0, `rgba(0,0,255,${((r + 5) * (r + 5)) / (targetDist * targetDist)})`);
- gradient.addColorStop(1, "transparent");
- ctx.fillStyle = gradient;
- ctx.fill();
- }
- } else {
- this.laserPos = this.position;
- }
- };
- }
- },
- laser(x, y, radius = 30) {
- //only on level 1
- mobs.spawn(x, y, 3, radius, "#f00");
- let me = mob[mob.length - 1];
- me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front
- Matter.Body.rotate(me, Math.random() * Math.PI * 2);
- me.accelMag = 0.00007 * game.accelScale;
- me.onHit = function () {
- //run this function on hitting player
- this.explode();
- };
- me.do = function () {
- this.healthBar();
- this.seePlayerByLookingAt();
- this.attraction();
- this.laser();
- };
- },
- striker(x, y, radius = 14 + Math.ceil(Math.random() * 25)) {
- mobs.spawn(x, y, 5, radius, "rgb(221,102,119)");
- let me = mob[mob.length - 1];
- me.accelMag = 0.0003 * game.accelScale;
- me.g = 0.0002; //required if using 'gravity'
- me.frictionStatic = 0;
- me.friction = 0;
- me.delay = 100;
- Matter.Body.rotate(me, Math.PI * 0.1);
- me.onDamage = function () {
- this.cd = game.cycle + this.delay;
- };
- me.do = function () {
- this.healthBar();
- this.seePlayerCheck();
- this.attraction();
- this.gravity();
- this.strike();
- };
- },
- sneaker(x, y, radius = 15 + Math.ceil(Math.random() * 25)) {
- let me;
- mobs.spawn(x, y, 5, radius, "transparent");
- me = mob[mob.length - 1];
- me.accelMag = 0.0007 * game.accelScale;
- me.g = 0.0002; //required if using 'gravity'
- me.stroke = "transparent"; //used for drawSneaker
- me.alpha = 1; //used in drawSneaker
- // me.leaveBody = false;
- me.canTouchPlayer = false; //used in drawSneaker
- me.collisionFilter.mask = 0x010111; //can't touch player
- // me.memory = 420;
- me.do = function () {
-
- this.seePlayerCheck();
- this.attraction();
- this.gravity();
- //draw
- if (!mech.isBodiesAsleep) {
- if (this.seePlayer.yes) {
- if (this.alpha < 1) this.alpha += 0.01;
- } else {
- if (this.alpha > 0) this.alpha -= 0.03;
- }
- }
- if (this.alpha > 0) {
- if (this.alpha > 0.95) {
- this.healthBar();
- if (!this.canTouchPlayer) {
- this.canTouchPlayer = true;
- this.collisionFilter.mask = 0x011111; //can touch player
- }
- }
- //draw body
- ctx.beginPath();
- const vertices = this.vertices;
- ctx.moveTo(vertices[0].x, vertices[0].y);
- for (let j = 1, len = vertices.length; j < len; ++j) {
- ctx.lineTo(vertices[j].x, vertices[j].y);
- }
- ctx.lineTo(vertices[0].x, vertices[0].y);
- ctx.fillStyle = `rgba(0,0,0,${this.alpha * this.alpha})`;
- ctx.fill();
- } else if (this.canTouchPlayer) {
- this.canTouchPlayer = false;
- this.collisionFilter.mask = 0x000111; //can't touch player
- }
- };
- },
- ghoster(x, y, radius = 40 + Math.ceil(Math.random() * 100)) {
- let me;
- mobs.spawn(x, y, 7, radius, "transparent");
- me = mob[mob.length - 1];
- me.seeAtDistance2 = 1000000;
- me.accelMag = 0.00012 * game.accelScale;
- if (map.length) me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; //required for search
- Matter.Body.setDensity(me, 0.00065); //normal is 0.001 //makes effective life much lower
- me.stroke = "transparent"; //used for drawGhost
- me.alpha = 1; //used in drawGhost
- me.canTouchPlayer = false; //used in drawGhost
- // me.leaveBody = false;
- me.collisionFilter.mask = 0x000100; //move through walls and player
- me.memory = 480;
- me.do = function () {
- //cap max speed
- if (this.speed > 5) {
- Matter.Body.setVelocity(this, {
- x: this.velocity.x * 0.8,
- y: this.velocity.y * 0.8
- });
- }
- this.seePlayerCheckByDistance();
- this.attraction();
- this.search();
- //draw
- if (this.distanceToPlayer2() - this.seeAtDistance2 < 0) {
- if (this.alpha < 1) this.alpha += 0.004;
- } else {
- if (this.alpha > 0) this.alpha -= 0.03;
- }
- if (this.alpha > 0) {
- if (this.alpha > 0.9) {
- this.healthBar();
- if (!this.canTouchPlayer) {
- this.canTouchPlayer = true;
- this.collisionFilter.mask = 0x001100; //can touch player but not walls
- }
- }
- //draw body
- ctx.beginPath();
- const vertices = this.vertices;
- ctx.moveTo(vertices[0].x, vertices[0].y);
- for (let j = 1, len = vertices.length; j < len; ++j) {
- ctx.lineTo(vertices[j].x, vertices[j].y);
- }
- ctx.lineTo(vertices[0].x, vertices[0].y);
- ctx.lineWidth = 1;
- ctx.strokeStyle = `rgba(0,0,0,${this.alpha * this.alpha})`;
- ctx.stroke();
- } else if (this.canTouchPlayer) {
- this.canTouchPlayer = false;
- this.collisionFilter.mask = 0x000100; //can't touch player or walls
- }
- };
- },
- blinker(x, y, radius = 45 + Math.ceil(Math.random() * 70)) {
- mobs.spawn(x, y, 6, radius, "transparent");
- let me = mob[mob.length - 1];
- Matter.Body.setDensity(me, 0.0005); //normal is 0.001 //makes effective life much lower
- me.stroke = "rgb(0,200,255)"; //used for drawGhost
- Matter.Body.rotate(me, Math.random() * 2 * Math.PI);
- me.blinkRate = 40 + Math.round(Math.random() * 60); //required for blink
- me.blinkLength = 150 + Math.round(Math.random() * 200); //required for blink
- me.isStatic = true;
- me.memory = 360;
- me.seePlayerFreq = Math.round((40 + 30 * Math.random()) * game.lookFreqScale);
- me.isBig = false;
- me.scaleMag = Math.max(5 - me.mass, 1.75);
- me.onDeath = function () {
- if (this.isBig) {
- Matter.Body.scale(this, 1 / this.scaleMag, 1 / this.scaleMag);
- this.isBig = false;
- }
- };
- me.do = function () {
- this.healthBar();
- this.seePlayerCheck();
- this.blink();
- //strike by expanding
- if (this.isBig) {
- if (this.cd - this.delay + 15 < game.cycle) {
- Matter.Body.scale(this, 1 / this.scaleMag, 1 / this.scaleMag);
- this.isBig = false;
- }
- } else if (this.seePlayer.yes && this.cd < game.cycle) {
- const dist = Matter.Vector.sub(this.seePlayer.position, this.position);
- const distMag2 = Matter.Vector.magnitudeSquared(dist);
- if (distMag2 < 80000) {
- this.cd = game.cycle + this.delay;
- Matter.Body.scale(this, this.scaleMag, this.scaleMag);
- this.isBig = true;
- }
- }
- };
- },
- bomber(x, y, radius = 120 + Math.ceil(Math.random() * 70)) {
- //boss that drops bombs from above and holds a set distance from player
- mobs.spawn(x, y, 3, radius, "transparent");
- let me = mob[mob.length - 1];
- Matter.Body.setDensity(me, 0.0015 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger
-
- me.stroke = "rgba(255,0,200)"; //used for drawGhost
- me.seeAtDistance2 = 2000000;
- me.fireFreq = Math.ceil(30 + 2000 / radius);
- me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; //required for search
- me.hoverElevation = 400 + (Math.random() - 0.5) * 200; //squared
- me.hoverXOff = (Math.random() - 0.5) * 100;
- me.accelMag = Math.floor(10 * (Math.random() + 5)) * 0.00001 * game.accelScale;
- me.g = 0.0002; //required if using 'gravity' // gravity called in hoverOverPlayer
- me.frictionStatic = 0;
- me.friction = 0;
- me.frictionAir = 0.01;
- // me.memory = 300;
- // Matter.Body.setDensity(me, 0.0015); //extra dense //normal is 0.001
- me.collisionFilter.mask = 0x001100; //move through walls
- spawn.shield(me, x, y);
- me.onDeath = function () {
- powerUps.spawnBossPowerUp(this.position.x, this.position.y)
- };
- me.do = function () {
- this.healthBar();
- this.seePlayerCheckByDistance();
- this.hoverOverPlayer();
- this.bomb();
- this.search();
- };
- },
- shooter(x, y, radius = 25 + Math.ceil(Math.random() * 50)) {
- mobs.spawn(x, y, 3, radius, "rgb(255,100,150)");
- let me = mob[mob.length - 1];
- me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front
- me.memory = 120;
- me.fireFreq = 0.007 + Math.random() * 0.005;
- me.noseLength = 0;
- me.fireAngle = 0;
- me.accelMag = 0.0005 * game.accelScale;
- me.frictionAir = 0.05;
- me.lookTorque = 0.0000025 * (Math.random() > 0.5 ? -1 : 1);
- me.fireDir = {
- x: 0,
- y: 0
- };
- if (Math.random() < Math.min(0.15 + (game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y);
- me.do = function () {
- this.healthBar();
- this.seePlayerByLookingAt();
- this.fire();
- };
- },
- shooterBoss(x, y, radius = 85 + Math.ceil(Math.random() * 50)) {
- //boss spawns on skyscraper level
- mobs.spawn(x, y, 3, radius, "rgb(255,70,180)");
- let me = mob[mob.length - 1];
- me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front
- me.memory = 240;
- me.fireFreq = 0.02;
- me.noseLength = 0;
- me.fireAngle = 0;
- me.accelMag = 0.005 * game.accelScale;
- me.frictionAir = 0.1;
- me.lookTorque = 0.000005 * (Math.random() > 0.5 ? -1 : 1);
- me.fireDir = {
- x: 0,
- y: 0
- };
- Matter.Body.setDensity(me, 0.001 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger
- spawn.shield(me, x, y);
- me.onDeath = function () {
- powerUps.spawnBossPowerUp(this.position.x, this.position.y)
- };
- me.do = function () {
- this.healthBar();
- this.seePlayerByLookingAt();
- this.fire();
- };
- },
- bullet(x, y, radius = 6, sides = 0) {
- //bullets
- mobs.spawn(x, y, sides, radius, "rgb(255,0,0)");
- let me = mob[mob.length - 1];
- me.stroke = "transparent";
- me.onHit = function () {
- this.explode();
- };
- Matter.Body.setDensity(me, 0.001); //normal is 0.001
- me.timeLeft = 240;
- me.g = 0.001; //required if using 'gravity'
- me.frictionAir = 0;
- me.restitution = 0.8;
- me.leaveBody = false;
- me.dropPowerUp = false;
- me.collisionFilter.category = 0x000010;
- me.collisionFilter.mask = 0x011101;
- me.do = function () {
- this.gravity();
- this.timeLimit();
- };
- },
- spawner(x, y, radius = 55 + Math.ceil(Math.random() * 50)) {
- mobs.spawn(x, y, 4, radius, "rgb(255,150,0)");
- let me = mob[mob.length - 1];
- me.g = 0.0004; //required if using 'gravity'
- me.leaveBody = false;
- // me.dropPowerUp = false;
- me.onDeath = function () { //run this function on death
- for (let i = 0; i < Math.ceil(this.mass * 0.2 + Math.random() * 3); ++i) {
- spawn.spawns(this.position.x + (Math.random() - 0.5) * radius * 2.5, this.position.y + (Math.random() - 0.5) * radius * 2.5);
- Matter.Body.setVelocity(mob[mob.length - 1], {
- x: this.velocity.x + (Math.random() - 0.5) * 15,
- y: this.velocity.x + (Math.random() - 0.5) * 15
- });
- }
- };
- if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.5)) spawn.shield(me, x, y);
- me.do = function () {
- this.healthBar();
- this.gravity();
- this.seePlayerCheck();
- this.attraction();
- };
- },
- spawns(x, y, radius = 15 + Math.ceil(Math.random() * 5)) {
- mobs.spawn(x, y, 4, radius, "rgb(255,0,0)");
- let me = mob[mob.length - 1];
- me.onHit = function () {
- //run this function on hitting player
- this.explode();
- };
- me.g = 0.0001; //required if using 'gravity'
- me.accelMag = 0.0003 * game.accelScale;
- me.memory = 30;
- me.leaveBody = false;
- me.seePlayerFreq = Math.round((80 + 50 * Math.random()) * game.lookFreqScale);
- me.frictionAir = 0.002;
- me.do = function () {
- this.healthBar();
- this.gravity();
- this.seePlayerCheck();
- this.attraction();
- };
- },
- exploder(x, y, radius = 25 + Math.ceil(Math.random() * 50)) {
- mobs.spawn(x, y, 4, radius, "rgb(255,0,0)");
- let me = mob[mob.length - 1];
- me.onHit = function () {
- //run this function on hitting player
- this.explode();
- };
- me.g = 0.0004; //required if using 'gravity'
- me.do = function () {
- this.healthBar();
- this.gravity();
- this.seePlayerCheck();
- this.attraction();
- };
- },
- snaker(x, y, radius = 80) {
- //snake boss with a laser head
- mobs.spawn(x, y, 8, radius, "rgb(255,50,130)");
- let me = mob[mob.length - 1];
- me.accelMag = 0.0012 * game.accelScale;
- me.memory = 200;
- me.laserRange = 500;
- Matter.Body.setDensity(me, 0.001 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger
- spawn.shield(me, x, y);
- if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y);
- me.onDeath = function () {
- powerUps.spawnBossPowerUp(this.position.x, this.position.y)
- };
- me.do = function () {
- this.healthBar();
- this.seePlayerCheck();
- this.attraction();
- this.laserBeam();
- };
-
- //snake tail
- const nodes = Math.min(3 + Math.ceil(Math.random() * game.difficulty + 2), 8)
- spawn.lineBoss(x + 105, y, "spawns", nodes);
- //constraint boss with first 3 mobs in lineboss
- consBB[consBB.length] = Constraint.create({
- bodyA: mob[mob.length - nodes],
- bodyB: mob[mob.length - 1 - nodes],
- stiffness: 0.05
- });
- consBB[consBB.length] = Constraint.create({
- bodyA: mob[mob.length - nodes + 1],
- bodyB: mob[mob.length - 1 - nodes],
- stiffness: 0.05
- });
- consBB[consBB.length] = Constraint.create({
- bodyA: mob[mob.length - nodes + 2],
- bodyB: mob[mob.length - 1 - nodes],
- stiffness: 0.05
- });
-
- },
- tether(x, y, radius = 90) {
- // constrained mob boss for the towers level
- // often has a ring of mobs around it
- mobs.spawn(x, y, 8, radius, "rgb(0,60,80)");
- let me = mob[mob.length - 1];
- me.g = 0.0001; //required if using 'gravity'
- me.accelMag = 0.002 * game.accelScale;
- me.memory = 20;
- Matter.Body.setDensity(me, 0.001 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger
- spawn.shield(me, x, y);
- if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y);
-
- me.onDeath = function () {
- powerUps.spawnBossPowerUp(this.position.x, this.position.y)
- this.removeCons(); //remove constraint
- };
- me.do = function () {
- this.healthBar();
- this.gravity();
- this.seePlayerCheck();
- this.attraction();
- };
- },
- shield(target, x, y, stiffness = 0.4) {
- if (this.allowShields) {
- mobs.spawn(x, y, 9, target.radius + 20, "rgba(220,220,255,0.6)");
- let me = mob[mob.length - 1];
- me.stroke = "rgb(220,220,255)";
- Matter.Body.setDensity(me, 0.0001) //very low density to not mess with the original mob's motion
- me.shield = true;
- me.collisionFilter.mask = 0x000100; //don't collide with bodies, map, player, and mobs, only bullets
- consBB[consBB.length] = Constraint.create({
- //attach shield to last spawned mob
- bodyA: me,
- bodyB: target,
- stiffness: stiffness,
- damping: 0.1
- });
- me.onDamage = function () {
- //make sure the mob that owns the shield can tell when damage is done
- this.alertNearByMobs();
- };
- me.leaveBody = false;
- me.dropPowerUp = false;
- //swap order of shield and mob, so that mob is behind shield graphically
- mob[mob.length - 1] = mob[mob.length - 2];
- mob[mob.length - 2] = me;
- me.do = function () {};
- }
- },
- bossShield(nodes, x, y, radius) {
- mobs.spawn(x, y, 9, radius, "rgba(220,220,255,0.65)");
- let me = mob[mob.length - 1];
- me.stroke = "rgb(220,220,255)";
- Matter.Body.setDensity(me, 0.00005) //very low density to not mess with the original mob's motion
- me.frictionAir = 0;
- me.shield = true;
- me.collisionFilter.mask = 0x000100; //don't collide with bodies, map, and mobs, only bullets and player
- //constrain to all mob nodes in boss
- for (let i = 0; i < nodes; ++i) {
- consBB[consBB.length] = Constraint.create({
- bodyA: me,
- bodyB: mob[mob.length - i - 2],
- stiffness: 0.4,
- damping: 0.1
- });
- }
- me.onDamage = function () {
- //make sure the mob that owns the shield can tell when damage is done
- this.alertNearByMobs();
- };
- me.leaveBody = false;
- me.dropPowerUp = false;
- mob[mob.length - 1] = mob[mob.length - 1 - nodes];
- mob[mob.length - 1 - nodes] = me;
- me.do = function () {};
- },
- //complex constrained mob templates**********************************************************************
- //*******************************************************************************************************
- allowShields: true,
- nodeBoss(
- x,
- y,
- spawn = "striker",
- nodes = Math.min(2 + Math.ceil(Math.random() * (game.difficulty + 2)), 8),
- //Math.ceil(Math.random() * 3) + Math.min(4,Math.ceil(game.difficulty/2)),
- radius = Math.ceil(Math.random() * 10) + 17, // radius of each node mob
- sideLength = Math.ceil(Math.random() * 100) + 70, // distance between each node mob
- stiffness = Math.random() * 0.03 + 0.005
- ) {
- this.allowShields = false; //don't want shields on boss mobs
- const angle = 2 * Math.PI / nodes
- for (let i = 0; i < nodes; ++i) {
- let whoSpawn = spawn;
- if (spawn === "random") {
- whoSpawn = this.fullPickList[Math.floor(Math.random() * this.fullPickList.length)];
- } else if (spawn === "randomList") {
- whoSpawn = this.pickList[Math.floor(Math.random() * this.pickList.length)];
- }
- this[whoSpawn](x + sideLength * Math.sin(i * angle), y + sideLength * Math.cos(i * angle), radius);
- }
- if (Math.random() < 0.3) {
- this.constrain2AdjacentMobs(nodes, stiffness * 2, true);
- } else {
- this.constrainAllMobCombos(nodes, stiffness);
- }
- //spawn shield for entire boss
- if (nodes > 2 && Math.random() < 0.998) {
- this.bossShield(nodes, x, y, sideLength + 2.5 * radius + nodes * 6 - 25);
- // this.bossShield(nodes, x, y, sideLength / (2 * Math.sin(Math.PI / nodes)));
- }
- this.allowShields = true;
- },
- lineBoss(
- x,
- y,
- spawn = "striker",
- nodes = Math.min(3 + Math.ceil(Math.random() * game.difficulty + 2), 8),
- //Math.ceil(Math.random() * 3) + Math.min(4,Math.ceil(game.difficulty/2)),
- radius = Math.ceil(Math.random() * 10) + 17,
- l = Math.ceil(Math.random() * 80) + 30,
- stiffness = Math.random() * 0.06 + 0.01
- ) {
- this.allowShields = false; //dont' want shields on boss mobs
- for (let i = 0; i < nodes; ++i) {
- let whoSpawn = spawn;
- if (spawn === "random") {
- whoSpawn = this.fullPickList[Math.floor(Math.random() * this.fullPickList.length)];
- } else if (spawn === "randomList") {
- whoSpawn = this.pickList[Math.floor(Math.random() * this.pickList.length)];
- }
- this[whoSpawn](x + i * radius + i * l, y, radius);
- }
- this.constrain2AdjacentMobs(nodes, stiffness);
- this.allowShields = true;
- },
- //constraints ************************************************************************************************
- //*************************************************************************************************************
- constrainAllMobCombos(nodes, stiffness) {
- //runs through every combination of last 'num' bodies and constrains them
- for (let i = 1; i < nodes + 1; ++i) {
- for (let j = i + 1; j < nodes + 1; ++j) {
- consBB[consBB.length] = Constraint.create({
- bodyA: mob[mob.length - i],
- bodyB: mob[mob.length - j],
- stiffness: stiffness
- });
- }
- }
- },
- constrain2AdjacentMobs(nodes, stiffness, loop = false) {
- //runs through every combination of last 'num' bodies and constrains them
- for (let i = 0; i < nodes - 1; ++i) {
- consBB[consBB.length] = Constraint.create({
- bodyA: mob[mob.length - i - 1],
- bodyB: mob[mob.length - i - 2],
- stiffness: stiffness
- });
- }
- if (nodes > 2) {
- for (let i = 0; i < nodes - 2; ++i) {
- consBB[consBB.length] = Constraint.create({
- bodyA: mob[mob.length - i - 1],
- bodyB: mob[mob.length - i - 3],
- stiffness: stiffness
- });
- }
- }
- //optional connect the tail to head
- if (loop && nodes > 3) {
- consBB[consBB.length] = Constraint.create({
- bodyA: mob[mob.length - 1],
- bodyB: mob[mob.length - nodes],
- stiffness: stiffness
- });
- consBB[consBB.length] = Constraint.create({
- bodyA: mob[mob.length - 2],
- bodyB: mob[mob.length - nodes],
- stiffness: stiffness
- });
- consBB[consBB.length] = Constraint.create({
- bodyA: mob[mob.length - 1],
- bodyB: mob[mob.length - nodes + 1],
- stiffness: stiffness
- });
- }
- },
- constraintPB(x, y, bodyIndex, stiffness) {
- cons[cons.length] = Constraint.create({
- pointA: {
- x: x,
- y: y
- },
- bodyB: body[bodyIndex],
- stiffness: stiffness
- });
- },
- constraintBB(bodyIndexA, bodyIndexB, stiffness) {
- consBB[consBB.length] = Constraint.create({
- bodyA: body[bodyIndexA],
- bodyB: body[bodyIndexB],
- stiffness: stiffness
- });
- },
- // body and map spawns ******************************************************************************
- //**********************************************************************************************
- wireHead() {
- //not a mob, just a graphic for level 1
- const breakingPoint = 1300
- mobs.spawn(breakingPoint, -100, 0, 7.5, "transparent");
- let me = mob[mob.length - 1];
- //touch nothing
- me.collisionFilter.category = 0x010000; //act like a body
- me.collisionFilter.mask = 0x000101; //only collide with map
- me.inertia = Infinity;
- me.g = 0.0004; //required for gravity
- me.restitution = 0;
- me.stroke = "transparent"
- me.freeOfWires = false;
- me.frictionStatic = 1;
- me.friction = 1;
- me.frictionAir = 0.01;
-
- me.do = function () {
- let wireX = -50;
- let wireY = -1000;
- if (this.freeOfWires) {
- this.gravity();
- } else {
- if (mech.pos.x > breakingPoint) {
- this.freeOfWires = true;
- this.fill = "#000"
- this.force.x += -0.003;
- player.force.x += 0.06;
- // player.force.y -= 0.15;
- }
-
- //player is extra heavy from wires
- Matter.Body.setVelocity(player, {
- x: player.velocity.x,
- y: player.velocity.y + 0.3
- })
-
- //player friction from the wires
- if (mech.pos.x > 700 && player.velocity.x > -2) {
- let wireFriction = 0.75 * Math.min(0.6, Math.max(0, 100 / (breakingPoint - mech.pos.x)));
- if (!mech.onGround) wireFriction *= 3
- Matter.Body.setVelocity(player, {
- x: player.velocity.x - wireFriction,
- y: player.velocity.y
- })
- }
- //move to player
- Matter.Body.setPosition(this, {
- x: mech.pos.x + (42 * Math.cos(mech.angle + Math.PI)),
- y: mech.pos.y + (42 * Math.sin(mech.angle + Math.PI))
- })
- }
- //draw wire
- ctx.beginPath();
- ctx.moveTo(wireX, wireY);
- ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y);
- if (!this.freeOfWires) ctx.lineTo(mech.pos.x + (30 * Math.cos(mech.angle + Math.PI)), mech.pos.y + (30 * Math.sin(mech.angle + Math.PI)));
- ctx.lineCap = "butt";
- ctx.lineWidth = 15;
- ctx.strokeStyle = "#000";
- ctx.stroke();
- ctx.lineCap = "round";
- };
- },
- wireKnee() {
- //not a mob, just a graphic for level 1
- const breakingPoint = 1425
- mobs.spawn(breakingPoint, -100, 0, 2, "transparent");
- let me = mob[mob.length - 1];
- //touch nothing
- me.collisionFilter.category = 0x010000; //act like a body
- me.collisionFilter.mask = 0x000101; //only collide with map
- me.g = 0.0003; //required for gravity
- // me.restitution = 0;
- me.stroke = "transparent"
- // me.inertia = Infinity;
- me.restitution = 0;
- me.freeOfWires = false;
- me.frictionStatic = 1;
- me.friction = 1;
- me.frictionAir = 0.01;
-
- me.do = function () {
- let wireX = -50 - 20;
- let wireY = -1000;
-
- if (this.freeOfWires) {
- this.gravity();
- } else {
- if (mech.pos.x > breakingPoint) {
- this.freeOfWires = true;
- this.force.x -= 0.0004;
- this.fill = "#222";
- }
- //move mob to player
- mech.calcLeg(0, 0);
- Matter.Body.setPosition(this, {
- x: mech.pos.x + mech.flipLegs * mech.knee.x - 5,
- y: mech.pos.y + mech.knee.y
- })
- }
- //draw wire
- ctx.beginPath();
- ctx.moveTo(wireX, wireY);
- ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y);
- ctx.lineWidth = 5;
- ctx.strokeStyle = "#222";
- ctx.lineCap = "butt";
- ctx.stroke();
- ctx.lineCap = "round";
- };
- },
- wireKneeLeft() {
- //not a mob, just a graphic for level 1
- const breakingPoint = 1400
- mobs.spawn(breakingPoint, -100, 0, 2, "transparent");
- let me = mob[mob.length - 1];
- //touch nothing
- me.collisionFilter.category = 0x010000; //act like a body
- me.collisionFilter.mask = 0x000101; //only collide with map
- me.g = 0.0003; //required for gravity
- // me.restitution = 0;
- me.stroke = "transparent"
- // me.inertia = Infinity;
- me.restitution = 0;
- me.freeOfWires = false;
- me.frictionStatic = 1;
- me.friction = 1;
- me.frictionAir = 0.01;
-
- me.do = function () {
- let wireX = -50 - 35;
- let wireY = -1000;
-
- if (this.freeOfWires) {
- this.gravity();
- } else {
- if (mech.pos.x > breakingPoint) {
- this.freeOfWires = true;
- this.force.x += -0.0003;
- this.fill = "#333";
- }
- //move mob to player
- mech.calcLeg(Math.PI, -3);
- Matter.Body.setPosition(this, {
- x: mech.pos.x + mech.flipLegs * mech.knee.x - 5,
- y: mech.pos.y + mech.knee.y
- })
- }
- //draw wire
- ctx.beginPath();
- ctx.moveTo(wireX, wireY);
- ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y);
- ctx.lineWidth = 5;
- ctx.lineCap = "butt";
- ctx.strokeStyle = "#333";
- ctx.stroke();
- ctx.lineCap = "round";
- };
- },
- wireFoot() {
- //not a mob, just a graphic for level 1
- const breakingPoint = 1350
- mobs.spawn(breakingPoint, -100, 0, 2, "transparent");
- let me = mob[mob.length - 1];
- //touch nothing
- me.collisionFilter.category = 0x010000; //act like a body
- me.collisionFilter.mask = 0x000101; //only collide with map
- me.g = 0.0003; //required for gravity
- me.restitution = 0;
- me.stroke = "transparent"
- // me.inertia = Infinity;
- me.freeOfWires = false;
- // me.frictionStatic = 1;
- // me.friction = 1;
- me.frictionAir = 0.01;
-
- me.do = function () {
- let wireX = -50 + 16;
- let wireY = -1000;
-
- if (this.freeOfWires) {
- this.gravity();
- } else {
- if (mech.pos.x > breakingPoint) {
- this.freeOfWires = true;
- this.force.x += -0.0006;
- this.fill = "#222";
- }
- //move mob to player
- mech.calcLeg(0, 0);
- Matter.Body.setPosition(this, {
- x: mech.pos.x + mech.flipLegs * mech.foot.x - 5,
- y: mech.pos.y + mech.foot.y - 1
- })
- }
- //draw wire
- ctx.beginPath();
- ctx.moveTo(wireX, wireY);
- ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y);
- ctx.lineWidth = 5;
- ctx.lineCap = "butt";
- ctx.strokeStyle = "#222";
- ctx.stroke();
- ctx.lineCap = "round";
- };
- },
- wireFootLeft() {
- //not a mob, just a graphic for level 1
- const breakingPoint = 1325
- mobs.spawn(breakingPoint, -100, 0, 2, "transparent");
- let me = mob[mob.length - 1];
- //touch nothing
- me.collisionFilter.category = 0x010000; //act like a body
- me.collisionFilter.mask = 0x000101; //only collide with map
- me.g = 0.0003; //required for gravity
- me.restitution = 0;
- me.stroke = "transparent"
- // me.inertia = Infinity;
- me.freeOfWires = false;
- // me.frictionStatic = 1;
- // me.friction = 1;
- me.frictionAir = 0.01;
-
- me.do = function () {
- let wireX = -50 + 26;
- let wireY = -1000;
-
- if (this.freeOfWires) {
- this.gravity();
- } else {
- if (mech.pos.x > breakingPoint) {
- this.freeOfWires = true;
- this.force.x += -0.0005;
- this.fill = "#333";
- }
- //move mob to player
- mech.calcLeg(Math.PI, -3);
- Matter.Body.setPosition(this, {
- x: mech.pos.x + mech.flipLegs * mech.foot.x - 5,
- y: mech.pos.y + mech.foot.y - 1
- })
- }
- //draw wire
- ctx.beginPath();
- ctx.moveTo(wireX, wireY);
- ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y);
- ctx.lineWidth = 5;
- ctx.strokeStyle = "#333";
- ctx.lineCap = "butt";
- ctx.stroke();
- ctx.lineCap = "round";
- };
- },
- boost(x, y, height = 1000) {
- spawn.mapVertex(x + 50, y + 35, "120 40 -120 40 -50 -40 50 -40");
- // level.addZone(x, y, 100, 30, "fling", {Vx:Vx, Vy: Vy});
- level.addQueryRegion(x, y - 20, 100, 20, "boost", [
- [player], body, mob, powerUp, bullet
- ], -1.1 * Math.sqrt(Math.abs(height)));
- let color = "rgba(200,0,255,";
- level.fillBG.push({
- x: x,
- y: y - 25,
- width: 100,
- height: 25,
- color: color + "0.2)"
- });
- level.fillBG.push({
- x: x,
- y: y - 55,
- width: 100,
- height: 55,
- color: color + "0.1)"
- });
- level.fillBG.push({
- x: x,
- y: y - 120,
- width: 100,
- height: 120,
- color: color + "0.05)"
- });
- },
- laserZone(x, y, width, height, dmg) {
- level.addZone(x, y, width, height, "laser", {
- dmg
- });
- level.fill.push({
- x: x,
- y: y,
- width: width,
- height: height,
- color: "#f00"
- });
- },
- deathQuery(x, y, width, height) {
- level.addQueryRegion(x, y, width, height, "death", [
- [player], mob
- ]);
- level.fill.push({
- x: x,
- y: y,
- width: width,
- height: height,
- color: "#f00"
- });
- },
- platform(x, y, width, height) {
- const size = 20;
- spawn.mapRect(x, y + height, width, 30);
- level.fillBG.push({
- x: x + width / 2 - size / 2,
- y: y,
- width: size,
- height: height,
- color: "#f0f0f3"
- });
- },
- debris(x, y, width, number = Math.floor(3 + Math.random() * 11)) {
- for (let i = 0; i < number; ++i) {
- if (Math.random() < 0.15) {
- powerUps.chooseRandomPowerUp(x + Math.random() * width, y);
- } else {
- const size = 18 + Math.random() * 25;
- spawn.bodyRect(x + Math.random() * width, y, size * (0.6 + Math.random()), size * (0.6 + Math.random()), 1);
- // body[body.length] = Bodies.rectangle(x + Math.random() * width, y, size * (0.6 + Math.random()), size * (0.6 + Math.random()));
- }
- }
- },
- bodyRect(x, y, width, height, chance = 1, properties = {
- friction: 0.05,
- frictionAir: 0.01
- }) {
- if (Math.random() < chance) body[body.length] = Bodies.rectangle(x + width / 2, y + height / 2, width, height, properties);
- },
- bodyVertex(x, y, vector, properties) { //adds shape to body array
- body[body.length] = Matter.Bodies.fromVertices(x, y, Vertices.fromPath(vector), properties);
- },
- mapRect(x, y, width, height, properties) { //adds rectangle to map array
- map[map.length] = Bodies.rectangle(x + width / 2, y + height / 2, width, height, properties);
- },
- mapVertex(x, y, vector, properties) { //adds shape to map array
- map[map.length] = Matter.Bodies.fromVertices(x, y, Vertices.fromPath(vector), properties);
- },
- //complex map templates
- spawnBuilding(x, y, w, h, leftDoor, rightDoor, walledSide) {
- this.mapRect(x, y, w, 25); //roof
- this.mapRect(x, y + h, w, 35); //ground
- if (walledSide === "left") {
- this.mapRect(x, y, 25, h); //wall left
- } else {
- this.mapRect(x, y, 25, h - 150); //wall left
- if (leftDoor) {
- this.bodyRect(x + 5, y + h - 150, 15, 150, this.propsFriction); //door left
- }
- }
- if (walledSide === "right") {
- this.mapRect(x - 25 + w, y, 25, h); //wall right
- } else {
- this.mapRect(x - 25 + w, y, 25, h - 150); //wall right
- if (rightDoor) {
- this.bodyRect(x + w - 20, y + h - 150, 15, 150, this.propsFriction); //door right
- }
- }
- },
- spawnStairs(x, y, num, w, h, stepRight) {
- w += 50;
- if (stepRight) {
- for (let i = 0; i < num; i++) {
- this.mapRect(x - (w / num) * (1 + i), y - h + (i * h) / num, w / num + 50, h - (i * h) / num + 50);
- }
- } else {
- for (let i = 0; i < num; i++) {
- this.mapRect(x + (i * w) / num, y - h + (i * h) / num, w / num + 50, h - (i * h) / num + 50);
- }
- }
- },
- //pre-made property options*************************************************************************************
- //*************************************************************************************************************
- //Object.assign({}, propsHeavy, propsBouncy, propsNoRotation) //will combine properties into a new object
- propsFriction: {
- friction: 0.5,
- frictionAir: 0.02,
- frictionStatic: 1
- },
- propsFrictionMedium: {
- friction: 0.15,
- frictionStatic: 1
- },
- propsBouncy: {
- friction: 0,
- frictionAir: 0,
- frictionStatic: 0,
- restitution: 1
- },
- propsSlide: {
- friction: 0.003,
- frictionStatic: 0.4,
- restitution: 0,
- density: 0.002
- },
- propsLight: {
- density: 0.001
- },
- propsOverBouncy: {
- friction: 0,
- frictionAir: 0,
- frictionStatic: 0,
- restitution: 1.05
- },
- propsHeavy: {
- density: 0.01 //default density is 0.001
- },
- propsIsNotHoldable: {
- isNotHoldable: true
- },
- propsNoRotation: {
- inertia: Infinity //prevents rotation
- },
- propsHoist: {
- inertia: Infinity, //prevents rotation
- frictionAir: 0.001,
- friction: 0.0001,
- frictionStatic: 0,
- restitution: 0,
- isNotHoldable: true
- // density: 0.0001
- },
- propsDoor: {
- density: 0.001, //default density is 0.001
- friction: 0,
- frictionAir: 0.03,
- frictionStatic: 0,
- restitution: 0
- },
- sandPaper: {
- friction: 1,
- frictionStatic: 1,
- restitution: 0
- }
+//main object for spawning things in a level
+const spawn = {
+ pickList: ["starter", "starter"],
+ fullPickList: [
+ "chaser", "chaser", "chaser",
+ "striker", "striker",
+ "spinner",
+ "hopper", "hopper", "hopper", "hopper",
+ "grower",
+ "springer",
+ "shooter", "shooter", "shooter", "shooter", "shooter",
+ "beamer",
+ "focuser",
+ "laser", "laser",
+ // "blinker", //make blinker a boss
+ "sucker",
+ "exploder", "exploder", "exploder",
+ "spawner",
+ "ghoster",
+ "sneaker",
+ ],
+ allowedBossList: ["chaser", "spinner", "striker", "springer", "laser", "focuser", "beamer", "exploder", "spawner", "shooter"], //"zoomer",
+ setSpawnList() {
+ //this is run at the start of each new level to determine the possible mobs for the level
+ //each level has 2 mobs: one new mob and one from the last level
+ spawn.pickList.splice(0, 1);
+ spawn.pickList.push(spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]);
+ },
+ randomMob(x, y, chance = 1) {
+ if (Math.random() < chance + 0.09 * (game.difficulty - 1) && mob.length < 4 + game.difficulty * 1.7) {
+ const pick = this.pickList[Math.floor(Math.random() * this.pickList.length)];
+ this[pick](x, y);
+ }
+ },
+ randomSmallMob(x, y,
+ num = Math.max(Math.min(Math.round(Math.random() * (game.difficulty - 1) * 0.45 - 0.4), 4), 0),
+ size = 16 + Math.ceil(Math.random() * 15),
+ chance = 1) {
+ if (Math.random() < chance + (game.difficulty - 1) * 0.03 && mob.length < 4 + game.difficulty * 1.7) {
+ for (let i = 0; i < num; ++i) {
+ const pick = this.pickList[Math.floor(Math.random() * this.pickList.length)];
+ this[pick](x + Math.round((Math.random() - 0.5) * 20) + i * size * 2.5, y + Math.round((Math.random() - 0.5) * 20), size);
+ }
+ }
+ },
+ randomBoss(x, y, chance = 1) {
+ if (Math.random() < chance + (game.difficulty - 1) * 0.14 && game.difficulty !== 1 && mob.length < 4 + game.difficulty * 2 || chance == Infinity) {
+ //choose from the possible picklist
+ let pick = this.pickList[Math.floor(Math.random() * this.pickList.length)];
+ //is the pick able to be a boss?
+ let canBeBoss = false;
+ for (let i = 0, len = this.allowedBossList.length; i < len; ++i) {
+ if (this.allowedBossList[i] === pick) {
+ canBeBoss = true;
+ break;
+ }
+ }
+ if (canBeBoss) {
+ if (Math.random() < 0.55) {
+ this.nodeBoss(x, y, pick);
+ } else {
+ this.lineBoss(x, y, pick);
+ }
+ } else {
+ if (Math.random() < 0.07) {
+ this[pick](x, y, 90 + Math.random() * 40); //one extra large mob
+ } else if (Math.random() < 0.35) {
+ this.groupBoss(x, y) //hidden grouping blocks
+ } else {
+ pick = (Math.random() < 0.5) ? "randomList" : "random";
+ if (Math.random() < 0.55) {
+ this.nodeBoss(x, y, pick);
+ } else {
+ this.lineBoss(x, y, pick);
+ }
+ }
+ }
+ }
+ },
+
+ //mob templates *********************************************************************************************
+ //***********************************************************************************************************
+ groupBoss(x, y, num = 5 + Math.random() * 8) {
+ for (let i = 0; i < num; i++) {
+ const radius = 25 + Math.floor(Math.random() * 20)
+ spawn.grouper(x + Math.random() * radius, y + Math.random() * radius, radius);
+ }
+ },
+ grouper(x, y, radius = 27 + Math.floor(Math.random() * 10)) {
+ mobs.spawn(x, y, 4, radius, "#777");
+ let me = mob[mob.length - 1];
+ me.g = 0.0002; //required if using 'gravity'
+ me.accelMag = 0.0007 * game.accelScale;
+ me.groupingRangeMax = 250000 + Math.random() * 100000;
+ me.groupingRangeMin = (radius * 8) * (radius * 8);
+ me.groupingStrength = 0.0005
+ me.memory = 200;
+
+ me.do = function () {
+ this.gravity();
+ if (this.seePlayer.recall) {
+ this.seePlayerByDistAndLOS();
+ this.healthBar();
+ this.attraction();
+ //tether to other blocks
+ ctx.beginPath();
+ for (let i = 0, len = mob.length; i < len; i++) {
+ if (mob[i] != this && mob[i].dropPowerUp) { //don't tether to self, bullets, shields, ...
+ const distance2 = Matter.Vector.magnitudeSquared(Matter.Vector.sub(this.position, mob[i].position))
+ if (distance2 < this.groupingRangeMax) {
+ if (!mob[i].seePlayer.recall) mob[i].seePlayerByDistAndLOS(); //wake up sleepy mobs
+ if (distance2 > this.groupingRangeMin) {
+ const angle = Math.atan2(mob[i].position.y - this.position.y, mob[i].position.x - this.position.x);
+ const forceMag = this.groupingStrength * mob[i].mass;
+ mob[i].force.x -= forceMag * Math.cos(angle);
+ mob[i].force.y -= forceMag * Math.sin(angle);
+ }
+ ctx.moveTo(this.position.x, this.position.y);
+ ctx.lineTo(mob[i].position.x, mob[i].position.y);
+ }
+ }
+ }
+ ctx.strokeStyle = "#000";
+ ctx.lineWidth = 1;
+ ctx.stroke();
+ }
+ }
+ },
+ starter(x, y, radius = 30) {
+ //easy mob for on level 1
+ mobs.spawn(x, y, 8, radius, "#9ccdc6");
+ let me = mob[mob.length - 1];
+ me.accelMag = 0.00055 * game.accelScale;
+ me.memory = 60;
+ Matter.Body.setDensity(me, 0.0005) // normal density is 0.001 // this reduces life by half and decreases knockback
+
+ me.do = function () {
+ this.healthBar();
+ this.seePlayerByLookingAt();
+ this.attraction();
+ };
+ },
+ healer(x, y, radius = 20) {
+ //easy mob for on level 1
+ mobs.spawn(x, y, 3, radius, "rgba(50,255,200,0.4)");
+ let me = mob[mob.length - 1];
+ me.frictionAir = 0.02;
+ me.accelMag = 0.0004 * game.accelScale;
+ if (map.length) me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; //required for search
+ me.lookFrequency = 160 + Math.floor(57 * Math.random())
+ me.lockedOn = null;
+ Matter.Body.setDensity(me, 0.003) // normal density is 0.001
+
+ me.do = function () {
+ this.healthBar();
+
+ if (!(game.cycle % this.lookFrequency)) {
+ //slow self heal
+ this.health += 0.02;
+ if (this.health > 1) this.health = 1;
+
+ //target mobs with low health
+ let closeDist = Infinity;
+ for (let i = 0; i < mob.length; i++) {
+ if (mob[i] != this && Matter.Query.ray(map, this.position, mob[i].position).length === 0) {
+ const TARGET_VECTOR = Matter.Vector.sub(this.position, mob[i].position)
+ const DIST = Matter.Vector.magnitude(TARGET_VECTOR) * mob[i].health * mob[i].health * mob[i].health; //distance is multiplied by mob health to prioritize low health mobs
+ if (DIST < closeDist) {
+ closeDist = DIST;
+ this.lockedOn = mob[i]
+ }
+ }
+ }
+ }
+
+ //move away from player if too close
+ if (this.distanceToPlayer2() < 400000) {
+ const TARGET_VECTOR = Matter.Vector.sub(this.position, player.position)
+ this.force = Matter.Vector.mult(Matter.Vector.normalise(TARGET_VECTOR), this.mass * this.accelMag * 1.4)
+ if (this.lockedOn) this.lockedOn = null
+ } else if (this.lockedOn && this.lockedOn.alive) {
+ //move towards and heal locked on target
+ const TARGET_VECTOR = Matter.Vector.sub(this.position, this.lockedOn.position)
+ const DIST = Matter.Vector.magnitude(TARGET_VECTOR);
+ if (DIST > 250) {
+ this.force = Matter.Vector.mult(Matter.Vector.normalise(TARGET_VECTOR), -this.mass * this.accelMag)
+ } else {
+ if (this.lockedOn.health < 1) {
+ this.lockedOn.health += 0.002;
+ if (this.lockedOn.health > 1) this.lockedOn.health = 1;
+ //spin when healing
+ this.torque = 0.000005 * this.inertia;
+ //draw heal
+ ctx.beginPath();
+ ctx.moveTo(this.position.x, this.position.y);
+ ctx.lineTo(this.lockedOn.position.x, this.lockedOn.position.y);
+ ctx.lineWidth = 10
+ ctx.strokeStyle = "rgba(50,255,200,0.4)"
+ ctx.stroke();
+ }
+ }
+ } else {
+ //wander if no heal targets visible
+ //be sure to declare searchTarget in mob spawn
+ const newTarget = function (that) {
+ that.searchTarget = mob[Math.floor(Math.random() * (mob.length - 1))].position;
+ };
+
+ const sub = Matter.Vector.sub(this.searchTarget, this.position);
+ if (Matter.Vector.magnitude(sub) > this.radius * 2) {
+ ctx.beginPath();
+ ctx.strokeStyle = "#aaa";
+ ctx.moveTo(this.position.x, this.position.y);
+ ctx.lineTo(this.searchTarget.x, this.searchTarget.y);
+ ctx.stroke();
+ //accelerate at 0.6 of normal acceleration
+ this.force = Matter.Vector.mult(Matter.Vector.normalise(sub), this.accelMag * this.mass * 0.6);
+ } else {
+ //after reaching random target switch to new target
+ newTarget(this);
+ }
+ //switch to a new target after a while
+ if (!(game.cycle % (this.lookFrequency * 15))) {
+ newTarget(this);
+ }
+
+ }
+ };
+ },
+ chaser(x, y, radius = 35 + Math.ceil(Math.random() * 40)) {
+ mobs.spawn(x, y, 8, radius, "#2c9790");
+ let me = mob[mob.length - 1];
+ // Matter.Body.setDensity(me, 0.0007); //extra dense //normal is 0.001 //makes effective life much lower
+ me.friction = 0;
+ me.frictionAir = 0;
+ me.accelMag = 0.001 * game.accelScale;;
+ me.g = me.accelMag * 0.6; //required if using 'gravity'
+ me.memory = 50;
+ if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y);
+ me.do = function () {
+ this.healthBar();
+ this.gravity();
+ this.seePlayerCheck();
+ this.attraction();
+ };
+ },
+ grower(x, y, radius = 15) {
+ mobs.spawn(x, y, 7, radius, "hsl(144, 15%, 50%)");
+ let me = mob[mob.length - 1];
+ me.big = false; //required for grow
+ me.accelMag = 0.00045 * game.accelScale;
+ me.do = function () {
+ this.healthBar();
+ this.seePlayerByLookingAt();
+ this.attraction();
+ this.grow();
+ };
+ },
+ springer(x, y, radius = 20 + Math.ceil(Math.random() * 35)) {
+ mobs.spawn(x, y, 8, radius, "#b386e8");
+ let me = mob[mob.length - 1];
+ me.friction = 0;
+ me.frictionAir = 0.1;
+ me.lookTorque = 0.000005;
+ me.g = 0.0002; //required if using 'gravity'
+ me.seePlayerFreq = Math.round((40 + 25 * Math.random()) * game.lookFreqScale);
+ const springStiffness = 0.002;
+ const springDampening = 0.1;
+
+ me.springTarget = {
+ x: me.position.x,
+ y: me.position.y
+ };
+ const len = cons.length;
+ cons[len] = Constraint.create({
+ pointA: me.springTarget,
+ bodyB: me,
+ stiffness: springStiffness,
+ damping: springDampening
+ });
+ cons[len].length = 100 + 1.5 * radius;
+ me.cons = cons[len];
+
+ me.springTarget2 = {
+ x: me.position.x,
+ y: me.position.y
+ };
+ const len2 = cons.length;
+ cons[len2] = Constraint.create({
+ pointA: me.springTarget2,
+ bodyB: me,
+ stiffness: springStiffness,
+ damping: springDampening
+ });
+ cons[len2].length = 100 + 1.5 * radius;
+ me.cons2 = cons[len2];
+
+ me.onDeath = function () {
+ this.removeCons();
+ };
+ if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y);
+ me.do = function () {
+ this.healthBar();
+ this.gravity();
+ this.searchSpring();
+ };
+ },
+ zoomer(x, y, radius = 20 + Math.ceil(Math.random() * 30)) {
+ mobs.spawn(x, y, 6, radius, "#ffe2fd");
+ let me = mob[mob.length - 1];
+ me.trailLength = 20; //required for trails
+ me.setupTrail(); //fill trails array up with the current position of mob
+ me.trailFill = "#ff00f0";
+ me.g = 0.001; //required if using 'gravity'
+ me.frictionAir = 0.02;
+ me.accelMag = 0.004 * game.accelScale;
+ me.memory = 30;
+ me.zoomMode = 150;
+ me.onHit = function () {
+ this.zoomMode = 150;
+ };
+ me.do = function () {
+ this.healthBar();
+ this.seePlayerByDistAndLOS();
+ this.zoom();
+ this.gravity();
+ };
+ },
+ hopper(x, y, radius = 30 + Math.ceil(Math.random() * 30)) {
+ mobs.spawn(x, y, 5, radius, "rgb(0,200,180)");
+ let me = mob[mob.length - 1];
+ me.accelMag = 0.04 * game.accelScale;
+ me.g = 0.0015; //required if using 'gravity'
+ me.frictionAir = 0.018;
+ me.restitution = 0;
+ me.delay = 110;
+ me.randomHopFrequency = 50 + Math.floor(Math.random() * 1000);
+ me.randomHopCD = game.cycle + me.randomHopFrequency;
+ me.do = function () {
+ this.healthBar();
+ this.gravity();
+ this.seePlayerCheck();
+ this.hop();
+ //randomly hob if not aware of player
+ if (this.randomHopCD < game.cycle && this.speed < 1 && !this.seePlayer.recall) {
+ this.randomHopCD = game.cycle + this.randomHopFrequency;
+ //slowly change randomHopFrequency after each hop
+ this.randomHopFrequency = Math.max(100, this.randomHopFrequency + (0.5 - Math.random()) * 200);
+ const forceMag = (this.accelMag + this.accelMag * Math.random()) * this.mass * (0.1 + Math.random() * 0.3);
+ const angle = -Math.PI / 2 + (Math.random() - 0.5) * Math.PI;
+ this.force.x += forceMag * Math.cos(angle);
+ this.force.y += forceMag * Math.sin(angle) - 0.04 * this.mass; //antigravity
+ }
+ };
+ },
+ spinner(x, y, radius = 30 + Math.ceil(Math.random() * 35)) {
+ mobs.spawn(x, y, 5, radius, "#000000");
+ let me = mob[mob.length - 1];
+ me.fill = "#28b";
+ me.rememberFill = me.fill;
+ me.cdBurst1 = 0; //must add for burstAttraction
+ me.cdBurst2 = 0; //must add for burstAttraction
+ me.delay = 0;
+ me.burstDir = {
+ x: 0,
+ y: 0
+ };
+ me.accelMag = 0.16 * game.accelScale;
+ me.frictionAir = 0.022;
+ me.lookTorque = 0.0000014;
+ me.restitution = 0;
+ me.do = function () {
+ this.healthBar();
+ this.seePlayerByLookingAt();
+ //accelerate towards the player after a delay
+ if (this.seePlayer.recall) {
+ if (this.cdBurst2 < game.cycle && this.angularSpeed < 0.01) {
+ this.cdBurst2 = Infinity;
+ this.cdBurst1 = game.cycle + 40;
+ this.burstDir = Matter.Vector.normalise(Matter.Vector.sub(this.seePlayer.position, this.position));
+ } else if (this.cdBurst1 < game.cycle) {
+ this.cdBurst2 = game.cycle + this.delay;
+ this.cdBurst1 = Infinity;
+ this.force = Matter.Vector.mult(this.burstDir, this.mass * 0.25);
+ this.fill = this.rememberFill;
+ } else if (this.cdBurst1 != Infinity) {
+ this.torque += 0.000035 * this.inertia;
+ this.fill = randomColor({
+ hue: "blue"
+ });
+ //draw attack vector
+ const mag = this.radius * 2 + 200;
+ const gradient = ctx.createRadialGradient(this.position.x, this.position.y, 0, this.position.x, this.position.y, mag);
+ gradient.addColorStop(0, "rgba(0,0,0,0.2)");
+ gradient.addColorStop(1, "transparent");
+ ctx.strokeStyle = gradient;
+ ctx.lineWidth = 5;
+ ctx.setLineDash([10, 20]); //30
+ const dir = Matter.Vector.add(this.position, Matter.Vector.mult(this.burstDir, mag));
+ ctx.beginPath();
+ ctx.moveTo(this.position.x, this.position.y);
+ ctx.lineTo(dir.x, dir.y);
+ ctx.stroke();
+ ctx.setLineDash([]);
+ } else {
+ this.fill = this.rememberFill;
+ }
+ } else {
+ this.cdBurst2 = 0;
+ }
+ };
+ },
+ sucker(x, y, radius = 30 + Math.ceil(Math.random() * 70)) {
+ radius = 9 + radius / 8; //extra small
+ mobs.spawn(x, y, 6, radius, "#000");
+ let me = mob[mob.length - 1];
+ me.stroke = "transparent"; //used for drawSneaker
+ me.eventHorizon = radius * 23; //required for blackhole
+ me.seeAtDistance2 = (me.eventHorizon + 500) * (me.eventHorizon + 500); //vision limit is event horizon
+ me.accelMag = 0.00009 * game.accelScale;
+ // me.frictionAir = 0.005;
+ me.memory = 600;
+ Matter.Body.setDensity(me, 0.004); //extra dense //normal is 0.001 //makes effective life much larger
+ me.do = function () {
+ //keep it slow, to stop issues from explosion knock backs
+ if (this.speed > 5) {
+ Matter.Body.setVelocity(this, {
+ x: this.velocity.x * 0.99,
+ y: this.velocity.y * 0.99
+ });
+ }
+ this.seePlayerByDistOrLOS();
+ if (this.seePlayer.recall) {
+ //eventHorizon waves in and out
+ eventHorizon = this.eventHorizon * (0.93 + 0.17 * Math.sin(game.cycle * 0.011))
+
+ //accelerate towards the player
+ const forceMag = this.accelMag * this.mass;
+ const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x);
+ this.force.x += forceMag * Math.cos(angle);
+ this.force.y += forceMag * Math.sin(angle);
+
+ //draw darkness
+ ctx.beginPath();
+ ctx.arc(this.position.x, this.position.y, eventHorizon * 0.25, 0, 2 * Math.PI);
+ ctx.fillStyle = "rgba(0,0,0,0.9)";
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(this.position.x, this.position.y, eventHorizon * 0.55, 0, 2 * Math.PI);
+ ctx.fillStyle = "rgba(0,0,0,0.5)";
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(this.position.x, this.position.y, eventHorizon, 0, 2 * Math.PI);
+ ctx.fillStyle = "rgba(0,0,0,0.1)";
+ ctx.fill();
+
+ this.healthBar();
+ //when player is inside event horizon
+ if (Matter.Vector.magnitude(Matter.Vector.sub(this.position, player.position)) < eventHorizon) {
+ mech.damage(0.00015 * game.dmgScale);
+ if (mech.fieldMeter > 0.1) mech.fieldMeter -= 0.007
+
+ const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x);
+ player.force.x -= 1.25 * Math.cos(angle) * player.mass * game.g * (mech.onGround ? 1.8 : 1);
+ player.force.y -= 0.96 * player.mass * game.g * Math.sin(angle);
+ //draw line to player
+ ctx.beginPath();
+ ctx.moveTo(this.position.x, this.position.y);
+ ctx.lineTo(mech.pos.x, mech.pos.y);
+ ctx.lineWidth = Math.min(60, this.radius * 2);
+ ctx.strokeStyle = "rgba(0,0,0,0.5)";
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI);
+ ctx.fillStyle = "rgba(0,0,0,0.3)";
+ ctx.fill();
+ }
+ }
+ }
+ },
+ suckerBoss(x, y, radius = 20) {
+ mobs.spawn(x, y, 12, radius, "#000");
+ let me = mob[mob.length - 1];
+ me.stroke = "transparent"; //used for drawSneaker
+ me.eventHorizon = 1100; //required for black hole
+ me.seeAtDistance2 = (me.eventHorizon + 1000) * (me.eventHorizon + 1000); //vision limit is event horizon
+ me.accelMag = 0.00003 * game.accelScale;
+ me.collisionFilter.mask = 0x001100
+ // me.frictionAir = 0.005;
+ me.memory = 1600;
+ Matter.Body.setDensity(me, 0.05); //extra dense //normal is 0.001 //makes effective life much larger
+ me.onDeath = function () {
+ //applying forces to player doesn't seem to work inside this method, not sure why
+ powerUps.spawnBossPowerUp(this.position.x, this.position.y)
+ if (game.difficulty > 5) {
+ for (let i = 0; i < (game.difficulty - 3); ++i) {
+ spawn.sucker(this.position.x + (Math.random() - 0.5) * radius * 2, this.position.y + (Math.random() - 0.5) * radius * 2, 70 * Math.random());
+ Matter.Body.setVelocity(mob[mob.length - 1], {
+ x: (Math.random() - 0.5) * 70,
+ y: (Math.random() - 0.5) * 70
+ });
+ }
+ }
+ };
+ me.do = function () {
+ //keep it slow, to stop issues from explosion knock backs
+ if (this.speed > 1) {
+ Matter.Body.setVelocity(this, {
+ x: this.velocity.x * 0.95,
+ y: this.velocity.y * 0.95
+ });
+ }
+ this.seePlayerByDistOrLOS();
+ if (this.seePlayer.recall) {
+ //accelerate towards the player
+ const forceMag = this.accelMag * this.mass;
+ const dx = this.seePlayer.position.x - this.position.x
+ const dy = this.seePlayer.position.y - this.position.y
+ const mag = Math.sqrt(dx * dx + dy * dy)
+ this.force.x += forceMag * dx / mag;
+ this.force.y += forceMag * dy / mag;
+
+ //eventHorizon waves in and out
+ eventHorizon = this.eventHorizon * (1 + 0.2 * Math.sin(game.cycle * 0.008))
+ // zoom camera in and out with the event horizon
+
+ //draw darkness
+ ctx.beginPath();
+ ctx.arc(this.position.x, this.position.y, eventHorizon * 0.2, 0, 2 * Math.PI);
+ ctx.fillStyle = "rgba(0,20,40,0.6)";
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(this.position.x, this.position.y, eventHorizon * 0.4, 0, 2 * Math.PI);
+ ctx.fillStyle = "rgba(0,20,40,0.4)";
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(this.position.x, this.position.y, eventHorizon * 0.6, 0, 2 * Math.PI);
+ ctx.fillStyle = "rgba(0,20,40,0.3)";
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(this.position.x, this.position.y, eventHorizon * 0.8, 0, 2 * Math.PI);
+ ctx.fillStyle = "rgba(0,20,40,0.2)";
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(this.position.x, this.position.y, eventHorizon, 0, 2 * Math.PI);
+ ctx.fillStyle = "rgba(0,0,0,0.05)";
+ ctx.fill();
+ //when player is inside event horizon
+ if (Matter.Vector.magnitude(Matter.Vector.sub(this.position, player.position)) < eventHorizon) {
+ mech.damage(0.00015 * game.dmgScale);
+ if (mech.fieldMeter > 0.1) mech.fieldMeter -= 0.007
+ const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x);
+ player.force.x -= 1.3 * Math.cos(angle) * player.mass * game.g * (mech.onGround ? 1.7 : 1);
+ player.force.y -= 1.2 * Math.sin(angle) * player.mass * game.g;
+ //draw line to player
+ ctx.beginPath();
+ ctx.moveTo(this.position.x, this.position.y);
+ ctx.lineTo(mech.pos.x, mech.pos.y);
+ ctx.lineWidth = Math.min(60, this.radius * 2);
+ ctx.strokeStyle = "rgba(0,0,0,0.5)";
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI);
+ ctx.fillStyle = "rgba(0,0,0,0.3)";
+ ctx.fill();
+ }
+ this.healthBar();
+ this.curl(eventHorizon);
+ }
+ }
+ },
+ beamer(x, y, radius = 15 + Math.ceil(Math.random() * 15)) {
+ mobs.spawn(x, y, 4, radius, "rgb(255,0,190)");
+ let me = mob[mob.length - 1];
+ me.repulsionRange = 73000; //squared
+ me.laserRange = 370;
+ me.accelMag = 0.0005 * game.accelScale;
+ me.frictionStatic = 0;
+ me.friction = 0;
+ if (Math.random() < Math.min(0.2 + (game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y);
+ me.do = function () {
+ this.healthBar();
+ this.seePlayerByLookingAt();
+ this.attraction();
+ this.repulsion();
+ //laser beam
+ this.laserBeam();
+ };
+ },
+ focuser(x, y, radius = 30 + Math.ceil(Math.random() * 10)) {
+ radius = Math.ceil(radius * 0.7);
+ mobs.spawn(x, y, 4, radius, "rgb(0,0,255)");
+ let me = mob[mob.length - 1];
+ Matter.Body.setDensity(me, 0.003); //extra dense //normal is 0.001
+ me.restitution = 0;
+ me.laserPos = me.position; //required for laserTracking
+ me.repulsionRange = 1200000; //squared
+ me.accelMag = 0.00009 * game.accelScale;
+ me.frictionStatic = 0;
+ me.friction = 0;
+ me.onDamage = function () {
+ this.laserPos = this.position;
+ };
+ // if (Math.random() < Math.min(0.2 + game.difficulty * 0.1, 0.7)) spawn.shield(me, x, y);
+ me.do = function () {
+ this.healthBar();
+ if (!mech.isBodiesAsleep) {
+ this.seePlayerByLookingAt();
+ const dist2 = this.distanceToPlayer2();
+ //laser Tracking
+ if (this.seePlayer.yes && dist2 < 4000000 && !mech.isStealth) {
+ this.attraction();
+ const rangeWidth = 2000; //this is sqrt of 4000000 from above if()
+ //targeting laser will slowly move from the mob to the player's position
+ this.laserPos = Matter.Vector.add(this.laserPos, Matter.Vector.mult(Matter.Vector.sub(player.position, this.laserPos), 0.1));
+ let targetDist = Matter.Vector.magnitude(Matter.Vector.sub(this.laserPos, mech.pos));
+ const r = 10;
+
+ // ctx.setLineDash([15, 200]);
+ // ctx.lineDashOffset = 20*(game.cycle % 215);
+ ctx.beginPath();
+ ctx.moveTo(this.position.x, this.position.y);
+ if (targetDist < r + 15) {
+ // || dist2 < 80000
+ targetDist = r + 10;
+ //charge at player
+ const forceMag = this.accelMag * 40 * this.mass;
+ const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x);
+ this.force.x += forceMag * Math.cos(angle);
+ this.force.y += forceMag * Math.sin(angle);
+ } else {
+ //high friction if can't lock onto player
+ Matter.Body.setVelocity(this, {
+ x: this.velocity.x * 0.96,
+ y: this.velocity.y * 0.96
+ });
+ }
+ if (dist2 > 80000) {
+ const laserWidth = 0.002;
+ let laserOffR = Matter.Vector.rotateAbout(this.laserPos, (targetDist - r) * laserWidth, this.position);
+ let sub = Matter.Vector.normalise(Matter.Vector.sub(laserOffR, this.position));
+ laserOffR = Matter.Vector.add(laserOffR, Matter.Vector.mult(sub, rangeWidth));
+ ctx.lineTo(laserOffR.x, laserOffR.y);
+
+ let laserOffL = Matter.Vector.rotateAbout(this.laserPos, (targetDist - r) * -laserWidth, this.position);
+ sub = Matter.Vector.normalise(Matter.Vector.sub(laserOffL, this.position));
+ laserOffL = Matter.Vector.add(laserOffL, Matter.Vector.mult(sub, rangeWidth));
+ ctx.lineTo(laserOffL.x, laserOffL.y);
+ // ctx.fillStyle = "rgba(0,0,255,0.15)";
+ var gradient = ctx.createRadialGradient(this.position.x, this.position.y, 0, this.position.x, this.position.y, rangeWidth);
+ gradient.addColorStop(0, `rgba(0,0,255,${((r + 5) * (r + 5)) / (targetDist * targetDist)})`);
+ gradient.addColorStop(1, "transparent");
+ ctx.fillStyle = gradient;
+ ctx.fill();
+ }
+ } else {
+ this.laserPos = this.position;
+ }
+ };
+ }
+ },
+ laser(x, y, radius = 30) {
+ //only on level 1
+ mobs.spawn(x, y, 3, radius, "#f00");
+ let me = mob[mob.length - 1];
+ me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front
+ Matter.Body.rotate(me, Math.random() * Math.PI * 2);
+ me.accelMag = 0.00007 * game.accelScale;
+ me.onHit = function () {
+ //run this function on hitting player
+ this.explode();
+ };
+ me.do = function () {
+ this.healthBar();
+ this.seePlayerByLookingAt();
+ this.attraction();
+ this.laser();
+ };
+ },
+ striker(x, y, radius = 14 + Math.ceil(Math.random() * 25)) {
+ mobs.spawn(x, y, 5, radius, "rgb(221,102,119)");
+ let me = mob[mob.length - 1];
+ me.accelMag = 0.0003 * game.accelScale;
+ me.g = 0.0002; //required if using 'gravity'
+ me.frictionStatic = 0;
+ me.friction = 0;
+ me.delay = 100;
+ Matter.Body.rotate(me, Math.PI * 0.1);
+ me.onDamage = function () {
+ this.cd = game.cycle + this.delay;
+ };
+ me.do = function () {
+ this.healthBar();
+ this.seePlayerCheck();
+ this.attraction();
+ this.gravity();
+ this.strike();
+ };
+ },
+ sneaker(x, y, radius = 15 + Math.ceil(Math.random() * 25)) {
+ let me;
+ mobs.spawn(x, y, 5, radius, "transparent");
+ me = mob[mob.length - 1];
+ me.accelMag = 0.0007 * game.accelScale;
+ me.g = 0.0002; //required if using 'gravity'
+ me.stroke = "transparent"; //used for drawSneaker
+ me.alpha = 1; //used in drawSneaker
+ // me.leaveBody = false;
+ me.canTouchPlayer = false; //used in drawSneaker
+ me.collisionFilter.mask = 0x010111; //can't touch player
+ // me.memory = 420;
+ me.do = function () {
+
+ this.seePlayerCheck();
+ this.attraction();
+ this.gravity();
+ //draw
+ if (!mech.isBodiesAsleep) {
+ if (this.seePlayer.yes) {
+ if (this.alpha < 1) this.alpha += 0.01;
+ } else {
+ if (this.alpha > 0) this.alpha -= 0.03;
+ }
+ }
+ if (this.alpha > 0) {
+ if (this.alpha > 0.95) {
+ this.healthBar();
+ if (!this.canTouchPlayer) {
+ this.canTouchPlayer = true;
+ this.collisionFilter.mask = 0x011111; //can touch player
+ }
+ }
+ //draw body
+ ctx.beginPath();
+ const vertices = this.vertices;
+ ctx.moveTo(vertices[0].x, vertices[0].y);
+ for (let j = 1, len = vertices.length; j < len; ++j) {
+ ctx.lineTo(vertices[j].x, vertices[j].y);
+ }
+ ctx.lineTo(vertices[0].x, vertices[0].y);
+ ctx.fillStyle = `rgba(0,0,0,${this.alpha * this.alpha})`;
+ ctx.fill();
+ } else if (this.canTouchPlayer) {
+ this.canTouchPlayer = false;
+ this.collisionFilter.mask = 0x000111; //can't touch player
+ }
+ };
+ },
+ ghoster(x, y, radius = 40 + Math.ceil(Math.random() * 100)) {
+ let me;
+ mobs.spawn(x, y, 7, radius, "transparent");
+ me = mob[mob.length - 1];
+ me.seeAtDistance2 = 1000000;
+ me.accelMag = 0.00012 * game.accelScale;
+ if (map.length) me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; //required for search
+ Matter.Body.setDensity(me, 0.00065); //normal is 0.001 //makes effective life much lower
+ me.stroke = "transparent"; //used for drawGhost
+ me.alpha = 1; //used in drawGhost
+ me.canTouchPlayer = false; //used in drawGhost
+ // me.leaveBody = false;
+ me.collisionFilter.mask = 0x000100; //move through walls and player
+ me.memory = 480;
+ me.do = function () {
+ //cap max speed
+ if (this.speed > 5) {
+ Matter.Body.setVelocity(this, {
+ x: this.velocity.x * 0.8,
+ y: this.velocity.y * 0.8
+ });
+ }
+ this.seePlayerCheckByDistance();
+ this.attraction();
+ this.search();
+ //draw
+ if (this.distanceToPlayer2() - this.seeAtDistance2 < 0) {
+ if (this.alpha < 1) this.alpha += 0.004;
+ } else {
+ if (this.alpha > 0) this.alpha -= 0.03;
+ }
+ if (this.alpha > 0) {
+ if (this.alpha > 0.9) {
+ this.healthBar();
+ if (!this.canTouchPlayer) {
+ this.canTouchPlayer = true;
+ this.collisionFilter.mask = 0x001100; //can touch player but not walls
+ }
+ }
+ //draw body
+ ctx.beginPath();
+ const vertices = this.vertices;
+ ctx.moveTo(vertices[0].x, vertices[0].y);
+ for (let j = 1, len = vertices.length; j < len; ++j) {
+ ctx.lineTo(vertices[j].x, vertices[j].y);
+ }
+ ctx.lineTo(vertices[0].x, vertices[0].y);
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = `rgba(0,0,0,${this.alpha * this.alpha})`;
+ ctx.stroke();
+ } else if (this.canTouchPlayer) {
+ this.canTouchPlayer = false;
+ this.collisionFilter.mask = 0x000100; //can't touch player or walls
+ }
+ };
+ },
+ blinker(x, y, radius = 45 + Math.ceil(Math.random() * 70)) {
+ mobs.spawn(x, y, 6, radius, "transparent");
+ let me = mob[mob.length - 1];
+ Matter.Body.setDensity(me, 0.0005); //normal is 0.001 //makes effective life much lower
+ me.stroke = "rgb(0,200,255)"; //used for drawGhost
+ Matter.Body.rotate(me, Math.random() * 2 * Math.PI);
+ me.blinkRate = 40 + Math.round(Math.random() * 60); //required for blink
+ me.blinkLength = 150 + Math.round(Math.random() * 200); //required for blink
+ me.isStatic = true;
+ me.memory = 360;
+ me.seePlayerFreq = Math.round((40 + 30 * Math.random()) * game.lookFreqScale);
+ me.isBig = false;
+ me.scaleMag = Math.max(5 - me.mass, 1.75);
+ me.onDeath = function () {
+ if (this.isBig) {
+ Matter.Body.scale(this, 1 / this.scaleMag, 1 / this.scaleMag);
+ this.isBig = false;
+ }
+ };
+ me.do = function () {
+ this.healthBar();
+ this.seePlayerCheck();
+ this.blink();
+ //strike by expanding
+ if (this.isBig) {
+ if (this.cd - this.delay + 15 < game.cycle) {
+ Matter.Body.scale(this, 1 / this.scaleMag, 1 / this.scaleMag);
+ this.isBig = false;
+ }
+ } else if (this.seePlayer.yes && this.cd < game.cycle) {
+ const dist = Matter.Vector.sub(this.seePlayer.position, this.position);
+ const distMag2 = Matter.Vector.magnitudeSquared(dist);
+ if (distMag2 < 80000) {
+ this.cd = game.cycle + this.delay;
+ Matter.Body.scale(this, this.scaleMag, this.scaleMag);
+ this.isBig = true;
+ }
+ }
+ };
+ },
+ bomber(x, y, radius = 120 + Math.ceil(Math.random() * 70)) {
+ //boss that drops bombs from above and holds a set distance from player
+ mobs.spawn(x, y, 3, radius, "transparent");
+ let me = mob[mob.length - 1];
+ Matter.Body.setDensity(me, 0.0015 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger
+
+ me.stroke = "rgba(255,0,200)"; //used for drawGhost
+ me.seeAtDistance2 = 2000000;
+ me.fireFreq = Math.ceil(30 + 2000 / radius);
+ me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; //required for search
+ me.hoverElevation = 400 + (Math.random() - 0.5) * 200; //squared
+ me.hoverXOff = (Math.random() - 0.5) * 100;
+ me.accelMag = Math.floor(10 * (Math.random() + 5)) * 0.00001 * game.accelScale;
+ me.g = 0.0002; //required if using 'gravity' // gravity called in hoverOverPlayer
+ me.frictionStatic = 0;
+ me.friction = 0;
+ me.frictionAir = 0.01;
+ // me.memory = 300;
+ // Matter.Body.setDensity(me, 0.0015); //extra dense //normal is 0.001
+ me.collisionFilter.mask = 0x001100; //move through walls
+ spawn.shield(me, x, y);
+ me.onDeath = function () {
+ powerUps.spawnBossPowerUp(this.position.x, this.position.y)
+ };
+ me.do = function () {
+ this.healthBar();
+ this.seePlayerCheckByDistance();
+ this.hoverOverPlayer();
+ this.bomb();
+ this.search();
+ };
+ },
+ shooter(x, y, radius = 25 + Math.ceil(Math.random() * 50)) {
+ mobs.spawn(x, y, 3, radius, "rgb(255,100,150)");
+ let me = mob[mob.length - 1];
+ me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front
+ me.memory = 120;
+ me.fireFreq = 0.007 + Math.random() * 0.005;
+ me.noseLength = 0;
+ me.fireAngle = 0;
+ me.accelMag = 0.0005 * game.accelScale;
+ me.frictionAir = 0.05;
+ me.lookTorque = 0.0000025 * (Math.random() > 0.5 ? -1 : 1);
+ me.fireDir = {
+ x: 0,
+ y: 0
+ };
+ if (Math.random() < Math.min(0.15 + (game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y);
+ me.do = function () {
+ this.healthBar();
+ this.seePlayerByLookingAt();
+ this.fire();
+ };
+ },
+ shooterBoss(x, y, radius = 85 + Math.ceil(Math.random() * 50)) {
+ //boss spawns on skyscraper level
+ mobs.spawn(x, y, 3, radius, "rgb(255,70,180)");
+ let me = mob[mob.length - 1];
+ me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front
+ me.memory = 240;
+ me.fireFreq = 0.02;
+ me.noseLength = 0;
+ me.fireAngle = 0;
+ me.accelMag = 0.005 * game.accelScale;
+ me.frictionAir = 0.1;
+ me.lookTorque = 0.000005 * (Math.random() > 0.5 ? -1 : 1);
+ me.fireDir = {
+ x: 0,
+ y: 0
+ };
+ Matter.Body.setDensity(me, 0.001 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger
+ spawn.shield(me, x, y);
+ me.onDeath = function () {
+ powerUps.spawnBossPowerUp(this.position.x, this.position.y)
+ };
+ me.do = function () {
+ this.healthBar();
+ this.seePlayerByLookingAt();
+ this.fire();
+ };
+ },
+ bullet(x, y, radius = 6, sides = 0) {
+ //bullets
+ mobs.spawn(x, y, sides, radius, "rgb(255,0,0)");
+ let me = mob[mob.length - 1];
+ me.stroke = "transparent";
+ me.onHit = function () {
+ this.explode();
+ };
+ Matter.Body.setDensity(me, 0.001); //normal is 0.001
+ me.timeLeft = 240;
+ me.g = 0.001; //required if using 'gravity'
+ me.frictionAir = 0;
+ me.restitution = 0.8;
+ me.leaveBody = false;
+ me.dropPowerUp = false;
+ me.collisionFilter.category = 0x000010;
+ me.collisionFilter.mask = 0x011101;
+ me.do = function () {
+ this.gravity();
+ this.timeLimit();
+ };
+ },
+ spawner(x, y, radius = 55 + Math.ceil(Math.random() * 50)) {
+ mobs.spawn(x, y, 4, radius, "rgb(255,150,0)");
+ let me = mob[mob.length - 1];
+ me.g = 0.0004; //required if using 'gravity'
+ me.leaveBody = false;
+ // me.dropPowerUp = false;
+ me.onDeath = function () { //run this function on death
+ for (let i = 0; i < Math.ceil(this.mass * 0.2 + Math.random() * 3); ++i) {
+ spawn.spawns(this.position.x + (Math.random() - 0.5) * radius * 2.5, this.position.y + (Math.random() - 0.5) * radius * 2.5);
+ Matter.Body.setVelocity(mob[mob.length - 1], {
+ x: this.velocity.x + (Math.random() - 0.5) * 15,
+ y: this.velocity.x + (Math.random() - 0.5) * 15
+ });
+ }
+ };
+ if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.5)) spawn.shield(me, x, y);
+ me.do = function () {
+ this.healthBar();
+ this.gravity();
+ this.seePlayerCheck();
+ this.attraction();
+ };
+ },
+ spawns(x, y, radius = 15 + Math.ceil(Math.random() * 5)) {
+ mobs.spawn(x, y, 4, radius, "rgb(255,0,0)");
+ let me = mob[mob.length - 1];
+ me.onHit = function () {
+ //run this function on hitting player
+ this.explode();
+ };
+ me.g = 0.0001; //required if using 'gravity'
+ me.accelMag = 0.0003 * game.accelScale;
+ me.memory = 30;
+ me.leaveBody = false;
+ me.seePlayerFreq = Math.round((80 + 50 * Math.random()) * game.lookFreqScale);
+ me.frictionAir = 0.002;
+ me.do = function () {
+ this.healthBar();
+ this.gravity();
+ this.seePlayerCheck();
+ this.attraction();
+ };
+ },
+ exploder(x, y, radius = 25 + Math.ceil(Math.random() * 50)) {
+ mobs.spawn(x, y, 4, radius, "rgb(255,0,0)");
+ let me = mob[mob.length - 1];
+ me.onHit = function () {
+ //run this function on hitting player
+ this.explode();
+ };
+ me.g = 0.0004; //required if using 'gravity'
+ me.do = function () {
+ this.healthBar();
+ this.gravity();
+ this.seePlayerCheck();
+ this.attraction();
+ };
+ },
+ snaker(x, y, radius = 80) {
+ //snake boss with a laser head
+ mobs.spawn(x, y, 8, radius, "rgb(255,50,130)");
+ let me = mob[mob.length - 1];
+ me.accelMag = 0.0012 * game.accelScale;
+ me.memory = 200;
+ me.laserRange = 500;
+ Matter.Body.setDensity(me, 0.001 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger
+ spawn.shield(me, x, y);
+ if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y);
+ me.onDeath = function () {
+ powerUps.spawnBossPowerUp(this.position.x, this.position.y)
+ };
+ me.do = function () {
+ this.healthBar();
+ this.seePlayerCheck();
+ this.attraction();
+ this.laserBeam();
+ };
+
+ //snake tail
+ const nodes = Math.min(3 + Math.ceil(Math.random() * game.difficulty + 2), 8)
+ spawn.lineBoss(x + 105, y, "spawns", nodes);
+ //constraint boss with first 3 mobs in lineboss
+ consBB[consBB.length] = Constraint.create({
+ bodyA: mob[mob.length - nodes],
+ bodyB: mob[mob.length - 1 - nodes],
+ stiffness: 0.05
+ });
+ consBB[consBB.length] = Constraint.create({
+ bodyA: mob[mob.length - nodes + 1],
+ bodyB: mob[mob.length - 1 - nodes],
+ stiffness: 0.05
+ });
+ consBB[consBB.length] = Constraint.create({
+ bodyA: mob[mob.length - nodes + 2],
+ bodyB: mob[mob.length - 1 - nodes],
+ stiffness: 0.05
+ });
+
+ },
+ tether(x, y, radius = 90) {
+ // constrained mob boss for the towers level
+ // often has a ring of mobs around it
+ mobs.spawn(x, y, 8, radius, "rgb(0,60,80)");
+ let me = mob[mob.length - 1];
+ me.g = 0.0001; //required if using 'gravity'
+ me.accelMag = 0.002 * game.accelScale;
+ me.memory = 20;
+ Matter.Body.setDensity(me, 0.001 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger
+ spawn.shield(me, x, y);
+ if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y);
+
+ me.onDeath = function () {
+ powerUps.spawnBossPowerUp(this.position.x, this.position.y)
+ this.removeCons(); //remove constraint
+ };
+ me.do = function () {
+ this.healthBar();
+ this.gravity();
+ this.seePlayerCheck();
+ this.attraction();
+ };
+ },
+ shield(target, x, y, stiffness = 0.4) {
+ if (this.allowShields) {
+ mobs.spawn(x, y, 9, target.radius + 20, "rgba(220,220,255,0.6)");
+ let me = mob[mob.length - 1];
+ me.stroke = "rgb(220,220,255)";
+ Matter.Body.setDensity(me, 0.0001) //very low density to not mess with the original mob's motion
+ me.shield = true;
+ me.collisionFilter.mask = 0x000100; //don't collide with bodies, map, player, and mobs, only bullets
+ consBB[consBB.length] = Constraint.create({
+ //attach shield to last spawned mob
+ bodyA: me,
+ bodyB: target,
+ stiffness: stiffness,
+ damping: 0.1
+ });
+ me.onDamage = function () {
+ //make sure the mob that owns the shield can tell when damage is done
+ this.alertNearByMobs();
+ };
+ me.leaveBody = false;
+ me.dropPowerUp = false;
+ //swap order of shield and mob, so that mob is behind shield graphically
+ mob[mob.length - 1] = mob[mob.length - 2];
+ mob[mob.length - 2] = me;
+ me.do = function () {};
+ }
+ },
+ bossShield(nodes, x, y, radius) {
+ mobs.spawn(x, y, 9, radius, "rgba(220,220,255,0.65)");
+ let me = mob[mob.length - 1];
+ me.stroke = "rgb(220,220,255)";
+ Matter.Body.setDensity(me, 0.00005) //very low density to not mess with the original mob's motion
+ me.frictionAir = 0;
+ me.shield = true;
+ me.collisionFilter.mask = 0x000100; //don't collide with bodies, map, and mobs, only bullets and player
+ //constrain to all mob nodes in boss
+ for (let i = 0; i < nodes; ++i) {
+ consBB[consBB.length] = Constraint.create({
+ bodyA: me,
+ bodyB: mob[mob.length - i - 2],
+ stiffness: 0.4,
+ damping: 0.1
+ });
+ }
+ me.onDamage = function () {
+ //make sure the mob that owns the shield can tell when damage is done
+ this.alertNearByMobs();
+ };
+ me.leaveBody = false;
+ me.dropPowerUp = false;
+ mob[mob.length - 1] = mob[mob.length - 1 - nodes];
+ mob[mob.length - 1 - nodes] = me;
+ me.do = function () {};
+ },
+ //complex constrained mob templates**********************************************************************
+ //*******************************************************************************************************
+ allowShields: true,
+ nodeBoss(
+ x,
+ y,
+ spawn = "striker",
+ nodes = Math.min(2 + Math.ceil(Math.random() * (game.difficulty + 2)), 8),
+ //Math.ceil(Math.random() * 3) + Math.min(4,Math.ceil(game.difficulty/2)),
+ radius = Math.ceil(Math.random() * 10) + 17, // radius of each node mob
+ sideLength = Math.ceil(Math.random() * 100) + 70, // distance between each node mob
+ stiffness = Math.random() * 0.03 + 0.005
+ ) {
+ this.allowShields = false; //don't want shields on boss mobs
+ const angle = 2 * Math.PI / nodes
+ for (let i = 0; i < nodes; ++i) {
+ let whoSpawn = spawn;
+ if (spawn === "random") {
+ whoSpawn = this.fullPickList[Math.floor(Math.random() * this.fullPickList.length)];
+ } else if (spawn === "randomList") {
+ whoSpawn = this.pickList[Math.floor(Math.random() * this.pickList.length)];
+ }
+ this[whoSpawn](x + sideLength * Math.sin(i * angle), y + sideLength * Math.cos(i * angle), radius);
+ }
+ if (Math.random() < 0.3) {
+ this.constrain2AdjacentMobs(nodes, stiffness * 2, true);
+ } else {
+ this.constrainAllMobCombos(nodes, stiffness);
+ }
+ //spawn shield for entire boss
+ if (nodes > 2 && Math.random() < 0.998) {
+ this.bossShield(nodes, x, y, sideLength + 2.5 * radius + nodes * 6 - 25);
+ // this.bossShield(nodes, x, y, sideLength / (2 * Math.sin(Math.PI / nodes)));
+ }
+ this.allowShields = true;
+ },
+ lineBoss(
+ x,
+ y,
+ spawn = "striker",
+ nodes = Math.min(3 + Math.ceil(Math.random() * game.difficulty + 2), 8),
+ //Math.ceil(Math.random() * 3) + Math.min(4,Math.ceil(game.difficulty/2)),
+ radius = Math.ceil(Math.random() * 10) + 17,
+ l = Math.ceil(Math.random() * 80) + 30,
+ stiffness = Math.random() * 0.06 + 0.01
+ ) {
+ this.allowShields = false; //dont' want shields on boss mobs
+ for (let i = 0; i < nodes; ++i) {
+ let whoSpawn = spawn;
+ if (spawn === "random") {
+ whoSpawn = this.fullPickList[Math.floor(Math.random() * this.fullPickList.length)];
+ } else if (spawn === "randomList") {
+ whoSpawn = this.pickList[Math.floor(Math.random() * this.pickList.length)];
+ }
+ this[whoSpawn](x + i * radius + i * l, y, radius);
+ }
+ this.constrain2AdjacentMobs(nodes, stiffness);
+ this.allowShields = true;
+ },
+ //constraints ************************************************************************************************
+ //*************************************************************************************************************
+ constrainAllMobCombos(nodes, stiffness) {
+ //runs through every combination of last 'num' bodies and constrains them
+ for (let i = 1; i < nodes + 1; ++i) {
+ for (let j = i + 1; j < nodes + 1; ++j) {
+ consBB[consBB.length] = Constraint.create({
+ bodyA: mob[mob.length - i],
+ bodyB: mob[mob.length - j],
+ stiffness: stiffness
+ });
+ }
+ }
+ },
+ constrain2AdjacentMobs(nodes, stiffness, loop = false) {
+ //runs through every combination of last 'num' bodies and constrains them
+ for (let i = 0; i < nodes - 1; ++i) {
+ consBB[consBB.length] = Constraint.create({
+ bodyA: mob[mob.length - i - 1],
+ bodyB: mob[mob.length - i - 2],
+ stiffness: stiffness
+ });
+ }
+ if (nodes > 2) {
+ for (let i = 0; i < nodes - 2; ++i) {
+ consBB[consBB.length] = Constraint.create({
+ bodyA: mob[mob.length - i - 1],
+ bodyB: mob[mob.length - i - 3],
+ stiffness: stiffness
+ });
+ }
+ }
+ //optional connect the tail to head
+ if (loop && nodes > 3) {
+ consBB[consBB.length] = Constraint.create({
+ bodyA: mob[mob.length - 1],
+ bodyB: mob[mob.length - nodes],
+ stiffness: stiffness
+ });
+ consBB[consBB.length] = Constraint.create({
+ bodyA: mob[mob.length - 2],
+ bodyB: mob[mob.length - nodes],
+ stiffness: stiffness
+ });
+ consBB[consBB.length] = Constraint.create({
+ bodyA: mob[mob.length - 1],
+ bodyB: mob[mob.length - nodes + 1],
+ stiffness: stiffness
+ });
+ }
+ },
+ constraintPB(x, y, bodyIndex, stiffness) {
+ cons[cons.length] = Constraint.create({
+ pointA: {
+ x: x,
+ y: y
+ },
+ bodyB: body[bodyIndex],
+ stiffness: stiffness
+ });
+ },
+ constraintBB(bodyIndexA, bodyIndexB, stiffness) {
+ consBB[consBB.length] = Constraint.create({
+ bodyA: body[bodyIndexA],
+ bodyB: body[bodyIndexB],
+ stiffness: stiffness
+ });
+ },
+ // body and map spawns ******************************************************************************
+ //**********************************************************************************************
+ wireHead() {
+ //not a mob, just a graphic for level 1
+ const breakingPoint = 1300
+ mobs.spawn(breakingPoint, -100, 0, 7.5, "transparent");
+ let me = mob[mob.length - 1];
+ //touch nothing
+ me.collisionFilter.category = 0x010000; //act like a body
+ me.collisionFilter.mask = 0x000101; //only collide with map
+ me.inertia = Infinity;
+ me.g = 0.0004; //required for gravity
+ me.restitution = 0;
+ me.stroke = "transparent"
+ me.freeOfWires = false;
+ me.frictionStatic = 1;
+ me.friction = 1;
+ me.frictionAir = 0.01;
+
+ me.do = function () {
+ let wireX = -50;
+ let wireY = -1000;
+ if (this.freeOfWires) {
+ this.gravity();
+ } else {
+ if (mech.pos.x > breakingPoint) {
+ this.freeOfWires = true;
+ this.fill = "#000"
+ this.force.x += -0.003;
+ player.force.x += 0.06;
+ // player.force.y -= 0.15;
+ }
+
+ //player is extra heavy from wires
+ Matter.Body.setVelocity(player, {
+ x: player.velocity.x,
+ y: player.velocity.y + 0.3
+ })
+
+ //player friction from the wires
+ if (mech.pos.x > 700 && player.velocity.x > -2) {
+ let wireFriction = 0.75 * Math.min(0.6, Math.max(0, 100 / (breakingPoint - mech.pos.x)));
+ if (!mech.onGround) wireFriction *= 3
+ Matter.Body.setVelocity(player, {
+ x: player.velocity.x - wireFriction,
+ y: player.velocity.y
+ })
+ }
+ //move to player
+ Matter.Body.setPosition(this, {
+ x: mech.pos.x + (42 * Math.cos(mech.angle + Math.PI)),
+ y: mech.pos.y + (42 * Math.sin(mech.angle + Math.PI))
+ })
+ }
+ //draw wire
+ ctx.beginPath();
+ ctx.moveTo(wireX, wireY);
+ ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y);
+ if (!this.freeOfWires) ctx.lineTo(mech.pos.x + (30 * Math.cos(mech.angle + Math.PI)), mech.pos.y + (30 * Math.sin(mech.angle + Math.PI)));
+ ctx.lineCap = "butt";
+ ctx.lineWidth = 15;
+ ctx.strokeStyle = "#000";
+ ctx.stroke();
+ ctx.lineCap = "round";
+ };
+ },
+ wireKnee() {
+ //not a mob, just a graphic for level 1
+ const breakingPoint = 1425
+ mobs.spawn(breakingPoint, -100, 0, 2, "transparent");
+ let me = mob[mob.length - 1];
+ //touch nothing
+ me.collisionFilter.category = 0x010000; //act like a body
+ me.collisionFilter.mask = 0x000101; //only collide with map
+ me.g = 0.0003; //required for gravity
+ // me.restitution = 0;
+ me.stroke = "transparent"
+ // me.inertia = Infinity;
+ me.restitution = 0;
+ me.freeOfWires = false;
+ me.frictionStatic = 1;
+ me.friction = 1;
+ me.frictionAir = 0.01;
+
+ me.do = function () {
+ let wireX = -50 - 20;
+ let wireY = -1000;
+
+ if (this.freeOfWires) {
+ this.gravity();
+ } else {
+ if (mech.pos.x > breakingPoint) {
+ this.freeOfWires = true;
+ this.force.x -= 0.0004;
+ this.fill = "#222";
+ }
+ //move mob to player
+ mech.calcLeg(0, 0);
+ Matter.Body.setPosition(this, {
+ x: mech.pos.x + mech.flipLegs * mech.knee.x - 5,
+ y: mech.pos.y + mech.knee.y
+ })
+ }
+ //draw wire
+ ctx.beginPath();
+ ctx.moveTo(wireX, wireY);
+ ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y);
+ ctx.lineWidth = 5;
+ ctx.strokeStyle = "#222";
+ ctx.lineCap = "butt";
+ ctx.stroke();
+ ctx.lineCap = "round";
+ };
+ },
+ wireKneeLeft() {
+ //not a mob, just a graphic for level 1
+ const breakingPoint = 1400
+ mobs.spawn(breakingPoint, -100, 0, 2, "transparent");
+ let me = mob[mob.length - 1];
+ //touch nothing
+ me.collisionFilter.category = 0x010000; //act like a body
+ me.collisionFilter.mask = 0x000101; //only collide with map
+ me.g = 0.0003; //required for gravity
+ // me.restitution = 0;
+ me.stroke = "transparent"
+ // me.inertia = Infinity;
+ me.restitution = 0;
+ me.freeOfWires = false;
+ me.frictionStatic = 1;
+ me.friction = 1;
+ me.frictionAir = 0.01;
+
+ me.do = function () {
+ let wireX = -50 - 35;
+ let wireY = -1000;
+
+ if (this.freeOfWires) {
+ this.gravity();
+ } else {
+ if (mech.pos.x > breakingPoint) {
+ this.freeOfWires = true;
+ this.force.x += -0.0003;
+ this.fill = "#333";
+ }
+ //move mob to player
+ mech.calcLeg(Math.PI, -3);
+ Matter.Body.setPosition(this, {
+ x: mech.pos.x + mech.flipLegs * mech.knee.x - 5,
+ y: mech.pos.y + mech.knee.y
+ })
+ }
+ //draw wire
+ ctx.beginPath();
+ ctx.moveTo(wireX, wireY);
+ ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y);
+ ctx.lineWidth = 5;
+ ctx.lineCap = "butt";
+ ctx.strokeStyle = "#333";
+ ctx.stroke();
+ ctx.lineCap = "round";
+ };
+ },
+ wireFoot() {
+ //not a mob, just a graphic for level 1
+ const breakingPoint = 1350
+ mobs.spawn(breakingPoint, -100, 0, 2, "transparent");
+ let me = mob[mob.length - 1];
+ //touch nothing
+ me.collisionFilter.category = 0x010000; //act like a body
+ me.collisionFilter.mask = 0x000101; //only collide with map
+ me.g = 0.0003; //required for gravity
+ me.restitution = 0;
+ me.stroke = "transparent"
+ // me.inertia = Infinity;
+ me.freeOfWires = false;
+ // me.frictionStatic = 1;
+ // me.friction = 1;
+ me.frictionAir = 0.01;
+
+ me.do = function () {
+ let wireX = -50 + 16;
+ let wireY = -1000;
+
+ if (this.freeOfWires) {
+ this.gravity();
+ } else {
+ if (mech.pos.x > breakingPoint) {
+ this.freeOfWires = true;
+ this.force.x += -0.0006;
+ this.fill = "#222";
+ }
+ //move mob to player
+ mech.calcLeg(0, 0);
+ Matter.Body.setPosition(this, {
+ x: mech.pos.x + mech.flipLegs * mech.foot.x - 5,
+ y: mech.pos.y + mech.foot.y - 1
+ })
+ }
+ //draw wire
+ ctx.beginPath();
+ ctx.moveTo(wireX, wireY);
+ ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y);
+ ctx.lineWidth = 5;
+ ctx.lineCap = "butt";
+ ctx.strokeStyle = "#222";
+ ctx.stroke();
+ ctx.lineCap = "round";
+ };
+ },
+ wireFootLeft() {
+ //not a mob, just a graphic for level 1
+ const breakingPoint = 1325
+ mobs.spawn(breakingPoint, -100, 0, 2, "transparent");
+ let me = mob[mob.length - 1];
+ //touch nothing
+ me.collisionFilter.category = 0x010000; //act like a body
+ me.collisionFilter.mask = 0x000101; //only collide with map
+ me.g = 0.0003; //required for gravity
+ me.restitution = 0;
+ me.stroke = "transparent"
+ // me.inertia = Infinity;
+ me.freeOfWires = false;
+ // me.frictionStatic = 1;
+ // me.friction = 1;
+ me.frictionAir = 0.01;
+
+ me.do = function () {
+ let wireX = -50 + 26;
+ let wireY = -1000;
+
+ if (this.freeOfWires) {
+ this.gravity();
+ } else {
+ if (mech.pos.x > breakingPoint) {
+ this.freeOfWires = true;
+ this.force.x += -0.0005;
+ this.fill = "#333";
+ }
+ //move mob to player
+ mech.calcLeg(Math.PI, -3);
+ Matter.Body.setPosition(this, {
+ x: mech.pos.x + mech.flipLegs * mech.foot.x - 5,
+ y: mech.pos.y + mech.foot.y - 1
+ })
+ }
+ //draw wire
+ ctx.beginPath();
+ ctx.moveTo(wireX, wireY);
+ ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y);
+ ctx.lineWidth = 5;
+ ctx.strokeStyle = "#333";
+ ctx.lineCap = "butt";
+ ctx.stroke();
+ ctx.lineCap = "round";
+ };
+ },
+ boost(x, y, height = 1000) {
+ spawn.mapVertex(x + 50, y + 35, "120 40 -120 40 -50 -40 50 -40");
+ // level.addZone(x, y, 100, 30, "fling", {Vx:Vx, Vy: Vy});
+ level.addQueryRegion(x, y - 20, 100, 20, "boost", [
+ [player], body, mob, powerUp, bullet
+ ], -1.1 * Math.sqrt(Math.abs(height)));
+ let color = "rgba(200,0,255,";
+ level.fillBG.push({
+ x: x,
+ y: y - 25,
+ width: 100,
+ height: 25,
+ color: color + "0.2)"
+ });
+ level.fillBG.push({
+ x: x,
+ y: y - 55,
+ width: 100,
+ height: 55,
+ color: color + "0.1)"
+ });
+ level.fillBG.push({
+ x: x,
+ y: y - 120,
+ width: 100,
+ height: 120,
+ color: color + "0.05)"
+ });
+ },
+ laserZone(x, y, width, height, dmg) {
+ level.addZone(x, y, width, height, "laser", {
+ dmg
+ });
+ level.fill.push({
+ x: x,
+ y: y,
+ width: width,
+ height: height,
+ color: "#f00"
+ });
+ },
+ deathQuery(x, y, width, height) {
+ level.addQueryRegion(x, y, width, height, "death", [
+ [player], mob
+ ]);
+ level.fill.push({
+ x: x,
+ y: y,
+ width: width,
+ height: height,
+ color: "#f00"
+ });
+ },
+ platform(x, y, width, height) {
+ const size = 20;
+ spawn.mapRect(x, y + height, width, 30);
+ level.fillBG.push({
+ x: x + width / 2 - size / 2,
+ y: y,
+ width: size,
+ height: height,
+ color: "#f0f0f3"
+ });
+ },
+ debris(x, y, width, number = Math.floor(3 + Math.random() * 11)) {
+ for (let i = 0; i < number; ++i) {
+ if (Math.random() < 0.15) {
+ powerUps.chooseRandomPowerUp(x + Math.random() * width, y);
+ } else {
+ const size = 18 + Math.random() * 25;
+ spawn.bodyRect(x + Math.random() * width, y, size * (0.6 + Math.random()), size * (0.6 + Math.random()), 1);
+ // body[body.length] = Bodies.rectangle(x + Math.random() * width, y, size * (0.6 + Math.random()), size * (0.6 + Math.random()));
+ }
+ }
+ },
+ bodyRect(x, y, width, height, chance = 1, properties = {
+ friction: 0.05,
+ frictionAir: 0.01
+ }) {
+ if (Math.random() < chance) body[body.length] = Bodies.rectangle(x + width / 2, y + height / 2, width, height, properties);
+ },
+ bodyVertex(x, y, vector, properties) { //adds shape to body array
+ body[body.length] = Matter.Bodies.fromVertices(x, y, Vertices.fromPath(vector), properties);
+ },
+ mapRect(x, y, width, height, properties) { //adds rectangle to map array
+ map[map.length] = Bodies.rectangle(x + width / 2, y + height / 2, width, height, properties);
+ },
+ mapVertex(x, y, vector, properties) { //adds shape to map array
+ map[map.length] = Matter.Bodies.fromVertices(x, y, Vertices.fromPath(vector), properties);
+ },
+ //complex map templates
+ spawnBuilding(x, y, w, h, leftDoor, rightDoor, walledSide) {
+ this.mapRect(x, y, w, 25); //roof
+ this.mapRect(x, y + h, w, 35); //ground
+ if (walledSide === "left") {
+ this.mapRect(x, y, 25, h); //wall left
+ } else {
+ this.mapRect(x, y, 25, h - 150); //wall left
+ if (leftDoor) {
+ this.bodyRect(x + 5, y + h - 150, 15, 150, this.propsFriction); //door left
+ }
+ }
+ if (walledSide === "right") {
+ this.mapRect(x - 25 + w, y, 25, h); //wall right
+ } else {
+ this.mapRect(x - 25 + w, y, 25, h - 150); //wall right
+ if (rightDoor) {
+ this.bodyRect(x + w - 20, y + h - 150, 15, 150, this.propsFriction); //door right
+ }
+ }
+ },
+ spawnStairs(x, y, num, w, h, stepRight) {
+ w += 50;
+ if (stepRight) {
+ for (let i = 0; i < num; i++) {
+ this.mapRect(x - (w / num) * (1 + i), y - h + (i * h) / num, w / num + 50, h - (i * h) / num + 50);
+ }
+ } else {
+ for (let i = 0; i < num; i++) {
+ this.mapRect(x + (i * w) / num, y - h + (i * h) / num, w / num + 50, h - (i * h) / num + 50);
+ }
+ }
+ },
+ //pre-made property options*************************************************************************************
+ //*************************************************************************************************************
+ //Object.assign({}, propsHeavy, propsBouncy, propsNoRotation) //will combine properties into a new object
+ propsFriction: {
+ friction: 0.5,
+ frictionAir: 0.02,
+ frictionStatic: 1
+ },
+ propsFrictionMedium: {
+ friction: 0.15,
+ frictionStatic: 1
+ },
+ propsBouncy: {
+ friction: 0,
+ frictionAir: 0,
+ frictionStatic: 0,
+ restitution: 1
+ },
+ propsSlide: {
+ friction: 0.003,
+ frictionStatic: 0.4,
+ restitution: 0,
+ density: 0.002
+ },
+ propsLight: {
+ density: 0.001
+ },
+ propsOverBouncy: {
+ friction: 0,
+ frictionAir: 0,
+ frictionStatic: 0,
+ restitution: 1.05
+ },
+ propsHeavy: {
+ density: 0.01 //default density is 0.001
+ },
+ propsIsNotHoldable: {
+ isNotHoldable: true
+ },
+ propsNoRotation: {
+ inertia: Infinity //prevents rotation
+ },
+ propsHoist: {
+ inertia: Infinity, //prevents rotation
+ frictionAir: 0.001,
+ friction: 0.0001,
+ frictionStatic: 0,
+ restitution: 0,
+ isNotHoldable: true
+ // density: 0.0001
+ },
+ propsDoor: {
+ density: 0.001, //default density is 0.001
+ friction: 0,
+ frictionAir: 0.03,
+ frictionStatic: 0,
+ restitution: 0
+ },
+ sandPaper: {
+ friction: 1,
+ frictionStatic: 1,
+ restitution: 0
+ }
};
\ No newline at end of file