Files
n-gon-improved/js/engine.js
landgreen 5e12cea685 4 community levels
new community levels: (you can enable community maps in the settings)
  rings by thatLittleFrog
  flappyGon by Digin
  trial by Cirryn and Tarantula Hawk
  arena level updated by Whyisthisnotavalable

difficulty scaling:  0.84->0.85x player damage per level
research spawn per level is no longer in the difficulty settings
  instead players get 1 research for only the first few levels
cell boss has less health at high difficulty

converted JUNK tech to additive, instead of the multiplicative
  makes the first JUNK you take do the same, but if you take too much you can get to 100%
right aligned some text in tech descriptions
added some circles to the in-game console messages

renamed slow light -> delayed-choice - a single 0.4 second delayed 0.7x damage laser beam
  also now works with reflection
plasma torch field gets 1.5x damage by default
molecular assembler coupling 0.8->0.6 energy per second
nail-bot upgrade 5->4x fire rate
foam-bot upgrade 3->2.5x size and fire rate
sound-bot upgrade 2.5->2x fire rate, damage, 1->2x wave packet length
boom-bot upgrade reduced range, bot acceleration
orbital-bot upgrade 1.5->2x radius
perimeter defense 0.95->0.96x damage taken per bot
network effect 1.05->1.04x damage per bot
tech: working mass - cleaned up physics and logic a bit
negative feedback scales with health below maxHealth, not health below 100
  1.007->1.006x damage per missing health
homeostasis scales with missing health, not health below 100
  limit of 0.2x at 0 health
2024-06-20 19:13:51 -07:00

290 lines
16 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 > m.hardLanding) {
m.doCrouch();
m.yOff = m.yOffWhen.jump;
m.hardLandCD = m.cycle + m.hardLandCDScale * 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 || m.immuneCycle > m.cycle) enter();
} else if (pair.bodyB === jumpSensor) {
m.standingOn = pair.bodyA; //keeping track to correctly provide recoil on jump
if (m.standingOn.alive !== true || m.immuneCycle > m.cycle) 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.85 * Math.min(1, 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
}
m.damage(dmg); //normal damage
if (tech.isCollisionRealitySwitch && m.alive) {
m.switchWorlds()
simulation.trails(90)
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(-4)
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
});
}
if (tech.isHarpoonDefense) { //fire harpoons at mobs after getting hit
const maxCount = 10 + 3 * tech.extraHarpoons //scale the number of hooks fired
let count = maxCount - 1
const angle = Math.atan2(mob[k].position.y - player.position.y, mob[k].position.x - player.position.x);
b.harpoon(m.pos, mob[k], angle, 0.75, true, 7) // harpoon(where, target, angle = m.angle, harpoonSize = 1, isReturn = false, totalCycles = 35, isReturnAmmo = true, thrust = 0.1) {
bullet[bullet.length - 1].drain = 0
for (; count > 0; count--) {
b.harpoon(m.pos, mob[k], angle + count * 2 * Math.PI / maxCount, 0.75, true, 7)
bullet[bullet.length - 1].drain = 0
}
}
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.1 && mob[k].damageReduction > 0) {
m.energy -= 0.1 //* 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.05 * m.coupling : 1);
if (mob[k].isShielded) dmg *= 0.7
mob[k].damage(dmg, true);
if (tech.isBlockPowerUps && !mob[k].alive && mob[k].isDropPowerUp && Math.random() < 0.5) {
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);
});