added spore mod
This commit is contained in:
1366
js/bullets.js
Normal file
1366
js/bullets.js
Normal file
File diff suppressed because it is too large
Load Diff
174
js/engine.js
Normal file
174
js/engine.js
Normal file
@@ -0,0 +1,174 @@
|
||||
//matter.js ***********************************************************
|
||||
// module aliases
|
||||
const Engine = Matter.Engine,
|
||||
World = Matter.World,
|
||||
Events = Matter.Events,
|
||||
Composites = Matter.Composites,
|
||||
Composite = Matter.Composite,
|
||||
Constraint = Matter.Constraint,
|
||||
Vertices = Matter.Vertices,
|
||||
Query = Matter.Query,
|
||||
Body = Matter.Body,
|
||||
Bodies = Matter.Bodies;
|
||||
|
||||
// create an engine
|
||||
const engine = Engine.create();
|
||||
engine.world.gravity.scale = 0; //turn off gravity (it's added back in later)
|
||||
// engine.velocityIterations = 100
|
||||
// engine.positionIterations = 100
|
||||
// engine.enableSleeping = true
|
||||
|
||||
// matter events *********************************************************
|
||||
//************************************************************************
|
||||
//************************************************************************
|
||||
//************************************************************************
|
||||
|
||||
function playerOnGroundCheck(event) {
|
||||
//runs on collisions events
|
||||
function enter() {
|
||||
mech.numTouching++;
|
||||
if (!mech.onGround) mech.enterLand();
|
||||
}
|
||||
const pairs = event.pairs;
|
||||
for (let i = 0, j = pairs.length; i != j; ++i) {
|
||||
let pair = pairs[i];
|
||||
if (pair.bodyA === jumpSensor) {
|
||||
mech.standingOn = pair.bodyB; //keeping track to correctly provide recoil on jump
|
||||
enter();
|
||||
} else if (pair.bodyB === jumpSensor) {
|
||||
mech.standingOn = pair.bodyA; //keeping track to correctly provide recoil on jump
|
||||
enter();
|
||||
}
|
||||
}
|
||||
mech.numTouching = 0;
|
||||
}
|
||||
|
||||
function playerOffGroundCheck(event) {
|
||||
//runs on collisions events
|
||||
function enter() {
|
||||
if (mech.onGround && mech.numTouching === 0) mech.enterAir();
|
||||
}
|
||||
const pairs = event.pairs;
|
||||
for (let i = 0, j = pairs.length; i != j; ++i) {
|
||||
if (pairs[i].bodyA === jumpSensor) {
|
||||
enter();
|
||||
} else if (pairs[i].bodyB === jumpSensor) {
|
||||
enter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function playerHeadCheck(event) {
|
||||
//runs on collisions events
|
||||
if (mech.crouch) {
|
||||
mech.isHeadClear = true;
|
||||
const pairs = event.pairs;
|
||||
for (let i = 0, j = pairs.length; i != j; ++i) {
|
||||
if (pairs[i].bodyA === headSensor) {
|
||||
mech.isHeadClear = false;
|
||||
} else if (pairs[i].bodyB === headSensor) {
|
||||
mech.isHeadClear = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mobCollisionChecks(event) {
|
||||
const pairs = event.pairs;
|
||||
for (let i = 0, j = pairs.length; i != j; i++) {
|
||||
for (let k = 0; k < mob.length; k++) {
|
||||
if (mob[k].alive && mech.alive) {
|
||||
if (pairs[i].bodyA === mob[k]) {
|
||||
collide(pairs[i].bodyB);
|
||||
break;
|
||||
} else if (pairs[i].bodyB === mob[k]) {
|
||||
collide(pairs[i].bodyA);
|
||||
break;
|
||||
}
|
||||
|
||||
function collide(obj) {
|
||||
//player and mob collision
|
||||
if (obj === playerBody || obj === playerHead) {
|
||||
if (mech.damageImmune < mech.cycle) {
|
||||
//player is immune to mob collision damage for 30 cycles
|
||||
mech.damageImmune = mech.cycle + 30;
|
||||
mob[k].foundPlayer();
|
||||
let dmg = Math.min(Math.max(0.025 * Math.sqrt(mob[k].mass), 0.05), 0.3) * game.dmgScale; //player damage is capped at 0.3*dmgScale of 1.0
|
||||
mech.damage(dmg);
|
||||
if (mob[k].onHit) mob[k].onHit(k);
|
||||
game.drawList.push({
|
||||
//add dmg to draw queue
|
||||
x: pairs[i].activeContacts[0].vertex.x,
|
||||
y: pairs[i].activeContacts[0].vertex.y,
|
||||
radius: dmg * 500,
|
||||
color: game.mobDmgColor,
|
||||
time: game.drawTime
|
||||
});
|
||||
}
|
||||
//extra kick between player and mob
|
||||
//this section would be better with forces but they don't work...
|
||||
let angle = Math.atan2(player.position.y - mob[k].position.y, player.position.x - mob[k].position.x);
|
||||
Matter.Body.setVelocity(player, {
|
||||
x: player.velocity.x + 8 * Math.cos(angle),
|
||||
y: player.velocity.y + 8 * Math.sin(angle)
|
||||
});
|
||||
Matter.Body.setVelocity(mob[k], {
|
||||
x: mob[k].velocity.x - 8 * Math.cos(angle),
|
||||
y: mob[k].velocity.y - 8 * Math.sin(angle)
|
||||
});
|
||||
return;
|
||||
}
|
||||
//bullet mob collisions
|
||||
if (obj.classType === "bullet" && obj.speed > obj.minDmgSpeed) {
|
||||
mob[k].foundPlayer();
|
||||
const dmg = b.dmgScale * (obj.dmg + 0.15 * obj.mass * Matter.Vector.magnitude(Matter.Vector.sub(mob[k].velocity, obj.velocity)));
|
||||
// console.log(dmg)
|
||||
mob[k].damage(dmg);
|
||||
obj.onDmg(); //some bullets do actions when they hits things, like despawn
|
||||
game.drawList.push({
|
||||
//add dmg to draw queue
|
||||
x: pairs[i].activeContacts[0].vertex.x,
|
||||
y: pairs[i].activeContacts[0].vertex.y,
|
||||
radius: Math.sqrt(dmg) * 40,
|
||||
color: game.playerDmgColor,
|
||||
time: game.drawTime
|
||||
});
|
||||
return;
|
||||
}
|
||||
//mob and body collisions
|
||||
if (obj.classType === "body" && obj.speed > 5) {
|
||||
const v = Matter.Vector.magnitude(Matter.Vector.sub(mob[k].velocity, obj.velocity));
|
||||
if (v > 8) {
|
||||
let dmg = b.dmgScale * v * Math.sqrt(obj.mass) * 0.05;
|
||||
mob[k].damage(dmg);
|
||||
if (mob[k].distanceToPlayer2() < 1000000) mob[k].foundPlayer();
|
||||
game.drawList.push({
|
||||
//add dmg to draw queue
|
||||
x: pairs[i].activeContacts[0].vertex.x,
|
||||
y: pairs[i].activeContacts[0].vertex.y,
|
||||
radius: Math.sqrt(dmg) * 40,
|
||||
color: game.playerDmgColor,
|
||||
time: game.drawTime
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//determine if player is on the ground
|
||||
Events.on(engine, "collisionStart", function (event) {
|
||||
playerOnGroundCheck(event);
|
||||
playerHeadCheck(event);
|
||||
mobCollisionChecks(event);
|
||||
});
|
||||
Events.on(engine, "collisionActive", function (event) {
|
||||
playerOnGroundCheck(event);
|
||||
playerHeadCheck(event);
|
||||
});
|
||||
Events.on(engine, "collisionEnd", function (event) {
|
||||
playerOffGroundCheck(event);
|
||||
});
|
||||
1028
js/game.js
Normal file
1028
js/game.js
Normal file
File diff suppressed because it is too large
Load Diff
197
js/index.js
Normal file
197
js/index.js
Normal file
@@ -0,0 +1,197 @@
|
||||
"use strict";
|
||||
/* TODO: *******************************************
|
||||
*****************************************************
|
||||
|
||||
make power ups keep moving to player if the field is turned off
|
||||
|
||||
levels 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
|
||||
|
||||
Find a diegetic way to see player damage (and or field meter too)
|
||||
a health meter, like the field meter above player? (doesn't work with the field meter)
|
||||
|
||||
field power up effects
|
||||
field allows player to hold and throw living mobs
|
||||
|
||||
mod power ups ideas
|
||||
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 rainworld
|
||||
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
|
||||
|
||||
track foot positions with velocity better as the player walks/crouch/runs
|
||||
|
||||
Boss ideas
|
||||
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.
|
||||
|
||||
|
||||
|
||||
// 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
|
||||
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
//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
|
||||
});
|
||||
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
1593
js/level.js
Normal file
1593
js/level.js
Normal file
File diff suppressed because it is too large
Load Diff
990
js/mobs.js
Normal file
990
js/mobs.js
Normal file
@@ -0,0 +1,990 @@
|
||||
//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) {
|
||||
mech.damage(0.0003 * game.dmgScale);
|
||||
if (mech.fieldMeter > 0.1) mech.fieldMeter -= 0.005
|
||||
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([]);
|
||||
}
|
||||
},
|
||||
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) {
|
||||
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.05) {
|
||||
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 < 430) {
|
||||
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) {
|
||||
this.health -= dmg / Math.sqrt(this.mass);
|
||||
//this.fill = this.color + this.health + ')';
|
||||
if (this.health < 0.1) this.death();
|
||||
this.onDamage(this); //custom damage effects
|
||||
if (b.modEnergySiphon) mech.fieldMeter += dmg * b.modEnergySiphon
|
||||
if (b.modHealthDrain) mech.addHealth(dmg * b.modHealthDrain)
|
||||
},
|
||||
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 (b.modSpores && Math.random() < 0.4) 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 + 200, 2);
|
||||
World.add(engine.world, mob[i]); //add to world
|
||||
}
|
||||
};
|
||||
1451
js/player.js
Normal file
1451
js/player.js
Normal file
File diff suppressed because it is too large
Load Diff
232
js/powerups.js
Normal file
232
js/powerups.js
Normal file
@@ -0,0 +1,232 @@
|
||||
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
|
||||
heal = Math.min(1 - mech.health, heal)
|
||||
mech.addHealth(heal);
|
||||
if (!game.lastLogTime && heal > 0) game.makeTextLog('heal for ' + (heal * 100).toFixed(0) + '%', 180)
|
||||
}
|
||||
},
|
||||
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", 180);
|
||||
} else {
|
||||
//ammo given scales as mobs take more hits to kill
|
||||
const ammo = Math.ceil((target.ammoPack * (0.6 + 0.04 * Math.random())) / b.dmgScale);
|
||||
target.ammo += ammo;
|
||||
game.updateGunHUD();
|
||||
if (!game.lastLogTime) game.makeTextLog("+" + ammo + " ammo: " + target.name, 180);
|
||||
}
|
||||
}
|
||||
},
|
||||
field: {
|
||||
name: "field",
|
||||
color: "#0bf",
|
||||
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.makeTextLog(`<strong style='font-size:30px;'>${b.mods[newMod].name}</strong><br> <p>${b.mods[newMod].description}</p>`, 1200);
|
||||
} else {
|
||||
//what should happen if you have all the mods?
|
||||
}
|
||||
}
|
||||
},
|
||||
gun: {
|
||||
name: "gun",
|
||||
color: "#37a",
|
||||
size() {
|
||||
return 35;
|
||||
},
|
||||
effect() {
|
||||
//find what guns I don't have
|
||||
let options = [];
|
||||
if (b.activeGun === null) { //choose the first gun to be one that is good for the early game
|
||||
options = [0, 1, 2, 3, 4, 5, 6, 8, 9, 12]
|
||||
} else {
|
||||
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
|
||||
if (options.length > 0) {
|
||||
let newGun = options[Math.floor(Math.random() * options.length)];
|
||||
// newGun = 4; //makes every gun you pick up this type //enable for testing one gun
|
||||
if (b.activeGun === null) {
|
||||
b.activeGun = newGun //if no active gun switch to new gun
|
||||
game.makeTextLog(
|
||||
// "<br><br><br><br><div class='wrapper'> <div class = 'grid-box'><strong>left mouse</strong>: fire weapon</div> <div class = 'grid-box'> <span class = 'mouse'>️<span class='mouse-line'></span></span> </div></div>",
|
||||
"Use <strong>left mouse</strong> to fire weapon.",
|
||||
Infinity
|
||||
);
|
||||
}
|
||||
game.makeTextLog(`<strong style='font-size:30px;'>${b.guns[newGun].name}</strong><br><span class='faded'>(left click)</span><p>${b.guns[newGun].description}</p>`, 1000);
|
||||
// if (b.inventory.length === 1) { //on the second gun pick up tell player how to change guns
|
||||
// game.makeTextLog(`(<strong>Q</strong>, <strong>E</strong>, and <strong>mouse wheel</strong> change weapons)<br><br><strong style='font-size:30px;'>${b.guns[newGun].name}</strong><br><span class='faded'>(left click)</span><p>${b.guns[newGun].description}</p>`, 1000);
|
||||
// } else {
|
||||
// game.makeTextLog(`<strong style='font-size:30px;'>${b.guns[newGun].name}</strong><br><span class='faded'>(left click)</span><p>${b.guns[newGun].description}</p>`, 1000);
|
||||
// }
|
||||
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: " + b.guns[ammoTarget].name, 180);
|
||||
}
|
||||
}
|
||||
},
|
||||
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");
|
||||
return;
|
||||
}
|
||||
if (Math.random() < 0.19) {
|
||||
if (b.inventory.length > 0) powerUps.spawn(x, y, "ammo");
|
||||
return;
|
||||
}
|
||||
if (Math.random() < 0.004 * (5 - b.inventory.length)) { //a new gun has a low chance for each not acquired gun to drop
|
||||
powerUps.spawn(x, y, "gun");
|
||||
return;
|
||||
}
|
||||
if (Math.random() < 0.008) {
|
||||
powerUps.spawn(x, y, "mod");
|
||||
return;
|
||||
}
|
||||
if (Math.random() < 0.005) {
|
||||
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")
|
||||
} else if (Math.random() < 0.35) {
|
||||
powerUps.spawn(x, y, "mod")
|
||||
} else if (Math.random() < 0.27) {
|
||||
powerUps.spawn(x, y, "field");
|
||||
} else if (Math.random() < 0.04 * (7 - b.inventory.length)) { //a new gun has a low chance for each not acquired gun to drop
|
||||
powerUps.spawn(x, y, "gun")
|
||||
} else if (mech.health < 0.5) {
|
||||
powerUps.spawn(x, y, "heal");
|
||||
} else {
|
||||
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) {
|
||||
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) {
|
||||
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
|
||||
},
|
||||
};
|
||||
1722
js/spawn.js
Normal file
1722
js/spawn.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user