//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 = 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 = 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: 300, //starts at 300 cycles instead of 0 to prevent bugs with mech.history
lastKillCycle: 0,
lastHarmCycle: 0,
width: 50,
radius: 30,
fillColor: "#fff",
fillColorDark: "#ccc",
color: {
hue: 0,
sat: 0,
light: 100,
},
setFillColors() {
this.fillColor = `hsl(${mech.color.hue},${mech.color.sat}%,${mech.color.light}%)`
this.fillColorDark = `hsl(${mech.color.hue},${mech.color.sat}%,${mech.color.light-20}%)`
},
height: 42,
yOffWhen: {
crouch: 22,
stand: 49,
jump: 70
},
defaultMass: 5,
mass: 5,
FxNotHolding: 0.015,
Fx: 0.016, //run Force on ground //
jumpForce: 0.42,
setMovement() {
mech.Fx = 0.016 * tech.squirrelFx * tech.fastTime;
mech.jumpForce = 0.42 * tech.squirrelJump * tech.fastTimeJump;
},
FxAir: 0.016, // 0.4/5/5 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
},
yPosDifference: 24.285923217549026, //player.position.y - mech.pos.y
Sy: 0, //adds a smoothing effect to vertical only
Vx: 0,
Vy: 0,
friction: {
ground: 0.01,
air: 0.0025
},
airSpeedLimit: 125, // 125/mass/mass = 5
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,
history: [], //tracks the last second of player position
resetHistory() {
for (let i = 0; i < 600; i++) { //reset history
mech.history[i] = {
position: {
x: player.position.x,
y: player.position.y,
},
velocity: {
x: player.velocity.x,
y: player.velocity.y
},
angle: mech.angle,
health: mech.health,
energy: mech.energy,
}
}
},
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;
//tracks the last second of player information
// console.log(mech.history)
mech.history.splice(mech.cycle % 600, 1, {
position: {
x: player.position.x,
y: player.position.y,
},
velocity: {
x: player.velocity.x,
y: player.velocity.y
},
angle: mech.angle,
health: mech.health,
energy: mech.energy,
activeGun: b.activeGun
});
// const back = 59 // 59 looks at 1 second ago //29 looks at 1/2 a second ago
// historyIndex = (mech.cycle - back) % 60
},
transSmoothX: 0,
transSmoothY: 0,
lastGroundedPositionY: 0,
// mouseZoom: 0,
look() {
//always on mouse look
mech.angle = Math.atan2(
simulation.mouseInGame.y - mech.pos.y,
simulation.mouseInGame.x - mech.pos.x
);
//smoothed mouse look translations
const scale = 0.8;
mech.transSmoothX = canvas.width2 - mech.pos.x - (simulation.mouse.x - canvas.width2) * scale;
mech.transSmoothY = canvas.height2 - mech.pos.y - (simulation.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;
if ((playerHead.position.y - player.position.y) < 0) {
Matter.Body.setPosition(playerHead, {
x: player.position.x,
y: player.position.y + 9.1740767
})
// Matter.Body.translate(playerHead, {
// x: 0,
// y: 40
// });
}
// playerHead.collisionFilter.group = -1
// playerHead.collisionFilter.category = 0
// playerHead.collisionFilter.mask = -1
// playerHead.isSensor = true; //works, but has a 2 second lag...
// collisionFilter: {
// group: 0,
// category: cat.player,
// mask: cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield
// },
}
},
undoCrouch() {
if (mech.crouch) {
mech.crouch = false;
mech.yOffGoal = mech.yOffWhen.stand;
if ((playerHead.position.y - player.position.y) > 0) {
Matter.Body.setPosition(playerHead, {
x: player.position.x,
y: player.position.y - 30.28592321
})
// Matter.Body.translate(playerHead, {
// x: 0,
// y: -40
// });
}
// playerHead.collisionFilter = {
// group: 0,
// category: cat.player,
// mask: cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield
// }
// playerHead.isSensor = false;
// playerHead.collisionFilter.category = cat.player
// playerHead.collisionFilter.mask = cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield
}
},
hardLandCD: 0,
checkHeadClear() {
if (Matter.Query.collides(headSensor, map).length > 0) {
return false
} else {
return true
}
},
buttonCD_jump: 0, //cool down for player buttons
groundControl() {
//check for crouch or jump
if (mech.crouch) {
if (!(input.down) && mech.checkHeadClear() && mech.hardLandCD < mech.cycle) mech.undoCrouch();
} else if (input.down || mech.hardLandCD > mech.cycle) {
mech.doCrouch(); //on ground && not crouched and pressing s or down
} else if ((input.up) && 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
});
}
if (input.left) {
if (player.velocity.x > -2) {
player.force.x -= mech.Fx * 1.5
} else {
player.force.x -= mech.Fx
}
// }
} else if (input.right) {
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
});
}
},
airControl() {
//check for short jumps //moving up //recently pressed jump //but not pressing jump key now
if (mech.buttonCD_jump + 60 > mech.cycle && !(input.up) && mech.Vy < 0) {
Matter.Body.setVelocity(player, {
//reduce player y-velocity every cycle
x: player.velocity.x,
y: player.velocity.y * 0.94
});
}
if (input.left) {
if (player.velocity.x > -mech.airSpeedLimit / player.mass / player.mass) player.force.x -= mech.FxAir; // move player left / a
} else if (input.right) {
if (player.velocity.x < mech.airSpeedLimit / player.mass / player.mass) player.force.x += mech.FxAir; //move player right / d
}
},
alive: false,
death() {
if (tech.isImmortal) { //if player has the immortality buff, spawn on the same level with randomized damage
simulation.isTextLogOpen = false;
//count tech
let totalTech = 0;
for (let i = 0; i < tech.tech.length; i++) {
if (!tech.tech[i].isNonRefundable) totalTech += tech.tech[i].count
}
if (tech.isDeterminism) totalTech -= 3 //remove the bonus tech
if (tech.isSuperDeterminism) totalTech -= 2 //remove the bonus tech
totalTech = totalTech * 1.15 + 1 // a few extra to make it stronger
const totalGuns = b.inventory.length //count guns
function randomizeTech() {
for (let i = 0; i < totalTech; i++) {
//find what tech I don't have
let options = [];
for (let i = 0, len = tech.tech.length; i < len; i++) {
if (tech.tech[i].count < tech.tech[i].maxCount &&
!tech.tech[i].isNonRefundable &&
tech.tech[i].name !== "quantum immortality" &&
tech.tech[i].name !== "Born rule" &&
tech.tech[i].allowed()
) options.push(i);
}
//add a new tech
if (options.length > 0) {
const choose = Math.floor(Math.random() * options.length)
let newTech = options[choose]
tech.giveTech(newTech)
options.splice(choose, 1);
}
}
simulation.updateTechHUD();
}
function randomizeField() {
mech.setField(Math.ceil(Math.random() * (mech.fieldUpgrades.length - 1)))
}
function randomizeHealth() {
mech.health = 0.7 + Math.random()
if (mech.health > 1) mech.health = 1;
mech.displayHealth();
}
function randomizeGuns() {
//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 < totalGuns; 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.sqrt(Math.random())))
}
}
simulation.makeGunHUD(); //update gun HUD
}
simulation.wipe = function() { //set wipe to have trails
ctx.fillStyle = "rgba(255,255,255,0)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
function randomizeEverything() {
spawn.setSpawnList(); //new mob types
simulation.clearNow = true; //triggers a map reset
tech.setupAllTech(); //remove all tech
for (let i = 0; i < bullet.length; ++i) Matter.World.remove(engine.world, bullet[i]);
bullet = []; //remove all bullets
randomizeHealth()
randomizeField()
randomizeGuns()
randomizeTech()
}
randomizeEverything()
const swapPeriod = 1000
for (let i = 0, len = 5; i < len; i++) {
setTimeout(function() {
randomizeEverything()
simulation.isTextLogOpen = true;
simulation.makeTextLog(`simulation.amplitude = 0.${len-i-1}`, swapPeriod);
simulation.isTextLogOpen = false;
simulation.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);
// pixelWindows()
}
}, (i + 1) * swapPeriod);
}
setTimeout(function() {
simulation.wipe = function() { //set wipe to normal
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
simulation.isTextLogOpen = true;
simulation.makeTextLog("simulation.amplitude = null");
}, 6 * swapPeriod);
} else if (mech.alive) { //normal death code here
mech.alive = false;
simulation.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
// build.shareURL(false)
setTimeout(function() {
World.clear(engine.world);
Engine.clear(engine);
simulation.splashReturn();
}, 3000);
}
},
health: 0,
maxHealth: 1, //set in simulation.reset()
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");
// health display follows a x^1.5 rule to make it seem like the player has lower health, this makes the player feel more excitement
id.style.width = Math.floor(300 * Math.pow(mech.health, 1.5)) + "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) {
if (!tech.isEnergyHealth) {
mech.health += heal * simulation.healScale;
if (mech.health > mech.maxHealth) mech.health = mech.maxHealth;
mech.displayHealth();
}
},
baseHealth: 1,
setMaxHealth() {
mech.maxHealth = mech.baseHealth + tech.bonusHealth + tech.armorFromPowerUps
if (mech.health > mech.maxHealth) mech.health = mech.maxHealth;
mech.displayHealth();
},
defaultFPSCycle: 0, //tracks when to return to normal fps
immuneCycle: 0, //used in engine
harmReduction() {
let dmg = 1
dmg *= mech.fieldHarmReduction
if (tech.isSpeedHarm) dmg *= 1 - Math.min(player.speed * 0.0185, 0.55)
if (tech.isSlowFPS) dmg *= 0.85
if (tech.isPiezo) dmg *= 0.85
if (tech.isHarmReduce && mech.fieldUpgrades[mech.fieldMode].name === "negative mass field" && mech.isFieldActive) dmg *= 0.6
if (tech.isBotArmor) dmg *= 0.97 ** tech.totalBots()
if (tech.isHarmArmor && mech.lastHarmCycle + 600 > mech.cycle) dmg *= 0.33;
if (tech.isNoFireDefense && mech.cycle > mech.fireCDcycle + 120) dmg *= 0.6
if (tech.energyRegen === 0) dmg *= 0.4
if (tech.isTurret && mech.crouch) dmg *= 0.5;
if (tech.isEntanglement && b.inventory[0] === b.activeGun) {
for (let i = 0, len = b.inventory.length; i < len; i++) dmg *= 0.87 // 1 - 0.15
}
return dmg
},
rewind(steps) { // mech.rewind(Math.floor(Math.min(599, 137 * mech.energy)))
if (tech.isRewindGrenade) {
for (let i = 1, len = Math.floor(2 + steps / 40); i < len; i++) {
b.grenade(Vector.add(mech.pos, { x: 10 * (Math.random() - 0.5), y: 10 * (Math.random() - 0.5) }), -i * Math.PI / len) //fire different angles for each grenade
const who = bullet[bullet.length - 1]
if (tech.isVacuumBomb) {
Matter.Body.setVelocity(who, {
x: who.velocity.x * 0.5,
y: who.velocity.y * 0.5
});
} else if (tech.isRPG) {
who.endCycle = (who.endCycle - simulation.cycle) * 0.2 + simulation.cycle
} else if (tech.isNeutronBomb) {
Matter.Body.setVelocity(who, {
x: who.velocity.x * 0.3,
y: who.velocity.y * 0.3
});
} else {
Matter.Body.setVelocity(who, {
x: who.velocity.x * 0.5,
y: who.velocity.y * 0.5
});
who.endCycle = (who.endCycle - simulation.cycle) * 0.5 + simulation.cycle
}
}
}
let history = mech.history[(mech.cycle - steps) % 600]
Matter.Body.setPosition(player, history.position);
Matter.Body.setVelocity(player, { x: history.velocity.x, y: history.velocity.y });
// b.activeGun = history.activeGun
// for (let i = 0; i < b.inventory.length; i++) {
// if (b.inventory[i] === b.activeGun) b.inventoryGun = i
// }
// simulation.updateGunHUD();
// simulation.boldActiveGunHUD();
// move bots to follow player
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType) {
Matter.Body.setPosition(bullet[i], Vector.add(player.position, {
x: 250 * (Math.random() - 0.5),
y: 250 * (Math.random() - 0.5)
}));
Matter.Body.setVelocity(bullet[i], {
x: 0,
y: 0
});
}
}
mech.energy = Math.max(mech.energy - steps / 136, 0.01)
mech.immuneCycle = mech.cycle + 30; //player is immune to collision damage for 30 cycles
let isDrawPlayer = true
const shortPause = function() {
if (mech.defaultFPSCycle < mech.cycle) { //back to default values
simulation.fpsCap = simulation.fpsCapDefault
simulation.fpsInterval = 1000 / simulation.fpsCap;
document.getElementById("dmg").style.transition = "opacity 1s";
document.getElementById("dmg").style.opacity = "0";
} else {
requestAnimationFrame(shortPause);
if (isDrawPlayer) {
isDrawPlayer = false
ctx.save();
ctx.translate(canvas.width2, canvas.height2); //center
ctx.scale(simulation.zoom / simulation.edgeZoomOutSmooth, simulation.zoom / simulation.edgeZoomOutSmooth); //zoom in once centered
ctx.translate(-canvas.width2 + mech.transX, -canvas.height2 + mech.transY); //translate
for (let i = 1; i < steps; i++) {
history = mech.history[(mech.cycle - i) % 600]
mech.pos.x = history.position.x
mech.pos.y = history.position.y
mech.draw();
}
ctx.restore();
mech.resetHistory()
}
}
};
if (mech.defaultFPSCycle < mech.cycle) requestAnimationFrame(shortPause);
simulation.fpsCap = 3 //1 is longest pause, 4 is standard
simulation.fpsInterval = 1000 / simulation.fpsCap;
mech.defaultFPSCycle = mech.cycle
if (tech.isRewindBot) {
const len = steps * 0.042 * tech.isRewindBot
for (let i = 0; i < len; i++) {
const where = mech.history[Math.abs(mech.cycle - i * 40) % 600].position //spread out spawn locations along past history
b.randomBot({
x: where.x + 100 * (Math.random() - 0.5),
y: where.y + 100 * (Math.random() - 0.5)
}, false, false)
bullet[bullet.length - 1].endCycle = simulation.cycle + 360 + Math.floor(180 * Math.random()) //6-9 seconds
}
}
},
damage(dmg) {
if (tech.isRewindAvoidDeath && mech.energy > 0.66) {
mech.rewind(Math.floor(Math.min(299, 137 * mech.energy)))
return
}
mech.lastHarmCycle = mech.cycle
if (tech.isDroneOnDamage) { //chance to build a drone on damage from tech
const len = Math.min((dmg - 0.06 * Math.random()) * 40, 40)
for (let i = 0; i < len; i++) {
if (Math.random() < 0.5) b.drone() //spawn drone
}
}
if (tech.isEnergyHealth) {
mech.energy -= dmg;
if (mech.energy < 0 || isNaN(mech.energy)) { //taking deadly damage
if (tech.isDeathAvoid && powerUps.reroll.rerolls && !tech.isDeathAvoidedThisLevel) {
tech.isDeathAvoidedThisLevel = true
powerUps.reroll.changeRerolls(-1)
simulation.makeTextLog(`mech.rerolls--
${powerUps.reroll.rerolls}`)
for (let i = 0; i < 6; i++) {
powerUps.spawn(mech.pos.x, mech.pos.y, "heal", false);
}
mech.energy = mech.maxEnergy
mech.immuneCycle = mech.cycle + 360 //disable this.immuneCycle bonus seconds
simulation.wipe = function() { //set wipe to have trails
ctx.fillStyle = "rgba(255,255,255,0.03)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
setTimeout(function() {
simulation.wipe = function() { //set wipe to normal
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}, 3000);
return;
} else { //death
mech.health = 0;
mech.energy = 0;
mech.death();
return;
}
}
} else {
dmg *= mech.harmReduction()
mech.health -= dmg;
if (mech.health < 0 || isNaN(mech.health)) {
if (tech.isDeathAvoid && powerUps.reroll.rerolls > 0 && !tech.isDeathAvoidedThisLevel) { //&& Math.random() < 0.5
tech.isDeathAvoidedThisLevel = true
mech.health = 0.05
powerUps.reroll.changeRerolls(-1)
simulation.makeTextLog(`mech.rerolls--
${powerUps.reroll.rerolls}`)
for (let i = 0; i < 6; i++) {
powerUps.spawn(mech.pos.x, mech.pos.y, "heal", false);
}
mech.immuneCycle = mech.cycle + 360 //disable this.immuneCycle bonus seconds
simulation.wipe = function() { //set wipe to have trails
ctx.fillStyle = "rgba(255,255,255,0.03)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
setTimeout(function() {
simulation.wipe = function() { //set wipe to normal
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}, 3000);
} else {
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);
}
if (dmg > 0.06 / mech.holdingMassScale) mech.drop(); //drop block if holding
const normalFPS = function() {
if (mech.defaultFPSCycle < mech.cycle) { //back to default values
simulation.fpsCap = simulation.fpsCapDefault
simulation.fpsInterval = 1000 / simulation.fpsCap;
document.getElementById("dmg").style.transition = "opacity 1s";
document.getElementById("dmg").style.opacity = "0";
} else {
requestAnimationFrame(normalFPS);
}
};
if (mech.defaultFPSCycle < mech.cycle) requestAnimationFrame(normalFPS);
if (tech.isSlowFPS) { // slow game
simulation.fpsCap = 30 //new fps
simulation.fpsInterval = 1000 / simulation.fpsCap;
//how long to wait to return to normal fps
mech.defaultFPSCycle = mech.cycle + 20 + Math.min(90, Math.floor(200 * dmg))
if (tech.isHarmFreeze) { //freeze all mobs
for (let i = 0, len = mob.length; i < len; i++) {
mobs.statusSlow(mob[i], 300)
}
}
} else {
if (dmg > 0.05) { // freeze game for high damage hits
simulation.fpsCap = 4 //40 - Math.min(25, 100 * dmg)
simulation.fpsInterval = 1000 / simulation.fpsCap;
} else {
simulation.fpsCap = simulation.fpsCapDefault
simulation.fpsInterval = 1000 / simulation.fpsCap;
}
mech.defaultFPSCycle = mech.cycle
}
// if (!noTransition) {
// document.getElementById("health").style.transition = "width 0s ease-out"
// } else {
// document.getElementById("health").style.transition = "width 1s ease-out"
// }
},
hitMob(i, dmg) {
//prevents damage happening too quick
},
buttonCD: 0, //cool down for player buttons
drawLeg(stroke) {
// if (simulation.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.globalAlpha = (mech.immuneCycle < mech.cycle) ? 1 : 0.7
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 = '#0cf';
// ctx.fill()
ctx.restore();
mech.yOff = mech.yOff * 0.85 + mech.yOffGoal * 0.15; //smoothly move leg height towards height goal
},
// *********************************************
// **************** fields *********************
// *********************************************
closest: {
dist: 1000,
index: 0
},
isHolding: false,
isCloak: false,
throwCharge: 0,
fireCDcycle: 0,
fieldCDcycle: 0,
fieldMode: 0, //basic field mode before upgrades
maxEnergy: 1, //can be increased by a tech
holdingTarget: null,
timeSkipLastCycle: 0,
// these values are set on reset by setHoldDefaults()
grabPowerUpRange2: 0,
isFieldActive: false,
fieldRange: 155,
fieldShieldingScale: 1,
fieldDamage: 1,
duplicateChance: 0,
energy: 0,
fieldRegen: 0,
fieldMode: 0,
fieldFire: false,
fieldHarmReduction: 1,
holdingMassScale: 0,
hole: {
isOn: false,
isReady: true,
pos1: {
x: 0,
y: 0
},
pos2: {
x: 0,
y: 0
},
},
fieldArc: 0,
fieldThreshold: 0,
calculateFieldThreshold() {
mech.fieldThreshold = Math.cos(mech.fieldArc * Math.PI)
},
setHoldDefaults() {
if (mech.energy < mech.maxEnergy) mech.energy = mech.maxEnergy;
mech.fieldRegen = tech.energyRegen; //0.001
mech.fieldMeterColor = "#0cf"
mech.fieldShieldingScale = 1;
mech.fieldBlockCD = 10;
mech.fieldHarmReduction = 1;
mech.fieldDamage = 1
mech.duplicateChance = 0
if (tech.duplicationChance() === 0) simulation.draw.powerUp = simulation.draw.powerUpNormal
mech.grabPowerUpRange2 = 156000;
mech.fieldRange = 155;
mech.fieldFire = false;
mech.fieldCDcycle = 0;
mech.isCloak = false;
player.collisionFilter.mask = cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield
mech.airSpeedLimit = 125
mech.drop();
mech.holdingMassScale = 0.5;
mech.isFieldActive = false; //only being used by negative mass field
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();
mech.setMaxEnergy();
mech.hole = {
isOn: false,
isReady: true,
pos1: {
x: 0,
y: 0
},
pos2: {
x: 0,
y: 0
},
}
},
setMaxEnergy() {
mech.maxEnergy = (tech.isMaxEnergyTech ? 0.5 : 1) + tech.bonusEnergy + tech.healMaxEnergyBonus
},
fieldMeterColor: "#0cf",
drawFieldMeter(bgColor = "rgba(0, 0, 0, 0.4)", range = 60) {
if (mech.energy < mech.maxEnergy) {
mech.energy += mech.fieldRegen;
ctx.fillStyle = bgColor;
const xOff = mech.pos.x - mech.radius * mech.maxEnergy
const yOff = mech.pos.y - 50
ctx.fillRect(xOff, yOff, range * mech.maxEnergy, 10);
ctx.fillStyle = mech.fieldMeterColor;
ctx.fillRect(xOff, yOff, range * mech.energy, 10);
if (mech.energy < 0) mech.energy = 0
} else if (mech.energy > mech.maxEnergy + 0.05) {
ctx.fillStyle = bgColor;
const xOff = mech.pos.x - mech.radius * mech.energy
const yOff = mech.pos.y - 50
// ctx.fillRect(xOff, yOff, range * mech.maxEnergy, 10);
ctx.fillStyle = mech.fieldMeterColor;
ctx.fillRect(xOff, yOff, range * mech.energy, 10);
}
// else {
// mech.energy = mech.maxEnergy
// }
},
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.fieldCDcycle = mech.cycle + 15;
mech.isHolding = false;
mech.throwCharge = 0;
mech.definePlayerMass()
if (mech.holdingTarget) {
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;
}
}
},
definePlayerMass(mass = mech.defaultMass) {
Matter.Body.setMass(player, mass);
//reduce air and ground move forces
mech.Fx = 0.08 / mass * tech.squirrelFx //base player mass is 5
mech.FxAir = 0.4 / mass / mass //base player mass is 5
//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.fireCDcycle < mech.cycle) mech.fireCDcycle = mech.cycle - 1
if (mech.holdingTarget) {
mech.energy -= mech.fieldRegen;
if (mech.energy < 0) mech.energy = 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
} else {
mech.isHolding = false
}
},
throwBlock() {
if (mech.holdingTarget) {
if (input.field) {
if (mech.energy > 0.001) {
if (mech.fireCDcycle < mech.cycle) mech.fireCDcycle = mech.cycle
mech.energy -= 0.001 / tech.throwChargeRate;
mech.throwCharge += 0.5 * tech.throwChargeRate / mech.holdingTarget.mass
//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 * mech.throwCharge;
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) { //Matter.Query.region(mob, player.bounds)
//throw the body
mech.fieldCDcycle = mech.cycle + 15;
mech.isHolding = false;
//bullet-like collisions
mech.holdingTarget.collisionFilter.category = cat.body; //cat.bullet;
mech.holdingTarget.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield;
//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 !== mech.holdingTarget) {
// that.collisionFilter.category = cat.body; //make solid
that.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet; //can hit player now
} else {
setTimeout(solid, 25, that);
}
};
setTimeout(solid, 150, mech.holdingTarget);
const charge = Math.min(mech.throwCharge / 5, 1)
//***** scale throw speed with the first number, 80 *****
let speed = 80 * charge * Math.min(1, 0.8 / Math.pow(mech.holdingTarget.mass, 0.25));
if (Matter.Query.collides(mech.holdingTarget, map).length !== 0) {
speed *= 0.7 //drop speed by 30% if touching map
if (Matter.Query.ray(map, mech.holdingTarget.position, mech.pos).length !== 0) speed = 0 //drop to zero if the center of the block can't see the center of the player through the map
//|| Matter.Query.ray(body, mech.holdingTarget.position, mech.pos).length > 1
}
mech.throwCharge = 0;
Matter.Body.setVelocity(mech.holdingTarget, {
x: player.velocity.x * 0.5 + Math.cos(mech.angle) * speed,
y: player.velocity.y * 0.5 + 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 : 10) * Math.sqrt(mech.holdingTarget.mass),
y: player.velocity.y - Math.sin(mech.angle) * speed / 30 * Math.sqrt(mech.holdingTarget.mass)
});
mech.definePlayerMass() //return to normal player mass
}
} else {
mech.isHolding = false
}
},
drawField() {
if (mech.holdingTarget) {
ctx.fillStyle = "rgba(110,170,200," + (mech.energy * (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.energy * (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(simulation.cycle * 0.1)
const range = mech.fieldRange;
ctx.beginPath();
ctx.arc(mech.pos.x, mech.pos.y, range, mech.angle - Math.PI * mech.fieldArc, mech.angle + Math.PI * mech.fieldArc, false);
ctx.lineWidth = 2;
ctx.lineCap = "butt"
ctx.stroke();
let eye = 13;
let aMag = 0.75 * Math.PI * mech.fieldArc
let a = mech.angle + aMag
let cp1x = mech.pos.x + 0.6 * range * Math.cos(a)
let cp1y = mech.pos.y + 0.6 * range * Math.sin(a)
ctx.quadraticCurveTo(cp1x, cp1y, mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle))
a = mech.angle - aMag
cp1x = mech.pos.x + 0.6 * range * Math.cos(a)
cp1y = mech.pos.y + 0.6 * range * Math.sin(a)
ctx.quadraticCurveTo(cp1x, cp1y, mech.pos.x + 1 * range * Math.cos(mech.angle - Math.PI * mech.fieldArc), mech.pos.y + 1 * range * Math.sin(mech.angle - Math.PI * mech.fieldArc))
ctx.fill();
// ctx.lineTo(mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle));
//draw random lines in field for cool effect
let offAngle = mech.angle + 1.7 * Math.PI * mech.fieldArc * (Math.random() - 0.5);
ctx.beginPath();
eye = 15;
ctx.moveTo(mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle));
ctx.lineTo(mech.pos.x + range * Math.cos(offAngle), mech.pos.y + range * Math.sin(offAngle));
ctx.strokeStyle = "rgba(120,170,255,0.6)";
ctx.lineWidth = 1;
ctx.stroke();
},
grabPowerUp() { //look for power ups to grab with field
if (mech.fireCDcycle < mech.cycle) mech.fireCDcycle = mech.cycle - 1
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 < mech.grabPowerUpRange2 &&
(mech.lookingAt(powerUp[i]) || dist2 < 16000) &&
!(mech.health === mech.maxHealth && powerUp[i].name === "heal") &&
Matter.Query.ray(map, powerUp[i].position, mech.pos).length === 0
) {
powerUp[i].force.x += 0.05 * (dxP / Math.sqrt(dist2)) * powerUp[i].mass;
powerUp[i].force.y += 0.05 * (dyP / Math.sqrt(dist2)) * powerUp[i].mass - powerUp[i].mass * simulation.g; //negate gravity
//extra friction
Matter.Body.setVelocity(powerUp[i], {
x: powerUp[i].velocity.x * 0.11,
y: powerUp[i].velocity.y * 0.11
});
if (dist2 < 5000 && !simulation.isChoosing) { //use power up if it is close enough
powerUps.onPickUp(mech.pos);
Matter.Body.setVelocity(player, { //player knock back, after grabbing power up
x: player.velocity.x + powerUp[i].velocity.x / player.mass * 5,
y: player.velocity.y + powerUp[i].velocity.y / player.mass * 5
});
powerUp[i].effect();
Matter.World.remove(engine.world, powerUp[i]);
powerUp.splice(i, 1);
return; //because the array order is messed up after splice
}
}
}
},
pushMass(who) {
const speed = Vector.magnitude(Vector.sub(who.velocity, player.velocity))
const fieldBlockCost = (0.03 + Math.sqrt(who.mass) * speed * 0.003) * mech.fieldShieldingScale;
const unit = Vector.normalise(Vector.sub(player.position, who.position))
if (mech.energy > fieldBlockCost * 0.2) { //shield needs at least some of the cost to block
mech.energy -= fieldBlockCost
if (mech.energy < 0) {
mech.energy = 0;
}
// if (mech.energy > mech.maxEnergy) mech.energy = mech.maxEnergy;
if (tech.blockDmg) {
who.damage(tech.blockDmg * b.dmgScale)
//draw electricity
const step = 40
ctx.beginPath();
for (let i = 0, len = 2.5 * tech.blockDmg; i < len; i++) {
let x = mech.pos.x - 20 * unit.x;
let y = mech.pos.y - 20 * unit.y;
ctx.moveTo(x, y);
for (let i = 0; i < 8; i++) {
x += step * (-unit.x + 1.5 * (Math.random() - 0.5))
y += step * (-unit.y + 1.5 * (Math.random() - 0.5))
ctx.lineTo(x, y);
}
}
ctx.lineWidth = 3;
ctx.strokeStyle = "#f0f";
ctx.stroke();
} else {
mech.drawHold(who);
}
// if (tech.isFreezeMobs) mobs.statusSlow(who, 60) //this works but doesn't have a fun effect
// mech.holdingTarget = null
//knock backs
if (mech.fieldShieldingScale > 0) {
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
});
mech.fieldCDcycle = mech.cycle + mech.fieldBlockCD;
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
});
}
} else {
if (tech.isStunField && mech.fieldUpgrades[mech.fieldMode].name === "perfect diamagnetism") mobs.statusStun(who, tech.isStunField)
// mobs.statusSlow(who, tech.isStunField)
const massRoot = Math.sqrt(Math.max(0.15, who.mass)); // masses above 12 can start to overcome the push back
Matter.Body.setVelocity(who, {
x: player.velocity.x - (20 * unit.x) / massRoot,
y: player.velocity.y - (20 * unit.y) / massRoot
});
if (who.dropPowerUp && player.speed < 12) {
const massRootCap = Math.sqrt(Math.min(10, Math.max(0.4, who.mass))); // masses above 12 can start to overcome the push back
Matter.Body.setVelocity(player, {
x: 0.9 * player.velocity.x + 0.6 * unit.x * massRootCap,
y: 0.9 * player.velocity.y + 0.6 * unit.y * massRootCap
});
}
}
}
},
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)) - mob[i].radius < mech.fieldRange &&
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.fieldRange * 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]);
}
}
},
lookForPickUp() { //find body to pickup
if (mech.energy > mech.fieldRegen) mech.energy -= mech.fieldRegen;
const grabbing = {
targetIndex: null,
targetRange: 150,
// 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)
}
}
}
if (tech.isFreezeMobs) {
for (let i = 0, len = mob.length; i < len; ++i) {
Matter.Sleeping.set(mob[i], false)
mobs.statusSlow(mob[i], 60)
}
} else {
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) {
if (isNaN(index)) { //find index by name
let found = false
for (let i = 0; i < mech.fieldUpgrades.length; i++) {
if (index === mech.fieldUpgrades[i].name) {
index = i;
found = true;
break;
}
}
if (!found) return //if you can't find the field don't give a field to avoid game crash
}
mech.fieldMode = index;
document.getElementById("field").innerHTML = mech.fieldUpgrades[index].name
mech.setHoldDefaults();
mech.fieldUpgrades[index].effect();
},
fieldUpgrades: [{
name: "field emitter",
description: "use energy to block mobs,
grab power ups, and throw blocks",
effect: () => {
mech.hold = function() {
if (mech.isHolding) {
mech.drawHold(mech.holdingTarget);
mech.holding();
mech.throwBlock();
} else if ((input.field && mech.fieldCDcycle < mech.cycle)) { //not hold but field button is pressed
mech.grabPowerUp();
mech.lookForPickUp();
if (mech.energy > 0.05) {
mech.drawField();
mech.pushMobsFacing();
}
} 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)
}
mech.drawFieldMeter()
}
}
},
{
name: "standing wave harmonics",
description: "3 oscillating shields are permanently active
blocking drains energy
blocking has no cool down",
effect: () => {
// mech.fieldHarmReduction = 0.80;
mech.fieldBlockCD = 0;
mech.hold = function() {
if (mech.isHolding) {
mech.drawHold(mech.holdingTarget);
mech.holding();
mech.throwBlock();
} else if ((input.field) && mech.fieldCDcycle < mech.cycle) { //not hold but field button is pressed
mech.grabPowerUp();
mech.lookForPickUp();
} 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.energy > 0.1 && mech.fieldCDcycle < mech.cycle) {
const fieldRange1 = (0.7 + 0.3 * Math.sin(mech.cycle / 23)) * mech.fieldRange
const fieldRange2 = (0.63 + 0.37 * Math.sin(mech.cycle / 37)) * mech.fieldRange
const fieldRange3 = (0.65 + 0.35 * Math.sin(mech.cycle / 47)) * mech.fieldRange
const netfieldRange = Math.max(fieldRange1, fieldRange2, fieldRange3)
ctx.fillStyle = "rgba(110,170,200," + (0.04 + mech.energy * (0.12 + 0.13 * Math.random())) + ")";
ctx.beginPath();
ctx.arc(mech.pos.x, mech.pos.y, fieldRange1, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.arc(mech.pos.x, mech.pos.y, fieldRange2, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.arc(mech.pos.x, mech.pos.y, fieldRange3, 0, 2 * Math.PI);
ctx.fill();
mech.pushMobs360(netfieldRange);
// mech.pushBody360(netfieldRange); //can't throw block when pushhing blocks away
}
mech.drawFieldMeter()
}
}
},
{
name: "perfect diamagnetism",
// description: "gain energy when blocking
no recoil when blocking",
description: "blocking does not drain energy
blocking has no cool down and less recoil
attract power ups from far away",
effect: () => {
mech.fieldShieldingScale = 0;
mech.grabPowerUpRange2 = 10000000
mech.hold = function() {
const wave = Math.sin(mech.cycle * 0.022);
mech.fieldRange = 170 + 12 * wave
mech.fieldArc = 0.33 + 0.045 * wave //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob)
mech.calculateFieldThreshold();
if (mech.isHolding) {
mech.drawHold(mech.holdingTarget);
mech.holding();
mech.throwBlock();
} else if ((input.field && mech.fieldCDcycle < mech.cycle)) { //not hold but field button is pressed
mech.grabPowerUp();
mech.lookForPickUp();
if (mech.energy > 0.05) {
//draw field
if (mech.holdingTarget) {
ctx.fillStyle = "rgba(110,170,200," + (0.06 + 0.03 * Math.random()) + ")";
ctx.strokeStyle = "rgba(110, 200, 235, " + (0.35 + 0.05 * Math.random()) + ")"
} else {
ctx.fillStyle = "rgba(110,170,200," + (0.27 + 0.2 * Math.random() - 0.1 * wave) + ")";
ctx.strokeStyle = "rgba(110, 200, 235, " + (0.4 + 0.5 * Math.random()) + ")"
}
ctx.beginPath();
ctx.arc(mech.pos.x, mech.pos.y, mech.fieldRange, mech.angle - Math.PI * mech.fieldArc, mech.angle + Math.PI * mech.fieldArc, false);
ctx.lineWidth = 2.5 - 1.5 * wave;
ctx.lineCap = "butt"
ctx.stroke();
const curve = 0.57 + 0.04 * wave
const aMag = (1 - curve * 1.2) * Math.PI * mech.fieldArc
let a = mech.angle + aMag
let cp1x = mech.pos.x + curve * mech.fieldRange * Math.cos(a)
let cp1y = mech.pos.y + curve * mech.fieldRange * Math.sin(a)
ctx.quadraticCurveTo(cp1x, cp1y, mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle))
a = mech.angle - aMag
cp1x = mech.pos.x + curve * mech.fieldRange * Math.cos(a)
cp1y = mech.pos.y + curve * mech.fieldRange * Math.sin(a)
ctx.quadraticCurveTo(cp1x, cp1y, mech.pos.x + 1 * mech.fieldRange * Math.cos(mech.angle - Math.PI * mech.fieldArc), mech.pos.y + 1 * mech.fieldRange * Math.sin(mech.angle - Math.PI * mech.fieldArc))
ctx.fill();
mech.pushMobsFacing();
}
} 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)
}
mech.drawFieldMeter()
if (tech.isPerfectBrake) { //cap mob speed around player
const range = 160 + 140 * wave + 150 * mech.energy
for (let i = 0; i < mob.length; i++) {
const distance = Vector.magnitude(Vector.sub(mech.pos, mob[i].position))
if (distance < range) {
const cap = mob[i].isShielded ? 8.5 : 4.5
if (mob[i].speed > cap && Vector.dot(mob[i].velocity, Vector.sub(mech.pos, mob[i].position)) > 0) { // if velocity is directed towards player
Matter.Body.setVelocity(mob[i], Vector.mult(Vector.normalise(mob[i].velocity), cap)); //set velocity to cap, but keep the direction
}
}
}
ctx.beginPath();
ctx.arc(mech.pos.x, mech.pos.y, range, 0, 2 * Math.PI);
ctx.fillStyle = "hsla(200,50%,61%,0.08)";
ctx.fill();
}
}
}
},
{
name: "nano-scale manufacturing",
description: "use energy to block mobs
excess energy used to build drones
double your default energy regeneration",
effect: () => {
mech.hold = function() {
if (mech.energy > mech.maxEnergy - 0.02 && mech.fieldCDcycle < mech.cycle && !input.field) {
if (tech.isSporeField) {
const len = Math.floor(5 + 4 * Math.random())
mech.energy -= len * 0.105;
for (let i = 0; i < len; i++) {
b.spore(mech.pos)
}
} else if (tech.isMissileField) {
mech.energy -= 0.55;
b.missile({ x: mech.pos.x, y: mech.pos.y - 40 }, -Math.PI / 2, 0, 1)
} else if (tech.isIceField) {
mech.energy -= 0.057;
b.iceIX(1)
} else {
mech.energy -= 0.45;
b.drone(1)
}
}
if (mech.isHolding) {
mech.drawHold(mech.holdingTarget);
mech.holding();
mech.throwBlock();
} else if ((input.field && mech.fieldCDcycle < mech.cycle)) { //not hold but field button is pressed
mech.grabPowerUp();
mech.lookForPickUp();
if (mech.energy > 0.05) {
mech.drawField();
mech.pushMobsFacing();
}
} 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)
}
mech.energy += mech.fieldRegen;
mech.drawFieldMeter()
}
}
},
{
name: "negative mass field",
description: "use energy to nullify gravity
reduce harm by 45%
blocks held by the field have a lower mass",
fieldDrawRadius: 0,
effect: () => {
mech.fieldFire = true;
mech.holdingMassScale = 0.03; //can hold heavier blocks with lower cost to jumping
mech.fieldMeterColor = "#000"
mech.fieldHarmReduction = 0.55;
mech.fieldDrawRadius = 0;
mech.hold = function() {
mech.airSpeedLimit = 125 //5 * player.mass * player.mass
mech.FxAir = 0.016
mech.isFieldActive = false;
if (mech.isHolding) {
mech.drawHold(mech.holdingTarget);
mech.holding();
mech.throwBlock();
} else if (input.field && mech.fieldCDcycle < mech.cycle) { //push away
mech.grabPowerUp();
mech.lookForPickUp();
const DRAIN = 0.00035
if (mech.energy > DRAIN) {
mech.isFieldActive = true; //used with tech.isHarmReduce
mech.airSpeedLimit = 400 // 7* player.mass * player.mass
mech.FxAir = 0.005
// mech.pushMobs360();
//repulse mobs
// for (let i = 0, len = mob.length; i < len; ++i) {
// sub = Vector.sub(mob[i].position, mech.pos);
// dist2 = Vector.magnitudeSquared(sub);
// if (dist2 < this.fieldDrawRadius * this.fieldDrawRadius && mob[i].speed > 6) {
// const force = Vector.mult(Vector.perp(Vector.normalise(sub)), 0.00004 * mob[i].speed * mob[i].mass)
// mob[i].force.x = force.x
// mob[i].force.y = force.y
// }
// }
//look for nearby objects to make zero-g
function zeroG(who, range, 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 < range) {
who[i].force.y -= who[i].mass * (simulation.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?
if (input.down) { //down
player.force.y -= 0.5 * player.mass * simulation.g;
this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 400 * 0.03;
zeroG(powerUp, this.fieldDrawRadius, 0.7);
zeroG(body, this.fieldDrawRadius, 0.7);
} else if (input.up) { //up
mech.energy -= 5 * DRAIN;
this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 850 * 0.03;
player.force.y -= 1.45 * player.mass * simulation.g;
zeroG(powerUp, this.fieldDrawRadius, 1.38);
zeroG(body, this.fieldDrawRadius, 1.38);
} else {
mech.energy -= DRAIN;
this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 650 * 0.03;
player.force.y -= 1.07 * player.mass * simulation.g; // slow upward drift
zeroG(powerUp, this.fieldDrawRadius);
zeroG(body, this.fieldDrawRadius);
}
if (mech.energy < 0.001) {
mech.fieldCDcycle = mech.cycle + 120;
mech.energy = 0;
}
//add extra friction for horizontal motion
if (input.down || input.up || input.left || input.right) {
Matter.Body.setVelocity(player, {
x: player.velocity.x * 0.99,
y: player.velocity.y * 0.98
});
} else { //slow rise and fall
Matter.Body.setVelocity(player, {
x: player.velocity.x * 0.99,
y: player.velocity.y * 0.98
});
}
if (tech.isFreezeMobs) {
const ICE_DRAIN = 0.0005
for (let i = 0, len = mob.length; i < len; i++) {
if (((mob[i].distanceToPlayer() + mob[i].radius) < this.fieldDrawRadius) && !mob[i].shield && !mob[i].isShielded) {
if (mech.energy > ICE_DRAIN * 2) {
mech.energy -= ICE_DRAIN;
this.fieldDrawRadius -= 2;
mobs.statusSlow(mob[i], 45)
} else {
break;
}
}
}
}
//draw zero-G range
ctx.beginPath();
ctx.arc(mech.pos.x, mech.pos.y, this.fieldDrawRadius, 0, 2 * Math.PI);
ctx.fillStyle = "#f5f5ff";
ctx.globalCompositeOperation = "difference";
ctx.fill();
ctx.globalCompositeOperation = "source-over";
}
} else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released
mech.pickUp();
this.fieldDrawRadius = 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)
this.fieldDrawRadius = 0
}
mech.drawFieldMeter("rgba(0,0,0,0.2)")
}
}
},
{
name: "plasma torch",
description: "use energy to emit short range plasma
damages and pushes mobs away",
effect() {
mech.fieldMeterColor = "#f0f"
mech.hold = function() {
b.isExtruderOn = false
if (mech.isHolding) {
mech.drawHold(mech.holdingTarget);
mech.holding();
mech.throwBlock();
} else if (input.field && mech.fieldCDcycle < mech.cycle) { //not hold but field button is pressed
mech.grabPowerUp();
mech.lookForPickUp();
if (tech.isExtruder) {
b.extruder();
} else {
b.plasma();
}
} 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)
}
mech.drawFieldMeter("rgba(0, 0, 0, 0.2)")
if (tech.isExtruder) {
if (input.field) {
b.wasExtruderOn = true
} else {
b.wasExtruderOn = false
b.canExtruderFire = true
}
ctx.lineWidth = 5;
ctx.strokeStyle = "#f07"
ctx.beginPath(); //draw all the wave bullets
for (let i = 0, len = bullet.length; i < len; i++) {
if (bullet[i].isWave) {
if (bullet[i].isBranch) {
ctx.stroke();
ctx.beginPath(); //draw all the wave bullets
} else {
ctx.lineTo(bullet[i].position.x, bullet[i].position.y)
}
}
}
if (b.wasExtruderOn && b.isExtruderOn) ctx.lineTo(mech.pos.x + 15 * Math.cos(mech.angle), mech.pos.y + 15 * Math.sin(mech.angle))
ctx.stroke();
}
}
}
},
{
name: "time dilation field",
description: "use energy to stop time
move and fire while time is stopped",
effect: () => {
// mech.fieldMeterColor = "#000"
mech.fieldFire = true;
mech.isBodiesAsleep = false;
mech.hold = function() {
if (mech.isHolding) {
mech.wakeCheck();
mech.drawHold(mech.holdingTarget);
mech.holding();
mech.throwBlock();
} else if (input.field && mech.fieldCDcycle < mech.cycle) {
mech.grabPowerUp();
mech.lookForPickUp(180);
const DRAIN = 0.0013
if (mech.energy > DRAIN) {
mech.energy -= DRAIN;
if (mech.energy < DRAIN) {
mech.fieldCDcycle = mech.cycle + 120;
mech.energy = 0;
mech.wakeCheck();
}
//draw field everywhere
ctx.globalCompositeOperation = "saturation"
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;
}
}
simulation.cycle--; //pause all functions that depend on game cycle increasing
if (tech.isTimeSkip) {
mech.immuneCycle = mech.cycle + 10;
simulation.isTimeSkipping = true;
mech.cycle++;
simulation.gravity();
Engine.update(engine, simulation.delta);
// level.checkZones();
// level.checkQuery();
mech.move();
simulation.checks();
// mobs.loop();
// mech.draw();
mech.walk_cycle += mech.flipLegs * mech.Vx;
// mech.hold();
// mech.energy += DRAIN; // 1 to undo the energy drain from time speed up, 0.5 to cut energy drain in half
b.fire();
// b.bulletRemove();
b.bulletDo();
simulation.isTimeSkipping = false;
}
// simulation.cycle--; //pause all functions that depend on game cycle increasing
// if (tech.isTimeSkip && !simulation.isTimeSkipping) { //speed up the rate of time
// simulation.timeSkip(1)
// mech.energy += 1.5 * DRAIN; //x1 to undo the energy drain from time speed up, x1.5 to cut energy drain in half
// }
}
} else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //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()
}
}
},
{
name: "metamaterial cloaking", //"weak photonic coupling" "electromagnetically induced transparency" "optical non-coupling" "slow light field" "electro-optic transparency"
description: "cloak after not using your gun or field
while cloaked mobs can't see you
increase damage by 133%",
effect: () => {
mech.fieldFire = true;
mech.fieldMeterColor = "#fff";
mech.fieldPhase = 0;
mech.isCloak = false
mech.fieldDamage = 2.33 // 1 + 111/100
mech.fieldDrawRadius = 0
const drawRadius = 1000
mech.hold = function() {
if (mech.isHolding) {
mech.drawHold(mech.holdingTarget);
mech.holding();
mech.throwBlock();
} else if (input.field && mech.fieldCDcycle < mech.cycle) { //not hold and field button is pressed
mech.grabPowerUp();
mech.lookForPickUp();
} else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding target exists, and field button is not pressed
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)
}
//120 cycles after shooting (or using field) enable cloak
if (mech.energy < 0.05 && mech.fireCDcycle < mech.cycle && !input.fire) mech.fireCDcycle = mech.cycle
if (mech.fireCDcycle + 50 < mech.cycle) {
if (!mech.isCloak) {
mech.isCloak = true //enter cloak
if (tech.isIntangible) {
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType && bullet[i].botType !== "orbit") bullet[i].collisionFilter.mask = cat.map | cat.bullet | cat.mobBullet | cat.mobShield
}
}
}
} else if (mech.isCloak) { //exit cloak
mech.isCloak = false
if (tech.isIntangible) {
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType && bullet[i].botType !== "orbit") bullet[i].collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield
}
}
if (tech.isCloakStun) { //stun nearby mobs after exiting cloak
let isMobsAround = false
const stunRange = mech.fieldDrawRadius * 1.15
const drain = 0.3 * mech.energy
for (let i = 0, len = mob.length; i < len; ++i) {
if (
Vector.magnitude(Vector.sub(mob[i].position, mech.pos)) < stunRange &&
Matter.Query.ray(map, mob[i].position, mech.pos).length === 0
) {
isMobsAround = true
mobs.statusStun(mob[i], 30 + drain * 300)
}
}
if (isMobsAround && mech.energy > drain) {
mech.energy -= drain
simulation.drawList.push({
x: mech.pos.x,
y: mech.pos.y,
radius: stunRange,
color: "hsla(0,50%,100%,0.6)",
time: 4
});
// ctx.beginPath();
// ctx.arc(mech.pos.x, mech.pos.y, 800, 0, 2 * Math.PI);
// ctx.fillStyle = "#000"
// ctx.fill();
}
}
}
function drawField() {
mech.fieldPhase += 0.007 + 0.07 * (1 - energy)
const wiggle = 0.15 * Math.sin(mech.fieldPhase * 0.5)
ctx.beginPath();
ctx.ellipse(mech.pos.x, mech.pos.y, mech.fieldDrawRadius * (1 - wiggle), mech.fieldDrawRadius * (1 + wiggle), mech.fieldPhase, 0, 2 * Math.PI);
if (mech.fireCDcycle > mech.cycle && (input.field)) {
ctx.lineWidth = 5;
ctx.strokeStyle = `rgba(0, 204, 255,1)`
ctx.stroke()
}
ctx.fillStyle = "#fff" //`rgba(0,0,0,${0.5+0.5*mech.energy})`;
ctx.globalCompositeOperation = "destination-in"; //in or atop
ctx.fill();
ctx.globalCompositeOperation = "source-over";
ctx.clip();
}
const energy = Math.max(0.01, Math.min(mech.energy, 1))
if (mech.isCloak) {
this.fieldRange = this.fieldRange * 0.9 + 0.1 * drawRadius
mech.fieldDrawRadius = this.fieldRange * Math.min(1, 0.3 + 0.5 * Math.min(1, energy * energy));
drawField()
} else {
if (this.fieldRange < 3000) {
this.fieldRange += 200
mech.fieldDrawRadius = this.fieldRange * Math.min(1, 0.3 + 0.5 * Math.min(1, energy * energy));
drawField()
}
}
if (tech.isIntangible) {
if (mech.isCloak) {
player.collisionFilter.mask = cat.map
let inPlayer = Matter.Query.region(mob, player.bounds)
if (inPlayer.length > 0) {
for (let i = 0; i < inPlayer.length; i++) {
if (mech.energy > 0) {
if (inPlayer[i].shield) { //shields drain player energy
mech.energy -= 0.014;
} else {
mech.energy -= 0.004;
}
}
}
}
} else {
player.collisionFilter.mask = cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield //normal collisions
}
}
if (mech.energy < mech.maxEnergy) { // replaces mech.drawFieldMeter() with custom code
mech.energy += mech.fieldRegen;
const xOff = mech.pos.x - mech.radius * mech.maxEnergy
const yOff = mech.pos.y - 50
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
ctx.fillRect(xOff, yOff, 60 * mech.maxEnergy, 10);
ctx.fillStyle = mech.fieldMeterColor;
ctx.fillRect(xOff, yOff, 60 * mech.energy, 10);
ctx.beginPath()
ctx.rect(xOff, yOff, 60 * mech.maxEnergy, 10);
ctx.strokeStyle = "rgb(0, 0, 0)";
ctx.lineWidth = 1;
ctx.stroke();
}
}
}
},
// {
// name: "phase decoherence field",
// description: "use energy to become intangible
firing and touching shields drains energy
unable to see and be seen by mobs",
// effect: () => {
// mech.fieldFire = true;
// mech.fieldMeterColor = "#fff";
// mech.fieldPhase = 0;
// mech.hold = function () {
// function drawField(radius) {
// radius *= Math.min(4, 0.9 + 2.2 * mech.energy * mech.energy);
// const rotate = mech.cycle * 0.005;
// mech.fieldPhase += 0.5 - 0.5 * Math.sqrt(Math.max(0.01, Math.min(mech.energy, 1)));
// const off1 = 1 + 0.06 * Math.sin(mech.fieldPhase);
// const off2 = 1 - 0.06 * Math.sin(mech.fieldPhase);
// ctx.beginPath();
// ctx.ellipse(mech.pos.x, mech.pos.y, radius * off1, radius * off2, rotate, 0, 2 * Math.PI);
// if (mech.fireCDcycle > mech.cycle && (input.field)) {
// ctx.lineWidth = 5;
// ctx.strokeStyle = `rgba(0, 204, 255,1)`
// ctx.stroke()
// }
// ctx.fillStyle = "#fff" //`rgba(0,0,0,${0.5+0.5*mech.energy})`;
// ctx.globalCompositeOperation = "destination-in"; //in or atop
// ctx.fill();
// ctx.globalCompositeOperation = "source-over";
// ctx.clip();
// }
// mech.isCloak = false //isCloak disables most uses of foundPlayer()
// player.collisionFilter.mask = cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield //normal collisions
// if (mech.isHolding) {
// if (this.fieldRange < 2000) {
// this.fieldRange += 100
// drawField(this.fieldRange)
// }
// mech.drawHold(mech.holdingTarget);
// mech.holding();
// mech.throwBlock();
// } else if (input.field) {
// mech.grabPowerUp();
// mech.lookForPickUp();
// if (mech.fieldCDcycle < mech.cycle) {
// // simulation.draw.bodyFill = "transparent"
// // simulation.draw.bodyStroke = "transparent"
// const DRAIN = 0.00013 + (mech.fireCDcycle > mech.cycle ? 0.005 : 0)
// if (mech.energy > DRAIN) {
// mech.energy -= DRAIN;
// // if (mech.energy < 0.001) {
// // mech.fieldCDcycle = mech.cycle + 120;
// // mech.energy = 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)
// // }
// this.fieldRange = this.fieldRange * 0.8 + 0.2 * 160
// drawField(this.fieldRange)
// mech.isCloak = true //isCloak disables most uses of foundPlayer()
// player.collisionFilter.mask = cat.map
// let inPlayer = Matter.Query.region(mob, player.bounds)
// if (inPlayer.length > 0) {
// for (let i = 0; i < inPlayer.length; i++) {
// if (inPlayer[i].shield) {
// mech.energy -= 0.005; //shields drain player energy
// //draw outline of shield
// ctx.fillStyle = `rgba(140,217,255,0.5)`
// ctx.fill()
// } else if (tech.superposition && inPlayer[i].dropPowerUp) {
// // inPlayer[i].damage(0.4 * b.dmgScale); //damage mobs inside the player
// // mech.energy += 0.005;
// mobs.statusStun(inPlayer[i], 300)
// //draw outline of mob in a few random locations to show blurriness
// const vertices = inPlayer[i].vertices;
// const off = 30
// for (let k = 0; k < 3; k++) {
// const xOff = off * (Math.random() - 0.5)
// const yOff = off * (Math.random() - 0.5)
// ctx.beginPath();
// ctx.moveTo(xOff + vertices[0].x, yOff + vertices[0].y);
// for (let j = 1, len = vertices.length; j < len; ++j) {
// ctx.lineTo(xOff + vertices[j].x, yOff + vertices[j].y);
// }
// ctx.lineTo(xOff + vertices[0].x, yOff + vertices[0].y);
// ctx.fillStyle = "rgba(0,0,0,0.1)"
// ctx.fill()
// }
// break;
// }
// }
// }
// } else {
// mech.fieldCDcycle = mech.cycle + 120;
// mech.energy = 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)
// drawField(this.fieldRange)
// }
// }
// } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released
// mech.pickUp();
// if (this.fieldRange < 2000) {
// this.fieldRange += 100
// drawField(this.fieldRange)
// }
// } else {
// // this.fieldRange = 3000
// if (this.fieldRange < 2000 && mech.holdingTarget === null) {
// this.fieldRange += 100
// drawField(this.fieldRange)
// }
// 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.energy < mech.maxEnergy) {
// mech.energy += mech.fieldRegen;
// const xOff = mech.pos.x - mech.radius * mech.maxEnergy
// const yOff = mech.pos.y - 50
// ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
// ctx.fillRect(xOff, yOff, 60 * mech.maxEnergy, 10);
// ctx.fillStyle = mech.fieldMeterColor;
// ctx.fillRect(xOff, yOff, 60 * mech.energy, 10);
// ctx.beginPath()
// ctx.rect(xOff, yOff, 60 * mech.maxEnergy, 10);
// ctx.strokeStyle = "rgb(0, 0, 0)";
// ctx.lineWidth = 1;
// ctx.stroke();
// }
// if (mech.energy < 0) mech.energy = 0
// }
// }
// },
{
name: "pilot wave",
description: "use energy to push blocks with your mouse
field radius decreases out of line of sight
allows tech that normally require other fields",
effect: () => {
mech.fieldPhase = 0;
mech.fieldPosition = {
x: simulation.mouseInGame.x,
y: simulation.mouseInGame.y
}
mech.lastFieldPosition = {
x: simulation.mouseInGame.x,
y: simulation.mouseInGame.y
}
mech.fieldOn = false;
mech.fieldRadius = 0;
mech.drop();
mech.hold = function() {
if (input.field) {
if (mech.fieldCDcycle < mech.cycle) {
const scale = 25
const bounds = {
min: {
x: mech.fieldPosition.x - scale,
y: mech.fieldPosition.y - scale
},
max: {
x: mech.fieldPosition.x + scale,
y: mech.fieldPosition.y + scale
}
}
const isInMap = Matter.Query.region(map, bounds).length
// const isInMap = Matter.Query.point(map, mech.fieldPosition).length
if (!mech.fieldOn) { // if field was off, and it starting up, teleport to new mouse location
mech.fieldOn = true;
mech.fieldPosition = { //smooth the mouse position
x: simulation.mouseInGame.x,
y: simulation.mouseInGame.y
}
mech.lastFieldPosition = { //used to find velocity of field changes
x: mech.fieldPosition.x,
y: mech.fieldPosition.y
}
} else { //when field is on it smoothly moves towards the mouse
mech.lastFieldPosition = { //used to find velocity of field changes
x: mech.fieldPosition.x,
y: mech.fieldPosition.y
}
const smooth = isInMap ? 0.985 : 0.96;
mech.fieldPosition = { //smooth the mouse position
x: mech.fieldPosition.x * smooth + simulation.mouseInGame.x * (1 - smooth),
y: mech.fieldPosition.y * smooth + simulation.mouseInGame.y * (1 - smooth),
}
}
//grab power ups into the field
for (let i = 0, len = powerUp.length; i < len; ++i) {
const dxP = mech.fieldPosition.x - powerUp[i].position.x;
const dyP = mech.fieldPosition.y - powerUp[i].position.y;
const dist2 = dxP * dxP + dyP * dyP;
// float towards field if looking at and in range or if very close to player
if (dist2 < mech.fieldRadius * mech.fieldRadius && (mech.lookingAt(powerUp[i]) || dist2 < 16000) && !(mech.health === mech.maxHealth && powerUp[i].name === "heal")) {
powerUp[i].force.x += 7 * (dxP / dist2) * powerUp[i].mass;
powerUp[i].force.y += 7 * (dyP / dist2) * powerUp[i].mass - powerUp[i].mass * simulation.g; //negate gravity
//extra friction
Matter.Body.setVelocity(powerUp[i], {
x: powerUp[i].velocity.x * 0.11,
y: powerUp[i].velocity.y * 0.11
});
if (dist2 < 5000 && !simulation.isChoosing) { //use power up if it is close enough
powerUps.onPickUp(powerUp[i].position);
powerUp[i].effect();
Matter.World.remove(engine.world, powerUp[i]);
powerUp.splice(i, 1);
// mech.fieldRadius += 50
break; //because the array order is messed up after splice
}
}
}
//grab power ups normally too
mech.grabPowerUp();
if (mech.energy > 0.01) {
//find mouse velocity
const diff = Vector.sub(mech.fieldPosition, mech.lastFieldPosition)
const speed = Vector.magnitude(diff)
const velocity = Vector.mult(Vector.normalise(diff), Math.min(speed, 45)) //limit velocity
let radius, radiusSmooth
if (Matter.Query.ray(map, mech.fieldPosition, player.position).length) { //is there something block the player's view of the field
radius = 0
radiusSmooth = Math.max(0, isInMap ? 0.96 - 0.02 * speed : 0.995); //0.99
} else {
radius = Math.max(50, 250 - 2 * speed)
radiusSmooth = 0.97
}
mech.fieldRadius = mech.fieldRadius * radiusSmooth + radius * (1 - radiusSmooth)
for (let i = 0, len = body.length; i < len; ++i) {
if (Vector.magnitude(Vector.sub(body[i].position, mech.fieldPosition)) < mech.fieldRadius && !body[i].isNotHoldable) {
const DRAIN = speed * body[i].mass * 0.000013
if (mech.energy > DRAIN) {
mech.energy -= DRAIN;
Matter.Body.setVelocity(body[i], velocity); //give block mouse velocity
Matter.Body.setAngularVelocity(body[i], body[i].angularVelocity * 0.8)
// body[i].force.y -= body[i].mass * simulation.g; //remove gravity effects
//blocks drift towards center of pilot wave
const sub = Vector.sub(mech.fieldPosition, body[i].position)
const unit = Vector.mult(Vector.normalise(sub), 0.00005 * Vector.magnitude(sub))
body[i].force.x += unit.x
body[i].force.y += unit.y - body[i].mass * simulation.g //remove gravity effects
} else {
mech.fieldCDcycle = mech.cycle + 120;
mech.fieldOn = false
mech.fieldRadius = 0
break
}
}
}
if (tech.isFreezeMobs) {
for (let i = 0, len = mob.length; i < len; ++i) {
if (Vector.magnitude(Vector.sub(mob[i].position, mech.fieldPosition)) < mech.fieldRadius) {
mobs.statusSlow(mob[i], 120)
}
}
}
ctx.beginPath();
const rotate = mech.cycle * 0.008;
mech.fieldPhase += 0.2 // - 0.5 * Math.sqrt(Math.min(mech.energy, 1));
const off1 = 1 + 0.06 * Math.sin(mech.fieldPhase);
const off2 = 1 - 0.06 * Math.sin(mech.fieldPhase);
ctx.beginPath();
ctx.ellipse(mech.fieldPosition.x, mech.fieldPosition.y, 1.2 * mech.fieldRadius * off1, 1.2 * mech.fieldRadius * off2, rotate, 0, 2 * Math.PI);
ctx.globalCompositeOperation = "exclusion"; //"exclusion" "difference";
ctx.fillStyle = "#fff"; //"#eef";
ctx.fill();
ctx.globalCompositeOperation = "source-over";
ctx.beginPath();
ctx.ellipse(mech.fieldPosition.x, mech.fieldPosition.y, 1.2 * mech.fieldRadius * off1, 1.2 * mech.fieldRadius * off2, rotate, 0, 2 * Math.PI * mech.energy / mech.maxEnergy);
ctx.strokeStyle = "#000";
ctx.lineWidth = 4;
ctx.stroke();
} else {
mech.fieldCDcycle = mech.cycle + 120;
mech.fieldOn = false
mech.fieldRadius = 0
}
} else {
mech.grabPowerUp();
}
} else {
mech.fieldOn = false
mech.fieldRadius = 0
}
mech.drawFieldMeter()
}
}
},
{
name: "wormhole",
description: "use energy to tunnel through a wormhole
wormholes attract blocks and power ups
10% chance to duplicate spawned power ups", //
bullets may also traverse wormholes
effect: function() {
mech.drop();
mech.duplicateChance = 0.1
simulation.draw.powerUp = simulation.draw.powerUpBonus //change power up draw
// if (tech.isRewindGun) {
// mech.hold = this.rewind
// } else {
mech.hold = this.teleport
// }
},
rewindCount: 0,
// rewind: function() {
// if (input.down) {
// if (input.field && mech.fieldCDcycle < mech.cycle) { //not hold but field button is pressed
// const DRAIN = 0.01
// if (this.rewindCount < 289 && mech.energy > DRAIN) {
// mech.energy -= DRAIN
// if (this.rewindCount === 0) {
// const shortPause = function() {
// if (mech.defaultFPSCycle < mech.cycle) { //back to default values
// simulation.fpsCap = simulation.fpsCapDefault
// simulation.fpsInterval = 1000 / simulation.fpsCap;
// // document.getElementById("dmg").style.transition = "opacity 1s";
// // document.getElementById("dmg").style.opacity = "0";
// } else {
// requestAnimationFrame(shortPause);
// }
// };
// if (mech.defaultFPSCycle < mech.cycle) requestAnimationFrame(shortPause);
// simulation.fpsCap = 4 //1 is longest pause, 4 is standard
// simulation.fpsInterval = 1000 / simulation.fpsCap;
// mech.defaultFPSCycle = mech.cycle
// }
// this.rewindCount += 10;
// simulation.wipe = function() { //set wipe to have trails
// // ctx.fillStyle = "rgba(255,255,255,0)";
// ctx.fillStyle = `rgba(221,221,221,${0.004})`;
// ctx.fillRect(0, 0, canvas.width, canvas.height);
// }
// let history = mech.history[(mech.cycle - this.rewindCount) % 300]
// Matter.Body.setPosition(player, history.position);
// Matter.Body.setVelocity(player, { x: history.velocity.x, y: history.velocity.y });
// if (history.health > mech.health) {
// mech.health = history.health
// mech.displayHealth();
// }
// //grab power ups
// for (let i = 0, len = powerUp.length; i < len; ++i) {
// const dxP = player.position.x - powerUp[i].position.x;
// const dyP = player.position.y - powerUp[i].position.y;
// if (dxP * dxP + dyP * dyP < 50000 && !simulation.isChoosing && !(mech.health === mech.maxHealth && powerUp[i].name === "heal")) {
// powerUps.onPickUp(player.position);
// powerUp[i].effect();
// Matter.World.remove(engine.world, powerUp[i]);
// powerUp.splice(i, 1);
// const shortPause = function() {
// if (mech.defaultFPSCycle < mech.cycle) { //back to default values
// simulation.fpsCap = simulation.fpsCapDefault
// simulation.fpsInterval = 1000 / simulation.fpsCap;
// // document.getElementById("dmg").style.transition = "opacity 1s";
// // document.getElementById("dmg").style.opacity = "0";
// } else {
// requestAnimationFrame(shortPause);
// }
// };
// if (mech.defaultFPSCycle < mech.cycle) requestAnimationFrame(shortPause);
// simulation.fpsCap = 3 //1 is longest pause, 4 is standard
// simulation.fpsInterval = 1000 / simulation.fpsCap;
// mech.defaultFPSCycle = mech.cycle
// break; //because the array order is messed up after splice
// }
// }
// mech.immuneCycle = mech.cycle + 5; //player is immune to collision damage for 30 cycles
// } else {
// mech.fieldCDcycle = mech.cycle + 30;
// // mech.resetHistory();
// }
// } else {
// if (this.rewindCount !== 0) {
// mech.fieldCDcycle = mech.cycle + 30;
// mech.resetHistory();
// this.rewindCount = 0;
// simulation.wipe = function() { //set wipe to normal
// ctx.clearRect(0, 0, canvas.width, canvas.height);
// }
// }
// 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()
// },
teleport: function() {
// mech.hole = { //this is reset with each new field, but I'm leaving it here for reference
// isOn: false,
// isReady: true,
// pos1: {x: 0,y: 0},
// pos2: {x: 0,y: 0},
// angle: 0,
// unit:{x:0,y:0},
// }
if (mech.hole.isOn) {
// draw holes
mech.fieldRange = 0.97 * mech.fieldRange + 0.03 * (50 + 10 * Math.sin(simulation.cycle * 0.025))
const semiMajorAxis = mech.fieldRange + 30
const edge1a = Vector.add(Vector.mult(mech.hole.unit, semiMajorAxis), mech.hole.pos1)
const edge1b = Vector.add(Vector.mult(mech.hole.unit, -semiMajorAxis), mech.hole.pos1)
const edge2a = Vector.add(Vector.mult(mech.hole.unit, semiMajorAxis), mech.hole.pos2)
const edge2b = Vector.add(Vector.mult(mech.hole.unit, -semiMajorAxis), mech.hole.pos2)
ctx.beginPath();
ctx.moveTo(edge1a.x, edge1a.y)
ctx.bezierCurveTo(mech.hole.pos1.x, mech.hole.pos1.y, mech.hole.pos2.x, mech.hole.pos2.y, edge2a.x, edge2a.y);
ctx.lineTo(edge2b.x, edge2b.y)
ctx.bezierCurveTo(mech.hole.pos2.x, mech.hole.pos2.y, mech.hole.pos1.x, mech.hole.pos1.y, edge1b.x, edge1b.y);
ctx.fillStyle = `rgba(255,255,255,${200 / mech.fieldRange / mech.fieldRange})` //"rgba(0,0,0,0.1)"
ctx.fill();
ctx.beginPath();
ctx.ellipse(mech.hole.pos1.x, mech.hole.pos1.y, mech.fieldRange, semiMajorAxis, mech.hole.angle, 0, 2 * Math.PI)
ctx.ellipse(mech.hole.pos2.x, mech.hole.pos2.y, mech.fieldRange, semiMajorAxis, mech.hole.angle, 0, 2 * Math.PI)
ctx.fillStyle = `rgba(255,255,255,${32 / mech.fieldRange})`
ctx.fill();
//suck power ups
for (let i = 0, len = powerUp.length; i < len; ++i) {
//which hole is closer
const dxP1 = mech.hole.pos1.x - powerUp[i].position.x;
const dyP1 = mech.hole.pos1.y - powerUp[i].position.y;
const dxP2 = mech.hole.pos2.x - powerUp[i].position.x;
const dyP2 = mech.hole.pos2.y - powerUp[i].position.y;
let dxP, dyP, dist2
if (dxP1 * dxP1 + dyP1 * dyP1 < dxP2 * dxP2 + dyP2 * dyP2) {
dxP = dxP1
dyP = dyP1
} else {
dxP = dxP2
dyP = dyP2
}
dist2 = dxP * dxP + dyP * dyP;
if (dist2 < 600000 && !(mech.health === mech.maxHealth && powerUp[i].name === "heal")) {
powerUp[i].force.x += 4 * (dxP / dist2) * powerUp[i].mass; // float towards hole
powerUp[i].force.y += 4 * (dyP / dist2) * powerUp[i].mass - powerUp[i].mass * simulation.g; //negate gravity
Matter.Body.setVelocity(powerUp[i], { //extra friction
x: powerUp[i].velocity.x * 0.05,
y: powerUp[i].velocity.y * 0.05
});
if (dist2 < 1000 && !simulation.isChoosing) { //use power up if it is close enough
mech.fieldRange *= 0.8
powerUps.onPickUp(powerUp[i].position);
powerUp[i].effect();
Matter.World.remove(engine.world, powerUp[i]);
powerUp.splice(i, 1);
break; //because the array order is messed up after splice
}
}
}
//suck and shrink blocks
const suckRange = 500
const shrinkRange = 100
const shrinkScale = 0.97;
const slowScale = 0.9
for (let i = 0, len = body.length; i < len; i++) {
if (!body[i].isNotHoldable) {
const dist1 = Vector.magnitude(Vector.sub(mech.hole.pos1, body[i].position))
const dist2 = Vector.magnitude(Vector.sub(mech.hole.pos2, body[i].position))
if (dist1 < dist2) {
if (dist1 < suckRange) {
const pull = Vector.mult(Vector.normalise(Vector.sub(mech.hole.pos1, body[i].position)), 1)
const slow = Vector.mult(body[i].velocity, slowScale)
Matter.Body.setVelocity(body[i], Vector.add(slow, pull));
//shrink
if (Vector.magnitude(Vector.sub(mech.hole.pos1, body[i].position)) < shrinkRange) {
Matter.Body.scale(body[i], shrinkScale, shrinkScale);
if (body[i].mass < 0.05) {
Matter.World.remove(engine.world, body[i]);
body.splice(i, 1);
mech.fieldRange *= 0.8
if (tech.isWormholeEnergy) mech.energy += 0.5
if (tech.isWormSpores) { //pandimensionalspermia
for (let i = 0, len = Math.ceil(3 * Math.random()); i < len; i++) {
b.spore(Vector.add(mech.hole.pos2, Vector.rotate({
x: mech.fieldRange * 0.4,
y: 0
}, 2 * Math.PI * Math.random())))
Matter.Body.setVelocity(bullet[bullet.length - 1], Vector.mult(Vector.rotate(mech.hole.unit, Math.PI / 2), -15));
}
}
break
}
}
}
} else if (dist2 < suckRange) {
const pull = Vector.mult(Vector.normalise(Vector.sub(mech.hole.pos2, body[i].position)), 1)
const slow = Vector.mult(body[i].velocity, slowScale)
Matter.Body.setVelocity(body[i], Vector.add(slow, pull));
//shrink
if (Vector.magnitude(Vector.sub(mech.hole.pos2, body[i].position)) < shrinkRange) {
Matter.Body.scale(body[i], shrinkScale, shrinkScale);
if (body[i].mass < 0.05) {
Matter.World.remove(engine.world, body[i]);
body.splice(i, 1);
mech.fieldRange *= 0.8
// if (tech.isWormholeEnergy && mech.energy < mech.maxEnergy * 2) mech.energy = mech.maxEnergy * 2
if (tech.isWormholeEnergy) mech.energy += 0.5
if (tech.isWormSpores) { //pandimensionalspermia
for (let i = 0, len = Math.ceil(3 * Math.random()); i < len; i++) {
b.spore(Vector.add(mech.hole.pos1, Vector.rotate({
x: mech.fieldRange * 0.4,
y: 0
}, 2 * Math.PI * Math.random())))
Matter.Body.setVelocity(bullet[bullet.length - 1], Vector.mult(Vector.rotate(mech.hole.unit, Math.PI / 2), 15));
}
}
break
}
}
}
}
}
if (tech.isWormBullets) {
//teleport bullets
for (let i = 0, len = bullet.length; i < len; ++i) { //teleport bullets from hole1 to hole2
if (!bullet[i].botType && !bullet[i].isInHole) { //don't teleport bots
if (Vector.magnitude(Vector.sub(mech.hole.pos1, bullet[i].position)) < mech.fieldRange) { //find if bullet is touching hole1
Matter.Body.setPosition(bullet[i], Vector.add(mech.hole.pos2, Vector.sub(mech.hole.pos1, bullet[i].position)));
mech.fieldRange += 5
bullet[i].isInHole = true
} else if (Vector.magnitude(Vector.sub(mech.hole.pos2, bullet[i].position)) < mech.fieldRange) { //find if bullet is touching hole1
Matter.Body.setPosition(bullet[i], Vector.add(mech.hole.pos1, Vector.sub(mech.hole.pos2, bullet[i].position)));
mech.fieldRange += 5
bullet[i].isInHole = true
}
}
}
// mobs get pushed away
for (let i = 0, len = mob.length; i < len; i++) {
if (Vector.magnitude(Vector.sub(mech.hole.pos1, mob[i].position)) < 200) {
const pull = Vector.mult(Vector.normalise(Vector.sub(mech.hole.pos1, mob[i].position)), -0.07)
Matter.Body.setVelocity(mob[i], Vector.add(mob[i].velocity, pull));
}
if (Vector.magnitude(Vector.sub(mech.hole.pos2, mob[i].position)) < 200) {
const pull = Vector.mult(Vector.normalise(Vector.sub(mech.hole.pos2, mob[i].position)), -0.07)
Matter.Body.setVelocity(mob[i], Vector.add(mob[i].velocity, pull));
}
}
}
}
if (input.field && mech.fieldCDcycle < mech.cycle) { //not hold but field button is pressed
const justPastMouse = Vector.add(Vector.mult(Vector.normalise(Vector.sub(simulation.mouseInGame, mech.pos)), 50), simulation.mouseInGame)
const scale = 60
// console.log(Matter.Query.region(map, bounds))
if (mech.hole.isReady &&
(
Matter.Query.region(map, {
min: {
x: simulation.mouseInGame.x - scale,
y: simulation.mouseInGame.y - scale
},
max: {
x: simulation.mouseInGame.x + scale,
y: simulation.mouseInGame.y + scale
}
}).length === 0 &&
Matter.Query.ray(map, mech.pos, justPastMouse).length === 0
// Matter.Query.ray(map, mech.pos, simulation.mouseInGame).length === 0 &&
// Matter.Query.ray(map, player.position, simulation.mouseInGame).length === 0 &&
// Matter.Query.ray(map, player.position, justPastMouse).length === 0
)
) {
const sub = Vector.sub(simulation.mouseInGame, mech.pos)
const mag = Vector.magnitude(sub)
const drain = 0.03 + 0.005 * Math.sqrt(mag)
if (mech.energy > drain && mag > 300) {
mech.energy -= drain
mech.hole.isReady = false;
mech.fieldRange = 0
Matter.Body.setPosition(player, simulation.mouseInGame);
mech.buttonCD_jump = 0 //this might fix a bug with jumping
const velocity = Vector.mult(Vector.normalise(sub), 18)
Matter.Body.setVelocity(player, {
x: velocity.x,
y: velocity.y - 4 //an extra vertical kick so the player hangs in place longer
});
mech.immuneCycle = mech.cycle + 15; //player is immune to collision damage
// move bots to follow player
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType) {
Matter.Body.setPosition(bullet[i], Vector.add(player.position, {
x: 250 * (Math.random() - 0.5),
y: 250 * (Math.random() - 0.5)
}));
Matter.Body.setVelocity(bullet[i], {
x: 0,
y: 0
});
}
}
//set holes
mech.hole.isOn = true;
mech.hole.pos1.x = mech.pos.x
mech.hole.pos1.y = mech.pos.y
mech.hole.pos2.x = player.position.x
mech.hole.pos2.y = player.position.y
mech.hole.angle = Math.atan2(sub.y, sub.x)
mech.hole.unit = Vector.perp(Vector.normalise(sub))
if (tech.isWormholeDamage) {
who = Matter.Query.ray(mob, mech.pos, simulation.mouseInGame, 80)
for (let i = 0; i < who.length; i++) {
if (who[i].body.alive) {
mobs.statusDoT(who[i].body, 0.6, 420)
mobs.statusStun(who[i].body, 240)
}
}
}
} else {
mech.grabPowerUp();
}
} else {
mech.grabPowerUp();
}
} 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)
mech.hole.isReady = true;
}
mech.drawFieldMeter()
},
},
],
};