Files
n-gon-improved/js/engine.js
landgreen ce74f420e3 grappling hook 2nd update
grappling hook
  added coupling effect - 4% extra ammo per coupling
  doesn't destroy blocks, instead the player grabs blocks
  doesn't automatically retract after hitting power ups
  improved momentum conservation on yank and catching blocks, power ups
  removed accidental 55% defense for grapple field
  tech  (no images yet)
    autonomous defense - fire harpoons at nearby mobs
    rupture - explosion on impact with map, block, mob

negative mass field has horizontal block motion by default
fixed tech sorting by "allowed tech" in experiment mode
2023-11-25 15:26:09 -08:00

350 lines
19 KiB
JavaScript

//matter.js ***********************************************************
// module aliases
const Engine = Matter.Engine,
Events = Matter.Events,
Composites = Matter.Composites,
Composite = Matter.Composite,
Constraint = Matter.Constraint,
Vertices = Matter.Vertices,
Query = Matter.Query,
Body = Matter.Body,
Bodies = Matter.Bodies,
Vector = Matter.Vector;
// create an engine
const engine = Engine.create();
engine.world.gravity.scale = 0; //turn off gravity (it's added back in later)
// engine.velocityIterations = 100
// engine.positionIterations = 100
// engine.enableSleeping = true
// matter events
function playerOnGroundCheck(event) {
//runs on collisions events
function enter() {
m.numTouching++;
if (!m.onGround) {
m.onGround = true;
if (m.crouch) {
if (m.checkHeadClear()) {
m.undoCrouch();
} else {
m.yOffGoal = m.yOffWhen.crouch;
}
} else {
//sets a hard land where player stays in a crouch for a bit and can't jump
//crouch is forced in groundControl below
const momentum = player.velocity.y * player.mass //player mass is 5 so this triggers at 26 down velocity, unless the player is holding something
if (momentum > tech.hardLanding) {
m.doCrouch();
m.yOff = m.yOffWhen.jump;
m.hardLandCD = m.cycle + Math.min(momentum / 6.5 - 6, 40)
//falling damage
if (tech.isFallingDamage && m.immuneCycle < m.cycle && momentum > 150) {
m.damage(Math.min(Math.sqrt(momentum - 133) * 0.01, 0.25));
if (m.immuneCycle < m.cycle + m.collisionImmuneCycles) m.immuneCycle = m.cycle + m.collisionImmuneCycles; //player is immune to damage for 30 cycles
}
} else {
m.yOffGoal = m.yOffWhen.stand;
}
}
}
}
const pairs = event.pairs;
for (let i = 0, j = pairs.length; i != j; ++i) {
let pair = pairs[i];
if (pair.bodyA === jumpSensor) {
m.standingOn = pair.bodyB; //keeping track to correctly provide recoil on jump
if (m.standingOn.alive !== true) enter();
} else if (pair.bodyB === jumpSensor) {
m.standingOn = pair.bodyA; //keeping track to correctly provide recoil on jump
if (m.standingOn.alive !== true) enter();
}
}
m.numTouching = 0;
}
function playerOffGroundCheck(event) {
//runs on collisions events
const pairs = event.pairs;
for (let i = 0, j = pairs.length; i != j; ++i) {
if (pairs[i].bodyA === jumpSensor || pairs[i].bodyB === jumpSensor) {
if (m.onGround && m.numTouching === 0) {
m.onGround = false;
m.lastOnGroundCycle = m.cycle;
m.hardLandCD = 0 // disable hard landing
if (m.checkHeadClear()) {
if (m.crouch) {
m.undoCrouch();
}
m.yOffGoal = m.yOffWhen.jump;
}
}
}
}
}
function collisionChecks(event) {
const pairs = event.pairs;
for (let i = 0, j = pairs.length; i != j; i++) {
//mob + (player,bullet,body) collisions
for (let k = 0; k < mob.length; k++) {
if (mob[k].alive) {
if (pairs[i].bodyA === mob[k]) {
collideMob(pairs[i].bodyB);
break;
} else if (pairs[i].bodyB === mob[k]) {
collideMob(pairs[i].bodyA);
break;
}
function collideMob(obj) {
//player + mob collision
if (
m.immuneCycle < m.cycle &&
(obj === playerBody || obj === playerHead) &&
!mob[k].isSlowed && !mob[k].isStunned
) {
let dmg = Math.min(Math.max(0.025 * Math.sqrt(mob[k].mass), 0.05), 0.3) * simulation.dmgScale; //player damage is capped at 0.3*dmgScale of 1.0
// if (m.isCloak) dmg *= 0.5
mob[k].foundPlayer();
if (tech.isRewindAvoidDeath && (m.energy + 0.05) > Math.min(0.95, m.maxEnergy) && dmg > 0.01) { //CPT reversal runs in m.damage, but it stops the rest of the collision code here too
m.damage(dmg);
return
}
if (tech.isFlipFlop) {
if (tech.isFlipFlopOn) {
tech.isFlipFlopOn = false
if (document.getElementById("tech-flip-flop")) document.getElementById("tech-flip-flop").innerHTML = ` = <strong>OFF</strong>`
m.eyeFillColor = 'transparent'
m.damage(dmg);
} else {
tech.isFlipFlopOn = true //immune to damage this hit, lose immunity for next hit
if (document.getElementById("tech-flip-flop")) document.getElementById("tech-flip-flop").innerHTML = ` = <strong>ON</strong>`
m.eyeFillColor = m.fieldMeterColor //'#0cf'
if (!tech.isFlipFlopHarm) m.damage(dmg);
}
if (tech.isFlipFlopHealth) {
m.setMaxHealth();
for (let i = 0; i < powerUp.length; i++) {
if (powerUp[i].name === "heal") {
const oldSize = powerUp[i].size
powerUp[i].size = powerUps.heal.size() //update current heals
const scale = powerUp[i].size / oldSize
Matter.Body.scale(powerUp[i], scale, scale); //grow
}
}
}
} else {
m.damage(dmg); //normal damage
}
if (tech.isCollisionRealitySwitch && m.alive) {
m.switchWorlds()
simulation.trails()
simulation.makeTextLog(`simulation.amplitude <span class='color-symbol'>=</span> ${Math.random()}`);
}
if (tech.isPiezo) m.energy += 20.48;
if (tech.isCouplingNoHit && m.coupling > 0) {
m.couplingChange(-5)
const unit = Vector.rotate({ x: 1, y: 0 }, 6.28 * Math.random())
let where = Vector.add(m.pos, Vector.mult(unit, 17))
simulation.drawList.push({ //add dmg to draw queue
x: where.x,
y: where.y,
radius: 22,
color: 'rgba(0, 171, 238, 0.33)',
time: 8
});
where = Vector.add(m.pos, Vector.mult(unit, 60))
simulation.drawList.push({ //add dmg to draw queue
x: where.x,
y: where.y,
radius: 18,
color: 'rgba(0, 171, 238, 0.5)',
time: 16
});
where = Vector.add(m.pos, Vector.mult(unit, 100))
simulation.drawList.push({ //add dmg to draw queue
x: where.x,
y: where.y,
radius: 14,
color: 'rgba(0, 171, 238, 0.6)',
time: 24
});
where = Vector.add(m.pos, Vector.mult(unit, 135))
simulation.drawList.push({ //add dmg to draw queue
x: where.x,
y: where.y,
radius: 10,
color: 'rgba(0, 171, 238, 0.7)',
time: 32
});
// simulation.drawList.push({ //add dmg to draw queue
// x: m.pos.x,
// y: m.pos.y,
// radius: 150,
// color: 'rgba(0, 171, 238, 0.33)',
// time: 6
// });
// simulation.drawList.push({ //add dmg to draw queue
// x: m.pos.x,
// y: m.pos.y,
// radius: 75,
// color: 'rgba(0, 171, 238, 0.5)',
// time: 16
// });
// simulation.drawList.push({ //add dmg to draw queue
// x: m.pos.x,
// y: m.pos.y,
// radius: 25,
// color: 'rgba(0, 171, 238, 0.75)',
// time: 25
// });
}
// if (true) { //fire harpoons at mobs after getting hit
// const countMax = 12
// let count = countMax
// const range = 300
// for (let i = 0; i < mob.length; i++) {
// if (count > 0 && Vector.magnitude(Vector.sub(m.pos, mob[i].position)) < range) {
// count--
// if (m.fieldCDcycle < m.cycle + 30) m.fieldCDcycle = m.cycle + 30
// const angle = Math.atan2(mob[i].position.y - player.position.y, mob[i].position.x - player.position.x);
// b.harpoon(m.pos, mob[i], angle, 0.75, true, 20) // harpoon(where, target, angle = m.angle, harpoonSize = 1, isReturn = false, totalCycles = 35, isReturnAmmo = true, thrust = 0.1) {
// for (; count > 0; count--) {
// b.harpoon(m.pos, mob[i], count * Math.PI / countMax, 0.75, true, 9) // harpoon(where, target, angle = m.angle, harpoonSize = 1, isReturn = false, totalCycles = 35, isReturnAmmo = true, thrust = 0.1) {
// bullet[bullet.length - 1].drain = 0
// }
// break
// }
// }
// }
if (tech.isStimulatedEmission) powerUps.ejectTech()
if (mob[k].onHit) mob[k].onHit();
if (m.immuneCycle < m.cycle + m.collisionImmuneCycles) m.immuneCycle = m.cycle + m.collisionImmuneCycles; //player is immune to damage for 30 cycles
//extra kick between player and mob //this section would be better with forces but they don't work...
let angle = Math.atan2(player.position.y - mob[k].position.y, player.position.x - mob[k].position.x);
Matter.Body.setVelocity(player, {
x: player.velocity.x + 8 * Math.cos(angle),
y: player.velocity.y + 8 * Math.sin(angle)
});
Matter.Body.setVelocity(mob[k], {
x: mob[k].velocity.x - 8 * Math.cos(angle),
y: mob[k].velocity.y - 8 * Math.sin(angle)
});
if (tech.isAnnihilation && !mob[k].shield && !mob[k].isShielded && !mob[k].isBoss && mob[k].isDropPowerUp && m.energy > 0.34 * m.maxEnergy && mob[k].damageReduction > 0) {
m.energy -= 0.33 * Math.max(m.maxEnergy, m.energy) //0.33 * m.energy
if (m.immuneCycle === m.cycle + m.collisionImmuneCycles) m.immuneCycle = 0; //player doesn't go immune to collision damage
mob[k].death();
simulation.drawList.push({ //add dmg to draw queue
x: pairs[i].activeContacts[0].vertex.x,
y: pairs[i].activeContacts[0].vertex.y,
radius: Math.sqrt(dmg) * 500,
color: "rgba(255,0,255,0.2)",
time: simulation.drawTime
});
} else {
simulation.drawList.push({ //add dmg to draw queue
x: pairs[i].activeContacts[0].vertex.x,
y: pairs[i].activeContacts[0].vertex.y,
radius: Math.sqrt(dmg) * 200,
color: simulation.mobDmgColor,
time: simulation.drawTime
});
}
// return;
// }
} else {
//mob + bullet collisions
if (obj.classType === "bullet" && obj.speed > obj.minDmgSpeed) {
obj.beforeDmg(mob[k]); //some bullets do actions when they hits things, like despawn //forces don't seem to work here
let dmg = m.dmgScale * (obj.dmg + 0.15 * obj.mass * Vector.magnitude(Vector.sub(mob[k].velocity, obj.velocity)))
if (tech.isCrit && mob[k].isStunned) dmg *= 4
// console.log(dmg) //remove this
mob[k].damage(dmg);
if (mob[k].alive) mob[k].foundPlayer();
if (mob[k].damageReduction) {
simulation.drawList.push({ //add dmg to draw queue
x: pairs[i].activeContacts[0].vertex.x,
y: pairs[i].activeContacts[0].vertex.y,
radius: Math.log(dmg + 1.1) * 40 * mob[k].damageReduction + 3,
color: simulation.playerDmgColor,
time: simulation.drawTime
});
}
if (tech.isLessDamageReduction && !mob[k].shield) mob[k].damageReduction *= mob[k].isBoss ? (mob[k].isFinalBoss ? 1.0005 : 1.0025) : 1.05
return;
}
//mob + body collisions
if (obj.classType === "body" && obj.speed > 6) {
const v = Vector.magnitude(Vector.sub(mob[k].velocity, obj.velocity));
if (v > 9) {
if (tech.blockDmg) { //electricity
Matter.Body.setVelocity(mob[k], { x: 0.5 * mob[k].velocity.x, y: 0.5 * mob[k].velocity.y });
if (tech.isBlockRadiation && !mob[k].isShielded && !mob[k].isMobBullet) {
mobs.statusDoT(mob[k], tech.blockDmg * 0.42, 180) //200% increase -> x (1+2) //over 7s -> 360/30 = 12 half seconds -> 3/12
} else {
mob[k].damage(tech.blockDmg * m.dmgScale)
simulation.drawList.push({
x: pairs[i].activeContacts[0].vertex.x,
y: pairs[i].activeContacts[0].vertex.y,
radius: 28 * mob[k].damageReduction + 3,
color: "rgba(255,0,255,0.8)",
time: 4
});
}
}
let dmg = tech.blockDamage * m.dmgScale * v * obj.mass * (tech.isMobBlockFling ? 2.5 : 1) * (tech.isBlockRestitution ? 2.5 : 1) * ((m.fieldMode === 0 || m.fieldMode === 8) ? 1 + 0.04 * m.coupling : 1);
if (mob[k].isShielded) dmg *= 0.7
mob[k].damage(dmg, true);
if (tech.isBlockPowerUps && !mob[k].alive && mob[k].isDropPowerUp && m.throwCycle > m.cycle) {
options = ["coupling", "boost", "heal", "research"]
if (!tech.isEnergyNoAmmo) options.push("ammo")
powerUps.spawn(mob[k].position.x, mob[k].position.y, options[Math.floor(Math.random() * options.length)]);
}
const stunTime = dmg / Math.sqrt(obj.mass)
if (stunTime > 0.5 && mob[k].memory !== Infinity) mobs.statusStun(mob[k], 60 + 60 * Math.sqrt(stunTime))
if (mob[k].alive && mob[k].distanceToPlayer2() < 1000000 && !m.isCloak) mob[k].foundPlayer();
if (tech.fragments && obj.speed > 10 && !obj.hasFragmented) {
obj.hasFragmented = true;
b.targetedNail(obj.position, tech.fragments * 4)
}
if (mob[k].damageReduction) {
simulation.drawList.push({
x: pairs[i].activeContacts[0].vertex.x,
y: pairs[i].activeContacts[0].vertex.y,
radius: Math.log(dmg + 1.1) * 40 * mob[k].damageReduction + 3,
color: simulation.playerDmgColor,
time: simulation.drawTime
});
}
return;
}
}
}
}
}
}
}
}
//determine if player is on the ground
Events.on(engine, "collisionStart", function (event) {
playerOnGroundCheck(event);
// playerHeadCheck(event);
collisionChecks(event);
});
Events.on(engine, "collisionActive", function (event) {
playerOnGroundCheck(event);
// playerHeadCheck(event);
});
Events.on(engine, "collisionEnd", function (event) {
playerOffGroundCheck(event);
});