//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
playerBody = Matter.Bodies.fromVertices(0, 0, vector);
jumpSensor = Bodies.rectangle(0, 46, 36, 6, {
//this sensor check if the player is on the ground to enable jumping
sleepThreshold: 99999999999,
isSensor: true
});
vector = Vertices.fromPath("16 -82 2 -66 2 -37 43 -37 43 -66 30 -82");
playerHead = Matter.Bodies.fromVertices(0, -55, vector); //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);
},
width: 50,
radius: 30,
fillColor: "#fff",
fillColorDark: "#ccc",
height: 42,
yOffWhen: {
crouch: 22,
stand: 49,
jump: 70
},
mass: 5,
Fx: 0.015, //run Force on ground
FxAir: 0.015, //run Force in Air
definePlayerMass(mass = 5) {
Matter.Body.setMass(player, mass);
//reduce air and ground move forces
this.Fx = 0.075 / mass
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;
},
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,
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;
},
gamepadLook() {
this.angle = Math.atan2(
game.gamepad.rightAxis.y,
game.gamepad.rightAxis.x
);
// this.transX += (canvas.width2 - this.pos.x - this.transX) * 0.07 - game.gamepad.rightAxis.x * 12;
// this.transY += (canvas.height2 - this.pos.y - this.transY) * 0.03 - game.gamepad.rightAxis.y * 6;
this.transX += (canvas.width2 - this.pos.x - this.transX) * 0.02 - game.gamepad.leftAxis.x * 6;
this.transY += (canvas.height2 - this.pos.y - this.transY) * 0.02 + game.gamepad.leftAxis.y * 8;
},
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 = game.cycle + Math.min(momentum / 6 - 6, 40)
} 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 < game.cycle) this.undoCrouch();
} else if (keys[83] || keys[40] || this.hardLandCD > game.cycle) {
this.doCrouch(); //on ground && not crouched and pressing s or down
} else if ((keys[87] || keys[38]) && this.buttonCD_jump + 20 < game.cycle && this.yOffWhen.stand > 23) {
this.buttonCD_jump = game.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
player.force.x -= this.Fx
if (player.velocity.x > -2) player.force.x -= this.Fx * 0.5
} else if (keys[68] || keys[39]) { //right / d
player.force.x += this.Fx
if (player.velocity.x < 2) player.force.x += this.Fx * 0.5
} 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 > game.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;
},
gamepadMove() {
if (this.onGround) { //on ground **********************
if (this.crouch) {
if (game.gamepad.leftAxis.y !== -1 && this.isHeadClear && this.hardLandCD < game.cycle) this.undoCrouch();
} else if (game.gamepad.leftAxis.y === -1 || this.hardLandCD > game.cycle) {
this.doCrouch(); //on ground && not crouched and pressing s or down
} else if (game.gamepad.jump && this.buttonCD_jump + 20 < game.cycle && this.yOffWhen.stand > 23) {
this.buttonCD_jump = game.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 (game.gamepad.leftAxis.x === -1) { //left / a
player.force.x -= this.Fx
if (player.velocity.x > -2) player.force.x -= this.Fx * 0.5
} else if (game.gamepad.leftAxis.x === 1) { //right / d
player.force.x += this.Fx
if (player.velocity.x < 2) player.force.x += this.Fx * 0.5
} 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;
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 > game.cycle && //just pressed jump
!game.gamepad.jump && //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 (game.gamepad.leftAxis.x === -1) {
if (player.velocity.x > -limit) player.force.x -= this.FxAir; // move player left / a
} else if (game.gamepad.leftAxis.x === 1) {
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 (this.alive) {
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();
}, 5000);
}
},
health: 0,
// regen() {
// if (this.health < 1 && game.cycle % 15 === 0) {
// this.addHealth(0.01);
// }
// },
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) this.health = 1;
// document.getElementById("health").setAttribute("width", 225 * this.health);
this.displayHealth();
},
defaultFPSCycle: 0, //tracks when to return to normal fps
damage(dmg) {
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);
//drop block if holding
if (dmg > 0.07) {
this.drop();
}
// freeze game and display a full screen red color
if (dmg > 0.05) {
game.fpsCap = 4 //40 - Math.min(25, 100 * dmg)
game.fpsInterval = 1000 / game.fpsCap;
} else {
game.fpsCap = 72
game.fpsInterval = 1000 / game.fpsCap;
}
mech.defaultFPSCycle = game.cycle
const normalFPS = function () {
if (mech.defaultFPSCycle < game.cycle) { //back to default values
game.fpsCap = 72
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, //cooldown for player buttons
usePowerUp(i) {
powerUp[i].effect();
Matter.World.remove(engine.world, powerUp[i]);
powerUp.splice(i, 1);
},
// *********************************************
// **************** 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,
holdingMassScale: 0,
throwChargeRate: 0,
throwChargeMax: 0,
fieldFireCD: 0,
fieldDamage: 0,
grabRange: 0,
fieldArc: 0,
fieldThreshold: 0,
calculateFieldThreshold() {
this.fieldThreshold = Math.cos(this.fieldArc * Math.PI)
},
setHoldDefaults() {
this.fieldMeter = 1;
this.fieldRegen = 0.0015;
this.fieldCDcycle = 0;
this.isStealth = false;
this.holdingMassScale = 0.5;
this.throwChargeRate = 2;
this.throwChargeMax = 50;
this.fieldFireCD = 15;
this.fieldDamage = 0; // a value of 1.0 kills a small mob in 2-3 hits on level 1
this.grabRange = 175;
this.fieldArc = 0.2;
this.calculateFieldThreshold();
this.jumpForce = 0.38;
this.Fx = 0.015; //run Force on ground
this.FxAir = 0.015; //run Force in Air
this.gravity = 0.0019;
// 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 = "rgb(50,220,255)";
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 = 0x000001;
this.holdingTarget.collisionFilter.mask = 0x011111;
this.holdingTarget = null;
this.throwCharge = 0;
}
},
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;
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.002) {
this.fieldMeter -= 0.002;
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 = game.cycle + this.fieldFireCD;
this.isHolding = false;
//bullet-like collisions
this.holdingTarget.collisionFilter.category = 0x000100;
this.holdingTarget.collisionFilter.mask = 0x111111;
//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 = 0x000001; //make solid
that.collisionFilter.mask = 0x011111;
} else {
setTimeout(solid, 50, that);
}
};
setTimeout(solid, 400, 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() {
//draw field
const range = this.grabRange - 20;
ctx.beginPath();
ctx.arc(this.pos.x, this.pos.y, range, this.angle - Math.PI * this.fieldArc, this.angle + Math.PI * this.fieldArc, false);
let eye = 13;
ctx.lineTo(mech.pos.x + eye * Math.cos(this.angle), mech.pos.y + eye * Math.sin(this.angle));
if (this.holdingTarget) {
ctx.fillStyle = "rgba(110,170,200," + (0.05 + 0.1 * Math.random()) + ")";
} else {
ctx.fillStyle = "rgba(110,170,200," + (0.15 + 0.15 * Math.random()) + ")";
}
ctx.fill();
//draw random lines in field for cool effect
let offAngle = this.angle + 2 * Math.PI * this.fieldArc * (Math.random() - 0.5);
ctx.beginPath();
eye = 15;
ctx.moveTo(mech.pos.x + eye * Math.cos(this.angle), mech.pos.y + eye * Math.sin(this.angle));
ctx.lineTo(this.pos.x + range * Math.cos(offAngle), this.pos.y + range * Math.sin(offAngle));
ctx.strokeStyle = "rgba(120,170,255,0.4)";
ctx.stroke();
},
grabPowerUp() {
//look for power ups to grab
if (mech.fieldCDcycle < game.cycle) {
const grabPowerUpRange2 = (this.grabRange + 200) * (this.grabRange + 200)
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 < 14000) {
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.4,
y: powerUp[i].velocity.y * 0.4
});
if (dist2 < 5000) { //use power up if it is close enough
//player knockback
Matter.Body.setVelocity(player, {
x: player.velocity.x + ((powerUp[i].velocity.x * powerUp[i].mass) / player.mass) * 0.2,
y: player.velocity.y + ((powerUp[i].velocity.y * powerUp[i].mass) / player.mass) * 0.2
});
mech.usePowerUp(i);
// this.fireCDcycle = game.cycle + 10; //cool down
return;
}
// return;
}
}
}
},
pushMobs() {
// push all mobs in range
for (let i = 0, len = mob.length; i < len; ++i) {
if (this.lookingAt(mob[i]) && Matter.Vector.magnitude(Matter.Vector.sub(mob[i].position, this.pos)) < this.grabRange && Matter.Query.ray(map, mob[i].position, this.pos).length === 0) {
const fieldBlockCost = Math.max(0.02, mob[i].mass * 0.012)
if (this.fieldMeter > fieldBlockCost) {
this.fieldMeter -= fieldBlockCost;
if (this.fieldDamage) mob[i].damage(b.dmgScale * this.fieldDamage);
mob[i].locatePlayer();
this.drawHold(mob[i]);
//mob and player knock back
const angle = Math.atan2(player.position.y - mob[i].position.y, player.position.x - mob[i].position.x);
const mass = Math.min(Math.sqrt(mob[i].mass), 4);
// console.log(mob[i].mass, Math.sqrt(mob[i].mass), mass)
Matter.Body.setVelocity(mob[i], {
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
});
}
}
}
},
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;
if (this.holdingTarget) {
this.holdingTarget.collisionFilter.category = 0x000001;
this.holdingTarget.collisionFilter.mask = 0x111111;
}
//combine momentum
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)
});
this.definePlayerMass(5 + this.holdingTarget.mass * this.holdingMassScale)
//collide with nothing
this.holdingTarget.collisionFilter.category = 0x000000;
this.holdingTarget.collisionFilter.mask = 0x000000;
},
hold() {},
fieldUpgrades: [
() => {
mech.fieldMode = 0;
game.makeTextLog("Field Emitter
(right click or space bar)
lets you pick up and throw objects
shields you from damage
field slows objects in range
field does not shield player
field does damage on contact
blocks are thrown at a higher velocity
nullifies gravity around player
can hold more massive objects
field does not shield player
improved energy regeneration
field emitter shield surrounds player
excess field energy builds drones
field does not shield player
localized invisibility field
", 1200); mech.setHoldDefaults(); mech.grabRange = 150; mech.fieldArc = 0.1; //0.2 is normal // mech.fieldArc = 1; //field covers full 360 degrees mech.calculateFieldThreshold(); mech.hold = function () { mech.isStealth = false //isStealth is checked in mob foundPlayer() if (mech.isHolding) { mech.drawHold(mech.holdingTarget); mech.holding(); mech.throw(); } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < game.cycle) { const DRAIN = 0.001 //mech.fieldRegen = 0.0015 if (mech.fieldMeter > DRAIN) { mech.fieldMeter -= DRAIN; mech.isStealth = true //isStealth is checked in mob foundPlayer() //draw stealth field ctx.fillStyle = "rgba(255,255,255,0.6)"; ctx.fill(); ctx.beginPath(); ctx.arc(mech.pos.x, mech.pos.y, mech.grabRange - 20, 0, 2 * Math.PI); ctx.globalCompositeOperation = "destination-in"; //in or atop ctx.fill(); ctx.globalCompositeOperation = "source-over"; ctx.beginPath(); ctx.arc(mech.pos.x, mech.pos.y, mech.grabRange - 20, 0, 2 * Math.PI); ctx.fillStyle = "rgba(210,230,255,0.5)"; ctx.fill(); mech.drawField(); mech.grabPowerUp(); mech.pushMobs(); mech.lookForPickUp(); } else { mech.fieldCDcycle = game.cycle + 120; } } else if (mech.holdingTarget && mech.fireCDcycle < game.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) } mech.drawFieldMeter() } }, () => { mech.fieldMode = 7; game.makeTextLog("Thermal Radiation Fieldfield damages all targets within range, including player
field does not shield player
field damages and pushes all targets within range
field damages player
field does not shield player