mod - neutron bomb is a grenade mod, it's damage is 33% higher direct hits also apply radiation damage over time missiles aim better, accelerate quicker, push blocks out of the way better crouch fire mode rapidly launches missiles vertically
2429 lines
127 KiB
JavaScript
2429 lines
127 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,
|
|
lastKillCycle: 0,
|
|
lastHarmCycle: 0,
|
|
width: 50,
|
|
radius: 30,
|
|
fillColor: "#fff",
|
|
fillColorDark: "#ccc",
|
|
color: {
|
|
hue: 0,
|
|
sat: 0,
|
|
light: 100,
|
|
},
|
|
setFillColors() {
|
|
// console.log(mech.color)
|
|
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 * mod.squirrelFx * mod.fastTime;
|
|
mech.jumpForce = 0.42 * mod.squirrelJump * mod.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
|
|
},
|
|
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,
|
|
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,
|
|
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 (mod.isImmortal) { //if player has the immortality buff, spawn on the same level with randomized stats
|
|
|
|
//count mods
|
|
let totalMods = 0;
|
|
for (let i = 0; i < mod.mods.length; i++) {
|
|
if (!mod.mods[i].isNonRefundable) totalMods += mod.mods[i].count
|
|
}
|
|
if (mod.isDeterminism) totalMods -= 3 //remove the bonus mods
|
|
if (mod.isSuperDeterminism) totalMods -= 2 //remove the bonus mods
|
|
totalMods = totalMods * 1.15 + 1 // a few extra to make it stronger
|
|
const totalGuns = b.inventory.length //count guns
|
|
|
|
function randomizeMods() {
|
|
for (let i = 0; i < totalMods; i++) {
|
|
//find what mods I don't have
|
|
let options = [];
|
|
for (let i = 0, len = mod.mods.length; i < len; i++) {
|
|
if (mod.mods[i].count < mod.mods[i].maxCount &&
|
|
!mod.mods[i].isNonRefundable &&
|
|
mod.mods[i].name !== "quantum immortality" &&
|
|
mod.mods[i].name !== "Born rule" &&
|
|
mod.mods[i].allowed()
|
|
) options.push(i);
|
|
}
|
|
//add a new mod
|
|
if (options.length > 0) {
|
|
const choose = Math.floor(Math.random() * options.length)
|
|
let newMod = options[choose]
|
|
mod.giveMod(newMod)
|
|
options.splice(choose, 1);
|
|
}
|
|
}
|
|
game.updateModHUD();
|
|
}
|
|
|
|
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())))
|
|
}
|
|
}
|
|
game.makeGunHUD(); //update gun HUD
|
|
}
|
|
|
|
|
|
function pixelWindows() {
|
|
|
|
//pixel graphics
|
|
let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); //copy current canvas pixel data
|
|
let data = imgData.data;
|
|
//change random pixels
|
|
//strange draw offset
|
|
// const off = canvas.height * canvas.width * 4 / 16
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
index = i % canvas.width
|
|
data[index + 0] = data[index + 0]; // red
|
|
data[index + 1] = data[index + 1]; // red
|
|
data[index + 2] = data[index + 2]; // red
|
|
data[index + 3] = data[index + 3]; // red
|
|
}
|
|
ctx.putImageData(imgData, 0, 0); //draw new pixel data to canvas
|
|
|
|
}
|
|
|
|
|
|
game.wipe = function() { //set wipe to have trails
|
|
ctx.fillStyle = "rgba(255,255,255,0)";
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
// pixelWindows()
|
|
}
|
|
|
|
function randomizeEverything() {
|
|
spawn.setSpawnList(); //new mob types
|
|
game.clearNow = true; //triggers a map reset
|
|
|
|
mod.setupAllMods(); //remove all mods
|
|
for (let i = 0; i < bullet.length; ++i) Matter.World.remove(engine.world, bullet[i]);
|
|
bullet = []; //remove all bullets
|
|
randomizeHealth()
|
|
randomizeField()
|
|
randomizeGuns()
|
|
randomizeMods()
|
|
}
|
|
|
|
randomizeEverything()
|
|
const swapPeriod = 1000
|
|
for (let i = 0, len = 5; i < len; i++) {
|
|
setTimeout(function() {
|
|
randomizeEverything()
|
|
game.replaceTextLog = true;
|
|
game.makeTextLog(`probability amplitude will synchronize in ${len-i-1} seconds`, swapPeriod);
|
|
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);
|
|
// pixelWindows()
|
|
}
|
|
}, (i + 1) * swapPeriod);
|
|
}
|
|
|
|
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);
|
|
}, 6 * swapPeriod);
|
|
|
|
} 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, //set in game.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 (!mod.isEnergyHealth) {
|
|
mech.health += heal * game.healScale;
|
|
if (mech.health > mech.maxHealth) mech.health = mech.maxHealth;
|
|
mech.displayHealth();
|
|
}
|
|
},
|
|
baseHealth: 1,
|
|
setMaxHealth() {
|
|
mech.maxHealth = mech.baseHealth + mod.bonusHealth + mod.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 (mod.isSlowFPS) dmg *= 0.85
|
|
if (mod.isPiezo) dmg *= 0.85
|
|
if (mod.isHarmReduce && mech.fieldUpgrades[mech.fieldMode].name === "negative mass field" && mech.isFieldActive) dmg *= 0.6
|
|
if (mod.isBotArmor) dmg *= 0.95 ** mod.totalBots()
|
|
if (mod.isHarmArmor && mech.lastHarmCycle + 600 > mech.cycle) dmg *= 0.5;
|
|
if (mod.isNoFireDefense && mech.cycle > mech.fireCDcycle + 120) dmg *= 0.6
|
|
if (mod.energyRegen === 0) dmg *= 0.5 //0.22 + 0.78 * mech.energy //77% damage reduction at zero energy
|
|
if (mod.isTurret && mech.crouch) dmg *= 0.5;
|
|
if (mod.isEntanglement && b.inventory[0] === b.activeGun) {
|
|
for (let i = 0, len = b.inventory.length; i < len; i++) {
|
|
dmg *= 0.85 // 1 - 0.15
|
|
}
|
|
}
|
|
return dmg
|
|
},
|
|
damage(dmg) {
|
|
mech.lastHarmCycle = mech.cycle
|
|
if (mod.isDroneOnDamage) { //chance to build a drone on damage from mod
|
|
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 (mod.isEnergyHealth) {
|
|
mech.energy -= dmg;
|
|
if (mech.energy < 0 || isNaN(mech.energy)) { //taking deadly damage
|
|
if (mod.isDeathAvoid && powerUps.reroll.rerolls && !mod.isDeathAvoidedThisLevel) {
|
|
mod.isDeathAvoidedThisLevel = true
|
|
powerUps.reroll.changeRerolls(-1)
|
|
game.makeTextLog(`<span style='font-size:115%;'> <strong>death</strong> avoided<br><strong>${powerUps.reroll.rerolls}</strong> <strong class='color-r'>rerolls</strong> left</span>`, 420)
|
|
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
|
|
game.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() {
|
|
game.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 (mod.isDeathAvoid && powerUps.reroll.rerolls > 0 && !mod.isDeathAvoidedThisLevel) { //&& Math.random() < 0.5
|
|
mod.isDeathAvoidedThisLevel = true
|
|
mech.health = 0.05
|
|
powerUps.reroll.changeRerolls(-1)
|
|
game.makeTextLog(`<span style='font-size:115%;'> <strong>death</strong> avoided<br><strong>${powerUps.reroll.rerolls}</strong> <strong class='color-r'>rerolls</strong> left</span>`, 420)
|
|
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
|
|
// game.makeTextLog("<span style='font-size:115%;'> <strong>death</strong> avoided<br><strong>1</strong> <strong class='color-r'>reroll</strong> consumed</span>", 420)
|
|
|
|
game.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() {
|
|
game.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
|
|
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);
|
|
}
|
|
};
|
|
|
|
if (mech.defaultFPSCycle < mech.cycle) requestAnimationFrame(normalFPS);
|
|
if (mod.isSlowFPS) { // slow game
|
|
game.fpsCap = 30 //new fps
|
|
game.fpsInterval = 1000 / game.fpsCap;
|
|
//how long to wait to return to normal fps
|
|
mech.defaultFPSCycle = mech.cycle + 20 + Math.min(90, Math.floor(200 * dmg))
|
|
if (mod.isHarmFreeze) { //freeze all mobs
|
|
for (let i = 0, len = mob.length; i < len; i++) {
|
|
mobs.statusSlow(mob[i], 240)
|
|
}
|
|
}
|
|
} else {
|
|
if (dmg > 0.05) { // freeze game for high damage hits
|
|
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
|
|
}
|
|
// 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 (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.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 = '#9cf' //'#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 mod
|
|
holdingTarget: null,
|
|
timeSkipLastCycle: 0,
|
|
// these values are set on reset by setHoldDefaults()
|
|
grabPowerUpRange2: 0,
|
|
isFieldActive: false,
|
|
fieldRange: 155,
|
|
fieldShieldingScale: 1,
|
|
fieldDamage: 1,
|
|
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 = mod.energyRegen; //0.001
|
|
mech.fieldMeterColor = "#0cf"
|
|
mech.fieldShieldingScale = 1;
|
|
mech.fieldBlockCD = 10;
|
|
mech.fieldHarmReduction = 1;
|
|
mech.fieldDamage = 1
|
|
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 = 1 + mod.bonusEnergy + mod.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 * mod.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 / mod.throwChargeRate;
|
|
mech.throwCharge += 0.5 * mod.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(game.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 * 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
|
|
});
|
|
if (dist2 < 5000 && !game.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 (mod.blockDmg) {
|
|
who.damage(mod.blockDmg * b.dmgScale)
|
|
//draw electricity
|
|
const step = 40
|
|
ctx.beginPath();
|
|
for (let i = 0, len = 2.5 * mod.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);
|
|
}
|
|
// 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 (mod.isStunField && mech.fieldUpgrades[mech.fieldMode].name === "perfect diamagnetism") mobs.statusStun(who, mod.isStunField)
|
|
// mobs.statusSlow(who, mod.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)
|
|
}
|
|
}
|
|
}
|
|
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 <strong class='color-f'>energy</strong> to <strong>block</strong> mobs<br><strong>throw</strong> blocks to <strong class='color-d'>damage</strong> mobs<br><strong>pick up</strong> power ups",
|
|
effect: () => {
|
|
game.replaceTextLog = true; //allow text over write
|
|
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: "<strong>3</strong> oscillating <strong>shields</strong> are permanently active<br><strong>blocking</strong> drains <strong class='color-f'>energy</strong><br><strong>blocking</strong> has no <strong>cool down</strong>",
|
|
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 <strong class='color-f'>energy</strong> when <strong>blocking</strong><br>no <strong>recoil</strong> when <strong>blocking</strong>",
|
|
description: "<strong>blocking</strong> does not drain <strong class='color-f'>energy</strong><br><strong>blocking</strong> has no <strong>cool down</strong> and less <strong>recoil</strong><br><strong>attract</strong> power ups from <strong>far away</strong>",
|
|
effect: () => {
|
|
mech.fieldShieldingScale = 0;
|
|
mech.grabPowerUpRange2 = 10000000
|
|
// mech.holdingMassScale = 0.03; //can hold heavier blocks with lower cost to jumping
|
|
// mech.fieldMeterColor = "#0af"
|
|
// mech.fieldArc = 0.3; //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob)
|
|
// mech.calculateFieldThreshold();
|
|
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 (mod.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 <strong class='color-f'>energy</strong> to <strong>block</strong> mobs<br>excess <strong class='color-f'>energy</strong> used to build <strong>drones</strong><br><strong>double</strong> your default <strong class='color-f'>energy</strong> regeneration",
|
|
effect: () => {
|
|
mech.hold = function() {
|
|
if (mech.energy > mech.maxEnergy - 0.02 && mech.fieldCDcycle < mech.cycle) {
|
|
if (mod.isSporeField) {
|
|
// mech.fieldCDcycle = mech.cycle + 10; // set cool down to prevent +energy from making huge numbers of drones
|
|
const len = Math.floor(6 + 5 * Math.random())
|
|
mech.energy -= len * 0.09;
|
|
for (let i = 0; i < len; i++) {
|
|
b.spore(mech.pos)
|
|
}
|
|
} else if (mod.isMissileField) {
|
|
// mech.fieldCDcycle = mech.cycle + 10; // set cool down to prevent +energy from making huge numbers of drones
|
|
mech.energy -= 0.45;
|
|
b.missile({ x: mech.pos.x, y: mech.pos.y - 40 }, -Math.PI / 2, 0, 1, mod.recursiveMissiles)
|
|
} else if (mod.isIceField) {
|
|
// mech.fieldCDcycle = mech.cycle + 17; // set cool down to prevent +energy from making huge numbers of drones
|
|
mech.energy -= 0.04;
|
|
b.iceIX(1)
|
|
} else {
|
|
// mech.fieldCDcycle = mech.cycle + 10; // set cool down to prevent +energy from making huge numbers of drones
|
|
mech.energy -= 0.33;
|
|
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 <strong class='color-f'>energy</strong> to nullify <strong style='letter-spacing: 7px;'>gravity</strong><br>reduce <strong class='color-harm'>harm</strong> by <strong>40%</strong><br><strong>blocks</strong> held by the field have a lower <strong>mass</strong>",
|
|
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.6;
|
|
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 mod.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 * (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?
|
|
|
|
if (input.down) { //down
|
|
player.force.y -= 0.5 * player.mass * game.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 * game.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 * game.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 (mod.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 <strong class='color-f'>energy</strong> to emit short range <strong class='color-plasma'>plasma</strong><br><strong class='color-d'>damages</strong> and <strong>pushes</strong> mobs away",
|
|
effect() {
|
|
mech.fieldMeterColor = "#f0f"
|
|
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();
|
|
const DRAIN = 0.0012
|
|
if (mech.energy > DRAIN) {
|
|
mech.energy -= DRAIN;
|
|
if (mech.energy < 0) {
|
|
mech.fieldCDcycle = mech.cycle + 120;
|
|
mech.energy = 0;
|
|
}
|
|
//calculate laser collision
|
|
let best;
|
|
let range = mod.isPlasmaRange * (120 + (mech.crouch ? 400 : 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(mech.angle),
|
|
y: mech.pos.y + 20 * Math.sin(mech.angle)
|
|
},
|
|
{
|
|
x: mech.pos.x + range * Math.cos(mech.angle),
|
|
y: mech.pos.y + range * Math.sin(mech.angle)
|
|
}
|
|
];
|
|
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.8 * 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.min(5, best.who.mass))
|
|
Matter.Body.applyForce(best.who, path[1], force)
|
|
Matter.Body.setVelocity(best.who, { //friction
|
|
x: best.who.velocity.x * 0.7,
|
|
y: best.who.velocity.y * 0.7
|
|
});
|
|
//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.007 * 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 = Vector.magnitude(Vector.sub(path[0], path[1])) / 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();
|
|
}
|
|
} 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)")
|
|
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: "time dilation field",
|
|
description: "use <strong class='color-f'>energy</strong> to <strong style='letter-spacing: 1px;'>stop time</strong><br><strong>move</strong> and <strong>fire</strong> 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.0011
|
|
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;
|
|
}
|
|
}
|
|
|
|
game.cycle--; //pause all functions that depend on game cycle increasing
|
|
if (mod.isTimeSkip) {
|
|
mech.immuneCycle = mech.cycle + 10;
|
|
game.isTimeSkipping = true;
|
|
mech.cycle++;
|
|
game.gravity();
|
|
Engine.update(engine, game.delta);
|
|
// level.checkZones();
|
|
// level.checkQuery();
|
|
mech.move();
|
|
game.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();
|
|
game.isTimeSkipping = false;
|
|
}
|
|
// game.cycle--; //pause all functions that depend on game cycle increasing
|
|
// if (mod.isTimeSkip && !game.isTimeSkipping) { //speed up the rate of time
|
|
// game.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: "<strong class='color-cloaked'>cloak</strong> after not using your gun or field<br>while <strong class='color-cloaked'>cloaked</strong> mobs can't see you<br>increase <strong class='color-d'>damage</strong> by <strong>66%</strong>",
|
|
effect: () => {
|
|
mech.fieldFire = true;
|
|
mech.fieldMeterColor = "#fff";
|
|
mech.fieldPhase = 0;
|
|
mech.isCloak = false
|
|
mech.fieldDamage = 1.66
|
|
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) mech.fireCDcycle = mech.cycle
|
|
if (mech.fireCDcycle + 50 < mech.cycle) {
|
|
if (!mech.isCloak) {
|
|
mech.isCloak = true //enter cloak
|
|
if (mod.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 (mod.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 (mod.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
|
|
game.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 (mod.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 <strong class='color-f'>energy</strong> to become <strong>intangible</strong><br><strong>firing</strong> and touching <strong>shields</strong> <strong>drains</strong> <strong class='color-f'>energy</strong><br>unable to <strong>see</strong> and be <strong>seen</strong> 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) {
|
|
// // game.draw.bodyFill = "transparent"
|
|
// // game.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 (mod.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 <strong class='color-f'>energy</strong> to push <strong>blocks</strong> with your mouse<br>field <strong>radius</strong> decreases out of <strong>line of sight</strong>",
|
|
effect: () => {
|
|
game.replaceTextLog = true; //allow text over write
|
|
mech.fieldPhase = 0;
|
|
mech.fieldPosition = {
|
|
x: game.mouseInGame.x,
|
|
y: game.mouseInGame.y
|
|
}
|
|
mech.lastFieldPosition = {
|
|
x: game.mouseInGame.x,
|
|
y: game.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: game.mouseInGame.x,
|
|
y: game.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 + game.mouseInGame.x * (1 - smooth),
|
|
y: mech.fieldPosition.y * smooth + game.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 * 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
|
|
});
|
|
if (dist2 < 5000 && !game.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 * game.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 * game.g //remove gravity effects
|
|
} else {
|
|
mech.fieldCDcycle = mech.cycle + 120;
|
|
mech.fieldOn = false
|
|
mech.fieldRadius = 0
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mod.isPilotFreeze) {
|
|
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, mech.energy * 2 * Math.PI);
|
|
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 <strong class='color-f'>energy</strong> to <strong>tunnel</strong> through a <strong class='color-worm'>wormhole</strong><br><strong class='color-worm'>wormholes</strong> attract blocks and power ups", //<br>bullets may also traverse <strong class='color-worm'>wormholes</strong>
|
|
effect: () => {
|
|
game.replaceTextLog = true; //allow text over write
|
|
mech.drop();
|
|
// 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},
|
|
// }
|
|
mech.hold = function() {
|
|
if (mech.hole.isOn) {
|
|
// draw holes
|
|
mech.fieldRange = 0.97 * mech.fieldRange + 0.03 * (50 + 10 * Math.sin(game.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 * game.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 && !game.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 (mod.isWormholeEnergy) mech.energy += 0.5
|
|
if (mod.isWormSpores) { //pandimensionalspermia
|
|
b.spore(Vector.add(mech.hole.pos2, Vector.rotate({
|
|
x: mech.fieldRange,
|
|
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 (mod.isWormholeEnergy && mech.energy < mech.maxEnergy * 2) mech.energy = mech.maxEnergy * 2
|
|
if (mod.isWormholeEnergy) mech.energy += 0.5
|
|
if (mod.isWormSpores) { //pandimensionalspermia
|
|
b.spore(Vector.add(mech.hole.pos1, Vector.rotate({
|
|
x: mech.fieldRange,
|
|
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 (mod.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(game.mouseInGame, mech.pos)), 50), game.mouseInGame)
|
|
const scale = 60
|
|
// console.log(Matter.Query.region(map, bounds))
|
|
if (mech.hole.isReady &&
|
|
(
|
|
Matter.Query.region(map, {
|
|
min: {
|
|
x: game.mouseInGame.x - scale,
|
|
y: game.mouseInGame.y - scale
|
|
},
|
|
max: {
|
|
x: game.mouseInGame.x + scale,
|
|
y: game.mouseInGame.y + scale
|
|
}
|
|
}).length === 0 &&
|
|
Matter.Query.ray(map, mech.pos, justPastMouse).length === 0
|
|
// Matter.Query.ray(map, mech.pos, game.mouseInGame).length === 0 &&
|
|
// Matter.Query.ray(map, player.position, game.mouseInGame).length === 0 &&
|
|
// Matter.Query.ray(map, player.position, justPastMouse).length === 0
|
|
)
|
|
) {
|
|
const sub = Vector.sub(game.mouseInGame, mech.pos)
|
|
const mag = Vector.magnitude(sub)
|
|
const drain = 0.04 + 0.007 * Math.sqrt(mag)
|
|
if (mech.energy > drain && mag > 300) {
|
|
mech.energy -= drain
|
|
mech.hole.isReady = false;
|
|
mech.fieldRange = 0
|
|
Matter.Body.setPosition(player, game.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 (mod.isWormholeDamage) {
|
|
who = Matter.Query.ray(mob, mech.pos, game.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()
|
|
}
|
|
}
|
|
},
|
|
],
|
|
}; |