Bitter electromagnet

harpoon
  default fire rate is 10% higher
  default harpoon range 15% higher
railgun
  tech: Bitter electromagnet - 33% slower charge time for railgun, 100% more density and damage
    area effect damage is increased 20%, scales with bitter electromagnet and total charge
  charging longer increases harpoon velocity/damage by up to 30%
  contributions to charge time are more uniform between:
    crouching, fire rate, Bitter electromagnet, and capacitor bank
  auto aims no longer disabled on crouch

emergence 2->1 extra choice, and +8% damage, and no added JUNK anymore
cache 16->15x ammo
many-worlds 1->0 research to enter an alternate reality on each new level

finalBoss
  health decays a bit faster
  spawns 6 mobs at each health threshold
  spawns from the different modes are reduced
  boss laser damage is 25% reduced
  hoppers spawn from the slime tunnel

JUNKtech: random - gives random +damage

bug fixes
This commit is contained in:
landgreen
2022-10-31 19:15:21 -07:00
parent f8188565a0
commit 500bb3f73e
9 changed files with 299 additions and 165 deletions

View File

@@ -1688,7 +1688,7 @@ const b = {
});
Composite.add(engine.world, bullet[me]); //add bullet to world
},
harpoon(where, target, angle = m.angle, harpoonSize = 1, isReturn = false, totalCycles = 35, isReturnAmmo = true) {
harpoon(where, target, angle = m.angle, harpoonSize = 1, isReturn = false, totalCycles = 35, isReturnAmmo = true, thrust = 0.1) {
const me = bullet.length;
const returnRadius = 100 * Math.sqrt(harpoonSize)
bullet[me] = Bodies.fromVertices(where.x, where.y, [{ x: -40 * harpoonSize, y: 2 * harpoonSize, index: 0, isInternal: false }, { x: -40 * harpoonSize, y: -2 * harpoonSize, index: 1, isInternal: false }, { x: 50 * harpoonSize, y: -3 * harpoonSize, index: 3, isInternal: false }, { x: 30 * harpoonSize, y: 2 * harpoonSize, index: 4, isInternal: false }], {
@@ -1696,7 +1696,7 @@ const b = {
angle: angle,
friction: 1,
frictionAir: 0.4,
thrustMag: 0.1,
// thrustMag: 0.1,
drain: tech.isRailEnergy ? 0.001 : 0.006,
turnRate: isReturn ? 0.1 : 0.03, //0.015
drawStringControlMagnitude: 3000 + 5000 * Math.random(),
@@ -1808,7 +1808,7 @@ const b = {
if (m.energy < 0.05) {
m.fireCDcycle = m.cycle + 120; //fire cooldown
} else if (m.cycle + 25 * b.fireCDscale < m.fireCDcycle) {
m.fireCDcycle = m.cycle + 35 * b.fireCDscale //lower cd to 25 if it is above 25
m.fireCDcycle = m.cycle + 25 * b.fireCDscale //lower cd to 25 if it is above 25
}
//recoil on catching
const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (input.down ? 0.0001 : 0.0002))
@@ -1828,7 +1828,7 @@ const b = {
if (m.energy > this.drain) m.energy -= this.drain
const sub = Vector.sub(this.position, m.pos)
const rangeScale = 1 + 0.000001 * Vector.magnitude(sub) * Vector.magnitude(sub) //return faster when far from player
const returnForce = Vector.mult(Vector.normalise(sub), rangeScale * this.thrustMag * this.mass)
const returnForce = Vector.mult(Vector.normalise(sub), rangeScale * thrust * this.mass)
this.force.x -= returnForce.x
this.force.y -= returnForce.y
this.grabPowerUp()
@@ -1850,7 +1850,7 @@ const b = {
Matter.Body.setPosition(powerUp[i], this.vertices[2])
powerUp[i].collisionFilter.category = 0
powerUp[i].collisionFilter.mask = 0
this.thrustMag *= 0.6
thrust *= 0.6
this.endCycle += 0.5 //it pulls back slower, so this prevents it from ending early
break //just pull 1 power up if possible
}
@@ -1887,16 +1887,16 @@ const b = {
Matter.Body.rotate(this, -this.turnRate);
}
}
this.force.x += this.thrustMag * this.mass * Math.cos(this.angle);
this.force.y += this.thrustMag * this.mass * Math.sin(this.angle);
this.force.x += thrust * this.mass * Math.cos(this.angle);
this.force.y += thrust * this.mass * Math.sin(this.angle);
}
this.draw()
},
});
if (!isReturn && !target) {
Matter.Body.setVelocity(bullet[me], {
x: m.Vx / 2 + 60 * Math.cos(bullet[me].angle),
y: m.Vy / 2 + 60 * Math.sin(bullet[me].angle)
x: m.Vx / 2 + 600 * thrust * Math.cos(bullet[me].angle),
y: m.Vy / 2 + 600 * thrust * Math.sin(bullet[me].angle)
});
bullet[me].frictionAir = 0.002
bullet[me].do = function() {
@@ -6790,7 +6790,7 @@ const b = {
}
//fire
if ((!input.fire && this.charge > 0.6)) {
tech.harpoonDensity = 0.0065 //0.001 is normal for blocks, 0.004 is normal for harpoon, 0.004*6 when buffed
// tech.harpoonDensity = 0.0065 //0.001 is normal for blocks, 0.004 is normal for harpoon, 0.004*6 when buffed
const where = {
x: m.pos.x + 30 * Math.cos(m.angle),
y: m.pos.y + 30 * Math.sin(m.angle)
@@ -6800,8 +6800,9 @@ const b = {
target: null
}
//push away blocks and mobs
const range = 1200 * this.charge
const range = 600 + 500 * this.charge
for (let i = 0, len = mob.length; i < len; ++i) { //push away mobs when firing
if (!mob[i].isUnblockable) {
const SUB = Vector.sub(mob[i].position, m.pos)
const DISTANCE = Vector.magnitude(SUB)
if (DISTANCE < range + mob[i].radius) {
@@ -6810,17 +6811,18 @@ const b = {
mob[i].force.x += FORCE.x;
mob[i].force.y += FORCE.y;
let dmg = m.dmgScale * (mob[i].isDropPowerUp ? 0.1 : 0.4)
let dmg = m.dmgScale * (mob[i].isDropPowerUp ? 350 : 1100) * tech.harpoonDensity * this.charge
simulation.drawList.push({ //add dmg to draw queue
x: mob[i].position.x,
y: mob[i].position.y,
radius: Math.log(dmg + 1.1) * 40 * mob[i].damageReduction + 3,
color: 'rgba(100, 0, 200, 0.2)',
color: 'rgba(100, 0, 200, 0.4)',
time: 15
});
mob[i].damage(dmg);
}
}
}
for (let i = 0, len = body.length; i < len; ++i) { //push away blocks when firing
const SUB = Vector.sub(body[i].position, m.pos)
const DISTANCE = Vector.magnitude(SUB)
@@ -6841,13 +6843,26 @@ const b = {
powerUp[i].force.y += FORCE.y - powerUp[i].mass * simulation.g * 1.5; //kick up a bit to give them some arc
}
}
//draw little dots near the edge of range
for (let i = 0, len = 10 + 25 * this.charge; i < len; i++) {
const unit = Vector.rotate({ x: 1, y: 0 }, 6.28 * Math.random())
const where = Vector.add(m.pos, Vector.mult(unit, range * (0.6 + 0.3 * Math.random())))
simulation.drawList.push({
x: where.x,
y: where.y,
radius: 5 + 12 * Math.random(),
color: "rgba(100, 0, 200, 0.1)",
time: Math.floor(5 + 35 * Math.random())
});
}
const recoil = Vector.mult(Vector.normalise(Vector.sub(where, m.pos)), input.down ? 0.03 : 0.06)
player.force.x -= recoil.x
player.force.y -= recoil.y
tech.harpoonDensity = 0.0065 //0.001 is normal for blocks, 0.004 is normal for harpoon, 0.004*6 when buffed
// tech.harpoonDensity = 0.0065 //0.001 is normal for blocks, 0.004 is normal for harpoon, 0.004*6 when buffed
const harpoonSize = tech.isLargeHarpoon ? 1 + 0.1 * Math.sqrt(this.ammo) : 1
const thrust = 0.15 * (this.charge)
if (tech.extraHarpoons) {
let targetCount = 0
const SPREAD = 0.06 + 0.05 * (!input.down)
@@ -6861,7 +6876,7 @@ const b = {
if (dot > 0.95 - Math.min(dist * 0.00015, 0.3)) { //lower dot product threshold for targeting then if you only have one harpoon //target closest mob that player is looking at and isn't too close to target
// if (this.ammo > -1) {
// this.ammo--
b.harpoon(where, input.down ? null : mob[i], angle, harpoonSize, false) //Vector.angle(Vector.sub(where, mob[i].position), { x: 0, y: 0 })
b.harpoon(where, input.down ? null : mob[i], angle, harpoonSize, false, 35, false, thrust) //harpoon(where, target, angle = m.angle, harpoonSize = 1, isReturn = false, totalCycles = 35, isReturnAmmo = true, thrust = 0.1) {
angle += SPREAD
targetCount++
if (targetCount > tech.extraHarpoons) break
@@ -6873,14 +6888,10 @@ const b = {
if (targetCount < tech.extraHarpoons + 1) {
const num = tech.extraHarpoons + 1 - targetCount
for (let i = 0; i < num; i++) {
// if (this.ammo > -1) {
// this.ammo--
b.harpoon(where, null, angle, harpoonSize, false)
b.harpoon(where, null, angle, harpoonSize, false, 35, false, thrust)
angle += SPREAD
// }
}
}
// this.ammo++ //make up for the ammo used up in fire()
simulation.updateGunHUD();
} else {
//look for closest mob in player's LoS
@@ -6895,7 +6906,7 @@ const b = {
}
}
}
b.harpoon(where, input.down ? null : closest.target, m.angle, harpoonSize, false)
b.harpoon(where, closest.target, m.angle, harpoonSize, false, 35, false, thrust)
}
this.charge = 0;
@@ -6909,13 +6920,18 @@ const b = {
player.force.y = 0
}
m.fireCDcycle = m.cycle + 10 //can't fire until mouse is released
const previousCharge = this.charge
//small b.fireCDscale = faster shots, b.fireCDscale=1 = normal shot, big b.fireCDscale = slower chot
let smoothRate = tech.isCapacitor ? 0.85 : Math.min(0.998, 0.985 * (0.98 + 0.02 * b.fireCDscale))
if (input.down) smoothRate *= 0.995
// const previousCharge = this.charge
this.charge = this.charge * smoothRate + 1 - smoothRate
//small b.fireCDscale = faster shots, b.fireCDscale=1 = normal shot, big b.fireCDscale = slower chot
// let smoothRate = tech.isCapacitor ? 0.85 : Math.min(0.998, 0.985 * (0.98 + 0.02 * b.fireCDscale))
const rate = Math.sqrt(b.fireCDscale) * tech.railChargeRate * (tech.isCapacitor ? 0.6 : 1) * (input.down ? 0.8 : 1)
let smoothRate = Math.min(0.998, 0.94 + 0.05 * rate)
this.charge = 1 - smoothRate + this.charge * smoothRate
if (m.energy > DRAIN) m.energy -= DRAIN
// console.log((this.charge).toFixed(2))
// m.energy += (this.charge - previousCharge) * ((tech.isRailEnergy ? 0.5 : -0.3)) //energy drain is proportional to charge gained, but doesn't stop normal m.fieldRegen
//draw magnetic field
@@ -6936,9 +6952,11 @@ const b = {
X, Y)
}
ctx.fillStyle = `rgba(50,0,100,0.05)`;
const magSize = 8 * this.charge * tech.railChargeRate ** 3
const arcSize = 6 * this.charge * tech.railChargeRate ** 3
for (let i = 3; i < 7; i++) {
const MAG = 8 * i * i * this.charge * (0.93 + 0.07 * Math.random())
const ARC = 6 * i * i * this.charge * (0.93 + 0.07 * Math.random())
const MAG = magSize * i * i * (0.93 + 0.07 * Math.random())
const ARC = arcSize * i * i * (0.93 + 0.07 * Math.random())
ctx.beginPath();
magField(MAG, ARC)
magField(MAG, -ARC)
@@ -6990,13 +7008,13 @@ const b = {
}
//look for closest mob in player's LoS
const harpoonSize = (tech.isLargeHarpoon ? 1 + 0.1 * Math.sqrt(this.ammo) : 1) //* (input.down ? 0.7 : 1)
const totalCycles = 5 * (tech.isFilament ? 1 + 0.012 * Math.min(110, this.ammo) : 1) * Math.sqrt(harpoonSize)
const totalCycles = 6 * (tech.isFilament ? 1 + 0.012 * Math.min(110, this.ammo) : 1) * Math.sqrt(harpoonSize)
if (tech.extraHarpoons && !input.down) { //multiple harpoons
const SPREAD = 0.1
let angle = m.angle - SPREAD * tech.extraHarpoons / 2;
const dir = { x: Math.cos(angle), y: Math.sin(angle) }; //make a vector for the player's direction of length 1; used in dot product
const range = 450 * (tech.isFilament ? 1 + 0.006 * Math.min(110, this.ammo) : 1)
const range = 450 * (tech.isFilament ? 1 + 0.012 * Math.min(110, this.ammo) : 1)
let targetCount = 0
for (let i = 0, len = mob.length; i < len; ++i) {
if (mob[i].alive && !mob[i].isBadTarget && !mob[i].shield && Matter.Query.ray(map, m.pos, mob[i].position).length === 0 && !mob[i].isInvulnerable) {
@@ -7036,9 +7054,8 @@ const b = {
}
this.ammo++ //make up for the ammo used up in fire()
simulation.updateGunHUD();
m.fireCDcycle = m.cycle + 90 // cool down
} else {
//single harpoon
m.fireCDcycle = m.cycle + 90 // cool down is set when harpoon bullet returns to player
} else { //input.down makes a single harpoon with longer range
const dir = { x: Math.cos(m.angle), y: Math.sin(m.angle) }; //make a vector for the player's direction of length 1; used in dot product
for (let i = 0, len = mob.length; i < len; ++i) {
if (mob[i].alive && !mob[i].isBadTarget && Matter.Query.ray(map, m.pos, mob[i].position).length === 0 && !mob[i].isInvulnerable) {
@@ -7055,7 +7072,7 @@ const b = {
} else {
b.harpoon(where, closest.target, m.angle, harpoonSize, true, totalCycles)
}
m.fireCDcycle = m.cycle + 45 // cool down
m.fireCDcycle = m.cycle + 45 // cool down is set when harpoon bullet returns to player
tech.harpoonDensity = 0.004 //0.001 is normal for blocks, 0.004 is normal for harpoon, 0.004*6 when buffed
}
const recoil = Vector.mult(Vector.normalise(Vector.sub(where, m.pos)), input.down ? 0.015 : 0.035)

View File

@@ -154,7 +154,7 @@ function collisionChecks(event) {
m.damage(dmg); //normal damage
}
if (tech.isCollisionRealitySwitch) {
if (tech.isCollisionRealitySwitch && m.alive) {
m.switchWorlds()
simulation.trails()
simulation.makeTextLog(`simulation.amplitude <span class='color-symbol'>=</span> ${Math.random()}`);

View File

@@ -963,7 +963,7 @@ window.addEventListener("keyup", function(event) {
});
window.addEventListener("keydown", function(event) {
console.log(event.code)
// console.log(event.code)
switch (event.code) {
case input.key.right:
case "ArrowRight":

View File

@@ -18,30 +18,31 @@ const level = {
// simulation.enableConstructMode() //used to build maps in testing mode
// simulation.isHorizontalFlipped = true
// tech.giveTech("performance")
// level.difficultyIncrease(2 * 4) //30 is near max on hard //60 is near max on why
// level.difficultyIncrease(15 * 4) //30 is near max on hard //60 is near max on why
// m.maxHealth = m.health = 100
// tech.isRerollDamage = true
// powerUps.research.changeRerolls(50)
// powerUps.research.changeRerolls(1000)
// m.immuneCycle = Infinity //you can't take damage
// tech.tech[297].frequency = 100
// m.couplingChange(5)
// m.setField("time dilation") //molecular assembler standing wave time dilation perfect diamagnetism metamaterial cloaking wormhole negative mass pilot wave
// m.setField("plasma torch") //molecular assembler standing wave time dilation perfect diamagnetism metamaterial cloaking wormhole negative mass pilot wave plasma torch
// simulation.molecularMode = 2
// m.damage(0.1);
// b.giveGuns("harpoon") //0 nail gun 1 shotgun 2 super balls 3 wave 4 missiles 5 grenades 6 spores 7 drones 8 foam 9 harpoon 10 mine 11 laser
// b.giveGuns("wave") //0 nail gun 1 shotgun 2 super balls 3 wave 4 missiles 5 grenades 6 spores 7 drones 8 foam 9 harpoon 10 mine 11 laser
// b.guns[9].ammo = 10000
// tech.giveTech("startle response")
// for (let i = 0; i < 1; ++i) tech.giveTech("junk DNA")
// b.guns[0].ammo = 10000
// tech.giveTech("plasma ball")
// tech.giveTech("dye laser")
// for (let i = 0; i < 1; ++i) tech.giveTech("grappling hook")
// for (let i = 0; i < 5; i++) tech.giveTech("laser-bot")
// for (let i = 0; i < 1; ++i) tech.giveTech("railgun")
// for (let i = 0; i < 3; ++i) tech.giveTech("Bitter electromagnet")
// for (let i = 0; i < 1; i++) tech.giveTech("capacitor bank")
// for (let i = 0; i < 9; i++) tech.giveTech("heuristics")
// for (let i = 0; i < 10; i++) powerUps.directSpawn(450, -50, "tech");
// for (let i = 0; i < 10; i++) powerUps.directSpawn(1750, -500, "boost");
// for (let i = 0; i < 10; i++) powerUps.directSpawn(1750, -500, "coupling");
// level.testing();
// spawn.starter(1900, -500, 200)
// spawn.shooter(1900, -500, 200)
// spawn.starter(1900, -500)
// spawn.timeBoss(2538, -950)
// for (let i = 0; i < 5; ++i) spawn.sniper(1000 + 5000 * Math.random(), -500 + 300 * Math.random())
@@ -66,7 +67,7 @@ const level = {
// level.null()
// localSettings.isHuman = true
// tech.isNoDraftPause = false //disable pause
// mobs.mobDeaths = 200
// mobs.mobDeaths = 200 //to prevent pacifist mode
// for (let i = 0; i < 13; i++) level.nextLevel(); //jump to final boss
// lore.unlockTesting();
@@ -101,6 +102,11 @@ const level = {
b.inventoryGun = tech.buffedGun;
simulation.switchGun();
}
if (tech.isGunChoice && Number.isInteger(tech.buffedGun) && b.inventory.length) {
var gun = b.guns[b.inventory[tech.buffedGun]].name
simulation.makeTextLog(`pigeonhole principle: <strong>+${(31 * Math.max(0, b.inventory.length)).toFixed(0)}%</strong> <strong class='color-d'>damage</strong> for <strong class="highlight">${gun}</strong>`, 600);
}
if (tech.isForeverDrones) {
if (tech.isDroneRadioactive) {
@@ -119,8 +125,7 @@ const level = {
tech.healMaxEnergyBonus += 0.1 * powerUps.totalPowerUps //Math.min(0.02 * powerUps.totalPowerUps, 0.51)
m.setMaxEnergy();
}
if (tech.isSwitchReality && powerUps.research.count > 0) {
powerUps.research.changeRerolls(-1);
if (tech.isSwitchReality) {
simulation.makeTextLog(`simulation.amplitude <span class='color-symbol'>=</span> ${Math.random()}`);
m.switchWorlds()
simulation.trails()
@@ -162,7 +167,8 @@ const level = {
if (tech.isSpawnExitTech) {
for (let i = 0; i < 2; i++) powerUps.spawn(level.exit.x + 10 * (Math.random() - 0.5), level.exit.y - 100 + 10 * (Math.random() - 0.5), "tech", false) //exit
}
if (m.plasmaBall) m.plasmaBall.reset()
// if (m.plasmaBall) m.plasmaBall.reset()
if (m.plasmaBall) m.plasmaBall.fire()
if (localSettings.entanglement && localSettings.entanglement.levelName === level.levels[level.onLevel]) {
const flip = localSettings.entanglement.isHorizontalFlipped === simulation.isHorizontalFlipped ? 1 : -1
powerUps.directSpawn(flip * localSettings.entanglement.position.x, localSettings.entanglement.position.y, "entanglement", false);

View File

@@ -342,12 +342,6 @@ const mobs = {
this.seePlayer.position.y = player.position.y;
}
},
// alwaysSeePlayerIfRemember() {
// if (!m.isCloak && this.seePlayer.recall) {
// this.seePlayer.position.x = player.position.x;
// this.seePlayer.position.y = player.position.y;
// }
// },
seePlayerByHistory(depth = 30) { //depth max 60? limit of history
if (!(simulation.cycle % this.seePlayerFreq)) {
if (Matter.Query.ray(map, this.position, this.playerPosRandomY()).length === 0 && !m.isCloak) {
@@ -1255,12 +1249,12 @@ const mobs = {
}
if (tech.isBotSpawnerReset) {
for (let i = 0, len = bullet.length; i < len; i++) {
if (bullet[i].botType && bullet[i].endCycle !== Infinity) bullet[i].endCycle = simulation.cycle + 840 //14 seconds
if (bullet[i].botType && bullet[i].endCycle !== Infinity) bullet[i].endCycle = simulation.cycle + 780 //13 seconds
}
}
if (Math.random() < tech.botSpawner) {
b.randomBot(this.position, false)
bullet[bullet.length - 1].endCycle = simulation.cycle + 840 //14 seconds
bullet[bullet.length - 1].endCycle = simulation.cycle + 780 //13 seconds
this.leaveBody = false; // no body since it turned into the bot
}
if (tech.isAddRemoveMaxHealth) {

View File

@@ -2385,11 +2385,15 @@ const m = {
if (this.circleRadius < this.radiusLimit) this.reset()
},
reset() {
// console.log(this.circleRadius)
const scale = 1 / m.plasmaBall.circleRadius
Matter.Body.scale(m.plasmaBall, scale, scale); //grow
// console.log(this.circleRadius)
// this.circleRadius = 0
this.alpha = 0.7
this.isOn = false
this.isPopping = false
// this.isAttached = true;
},
do() {
if (this.isOn) {

View File

@@ -297,7 +297,7 @@ const spawn = {
me.showHealthBar = false;
me.collisionFilter.category = 0;
me.collisionFilter.mask = 0; //cat.player //| cat.body
me.chaseSpeed = 1.2 + 2 * Math.random()
me.chaseSpeed = 1.2 + 2.3 * Math.random()
me.awake = function() {
//chase player
@@ -394,6 +394,9 @@ const spawn = {
this.pushAway();
this.mode[this.totalModes].enter() //enter new mode
this.totalModes++
//spawn 6 mobs
me.mobType = spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]; //fire a bullet from each vertex
for (let i = 0; i < 6; i++) me.spawnMobs(i)
}
ctx.beginPath(); //draw invulnerable
let vertices = this.vertices;
@@ -408,12 +411,21 @@ const spawn = {
}
}
me.damageReductionDecay = function() { //slowly make the boss take more damage over time //damageReduction resets with each invulnerability phase
if (!(me.cycle % 60) && this.lastDamageCycle + 240 > this.cycle) this.damageReduction *= 1.015 //only decay once a second //only decay if the player has done damage in the last 4 seconds
if (!(me.cycle % 60) && this.lastDamageCycle + 240 > this.cycle) this.damageReduction *= 1.017 //only decay once a second //only decay if the player has done damage in the last 4 seconds
}
me.mobType = spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]
me.spawnMobs = function(index = 0) {
const vertex = me.vertices[index]
const unit = Vector.normalise(Vector.sub(me.position, vertex))
const where = Vector.add(vertex, Vector.mult(unit, -30))
spawn[me.mobType](where.x + 50 * (Math.random() - 0.5), where.y + 50 * (Math.random() - 0.5));
const velocity = Vector.mult(Vector.perp(unit), -10) //give the mob a rotational velocity as if they were attached to a vertex
Matter.Body.setVelocity(mob[mob.length - 1], { x: me.velocity.x + velocity.x, y: me.velocity.y + velocity.y });
}
me.maxMobs = 400
me.mode = [{
name: "boulders",
spawnRate: 120 - 6 * simulation.difficultyMode,
spawnRate: 170 - 6 * simulation.difficultyMode,
do() {
if (!(me.cycle % this.spawnRate) && mob.length < me.maxMobs) {
me.boulder(me.position.x, me.position.y + 250)
@@ -423,19 +435,20 @@ const spawn = {
exit() {},
}, {
name: "mobs",
whoSpawn: spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)],
spawnRate: 240 - 20 * simulation.difficultyMode,
// whoSpawn: spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)],
spawnRate: 280 - 20 * simulation.difficultyMode,
do() {
if (!(me.cycle % this.spawnRate) && mob.length < me.maxMobs) {
me.torque += 0.000015 * me.inertia; //spin
const index = Math.floor((me.cycle % (this.spawnRate * 6)) / this.spawnRate) //int from 0 to 5
if (index === 0) this.whoSpawn = spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]; //fire a bullet from each vertex
const vertex = me.vertices[index]
const unit = Vector.normalise(Vector.sub(me.position, vertex))
const where = Vector.add(vertex, Vector.mult(unit, -30))
spawn[this.whoSpawn](where.x + 50 * (Math.random() - 0.5), where.y + 50 * (Math.random() - 0.5));
const velocity = Vector.mult(Vector.perp(unit), -18) //give the mob a rotational velocity as if they were attached to a vertex
Matter.Body.setVelocity(mob[mob.length - 1], { x: me.velocity.x + velocity.x, y: me.velocity.y + velocity.y });
if (index === 0) me.mobType = spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]; //fire a bullet from each vertex
me.spawnMobs(index)
// const vertex = me.vertices[index]
// const unit = Vector.normalise(Vector.sub(me.position, vertex))
// const where = Vector.add(vertex, Vector.mult(unit, -30))
// spawn[me.mobType](where.x + 50 * (Math.random() - 0.5), where.y + 50 * (Math.random() - 0.5));
// const velocity = Vector.mult(Vector.perp(unit), -18) //give the mob a rotational velocity as if they were attached to a vertex
// Matter.Body.setVelocity(mob[mob.length - 1], { x: me.velocity.x + velocity.x, y: me.velocity.y + velocity.y });
}
},
enter() {},
@@ -443,7 +456,7 @@ const spawn = {
},
{
name: "hoppers",
spawnRate: 420 - 16 * simulation.difficultyMode,
spawnRate: 480 - 16 * simulation.difficultyMode,
do() {
if (!(me.cycle % this.spawnRate) && mob.length < me.maxMobs) {
me.torque += 0.00002 * me.inertia; //spin
@@ -457,6 +470,10 @@ const spawn = {
y: me.velocity.y + velocity.y
});
}
let where = { x: 600 - Math.random() * 100, y: -225 }
if (simulation.isHorizontalFlipped) where.x = -600 + Math.random() * 100
spawn.hopBullet(where.x, where.y, 13 + Math.ceil(Math.random() * 8)); //hopBullet(x, y, radius = 10 + Math.ceil(Math.random() * 8))
Matter.Body.setDensity(mob[mob.length - 1], 0.002); //normal is 0.001
}
},
enter() {},
@@ -464,7 +481,7 @@ const spawn = {
},
{
name: "seekers",
spawnRate: 60 - 3 * simulation.difficultyMode,
spawnRate: 100 - 3 * simulation.difficultyMode,
do() {
if (!(me.cycle % this.spawnRate) && mob.length < me.maxMobs) { //spawn seeker
const index = Math.floor((me.cycle % 360) / 60)
@@ -482,7 +499,7 @@ const spawn = {
{
name: "mines",
bombCycle: 0,
bombInterval: 34 - 2 * simulation.difficultyMode,
bombInterval: 55 - 2 * simulation.difficultyMode,
do() {
const yOff = 120
this.bombCycle++
@@ -544,7 +561,7 @@ const spawn = {
},
{
name: "orbiters",
spawnRate: 30 - 2 * simulation.difficultyMode,
spawnRate: 42 - 2 * simulation.difficultyMode,
do() {
if (!(me.cycle % this.spawnRate) && mob.length < me.maxMobs) {
const speed = (0.01 + 0.0005 * simulation.difficultyMode) * ((Math.random() < 0.5) ? 0.85 : -1.15)
@@ -602,7 +619,7 @@ const spawn = {
{
name: "black hole",
eventHorizon: 0,
eventHorizonRadius: 2200,
eventHorizonRadius: 2100,
eventHorizonCycle: 0,
do() {
this.eventHorizonCycle++
@@ -690,10 +707,10 @@ const spawn = {
for (let i = 0; i < this.totalModes; i++) this.mode[i].do()
}
// this.cycle++;
// this.mode[0].do()
// this.mode[4].do()
// this.mode[7].do()
};
me.spawnRate = 4800 - 30 * simulation.difficultyMode * simulation.difficultyMode
me.spawnRate = 5800 - 30 * simulation.difficultyMode * simulation.difficultyMode
me.spawnBoss = function() { //if the fight lasts too long start spawning bosses
if (!(me.cycle % this.spawnRate) && this.health < 1) {
this.spawnRate = Math.max(300, this.spawnRate - 10 * simulation.difficultyMode * simulation.difficultyMode) //reduce the timer each time a boss spawns
@@ -816,7 +833,7 @@ const spawn = {
}
};
}
me.lasers = function(where, angle, dmg = 0.13 * simulation.dmgScale) {
me.lasers = function(where, angle, dmg = 0.1 * simulation.dmgScale) {
const vertexCollision = function(v1, v1End, domain) {
for (let i = 0; i < domain.length; ++i) {
let vertices = domain[i].vertices;
@@ -1752,7 +1769,7 @@ const spawn = {
me.seeAtDistance2 = 1400000;
me.cellMassMax = 70
me.collisionFilter.mask = cat.player | cat.bullet //| cat.body | cat.map
Matter.Body.setDensity(me, 0.0002 + 0.00001 * simulation.difficulty) // normal density is 0.001
Matter.Body.setDensity(me, 0.0001 + 0.00002 * simulation.difficulty) // normal density is 0.001
me.damageReduction = 0.17 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1); //me.damageReductionGoal
const k = 642 //k=r^2/m
@@ -2542,13 +2559,13 @@ const spawn = {
mobs.spawn(x, y, 6, radius, "transparent");
let me = mob[mob.length - 1];
me.stroke = "transparent"; //used for drawSneaker
me.eventHorizon = radius * 23; //required for blackhole
me.eventHorizon = radius * 27; //required for blackhole
me.seeAtDistance2 = (me.eventHorizon + 400) * (me.eventHorizon + 400); //vision limit is event horizon
me.accelMag = 0.0001 * simulation.accelScale;
me.accelMag = 0.00012 * simulation.accelScale;
me.frictionAir = 0.025;
me.collisionFilter.mask = cat.player | cat.bullet //| cat.body
me.memory = Infinity;
Matter.Body.setDensity(me, 0.008); //extra dense //normal is 0.001 //makes effective life much larger
Matter.Body.setDensity(me, 0.01); //extra dense //normal is 0.001 //makes effective life much larger
me.do = function() {
//keep it slow, to stop issues from explosion knock backs
if (this.speed > 5) {
@@ -2675,11 +2692,11 @@ const spawn = {
me.stroke = "transparent"; //used for drawSneaker
me.eventHorizon = 1100; //required for black hole
me.seeAtDistance2 = (me.eventHorizon + 1200) * (me.eventHorizon + 1200); //vision limit is event horizon
me.accelMag = 0.00003 * simulation.accelScale;
me.accelMag = 0.00004 * simulation.accelScale;
me.collisionFilter.mask = cat.player | cat.bullet //| cat.body
// me.frictionAir = 0.005;
me.memory = 1600;
Matter.Body.setDensity(me, 0.03); //extra dense //normal is 0.001 //makes effective life much larger
Matter.Body.setDensity(me, 0.04); //extra dense //normal is 0.001 //makes effective life much larger
me.onDeath = function() {
//applying forces to player doesn't seem to work inside this method, not sure why
powerUps.spawnBossPowerUp(this.position.x, this.position.y)

View File

@@ -430,16 +430,11 @@ const tech = {
{
name: "pigeonhole principle",
descriptionFunction() {
var info = ""
let info = ""
if (this.count > 0 && Number.isInteger(tech.buffedGun) && b.inventory.length) {
var gun = b.guns[b.inventory[tech.buffedGun]].name
var info = `<br>this level: <strong>+${(31 * Math.max(0, b.inventory.length)).toFixed(0)}%</strong> <strong class='color-d'>damage</strong> for <strong class="highlight">${gun}</strong>`
let gun = b.guns[b.inventory[tech.buffedGun]].name
info = `<br>this level: <strong>+${(31 * Math.max(0, b.inventory.length)).toFixed(0)}%</strong> <strong class='color-d'>damage</strong> for <strong class="highlight">${gun}</strong>`
}
// return `
// a <strong class='color-g'>gun</strong> is <strong>chosen</strong> to be improved each <strong>level</strong>
// <br><strong>+${(31*b.inventory.length).toFixed(0)}%</strong> <strong class='color-d'>damage</strong> for ${gun}
// <br><strong class='color-d'>damage</strong> scales by 31% per unequipped <strong class='color-g'>gun</strong>`
return `
a new <strong class='color-g'>gun</strong> is <strong>chosen</strong> to be improved each <strong>level</strong>
<br><strong>+31%</strong> <strong class='color-d'>damage</strong> per <strong class='color-g'>gun</strong> for the <strong>chosen</strong> <strong class='color-g'>gun</strong>${info}`
@@ -574,7 +569,7 @@ const tech = {
{
name: "cache",
link: `<a target="_blank" href='https://en.wikipedia.org/wiki/Cache_(computing)' class="link">cache</a>`,
description: `${powerUps.orb.ammo()} give <strong>1600%</strong> more <strong class='color-ammo'>ammo</strong>, but<br>you can't <strong>store</strong> any more <strong class='color-ammo'>ammo</strong> than that`,
description: `${powerUps.orb.ammo()} give <strong>1500%</strong> more <strong class='color-ammo'>ammo</strong>, but<br>you can't <strong>store</strong> any more <strong class='color-ammo'>ammo</strong> than that`,
// ammo powerups always max out your gun,
// but the maximum ammo ti limited
// description: `${powerUps.orb.ammo()} give <strong>13x</strong> more <strong class='color-ammo'>ammo</strong>, but<br>you can't <strong>store</strong> any more <strong class='color-ammo'>ammo</strong> than that`,
@@ -587,7 +582,7 @@ const tech = {
},
requires: "not non-renewables",
effect() {
tech.ammoCap = 16;
tech.ammoCap = 15;
powerUps.ammo.effect()
},
remove() {
@@ -1049,7 +1044,7 @@ const tech = {
},
{
name: "anticorrelation",
description: "<strong>+100%</strong> <strong class='color-d'>damage</strong><br>after not using your <strong class='color-g'>gun</strong> or <strong class='color-f'>field</strong> for <strong>2</strong> seconds",
description: "if your <strong class='color-g'>gun</strong> or <strong class='color-f'>field</strong> are unused for <strong>2</strong> seconds<br><strong>+100%</strong> <strong class='color-d'>damage</strong>",
maxCount: 1,
count: 0,
frequency: 1,
@@ -1068,7 +1063,7 @@ const tech = {
{
name: "scrap bots",
link: `<a target="_blank" href='https://en.wikipedia.org/wiki/Scrap' class="link">scrap bots</a>`,
description: "after mobs <strong>die</strong> you have a <strong>+33%</strong> chance<br>to build scrap <strong class='color-bot'>bots</strong> that operate for <strong>14</strong> seconds",
description: "after mobs <strong>die</strong> you have a <strong>+33%</strong> chance<br>to build scrap <strong class='color-bot'>bots</strong> that operate for <strong>13</strong> seconds",
maxCount: 3,
count: 0,
frequency: 1,
@@ -1088,7 +1083,7 @@ const tech = {
{
name: "scrap refit",
link: `<a target="_blank" href='https://en.wikipedia.org/wiki/Scrap' class="link">scrap refit</a>`,
description: "after mobs <strong>die</strong><br>reset scrap <strong class='color-bot'>bots</strong> to <strong>14</strong> seconds of operation",
description: "after mobs <strong>die</strong><br>reset scrap <strong class='color-bot'>bots</strong> to <strong>13</strong> seconds of operation",
maxCount: 1,
count: 0,
frequency: 3,
@@ -3036,7 +3031,7 @@ const tech = {
{
name: "many-worlds",
// description: "each <strong>level</strong> is an <strong class='alt'>alternate reality</strong>, where you<br>find a <strong class='color-m'>tech</strong> at the start of each level",
description: `on each new <strong>level</strong> use ${powerUps.orb.research(1)} to enter an<br><strong class='alt'>alternate reality</strong> and spawn a <strong class='color-m'>tech</strong> power up`,
description: `on each new <strong>level</strong> enter an<br><strong class='alt'>alternate reality</strong> and spawn a <strong class='color-m'>tech</strong> power up`,
maxCount: 1,
count: 0,
frequency: 1,
@@ -3243,7 +3238,8 @@ const tech = {
},
{
name: "emergence",
description: "<strong class='color-m'>tech</strong>, <strong class='color-f'>fields</strong>, and <strong class='color-g'>guns</strong> have <strong>+2</strong> <strong>choices</strong><br><strong>+3%</strong> <strong class='color-j'>JUNK</strong> to <strong class='color-m'>tech</strong> pool",
description: "<strong class='color-m'>tech</strong>, <strong class='color-f'>fields</strong>, and <strong class='color-g'>guns</strong> have <strong>+1</strong> <strong>choice</strong><br><strong>+8%</strong> <strong class='color-d'>damage</strong>",
// description: "<strong class='color-m'>tech</strong>, <strong class='color-f'>fields</strong>, and <strong class='color-g'>guns</strong> have <strong>+2</strong> <strong>choices</strong><br><strong>+3%</strong> <strong class='color-j'>JUNK</strong> to <strong class='color-m'>tech</strong> pool",
maxCount: 9,
count: 0,
frequency: 1,
@@ -3252,16 +3248,18 @@ const tech = {
return !tech.isDeterminism
},
requires: "not determinism",
damage: 1.08,
effect() {
tech.extraChoices += 2;
this.refundAmount += tech.addJunkTechToPool(0.03)
tech.extraChoices += 1;
tech.damage *= this.damage
// this.refundAmount += tech.addJunkTechToPool(0.03)
},
refundAmount: 0,
remove() {
tech.extraChoices = 0;
if (this.count > 0 && this.refundAmount > 0) {
tech.removeJunkTechFromPool(this.refundAmount)
this.refundAmount = 0
if (this.count > 0) {
tech.damage /= this.damage
// if (this.refundAmount > 0) tech.removeJunkTechFromPool(this.refundAmount)
}
}
},
@@ -4825,7 +4823,7 @@ const tech = {
},
remove() {
tech.infiniteWaveAmmo = 1
if (this.count > 1 && b.guns[3].savedAmmo !== undefined) {
if (this.count > 0 && b.guns[3].savedAmmo !== undefined) {
b.guns[3].ammo = b.guns[3].savedAmmo
simulation.updateGunHUD();
}
@@ -6188,9 +6186,31 @@ const tech = {
tech.isCapacitor = false;
}
},
{
name: "Bitter electromagnet",
descriptionFunction() { return `<strong>railgun</strong> charges <strong>+33%</strong> slower<br><strong>+100%</strong> <strong>harpoon</strong> density and <strong class='color-d'>damage</strong>` },
isGunTech: true,
maxCount: 3,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("harpoon") && tech.isRailGun
},
requires: "harpoon, railgun",
effect() {
tech.railChargeRate *= 1.06
tech.harpoonDensity += 0.0065
},
remove() {
tech.railChargeRate = 0.97;
tech.harpoonDensity = 0.0065
}
},
{
name: "railgun",
description: `<strong>+50%</strong> <strong>harpoon</strong> density, but they don't <strong>retract</strong><br><strong>+900%</strong> harpoon <strong class='color-ammo'>ammo</strong> per ${powerUps.orb.ammo(1)}`,
description: `<strong>harpoons</strong> can't <strong>retract</strong>, hold fire to charge<br><strong>+50%</strong> <strong>harpoon</strong> density and <strong class='color-d'>damage</strong>`,
// description: `<strong>+900%</strong> <strong>harpoon</strong> <strong class='color-ammo'>ammo</strong>, but it can't <strong>retract</strong><br><strong>+50%</strong> <strong>harpoon</strong> density and <strong class='color-d'>damage</strong>`,
isGunTech: true,
maxCount: 1,
count: 0,
@@ -6414,7 +6434,6 @@ const tech = {
},
remove() {
tech.isHarpoonPowerUp = false
tech.harpoonDensity = 0.004
}
},
{
@@ -8260,6 +8279,39 @@ const tech = {
},
remove() {}
},
{
name: "random",
link: `<a target="_blank" href='https://en.wikipedia.org/wiki/Special:Random' class="link">random</a>`,
delay: 333,
descriptionFunction() {
const delay = 333
const loop = () => {
if ((simulation.isChoosing) && m.alive && !build.isExperimentSelection) {
const dmg = Math.floor(33 * Math.random()) * 0.01
this.text = `<strong style = "font-family: 'Courier New', monospace;">+${(dmg*100).toFixed(0).padStart(2, '0')}%</strong> <strong class='color-d'>damage</strong>`
this.damage = 1 + dmg
if (document.getElementById(`damage-JUNK-id${this.id}`)) document.getElementById(`damage-JUNK-id${this.id}`).innerHTML = this.text
setTimeout(() => { loop() }, delay);
}
}
setTimeout(() => { loop() }, delay);
this.id++
return `<span id = "damage-JUNK-id${this.id}">${this.text}</span>`
},
maxCount: 3,
count: 0,
frequency: 1,
isJunk: true,
allowed() { return !build.isExperimentSelection },
requires: "NOT EXPERIMENT MODE",
damage: 0,
effect() {
tech.damage *= this.damage
},
remove() {
if (this.count > 0) tech.damage /= this.damage
}
},
{
name: "boost",
maxCount: 1,
@@ -8267,9 +8319,7 @@ const tech = {
frequency: 0,
isJunk: true,
isNonRefundable: true,
allowed() {
return !build.isExperimentSelection
},
allowed() { return !build.isExperimentSelection },
requires: "NOT EXPERIMENT MODE",
effect() {
powerUps.spawnDelay("boost", this.spawnCount)
@@ -8306,7 +8356,7 @@ const tech = {
allowed: () => true,
requires: "",
effect() {
if (Math.random() < 0.1) tech.damage *= 7.77
if (Math.random() < 0.1) tech.damage *= 8.77
},
remove() {}
},
@@ -10066,7 +10116,7 @@ const tech = {
allowed() { return m.isShipMode },
requires: "",
effect() {
tech.damage *= 2
tech.damage *= 3
m.look = () => {
// const scale = 0;
@@ -10984,4 +11034,5 @@ const tech = {
isJunkDNA: null,
buffedGun: 0,
isGunChoice: null,
railChargeRate: null,
}

125
todo.txt
View File

@@ -1,20 +1,93 @@
******************************************************** NEXT PATCH **************************************************
harpoon
default fire rate is 10% higher
default harpoon range 15% higher
railgun
tech: Bitter electromagnet - 33% slower charge time for railgun, 100% more density and damage
area effect damage is increased 20%, scales with bitter electromagnet and total charge
charging longer increases harpoon velocity/damage by up to 30%
contributions to charge time are more uniform between:
crouching, fire rate, Bitter electromagnet, and capacitor bank
auto aims no longer disabled on crouch
plasma-bot does 15% more damage, but costs 2 research
fault tolerance 4->5 forever drones, but costs 2 research
surfactant 2->3 foam bots, but costs 2 research
missile-bot costs 1 research
shaped charge 4->3 research cost
renormalization 40->44% chance to refund research
exciton 18->16% chance to spawn
ground state 50->40% reduced energy regen
Bayesian statistics 3.8->3% damage per research, and spawns 3 research
emergence 2->1 extra choice, and +8% damage, and no added JUNK anymore
cache 16->15x ammo
many-worlds 1->0 research to enter an alternate reality on each new level
JUNK tech: startle response - if mobs are near boost damage, and lock mouse until you press escape
finalBoss
health decays a bit faster
spawns 6 mobs at each health threshold
spawns from the different modes are reduced
boss laser damage is 25% reduced
hoppers spawn from the slime tunnel
JUNKtech: random - gives random +damage
bug fixes
*********************************************************** TODO *****************************************************
tech: zombie - sporangium infect mobs, making them fight for you
infected mobs get a status debuff. when they die they return as zombie mob type
zombie mobs run code similar to drones
they inherit color, sides, radius from host
tech: sporangium that grow little trees
the trees have an area of effect damage for about 6-10 seconds
maybe something similar to radioactive drones, but maybe a few smaller shapes
new bot type that makes phonon waves
name: phono-bot?
each bot has to generate it themselves, can't run code in gun.do
synergy with 2 resonance tech
not isotropic? I think no
synergy with bound? phase velocity, amplitude, propagation
harpoon tech that makes auto aim work much better
tech - super balls gain 20 seconds of time and are reset to original launch speed after hitting a mob
railgun
magnetic pinch: harpoon does damage to nearby mobs
draw charge graphic on harpoon
use same code as the damage when fire effect
hookBoss fires a hook that pulls player towards it
hook does a bit of damage
player targeted unless cloaking
also add effect to finalBoss
finalBoss
add synergies between modes:
new modes:
rotating quadrant immunity shield, can't take damage from that quadrant
maybe also attack player near that quadrant
but how to tell the angle of incoming damage
maybe a physics body like the shield but it only covers 1/3 of mob?
falling object warps to ceiling after hitting floor
doesn't end, player needs to kill it
slowly grows?
slow effect zones
random placement or place over player or both!
draw white dot and an outline of area of effect
expanding circle stroke, freeze effect triggers when stroke circle hits fill circle
after 1-2 seconds freeze player if in the zone
also freeze mobs
effect that makes player have to be close to boss
hook that tries to yank the player into hitting finalBoss
does damage
pulls player into center
counter with wormhole, negative mass
player targeted unless cloaking
mob status effect - emit - mobs fire lasers for a few seconds
tech: phosphorescence - mobs emit after being hit with laser beams
run canvas direct pixel editing while game is paused
(I tried it an it really hits performance hard)
just update once every second?
if it uses too much processing have a setting to toggle it off
this might help get players to spend more time relaxing on the power up selection
@@ -41,8 +114,6 @@ it would be nice if there was incentive to go slow when choosing tech so n-gon i
make freeze and __ not cause death at 50% health, but just 600% extra damage for that bullet
new mob status effect - phosphorescence - mobs fire lasers for a few seconds after being hit with lasers
make a new coupling effect for perfect diamagnetism or standing wave
make a faster smaller version of cell boss that also has map collisions
@@ -50,32 +121,6 @@ make a faster smaller version of cell boss that also has map collisions
laserMines need a copy of laser-bot method
this is a very rare bug, so not a priority
finalBoss
add synergies between modes:
black hole can pull in bullets
laser can hurt bullets? maybe bad idea
old mode changes
mine color needs to be move vibrant
hoppers can hop out of the slime and the door on the sides
new modes:
rotating quadrant immunity shield, can't take damage from that quadrant
maybe also attack player near that quadrant
falling object warps to ceiling after hitting floor
doesn't end, player needs to kill it
slowly grows?
slow effect zones
random placement or place over player or both!
draw white dot and an outline of area of effect
expanding circle stroke, freeze effect triggers when stroke circle hits fill circle
after 1-2 seconds freeze player if in the zone
also freeze mobs
effect that makes player have to be close to boss
hook that tries to yank the player into hitting finalBoss
does damage
pulls player into center
counter with wormhole, negative mass
player targeted unless cloaking
JUNK tech description that changes similar to cards in inscription
that changes based on mouse position
can you tell if mouse is over card?
@@ -1059,8 +1104,8 @@ possible names for tech
lenticular lens: is an array of lenses, designed so that when viewed from slightly different angles, different parts of the image underneath are shown.
p-zombie
p-hacking JUNK tech
https://en.wikipedia.org/wiki/High-entropy_alloys
https://en.wikipedia.org/wiki/Refractory_metals
https://en.wikipedia.org/wiki/High-entropy_alloys high yield strength and low ductility, high temp resistance
https://en.wikipedia.org/wiki/Refractory_metals hard, high temp resistance
https://en.wikipedia.org/wiki/Upper-atmospheric_lightning#Elves
prion quine - self replicating protein
Unitarity - https://en.wikipedia.org/wiki/Unitarity_(physics) - all probabilities add up to 1, calculations work the same forward and backwards in time