Files
n-gon-improved/js/player.js

1572 lines
62 KiB
JavaScript

//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: cat.player,
mask: cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield
},
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);
// mech.transX = -player.position.x
// mech.transY = player.position.y
},
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() {
mech.pos.x = player.position.x;
mech.pos.y = playerBody.position.y - mech.yOff;
mech.Vx = player.velocity.x;
mech.Vy = player.velocity.y;
},
transSmoothX: 0,
transSmoothY: 0,
lastGroundedPositionY: 0,
// mouseZoom: 0,
look() {
//always on mouse look
mech.angle = Math.atan2(
game.mouseInGame.y - mech.pos.y,
game.mouseInGame.x - mech.pos.x
);
//smoothed mouse look translations
const scale = 0.8;
mech.transSmoothX = canvas.width2 - mech.pos.x - (game.mouse.x - canvas.width2) * scale;
mech.transSmoothY = canvas.height2 - mech.pos.y - (game.mouse.y - canvas.height2) * scale;
mech.transX += (mech.transSmoothX - mech.transX) * 0.07;
mech.transY += (mech.transSmoothY - mech.transY) * 0.07;
},
doCrouch() {
if (!mech.crouch) {
mech.crouch = true;
mech.yOffGoal = mech.yOffWhen.crouch;
Matter.Body.translate(playerHead, {
x: 0,
y: 40
});
}
},
undoCrouch() {
if (mech.crouch) {
mech.crouch = false;
mech.yOffGoal = mech.yOffWhen.stand;
Matter.Body.translate(playerHead, {
x: 0,
y: -40
});
}
},
hardLandCD: 0,
enterAir() {
//triggered in engine.js on collision
mech.onGround = false;
mech.hardLandCD = 0 // disable hard landing
if (mech.isHeadClear) {
if (mech.crouch) {
mech.undoCrouch();
}
mech.yOffGoal = mech.yOffWhen.jump;
}
},
//triggered in engine.js on collision
enterLand() {
mech.onGround = true;
if (mech.crouch) {
if (mech.isHeadClear) {
mech.undoCrouch();
} else {
mech.yOffGoal = mech.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 > 120) {
mech.doCrouch();
mech.yOff = mech.yOffWhen.jump;
mech.hardLandCD = mech.cycle + Math.min(momentum / 6 - 6, 40)
if (game.isBodyDamage && player.velocity.y > 26 && momentum > 165 * b.modSquirrelFx) { //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 {
mech.yOffGoal = mech.yOffWhen.stand;
}
}
},
buttonCD_jump: 0, //cool down for player buttons
keyMove() {
if (mech.onGround) { //on ground **********************
if (mech.crouch) {
if (!(keys[83] || keys[40]) && mech.isHeadClear && mech.hardLandCD < mech.cycle) mech.undoCrouch();
} else if (keys[83] || keys[40] || mech.hardLandCD > mech.cycle) {
mech.doCrouch(); //on ground && not crouched and pressing s or down
} else if ((keys[87] || keys[38]) && mech.buttonCD_jump + 20 < mech.cycle && mech.yOffWhen.stand > 23) {
mech.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: mech.jumpForce * 0.12 * Math.min(mech.standingOn.mass, 5)
});
player.force.y = -mech.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 -= mech.Fx * 1.5
} else {
player.force.x -= mech.Fx
}
} else if (keys[68] || keys[39]) { //right / d
if (player.velocity.x < 2) {
player.force.x += mech.Fx * 1.5
} else {
player.force.x += mech.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 = (mech.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 (
mech.buttonCD_jump + 60 > mech.cycle && //just pressed jump
!(keys[87] || keys[38]) && //but not pressing jump key
mech.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 -= mech.FxAir; // move player left / a
} else if (keys[68] || keys[39]) {
if (player.velocity.x < limit) player.force.x += mech.FxAir; //move player right / d
}
}
//smoothly move leg height towards height goal
mech.yOff = mech.yOff * 0.85 + mech.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 2 mods for balance reasons
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, len = b.mods.length; i < len; i++) {
//can't get quantum immortality again
if (b.mods[i].name !== "quantum immortality" && !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.setField(Math.floor(Math.random() * (mech.fieldUpgrades.length)))
} else {
mech.setField(0)
}
}
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;
}
//give random guns
for (let i = 0; i < length; i++) {
b.giveGuns()
}
//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 (mech.alive) { //normal death code here
mech.alive = false;
game.paused = true;
mech.health = 0;
mech.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,
maxHealth: 1,
drawHealth() {
if (mech.health < 1) {
ctx.fillStyle = "rgba(100, 100, 100, 0.5)";
ctx.fillRect(mech.pos.x - mech.radius, mech.pos.y - 50, 60, 10);
ctx.fillStyle = "#f00";
ctx.fillRect(
mech.pos.x - mech.radius,
mech.pos.y - 50,
60 * mech.health,
10
);
}
},
displayHealth() {
id = document.getElementById("health");
id.style.width = Math.floor(300 * mech.health) + "px";
//css animation blink if health is low
if (mech.health < 0.3) {
id.classList.add("low-health");
} else {
id.classList.remove("low-health");
}
},
addHealth(heal) {
mech.health += heal * game.healScale;
if (mech.health > mech.maxHealth) mech.health = mech.maxHealth;
mech.displayHealth();
},
defaultFPSCycle: 0, //tracks when to return to normal fps
damage(dmg) {
if (b.isModEntanglement && b.inventory[0] === b.activeGun) {
for (let i = 0, len = b.inventory.length; i < len; i++) {
dmg *= 0.93
}
}
mech.health -= dmg;
if (mech.health < 0) {
mech.health = 0;
mech.death();
return;
}
mech.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.drone() //spawn drone
}
}
// freeze game and display a full screen red color
if (dmg > 0.05) {
if (dmg > 0.07 && mech.holdingMassScale > 0.2) mech.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) {
// mech.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);
if (b.isModMassEnergy) {
mech.fieldMeter = mech.fieldEnergyMax;
mech.addHealth(0.05);
}
},
drawLeg(stroke) {
// if (game.mouseInGame.x > mech.pos.x) {
if (mech.angle > -Math.PI / 2 && mech.angle < Math.PI / 2) {
mech.flipLegs = 1;
} else {
mech.flipLegs = -1;
}
ctx.save();
ctx.scale(mech.flipLegs, 1); //leg lines
ctx.beginPath();
ctx.moveTo(mech.hip.x, mech.hip.y);
ctx.lineTo(mech.knee.x, mech.knee.y);
ctx.lineTo(mech.foot.x, mech.foot.y);
ctx.strokeStyle = stroke;
ctx.lineWidth = 7;
ctx.stroke();
//toe lines
ctx.beginPath();
ctx.moveTo(mech.foot.x, mech.foot.y);
ctx.lineTo(mech.foot.x - 15, mech.foot.y + 5);
ctx.moveTo(mech.foot.x, mech.foot.y);
ctx.lineTo(mech.foot.x + 15, mech.foot.y + 5);
ctx.lineWidth = 4;
ctx.stroke();
//hip joint
ctx.beginPath();
ctx.arc(mech.hip.x, mech.hip.y, 11, 0, 2 * Math.PI);
//knee joint
ctx.moveTo(mech.knee.x + 7, mech.knee.y);
ctx.arc(mech.knee.x, mech.knee.y, 7, 0, 2 * Math.PI);
//foot joint
ctx.moveTo(mech.foot.x + 6, mech.foot.y);
ctx.arc(mech.foot.x, mech.foot.y, 6, 0, 2 * Math.PI);
ctx.fillStyle = mech.fillColor;
ctx.fill();
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
},
calcLeg(cycle_offset, offset) {
mech.hip.x = 12 + offset;
mech.hip.y = 24 + offset;
//stepSize goes to zero if Vx is zero or not on ground (make mech transition cleaner)
mech.stepSize = 0.8 * mech.stepSize + 0.2 * (7 * Math.sqrt(Math.min(9, Math.abs(mech.Vx))) * mech.onGround);
//changes to stepsize are smoothed by adding only a percent of the new value each cycle
const stepAngle = 0.034 * mech.walk_cycle + cycle_offset;
mech.foot.x = 2.2 * mech.stepSize * Math.cos(stepAngle) + offset;
mech.foot.y = offset + 1.2 * mech.stepSize * Math.sin(stepAngle) + mech.yOff + mech.height;
const Ymax = mech.yOff + mech.height;
if (mech.foot.y > Ymax) mech.foot.y = Ymax;
//calculate knee position as intersection of circle from hip and foot
const d = Math.sqrt((mech.hip.x - mech.foot.x) * (mech.hip.x - mech.foot.x) + (mech.hip.y - mech.foot.y) * (mech.hip.y - mech.foot.y));
const l = (mech.legLength1 * mech.legLength1 - mech.legLength2 * mech.legLength2 + d * d) / (2 * d);
const h = Math.sqrt(mech.legLength1 * mech.legLength1 - l * l);
mech.knee.x = (l / d) * (mech.foot.x - mech.hip.x) - (h / d) * (mech.foot.y - mech.hip.y) + mech.hip.x + offset;
mech.knee.y = (l / d) * (mech.foot.y - mech.hip.y) + (h / d) * (mech.foot.x - mech.hip.x) + mech.hip.y;
},
draw() {
ctx.fillStyle = mech.fillColor;
mech.walk_cycle += mech.flipLegs * mech.Vx;
//draw body
ctx.save();
ctx.translate(mech.pos.x, mech.pos.y);
mech.calcLeg(Math.PI, -3);
mech.drawLeg("#4a4a4a");
mech.calcLeg(0, 0);
mech.drawLeg("#333");
ctx.rotate(mech.angle);
ctx.beginPath();
ctx.arc(0, 0, 30, 0, 2 * Math.PI);
let grd = ctx.createLinearGradient(-30, 0, 30, 0);
grd.addColorStop(0, mech.fillColorDark);
grd.addColorStop(1, mech.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
fieldEnergyMax: 1, //can be increased by a mod
holdingTarget: null,
// these values are set on reset by setHoldDefaults()
fieldMeter: 0,
fieldRegen: 0,
fieldMode: 0,
fieldFire: false,
holdingMassScale: 0,
throwChargeRate: 0,
throwChargeMax: 0,
fieldShieldingScale: 0,
grabRange: 0,
fieldArc: 0,
fieldThreshold: 0,
calculateFieldThreshold() {
mech.fieldThreshold = Math.cos(mech.fieldArc * Math.PI)
},
setHoldDefaults() {
mech.fieldMeter = mech.fieldEnergyMax;
mech.fieldRegen = 0.001;
mech.fieldFire = false;
mech.fieldCDcycle = 0;
mech.isStealth = false;
player.collisionFilter.mask = cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield
mech.holdingMassScale = 0.5;
mech.fieldShieldingScale = 1; //scale energy loss after collision with mob
mech.grabRange = 175;
mech.fieldArc = 0.2; //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob)
mech.calculateFieldThreshold(); //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob)
mech.isBodiesAsleep = true;
mech.wakeCheck();
},
drawFieldMeter(range = 60) {
if (mech.fieldMeter < mech.fieldEnergyMax) {
mech.fieldMeter += mech.fieldRegen;
ctx.fillStyle = "rgba(0, 0, 0, 0.4)";
ctx.fillRect(mech.pos.x - mech.radius, mech.pos.y - 50, range, 10);
ctx.fillStyle = "#0cf";
ctx.fillRect(mech.pos.x - mech.radius, mech.pos.y - 50, range * mech.fieldMeter, 10);
} else {
mech.fieldMeter = mech.fieldEnergyMax
}
},
lookingAt(who) {
//calculate a vector from body to player and make it length 1
const diff = Vector.normalise(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(Vector.dot(dir, diff))
if (Vector.dot(dir, diff) > mech.fieldThreshold) {
return true;
}
return false;
},
drop() {
if (mech.isHolding && mech.holdingTarget) {
mech.isHolding = false;
mech.definePlayerMass()
mech.holdingTarget.collisionFilter.category = cat.body;
mech.holdingTarget.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet
mech.holdingTarget = null;
mech.throwCharge = 0;
}
},
definePlayerMass(mass = mech.defaultMass) {
Matter.Body.setMass(player, mass);
//reduce air and ground move forces
mech.Fx = 0.075 / mass * b.modSquirrelFx
mech.FxAir = 0.375 / mass / mass
//make player stand a bit lower when holding heavy masses
mech.yOffWhen.stand = Math.max(mech.yOffWhen.crouch, Math.min(49, 49 - (mass - 5) * 6))
if (mech.onGround && !mech.crouch) mech.yOffGoal = mech.yOffWhen.stand;
},
drawHold(target, stroke = true) {
if (target) {
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(mech.angle),
mech.pos.y + eye * Math.sin(mech.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(mech.angle),
mech.pos.y + eye * Math.sin(mech.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() {
if (mech.holdingTarget) {
mech.fieldMeter -= mech.fieldRegen;
if (mech.fieldMeter < 0) mech.fieldMeter = 0;
Matter.Body.setPosition(mech.holdingTarget, {
x: mech.pos.x + 70 * Math.cos(mech.angle),
y: mech.pos.y + 70 * Math.sin(mech.angle)
});
Matter.Body.setVelocity(mech.holdingTarget, player.velocity);
Matter.Body.rotate(mech.holdingTarget, 0.01 / mech.holdingTarget.mass); //gently spin the block
}
},
throwBlock() {
if (mech.holdingTarget && (keys[32] || game.mouseDownRight)) {
if (mech.fieldMeter > 0.0007) {
mech.fieldMeter -= 0.0007;
mech.throwCharge += mech.throwChargeRate;;
//draw charge
const x = mech.pos.x + 15 * Math.cos(mech.angle);
const y = mech.pos.y + 15 * Math.sin(mech.angle);
const len = mech.holdingTarget.vertices.length - 1;
const edge = mech.throwCharge * mech.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(mech.holdingTarget.vertices[len].x, mech.holdingTarget.vertices[len].y);
ctx.lineTo(mech.holdingTarget.vertices[0].x, mech.holdingTarget.vertices[0].y);
ctx.fill();
for (let i = 0; i < len; i++) {
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(mech.holdingTarget.vertices[i].x, mech.holdingTarget.vertices[i].y);
ctx.lineTo(mech.holdingTarget.vertices[i + 1].x, mech.holdingTarget.vertices[i + 1].y);
ctx.fill();
}
} else {
mech.drop()
}
} else if (mech.throwCharge > 0) {
//throw the body
mech.fieldCDcycle = mech.cycle + 15;
mech.isHolding = false;
//bullet-like collisions
mech.holdingTarget.collisionFilter.category = cat.body;
mech.holdingTarget.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet;
//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 = cat.body; //make solid
that.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet;
} else {
setTimeout(solid, 50, that);
}
};
setTimeout(solid, 200, mech.holdingTarget);
//throw speed scales a bit with mass
const speed = Math.min(85, Math.min(54 / mech.holdingTarget.mass + 5, 48) * Math.min(mech.throwCharge, mech.throwChargeMax) / 50);
mech.throwCharge = 0;
Matter.Body.setVelocity(mech.holdingTarget, {
x: player.velocity.x + Math.cos(mech.angle) * speed,
y: player.velocity.y + Math.sin(mech.angle) * speed
});
//player recoil //stronger in x-dir to prevent jump hacking
Matter.Body.setVelocity(player, {
x: player.velocity.x - Math.cos(mech.angle) * speed / (mech.crouch ? 30 : 5) * Math.sqrt(mech.holdingTarget.mass),
y: player.velocity.y - Math.sin(mech.angle) * speed / 40 * Math.sqrt(mech.holdingTarget.mass)
});
mech.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 = mech.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
const grabPowerUpRange2 = (mech.grabRange + 220) * (mech.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 && mech.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;
}
mech.fieldMeter -= mech.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 speed = Vector.magnitude(Vector.sub(who.velocity, player.velocity))
const fieldBlockCost = 0.03 + Math.sqrt(who.mass) * speed * 0.003 //0.012
if (mech.fieldMeter > fieldBlockCost * 0.6) { //shield needs at least some of the cost to block
mech.fieldMeter -= fieldBlockCost * mech.fieldShieldingScale;
if (mech.fieldMeter < 0) mech.fieldMeter = 0;
mech.drawHold(who);
mech.fieldCDcycle = mech.cycle + 10;
mech.holdingTarget = null
//knock backs
const unit = Vector.normalise(Vector.sub(player.position, who.position))
const massRoot = Math.sqrt(Math.min(12, Math.max(0.15, who.mass))); // masses above 12 can start to overcome the push back
Matter.Body.setVelocity(who, {
x: player.velocity.x - (15 * unit.x) / massRoot,
y: player.velocity.y - (15 * unit.y) / massRoot
});
if (mech.crouch) {
Matter.Body.setVelocity(player, {
x: player.velocity.x + 0.4 * unit.x * massRoot,
y: player.velocity.y + 0.4 * unit.y * massRoot
});
} else {
Matter.Body.setVelocity(player, {
x: player.velocity.x + 5 * unit.x * massRoot,
y: player.velocity.y + 5 * unit.y * massRoot
});
}
}
},
pushMobsFacing() { // find mobs in range and in direction looking
for (let i = 0, len = mob.length; i < len; ++i) {
if (
Vector.magnitude(Vector.sub(mob[i].position, player.position)) < mech.grabRange &&
mech.lookingAt(mob[i]) &&
Matter.Query.ray(map, mob[i].position, mech.pos).length === 0
) {
mob[i].locatePlayer();
mech.pushMass(mob[i]);
}
}
},
pushMobs360(range = mech.grabRange * 0.75) { // find mobs in range in any direction
for (let i = 0, len = mob.length; i < len; ++i) {
if (
Vector.magnitude(Vector.sub(mob[i].position, mech.pos)) < range &&
Matter.Query.ray(map, mob[i].position, mech.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 &&
// Vector.magnitude(Vector.sub(body[i].position, mech.pos)) < mech.grabRange &&
// mech.lookingAt(body[i]) &&
// Matter.Query.ray(map, body[i].position, mech.pos).length === 0
// ) {
// mech.pushMass(body[i]);
// }
// }
// },
// pushBody360(range = mech.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 &&
// Vector.magnitude(Vector.sub(body[i].position, mech.pos)) < range &&
// mech.lookingAt(body[i]) &&
// Matter.Query.ray(map, body[i].position, mech.pos).length === 0 &&
// body[i].collisionFilter.category === cat.body
// ) {
// mech.pushMass(body[i]);
// }
// }
// },
lookForPickUp(range = mech.grabRange) { //find body to pickup
mech.fieldMeter -= mech.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, mech.pos).length === 0) {
//is mech next body a better target then my current best
const dist = Vector.magnitude(Vector.sub(body[i].position, mech.pos));
const looking = mech.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]) {
mech.holdingTarget = body[grabbing.targetIndex];
//
ctx.beginPath(); //draw on each valid body
let vertices = mech.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;
mech.drawHold(mech.holdingTarget);
ctx.globalAlpha = 1;
} else {
mech.holdingTarget = null;
}
},
pickUp() {
//triggers when a hold target exits and field button is released
mech.isHolding = true;
//conserve momentum when player mass changes
totalMomentum = Vector.add(Vector.mult(player.velocity, player.mass), Vector.mult(mech.holdingTarget.velocity, mech.holdingTarget.mass))
Matter.Body.setVelocity(player, Vector.mult(totalMomentum, 1 / (mech.defaultMass + mech.holdingTarget.mass)));
mech.definePlayerMass(mech.defaultMass + mech.holdingTarget.mass * mech.holdingMassScale)
//make block collide with nothing
mech.holdingTarget.collisionFilter.category = 0;
mech.holdingTarget.collisionFilter.mask = 0;
},
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() {},
setField(index) {
mech.fieldMode = index;
document.getElementById("field").innerHTML = mech.fieldUpgrades[index].name
mech.setHoldDefaults();
mech.fieldUpgrades[index].effect();
},
fieldUpgrades: [{
name: "field emitter",
description: "use <strong class='color-f'>energy</strong> to <strong>shield</strong> yourself from <strong class='color-d'>damage</strong><br>lets you <strong>pick up</strong> and <strong>throw</strong> objects",
effect: () => {
game.replaceTextLog = true; //allow text over write
mech.hold = function () {
if (mech.isHolding) {
mech.drawHold(mech.holdingTarget);
mech.holding();
mech.throwBlock();
} else if ((keys[32] || game.mouseDownRight && mech.fieldMeter > 0.05 && mech.fieldCDcycle < mech.cycle)) { //not hold but field button is pressed
mech.drawField();
mech.grabPowerUp();
mech.lookForPickUp();
mech.pushMobsFacing();
} else if (mech.holdingTarget && mech.fieldCDcycle < 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 <strong class='color-f'>energy</strong> to <strong style='letter-spacing: 1px;'>stop time</strong><br><em>can fire bullets while field is active</em>",
effect: () => {
mech.fieldFire = true;
mech.grabRange = 130
mech.isBodiesAsleep = false;
mech.hold = function () {
if (mech.isHolding) {
mech.wakeCheck();
mech.drawHold(mech.holdingTarget);
mech.holding();
mech.throwBlock();
} else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) {
const DRAIN = 0.0023
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.fieldCDcycle < 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) mech.wakeCheck(); //wake up if this is no longer the current field mode, like after a new power up
}
}
},
{
name: "plasma torch",
description: "use <strong class='color-f'>energy</strong> to emit <strong class='color-d'>damaging</strong> plasma<br><em>effective at close range</em>",
effect: () => {
// 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.throwBlock();
} else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) { //not hold but field button is pressed
const DRAIN = 0.0005
if (mech.fieldMeter > DRAIN) {
mech.fieldMeter -= DRAIN;
mech.grabPowerUp();
mech.lookForPickUp();
mech.pushMobs360(130);
//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.4 * b.dmgScale; //********** SCALE DAMAGE HERE *********************
best.who.damage(dmg);
best.who.locatePlayer();
//push mobs away
const force = Vector.mult(Vector.normalise(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 = Vector.mult(Vector.normalise(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.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();
//draw shield around player
ctx.beginPath();
ctx.arc(mech.pos.x, mech.pos.y, 110, 0, 2 * Math.PI);
ctx.fillStyle = "rgba(255,0,255,0.05)"
ctx.fill();
// mech.pushBody360(100); //disabled because doesn't work at short range
} else {
mech.fieldCDcycle = mech.cycle + 120; //if out of energy
}
} else if (mech.holdingTarget && mech.fieldCDcycle < 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 <strong class='color-f'>energy</strong> to nullify &nbsp; <strong style='letter-spacing: 10px;'>gravity</strong><br><em>can fire bullets while active</em>",
effect: () => {
mech.fieldFire = true;
mech.hold = function () {
if (mech.isHolding) {
mech.drawHold(mech.holdingTarget);
mech.holding();
mech.throwBlock();
} else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) { //push away
const DRAIN = 0.0004
if (mech.fieldMeter > DRAIN) {
mech.grabPowerUp();
mech.lookForPickUp(170);
mech.pushMobs360(200);
//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 = Vector.sub(who[i].position, mech.pos);
dist = 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.fieldCDcycle < 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: "three oscillating <strong>shields</strong> are permanently active<br><strong class='color-f'>energy</strong> regenerates while field is active",
effect: () => {
// mech.fieldRegen *= 0.6;
mech.fieldShieldingScale = 1.33;
mech.hold = function () {
if (mech.isHolding) {
mech.drawHold(mech.holdingTarget);
mech.holding();
mech.throwBlock();
} else if (((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle && mech.fieldMeter > 0)) { //not hold but field button is pressed
mech.grabPowerUp();
mech.lookForPickUp(180);
} else if (mech.holdingTarget && mech.fieldCDcycle < 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 && mech.fieldCDcycle < mech.cycle) {
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); //can't throw block when pushhing blocks away
}
mech.drawFieldMeter()
}
}
},
{
name: "nano-scale manufacturing",
description: "excess <strong class='color-f'>energy</strong> used to build <strong>drones</strong><br><strong>2x</strong> <strong class='color-f'>energy</strong> regeneration",
effect: () => {
mech.fieldRegen *= 2;
mech.hold = function () {
if (mech.fieldMeter > mech.fieldEnergyMax - 0.02 && mech.fieldCDcycle < mech.cycle) {
mech.fieldCDcycle = mech.cycle + 17; // set cool down to prevent +energy from making huge numbers of drones
mech.fieldMeter -= 0.32;
b.drone(1)
}
if (mech.isHolding) {
mech.drawHold(mech.holdingTarget);
mech.holding();
mech.throwBlock();
} else if ((keys[32] || game.mouseDownRight && mech.fieldMeter > 0.1 && mech.fieldCDcycle < mech.cycle)) { //not hold but field button is pressed
mech.drawField();
mech.grabPowerUp();
mech.lookForPickUp();
mech.pushMobsFacing();
} else if (mech.holdingTarget && mech.fieldCDcycle < 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 <strong class='color-f'>energy</strong> to to become <strong>intangible</strong><br><em style='opacity: 0.6;'>can't see or be seen outside field</em>",
effect: () => {
// mech.grabRange = 230
mech.hold = function () {
mech.isStealth = false //isStealth is checked in mob foundPlayer()
player.collisionFilter.mask = cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield //normal collisions
if (mech.isHolding) {
mech.drawHold(mech.holdingTarget);
mech.holding();
mech.throwBlock();
} 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 = cat.map
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.fieldCDcycle < 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: "code injection field",
// description: "capture an enemy in your field for 3 seconds<br>rewrite thier behavior to target your enemies",
// effect: () => {
// mech.fieldMode = 7;
// mech.fieldText();
// mech.setHoldDefaults();
// mech.hackProgress = 0;
// // mech.grabRange = 230
// mech.hold = function () {
// mech.isStealth = false //isStealth is checked in mob foundPlayer()
// player.collisionFilter.mask = 0x010011
// if (mech.isHolding) {
// mech.hackProgress = 0
// mech.drawHold(mech.holdingTarget);
// mech.holding();
// mech.throwBlock();
// } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) {
// const DRAIN = 0.0005
// if (mech.fieldMeter > DRAIN) {
// mech.fieldMeter -= DRAIN;
// //try to hack a mob
// for (let i = 0, len = mob.length; i < len; ++i) {
// if (
// Vector.magnitude(Vector.sub(mob[i].position, this.pos)) < this.grabRange &&
// this.lookingAt(mob[i]) &&
// Matter.Query.ray(map, mob[i].position, this.pos).length === 0
// ) {
// if (mech.hackProgress > 180) { //hack the mob
// mech.fieldMeter = 0;
// mob[i].hackedTarget = null;
// mob[i].seePlayerFreq = Math.round((30 + 30 * Math.random()) * game.lookFreqScale)
// mob[i].do = function () {
// this.healthBar();
// this.hacked();
// }
// } else { //hold the mob still
// mech.hackProgress++
// range = this.grabRange * 0.9
// Matter.Body.setPosition(mob[i], {
// x: mech.pos.x + range * Math.cos(mech.angle),
// y: mech.pos.y + range * Math.sin(mech.angle),
// });
// Matter.Body.setVelocity(mob[i], player.velocity);
// }
// }
// }
// mech.pushBodyFacing();
// mech.drawField();
// mech.grabPowerUp();
// mech.lookForPickUp();
// } else {
// mech.hackProgress = 0
// 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.hackProgress = 0
// } else {
// mech.hackProgress = 0
// 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()
// }
// }
// },
],
};