const tech = {
totalCount: null,
removeCount: 0,
setupAllTech() {
for (let i = 0, len = tech.tech.length; i < len; i++) {
tech.tech[i].isLost = false
tech.tech[i].isBanished = false
tech.tech[i].remove();
tech.tech[i].count = 0
if (tech.tech[i].isJunk) {
tech.tech[i].frequency = 0
} else if (tech.tech[i].frequencyDefault) {
tech.tech[i].frequency = tech.tech[i].frequencyDefault
} else {
tech.tech[i].frequency = 2
}
if (tech.tech[i].name === "heals" || tech.tech[i].name === "ammo" || tech.tech[i].name === "research") tech.tech[i].value = tech.tech[i].defaultValue
}
//remove lore if it's your first time playing since it's confusing
//also remove lore if cheating
tech.removeCount = 0;
tech.pauseEjectTech = 1; //used in paradigm shift
lore.techCount = 0;
if (simulation.isCheating || localSettings.runCount < 1) { //simulation.isCommunityMaps ||
for (let i = 0, len = tech.tech.length; i < len; i++) {
if (tech.tech[i].isLore) {
tech.tech[i].frequency = 0;
tech.tech[i].count = 0;
}
}
}
tech.damage = 1
tech.junkChance = 0;
tech.extraMaxHealth = 0;
tech.totalCount = 0;
simulation.updateTechHUD();
simulation.updateGunHUD();
},
removeTech(index = 'random') {
if (index === 'random') {
const have = [] //find which tech you have
for (let i = 0; i < tech.tech.length; i++) {
if (tech.tech[i].count > 0 && !tech.tech[i].isInstant) have.push(i)
}
if (have.length) {
index = have[Math.floor(Math.random() * have.length)]
} else {
return 0 //if none found don't remove any tech
}
} else if (isNaN(index)) { //find index by name
let found = false;
for (let i = 0; i < tech.tech.length; i++) {
if (index === tech.tech[i].name) {
index = i;
found = true;
break;
}
}
if (!found) return 0 //if name not found don't remove any tech
}
if (tech.tech[index].count === 0) return 0
const totalRemoved = tech.tech[index].count
simulation.makeTextLog(`tech.removeTech("${tech.tech[index].name}")`, 360)
tech.tech[index].remove();
tech.removeCount += totalRemoved
tech.tech[index].count = 0;
tech.totalCount -= totalRemoved
simulation.updateTechHUD();
tech.tech[index].isLost = true
simulation.updateTechHUD();
return totalRemoved //return the total number of tech removed
},
junkChance: 0,
addJunkTechToPool(percent) { //percent is number between 0-1
simulation.makeTextLog(`+${(100 * percent).toFixed(0)}% JUNKtech chance (${(100 * tech.junkChance).toFixed(0)} total chance)`)
// tech.junkChance += (1 - tech.junkChance) * percent
tech.junkChance += percent
if (tech.junkChance < 0.001 || tech.junkChance === undefined) tech.junkChance = 0
if (tech.junkChance > 1) tech.junkChance = 1
return percent
//make an array for possible junk tech to add
// let options = [];
// for (let i = 0; i < tech.tech.length; i++) {
// if (tech.tech[i].count < tech.tech[i].maxCount && tech.tech[i].isJunk) options.push(i);
// }
// if (options.length) {
// let countNonJunk = 0 // count total non junk tech
// for (let i = 0, len = tech.tech.length; i < len; i++) {
// if (tech.tech[i].count < tech.tech[i].maxCount && tech.tech[i].allowed() && !tech.tech[i].isJunk) countNonJunk += tech.tech[i].frequency
// }
// const num = Math.ceil(percent * countNonJunk) //scale number added
// for (let i = 0; i < num; i++) tech.tech[options[Math.floor(Math.random() * options.length)]].frequency++ //add random array options to tech pool
// simulation.makeTextLog(`tech.tech.push(${num.toFixed(0)} JUNK)`)
// return num
// } else {
// return 0
// }
},
removeJunkTechFromPool(percent) {
// for (let j = 0; j < num; j++) {
// for (let i = 0; i < tech.tech.length; i++) {
// if (tech.tech[i].isJunk && tech.tech[i].frequency > 0 && tech.tech[i].count < tech.tech[i].maxCount) {
// tech.tech[i].frequency--
// break
// }
// }
// }
// if (percent > 0) {
// tech.junkChance = (tech.junkChance - percent) / (1 - percent)
// if (tech.junkChance < 0.001 || tech.junkChance === undefined) tech.junkChance = 0
// }
tech.junkChance -= percent
if (tech.junkChance < 0.001 || tech.junkChance === undefined) tech.junkChance = 0
if (tech.junkChance > 1) tech.junkChance = 1
},
giveTech(index = 'random') {
if (index === 'random') {
let options = [];
for (let i = 0; i < tech.tech.length; i++) {
if (tech.tech[i].count < tech.tech[i].maxCount && tech.tech[i].allowed() && !tech.tech[i].isJunk && !tech.tech[i].isLore && !tech.tech[i].isBadRandomOption) options.push(i);
}
// give a random tech from the tech I don't have
if (options.length > 0) {
let newTech = options[Math.floor(Math.random() * options.length)]
simulation.makeTextLog(`tech.giveTech("${tech.tech[newTech].name}") //random tech`);
tech.giveTech(newTech)
}
} else {
if (isNaN(index)) { //find index by name
let found = false;
for (let i = 0; i < tech.tech.length; i++) {
if (index === tech.tech[i].name) {
index = i;
found = true;
break;
}
}
if (!found) return //if name not found don't give any tech
}
if (tech.isMetaAnalysis && tech.tech[index].isJunk) {
simulation.makeTextLog(`//tech: meta-analysis replaced junk tech with random tech`);
tech.giveTech('random')
for (let i = 0; i < 2; i++) powerUps.spawn(m.pos.x + 40 * Math.random(), m.pos.y + 40 * Math.random(), "research");
return
}
if (tech.tech[index].isLost) tech.tech[index].isLost = false; //give specific tech
if (tech.isBanish && tech.tech[index].isBanished) tech.tech[index].isBanished = false //stops the bug where you can't gets stacks of tech you take with decoherence, I think
tech.tech[index].effect(); //give specific tech
tech.tech[index].count++
tech.totalCount++ //used in power up randomization
requestAnimationFrame(() => {
//move new tech to the top of the tech list
if (index > 0 && !build.isExperimentSelection) {
const [item] = tech.tech.splice(index, 1); // Remove the element from the array
tech.tech.unshift(item); // Add the element to the front of the array
}
simulation.updateTechHUD();
})
}
},
setCheating() {
if (!simulation.isCheating) {
simulation.isCheating = true;
level.levelAnnounce();
lore.techCount = 0;
for (let i = 0, len = tech.tech.length; i < len; i++) {
if (tech.tech[i].isLore) {
tech.tech[i].frequency = 0;
tech.tech[i].count = 0;
}
}
console.log('cheating')
sound.tone(250)
sound.tone(300)
sound.tone(375)
}
},
haveGunCheck(name, needActive = true) {
// if (
// !build.isExperimentSelection &&
// b.inventory.length > 2 &&
// name !== b.guns[b.activeGun].name &&
// Math.random() > 2 - b.inventory.length * 0.5
// ) {
// return false
// }
// for (i = 0, len = b.inventory.length; i < len; i++) {
// if (b.guns[b.inventory[i]].name === name) return true
// }
// return false
if (build.isExperimentSelection || !needActive) {
for (i = 0, len = b.inventory.length; i < len; i++) {
if (b.guns[b.inventory[i]].name === name) return true
}
return false
} else { //must be holding gun, this is the standard while playing
return b.inventory.length > 0 && b.guns[b.activeGun].name === name
}
},
hasExplosiveDamageCheck() {
return tech.haveGunCheck("missiles") || (m.fieldMode === 4 && simulation.molecularMode === 1) || tech.missileBotCount > 0 || tech.isBoomBotUpgrade || tech.isIncendiary || tech.isPulseLaser || tech.isTokamak || (tech.haveGunCheck("grenades") && !tech.isNeutronBomb)
},
damage: 1, //used for tech changes to player damage that don't have complex conditions
damageFromTech() {
let dmg = tech.damage * m.fieldDamage
if (tech.isMaxHealthDamage && m.health === m.maxHealth) dmg *= 1.5
if (tech.isNoDefenseDamage && m.defense() === 1) dmg *= 2
if (tech.isImmunityDamage && m.immuneCycle > m.cycle) dmg *= 4
if (tech.isPowerUpDamage) dmg *= 1 + 0.05 * powerUp.length
if (tech.isDamageCooldown) dmg *= m.lastKillCycle + tech.isDamageCooldownTime > m.cycle ? 0.5 : 4
if (tech.isDamageAfterKillNoRegen && m.lastKillCycle + 300 > m.cycle) dmg *= 2
if (tech.isDivisor && b.activeGun !== undefined && b.activeGun !== null && b.guns[b.activeGun].ammo % 3 === 0) dmg *= 1.8
if (tech.isNoGroundDamage) dmg *= m.onGround ? 0.9 : 2
if (tech.isDilate) dmg *= 1.9 + 1.1 * Math.sin(m.cycle * 0.01)
if (tech.isGunChoice && tech.buffedGun === b.inventoryGun) dmg *= 1 + 0.3 * b.inventory.length
if (powerUps.boost.endCycle > m.cycle) dmg *= 1 + powerUps.boost.damage
if (m.coupling && (m.fieldMode === 0 || m.fieldMode === 5)) dmg *= 1 + 0.015 * m.coupling
if (tech.deathSkipTime) dmg *= 1 + 0.6 * tech.deathSkipTime
if (tech.isTechDebt) dmg *= tech.totalCount > 20 ? Math.pow(0.85, tech.totalCount - 20) : 4 - 0.15 * tech.totalCount
if (tech.isAnthropicDamage && tech.isDeathAvoidedThisLevel) dmg *= 2.71828
if (tech.isDupDamage) dmg *= 1 + Math.min(1, tech.duplicationChance())
if (tech.isDamageForGuns) dmg *= 1 + 0.22 * Math.max(0, b.inventory.length - 1)
if (tech.isOneGun && b.inventory.length < 2) dmg *= 1.3
if (tech.isAcidDmg && m.health > 1) dmg *= 1.35;
if (tech.isRerollDamage) dmg *= 1 + Math.max(0, 0.05 * powerUps.research.count)
if (tech.isBotDamage) dmg *= 1 + 0.04 * b.totalBots()
if (tech.restDamage > 1 && player.speed < 1) dmg *= tech.restDamage
if (tech.isLowEnergyDamage) dmg *= 1 + 0.5 * Math.max(0, m.maxEnergy - m.energy)
if (tech.energyDamage) dmg *= 1 + m.energy * 0.23 * tech.energyDamage;
if (tech.isDamageFromBulletCount) dmg *= 1 + bullet.length * 0.01
if (tech.isNoFireDamage && m.cycle > m.fireCDcycle + 120) dmg *= 2
if (tech.isSpeedDamage) dmg *= 1 + Math.min(1, (tech.speedAdded + player.speed) * 0.0193)
if (tech.isAxion && tech.isHarmDarkMatter) dmg *= ((tech.isMoveDarkMatter || tech.isNotDarkMatter) ? 3.2 : 2)
if (tech.isHarmDamage && m.lastHarmCycle + 480 > m.cycle) dmg *= 3;
if (tech.lastHitDamage && m.lastHit) dmg *= 1 + tech.lastHitDamage * m.lastHit
// if (tech.isLowHealthDmg) dmg *= 1 + 0.6 * Math.max(0, 1 - (tech.isEnergyHealth ? m.energy : m.health))
if (tech.isLowHealthDmg) dmg *= 1 + 0.6 * Math.max(0, (tech.isEnergyHealth ? m.maxEnergy - m.energy : m.maxHealth - m.health))
if (tech.isJunkDNA) dmg *= 1 + 2 * tech.junkChance
return dmg
},
duplicationChance() {
return Math.min(1, Math.max(0, (tech.isPowerUpsVanish ? 0.13 : 0) + (tech.isStimulatedEmission ? 0.2 : 0) + tech.duplication + tech.duplicateChance + 0.05 * tech.isExtraGunField + m.duplicateChance + tech.fieldDuplicate + 0.08 * tech.isDuplicateMobs + 0.03 * tech.isMassProduction + 0.04 * tech.isHealAttract + tech.cloakDuplication + (tech.isAnthropicTech && tech.isDeathAvoidedThisLevel ? 0.6 : 0) + 0.06 * tech.isDupEnergy))
},
setTechFrequency(name, frequency) {
for (let i = 0, len = tech.tech.length; i < len; i++) {
if (tech.tech[i].name === name) tech.tech[i].frequency = frequency
}
},
setBotTechFrequency(f = 0) {
for (let i = 0, len = tech.tech.length; i < len; i++) {
if (tech.tech[i].isBotTech) {
switch (tech.tech[i].name) {
case "dynamo-bot":
tech.tech[i].frequency = f
break;
case "orbital-bot":
tech.tech[i].frequency = f
break;
case "laser-bot":
tech.tech[i].frequency = f
break;
case "boom-bot":
tech.tech[i].frequency = f
break;
case "foam-bot":
tech.tech[i].frequency = f
break;
case "nail-bot":
tech.tech[i].frequency = f
break;
}
}
}
},
tech: [{
name: "tungsten carbide",
description: "+400 maximum health
lose health after hard landings",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isSkin: true,
allowed() {
return !m.isAltSkin
},
requires: "not skin",
effect() {
tech.isFallingDamage = true;
m.setMaxHealth();
m.addHealth(3 / simulation.healScale)
m.skin.tungsten()
},
remove() {
tech.isFallingDamage = false;
m.setMaxHealth();
if (this.count) m.resetSkin();
}
},
{
name: "nitinol",
description: "1.3x movement and jumping
0.17 seconds of coyote time",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isSkin: true,
allowed() {
return !m.isAltSkin
},
requires: "not skinned",
effect() {
m.skin.mech();
m.setMovement()
},
remove() {
if (this.count) m.resetSkin();
}
},
{
name: "Higgs mechanism",
description: "4x fire rate
while firing your position is fixed",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isSkin: true,
allowed() {
return !m.isAltSkin && !m.isShipMode && !tech.isAlwaysFire
},
requires: "not skinned, ship mode, automatic",
effect() {
tech.isFireMoveLock = true;
b.setFireCD();
b.setFireMethod();
m.skin.strokeGap();
},
remove() {
tech.isFireMoveLock = false
if (tech.isFireMoveLock) {
b.setFireCD();
b.setFireMethod();
if (this.count) m.resetSkin();
}
}
},
{
name: "Hilbert space",
description: "4x damage
after a collision enter an alternate reality",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isAltRealityTech: true,
isSkin: true,
allowed() {
return !m.isAltSkin && !tech.isResearchReality && !tech.isSwitchReality
},
requires: "not skinned, Ψ(t) collapse, many-worlds",
damage: 4,
effect() {
m.skin.anodize();
tech.damage *= this.damage
tech.isCollisionRealitySwitch = true;
},
remove() {
tech.isCollisionRealitySwitch = false;
if (this.count && m.alive) {
tech.damage /= this.damage
m.resetSkin();
}
}
},
{
name: "aperture",
description: "every 4 seconds your damage cycles
between 0.8x and 3x damage",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isSkin: true,
allowed() {
return !m.isAltSkin
},
requires: "not skinned",
effect() {
tech.isDilate = true
m.skin.dilate()
},
remove() {
tech.isDilate = false
if (this.count) m.resetSkin();
}
},
{
name: "diaphragm",
description: "every 4 seconds your damage taken cycles
between 0.9x and 0.2x damage taken",
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
isSkin: true,
allowed() {
return tech.isDilate
},
requires: "aperture",
effect() {
tech.isDiaphragm = true
m.resetSkin();
m.skin.dilate2()
},
remove() {
tech.isDiaphragm = false
if (this.count) m.resetSkin();
}
},
{
name: "mass-energy equivalence",
description: `energy protects you instead of health`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isSkin: true,
allowed() {
return !m.isAltSkin && !tech.isPiezo && !tech.isRewindAvoidDeath && !tech.isAnnihilation //&& !tech.isAmmoFromHealth && !tech.isRewindGun
},
requires: "not piezoelectricity, CPT, annihilation",
effect() {
m.health = 0
document.getElementById("health").style.display = "none"
document.getElementById("health-bg").style.display = "none"
document.getElementById("dmg").style.backgroundColor = "#0cf";
tech.isEnergyHealth = true;
simulation.mobDmgColor = "rgba(0, 255, 255,0.6)" //"#0cf"
m.displayHealth();
m.lastCalculatedDefense = 0 //this triggers a redraw of the defense bar
m.skin.energy();
},
remove() {
if (this.count > 0) {
tech.isEnergyHealth = false;
document.getElementById("health").style.display = "inline"
document.getElementById("health-bg").style.display = "inline"
document.getElementById("dmg").style.backgroundColor = "#f67";
m.health = Math.max(Math.min(m.maxHealth, m.energy), 0.1);
simulation.mobDmgColor = "rgba(255,0,0,0.7)"
m.displayHealth();
m.lastCalculatedDefense = 0 //this triggers a redraw of the defense bar
m.resetSkin();
}
tech.isEnergyHealth = false;
}
},
{
name: "1st ionization energy",
link: `1st ionization energy`,
descriptionFunction() {
return `convert current and future
into
give +${14 * tech.largerHeals * (tech.isHalfHeals ? 0.5 : 1)} maximum energy`
},
maxCount: 1,
count: 0,
frequency: 5,
frequencyDefault: 5,
allowed() {
return tech.isEnergyHealth && !tech.isOverHeal
},
requires: "mass-energy equivalence, not quenching",
effect() {
powerUps.healGiveMaxEnergy = true; //tech.healMaxEnergyBonus given from heal power up
powerUps.heal.color = "#ff0" //"#0ae"
for (let i = 0; i < powerUp.length; i++) { //find active heal power ups and adjust color live
if (powerUp[i].name === "heal") powerUp[i].color = powerUps.heal.color
}
},
remove() {
powerUps.healGiveMaxEnergy = false;
// tech.healMaxEnergyBonus = 0
powerUps.heal.color = "#0eb"
for (let i = 0; i < powerUp.length; i++) { //find active heal power ups and adjust color live
if (powerUp[i].name === "heal") powerUp[i].color = powerUps.heal.color
}
}
},
{
name: "depolarization",
descriptionFunction() {
return `4x damage, but if a mob dies
0.5x damage for ${(tech.isDamageCooldownTime / 60).toFixed(1)} seconds instead`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isSkin: true,
allowed() {
return !m.isAltSkin
},
requires: "not skinned",
effect() {
m.skin.polar();
tech.isDamageCooldown = true;
},
remove() {
tech.isDamageCooldown = false;
if (this.count) m.resetSkin();
}
},
{
name: "repolarization",
descriptionFunction() {
return `the damage from depolarization
resets 1.25 seconds sooner after a mob dies`
},
maxCount: 3,
count: 0,
frequency: 4,
frequencyDefault: 4,
allowed() {
return tech.isDamageCooldown
},
requires: "depolarization",
effect() {
tech.isDamageCooldownTime -= 75
},
remove() {
tech.isDamageCooldownTime = 240
}
},
{
name: "CPT symmetry",
descriptionFunction() {
return `after losing health, if you have above ${(85 * Math.min(100, m.maxEnergy)).toFixed(0)} energy
rewind time for 20 energy per second`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isSkin: true,
allowed() {
return !m.isAltSkin && m.fieldUpgrades[m.fieldMode].name !== "standing wave" && !tech.isRewindField && !tech.isEnergyHealth
},
requires: "not skinned, standing wave, max energy reduction, retrocausality, mass-energy",
effect() {
tech.isRewindAvoidDeath = true;
m.skin.CPT()
},
remove() {
tech.isRewindAvoidDeath = false;
if (this.count) m.resetSkin();
}
},
{
name: "causality bots",
link: `causality bots`,
description: "when you rewind build scrap bots
that protect you for about 9 seconds",
maxCount: 3,
count: 0,
frequency: 2,
frequencyDefault: 2,
isBotTech: true,
allowed() {
return tech.isRewindAvoidDeath || tech.isRewindField
},
requires: "CPT, retrocausality",
effect() {
tech.isRewindBot++;
},
remove() {
tech.isRewindBot = 0;
}
},
{
name: "causality bombs",
link: `causality bombs`,
description: "when you rewind drop several grenades", //
become invulnerable until they explode
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isRewindAvoidDeath || tech.isRewindField
},
requires: "CPT, retrocausality",
effect() {
tech.isRewindGrenade = true;
},
remove() {
tech.isRewindGrenade = false;
}
},
{
name: "ternary", //"divisor",
descriptionFunction() {
return `1.8x damage while your current gun
has ammo divisible by 3`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed: () => true,
requires: "",
// divisible: 3, // + Math.floor(6 * Math.random()),
effect() {
tech.isDivisor = true;
},
remove() {
tech.isDivisor = false;
}
},
{
name: "integrated armament",
link: `integrated armament`,
description: `1.3x damage, but new guns replace
your current gun and convert guntech`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return b.inventory.length === 1
},
requires: "only 1 gun",
effect() {
tech.isOneGun = true;
},
remove() {
tech.isOneGun = false;
}
},
{
name: "ordnance",
description: "spawn a gun, gain 2x guntech frequency
+6% JUNKtech chance",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isInstant: true,
isBadRandomOption: true,
allowed: () => tech.junkChance < 1,
requires: "",
effect() {
powerUps.spawn(m.pos.x, m.pos.y, "gun");
for (let i = 0, len = tech.tech.length; i < len; i++) {
if (tech.tech[i].isGunTech) tech.tech[i].frequency *= 2
}
this.refundAmount += tech.addJunkTechToPool(0.06)
},
refundAmount: 0,
remove() {
if (this.count > 0 && this.refundAmount > 0) {
tech.removeJunkTechFromPool(this.refundAmount)
this.refundAmount = 0
}
}
},
{
name: "arsenal",
descriptionFunction() {
return `1.25x damage per unequipped gun
(${(1 + 0.25 * Math.max(0, b.inventory.length - 1)).toFixed(2)}x)`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed: () => b.inventory.length > 1,
requires: "at least 2 guns",
effect() {
tech.isDamageForGuns = true;
},
remove() {
tech.isDamageForGuns = false;
}
},
{
name: "active cooling",
descriptionFunction() {
return `1.25x fire rate per unequipped gun
(${(1 / Math.pow(0.8, Math.max(0, b.inventory.length - 1))).toFixed(2)}x)`
}, //
but not including your equipped gun` },
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed: () => b.inventory.length > 1,
requires: "at least 2 guns",
effect() {
tech.isFireRateForGuns = true;
b.setFireCD();
},
remove() {
tech.isFireRateForGuns = false;
b.setFireCD();
}
},
{
name: "pigeonhole principle",
descriptionFunction() {
let info = ""
if (this.count > 0 && Number.isInteger(tech.buffedGun) && b.inventory.length) {
let gun = b.guns[b.inventory[tech.buffedGun]].name
info = `
this level: ${(1.3 * Math.max(0, b.inventory.length)).toFixed(2)}x damage for ${gun}`
}
return `a new gun is chosen to be improved each level
1.3x damage per gun for the chosen gun${info}`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return b.inventory.length > 1
},
requires: "at least 2 guns",
effect() {
tech.isGunChoice = true
//switches gun on new level
//generalist uses the same chosen gun so they match
},
remove() {
tech.isGunChoice = false;
}
},
{
name: "generalist",
description: "spawn 7 guns, but you can't switch guns
your equipped gun cycles after each level",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isInstant: true,
isBadRandomOption: true,
allowed() {
return b.inventory.length < b.guns.length - 5 && b.inventory.length > 1
},
requires: "at least 2 guns, at least 5 unclaimed guns",
effect() {
tech.isGunCycle = true;
for (let i = 0; i < 7; i++) powerUps.spawn(m.pos.x + 10 * Math.random(), m.pos.y + 10 * Math.random(), "gun");
},
remove() {
tech.isGunCycle = false; // only set to false if you don't have this tech
}
},
{
name: "ad hoc",
descriptionFunction() {
return `spawn a ${powerUps.orb.heal()}, ${powerUps.orb.research(1)}, ${powerUps.orb.ammo(1)}, field, gun, or tech
for each of your guns`
},
maxCount: 1, //random power up
count: 0,
frequency: 1,
frequencyDefault: 1,
isInstant: true,
allowed() {
return b.inventory.length > 1
},
requires: "at least 2 guns",
effect() {
for (let i = 0; i < b.inventory.length; i++) {
if (Math.random() < 1 / 6) {
powerUps.spawn(m.pos.x + 10 * Math.random(), m.pos.y + 10 * Math.random(), "gun");
} else if (Math.random() < 1 / 5) {
powerUps.spawn(m.pos.x + 10 * Math.random(), m.pos.y + 10 * Math.random(), "tech");
} else if (Math.random() < 1 / 4) {
powerUps.spawn(m.pos.x + 10 * Math.random(), m.pos.y + 10 * Math.random(), "field");
} else if (Math.random() < 1 / 3) {
powerUps.spawn(m.pos.x + 10 * Math.random(), m.pos.y + 10 * Math.random(), "heal");
} else if (Math.random() < 1 / 2) {
powerUps.spawn(m.pos.x + 10 * Math.random(), m.pos.y + 10 * Math.random(), "ammo");
} else {
powerUps.spawn(m.pos.x + 10 * Math.random(), m.pos.y + 10 * Math.random(), "research");
}
}
},
remove() { }
},
{
name: "applied science",
description: `get a random guntech
for each of your guns`, //spawn ${powerUps.orb.research(1)} and
maxCount: 1,
count: 0,
isInstant: true,
frequency: 2,
frequencyDefault: 2,
allowed() {
return b.inventory.length > 1
},
requires: "at least 2 guns",
effect() {
for (let i = b.inventory.length - 1; i > -1; i--) { //backwards because some tech can remove or add guns
const gunTechPool = [] //find gun tech for this gun
for (let j = 0, len = tech.tech.length; j < len; j++) {
// console.log(j, tech.tech[j].isGunTech, tech.tech[j].allowed(), !tech.tech[j].isJunk, !tech.tech[j].isBadRandomOption, tech.tech[j].count < tech.tech[j].maxCount)
const originalActiveGunIndex = b.activeGun //set current gun to active so allowed works
b.activeGun = b.inventory[i] //to make the .allowed work for guns that aren't active
if (tech.tech[j].isGunTech && tech.tech[j].allowed() && !tech.tech[j].isJunk && !tech.tech[j].isBadRandomOption && tech.tech[j].count < tech.tech[j].maxCount) {
const regex = tech.tech[j].requires.search(b.guns[b.inventory[i]].name) //get string index of gun name
const not = tech.tech[j].requires.search(' not ') //get string index of ' not '
if (regex !== -1 && (not === -1 || not > regex)) gunTechPool.push(j) //look for the gun name in the requirements, but the gun name needs to show up before the word ' not '
}
b.activeGun = originalActiveGunIndex
if (!b.guns[b.activeGun].have) {
if (b.inventory.length === 0) {
b.activeGun = null
} else {
b.activeGun = b.inventory[0]
}
b.inventoryGun = 0;
}
}
if (gunTechPool.length) {
const index = Math.floor(Math.random() * gunTechPool.length)
// console.log(gunTechPool, index, gunTechPool[index], tech.tech[gunTechPool[index]].name)
simulation.makeTextLog(`tech.giveTech("${tech.tech[gunTechPool[index]].name}")`, 360)
// tech.tech[gunTechPool[index]].isInstant = true //makes it not remove properly under paradigm shift
tech.giveTech(gunTechPool[index]) // choose from the gun pool
// console.log(gunTechPool, index, gunTechPool[index], tech.tech[gunTechPool[index]].name)
// tech.tech[gunTechPool[index]].isFromAppliedScience = true //makes it not remove properly under paradigm shift
}
}
simulation.boldActiveGunHUD();
},
remove() { }
},
{
name: "supply chain",
descriptionFunction() {
return `spawn a gun
spawn ${powerUps.orb.ammo(1)} that will 2x your gun's ammo`
},
maxCount: 9,
count: 0,
isInstant: true,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
let ammoCount = 0 //count ammo
if (b.activeGun && b.activeGun !== undefined && b.guns[b.activeGun].have && b.guns[b.activeGun].ammo !== Infinity) {
ammoCount += b.guns[b.activeGun].ammo / b.guns[b.activeGun].ammoPack
}
powerUps.spawnDelay("ammo", Math.ceil(ammoCount))
powerUps.spawn(m.pos.x, m.pos.y, "gun");
},
remove() { }
},
{
name: "marginal utility",
descriptionFunction() {
if (this.count === 0) this.gun = Math.floor(Math.random() * (b.guns.length - 1)) //don't pick laser
return `2x ammo per ${powerUps.orb.ammo(1)} for ${b.guns[this.gun].name}`
},
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() { return true },
requires: "",
gun: undefined,
effect() {
if (this.gun === undefined) this.gun = Math.floor(Math.random() * (b.guns.length - 1)) //don't pick laser
simulation.makeTextLog(`${b.guns[this.gun].ammoPack} → ${2 * b.guns[this.gun].ammoPack} average ammo per ${powerUps.orb.ammo(1)} for ${b.guns[this.gun].name}`)
b.guns[this.gun].ammoPack *= 2
// simulation.makeTextLog(`${(tech.interestRate * 100).toFixed(0)}% interest on health = ${h > 20 ? h + powerUps.orb.heal(1) : powerUps.orb.heal(h)}`)
// for (let i = 0; i < 4; i++) powerUps.spawn(m.pos.x + 10 * Math.random(), m.pos.y + 10 * Math.random(), "ammo");
},
remove() {
if (this.count) {
b.guns[this.gun].ammoPack /= 2
}
}
},
{
name: "Pareto efficiency",
descriptionFunction() {
return `for each of your guns
randomly get 5x or 0.2x ammo per ${powerUps.orb.ammo(1)}`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBadRandomOption: true,
allowed() {
return b.inventory.length > 2
},
requires: "at least 3 guns",
effect() {
let options = []
for (let i = 0; i < b.inventory.length; i++) options.push(b.inventory[i])
options = shuffle(options)
for (let i = 0; i < options.length; i++) {
const index = options[i]
const scale = (i < options.length / 2) ? 4 : 0.25
simulation.makeTextLog(`${(b.guns[index].ammoPack).toFixed(1)} → ${(b.guns[index].ammoPack * scale).toFixed(1)} average ammo per ${powerUps.orb.ammo(1)} for ${b.guns[index].name}`, Infinity)
b.guns[index].ammoPack *= scale
}
},
remove() { }
},
{
name: "logistics",
description: `2x ammo per ${powerUps.orb.ammo()}, but
ammo is only added to your current gun`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isEnergyNoAmmo
},
requires: "not non-renewables",
effect() {
tech.isAmmoForGun = true;
},
remove() {
tech.isAmmoForGun = false;
}
},
{
name: "cache",
link: `cache`,
description: `17x ammo per ${powerUps.orb.ammo()}, but
you can't store additional ammo`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isEnergyNoAmmo
},
requires: "not non-renewables",
effect() {
tech.ammoCap = 17;
powerUps.ammo.effect()
},
remove() {
tech.ammoCap = 0;
}
},
{
name: "catabolism",
descriptionFunction() {
return `if you fire while out of ammo
spawn ${powerUps.orb.ammo(4)} and ${tech.isEnergyHealth ? "–4 maximum energy" : "–2 maximum health"}`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isEnergyNoAmmo
},
requires: "not non-renewables",
effect() {
tech.isAmmoFromHealth = true;
},
remove() {
tech.isAmmoFromHealth = false;
}
},
{
name: "non-renewables",
description: `2x damage
${powerUps.orb.ammo()} can't spawn`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isAmmoFromHealth && !tech.isBoostReplaceAmmo
},
requires: "not catabolism, quasiparticles",
damage: 2,
effect() {
tech.damage *= this.damage
tech.isEnergyNoAmmo = true;
},
remove() {
if (this.count && m.alive) tech.damage /= this.damage
tech.isEnergyNoAmmo = false;
}
},
{
name: "desublimated ammunition",
description: `if crouching
alternating shots use no ammo`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed: () => true,
requires: "",
effect() {
tech.crouchAmmoCount = true
},
remove() {
tech.crouchAmmoCount = false;
}
},
{
name: "gun turret",
description: "if crouching
0.3x damage taken",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isTurret = true
},
remove() {
tech.isTurret = false;
}
},
{
name: "dead reckoning",
description: `if your speed is 0
1.5x damage`,
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.restDamage += 0.5
},
remove() {
tech.restDamage = 1;
}
},
{
name: "Newtons 1st law",
descriptionFunction() {
return `damage taken is proportional to your speed
up to 0.2x damage taken at 55 speed (${(1 - Math.min((tech.speedAdded + player.speed) * 0.0193, 0.8)).toFixed(2)}x)`
},
description: "",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isSpeedHarm = true //max at speed = 40
},
remove() {
tech.isSpeedHarm = false
}
},
{
name: "Newtons 2nd law",
descriptionFunction() {
return `damage is proportional to your speed
up to 2x damage at 55 speed (${(1 + Math.min(1, ((tech.speedAdded + player.speed) * 0.0193))).toFixed(2)}x)`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isSpeedDamage = true //max at speed = 40
},
remove() {
tech.isSpeedDamage = false
}
},
{
name: "modified Newtonian dynamics",
descriptionFunction() {
return `your speed counts as +20 higher
(for Newton's 1st and 2nd laws)`
},
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isSpeedDamage || tech.isSpeedHarm
},
requires: "Newtons 1st or 2nd law",
effect() {
tech.speedAdded = 20
},
remove() {
tech.speedAdded = 0
}
},
{
name: "kinetic bombardment",
description: "far away mobs take more damage
up to 1.3x damage at 3000 displacement",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isFarAwayDmg = true;
},
remove() {
tech.isFarAwayDmg = false;
}
},
{
name: "microstates",
link: `microstates`,
descriptionFunction() {
return `use ${powerUps.orb.research(3)}
1.01x damage per bullet or bot`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return powerUps.research.count > 2 || build.isExperimentSelection
},
requires: "",
effect() {
tech.isDamageFromBulletCount = true
for (let i = 0; i < 3; i++) {
if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
}
},
remove() {
tech.isDamageFromBulletCount = false
if (this.count > 0) {
powerUps.research.changeRerolls(3)
}
}
},
{
name: "regression",
description: "bullet collisions increase vulnerability to
damage by 1.05x for mobs and +1.025x for bosses",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isLessDamageReduction = true
},
remove() {
tech.isLessDamageReduction = false
}
},
{
name: "simulated annealing",
description: "1.2x damage
0.8x fire rate",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
damage: 1.2,
effect() {
tech.damage *= this.damage
tech.slowFire = 1.25
b.setFireCD();
},
remove() {
if (this.count && m.alive) tech.damage /= this.damage
tech.slowFire = 1;
b.setFireCD();
}
},
{
name: "heuristics",
description: "1.3x fire rate",
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.fireRate *= 0.77
b.setFireCD();
},
remove() {
tech.fireRate = 1;
b.setFireCD();
}
},
{
name: "mechatronics",
descriptionFunction() {
let damageTotal = 1
for (let i = 0; i < this.damageSoFar.length; i++) damageTotal *= this.damageSoFar[i]
let currentDamage = ""
if (this.count) currentDamage = `
(${(damageTotal).toFixed(2)}x)`
return `randomly gain between 1x and 1.3x damage` + currentDamage
},
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() { return true },
requires: "",
damage: 1.1,
damageSoFar: [], //tracks the random damage upgrades so it can be removed and in descriptionFunction
effect() {
const damage = (Math.floor((Math.random() * 0.3 + 1) * 100)) / 100
tech.damage *= damage
this.damageSoFar.push(damage)
},
remove() {
if (this.count && m.alive) for (let i = 0; i < this.damageSoFar.length; i++) tech.damage /= this.damageSoFar[i]
this.damageSoFar.length = 0
}
},
{
name: "dynamical systems",
description: `use ${powerUps.orb.research(2)}
1.3x damage`,
// isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return powerUps.research.count > 1 || build.isExperimentSelection
},
requires: "",
// allowed() {
// return (m.fieldMode === 5 || m.fieldMode === 7 || m.fieldMode === 8) && (build.isExperimentSelection || powerUps.research.count > 1)
// },
// requires: "cloaking, pilot wave, or plasma torch",
damage: 1.3,
effect() {
tech.damage *= this.damage
tech.isCloakingDamage = true
for (let i = 0; i < 2; i++) {
if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
}
},
remove() {
tech.isCloakingDamage = false
if (this.count && m.alive) {
tech.damage /= this.damage
powerUps.research.changeRerolls(2)
}
}
},
{
name: "anti-shear topology",
link: `anti-shear topology`,
description: "your bullets last 1.3x longer", //
drone spore worm flea missile foam wave neutron ice",
maxCount: 3,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed: () => true,
requires: "",
effect() {
tech.bulletsLastLonger += 0.3
},
remove() {
tech.bulletsLastLonger = 1;
}
},
{
name: "fracture analysis",
description: "if a mob is stunned it takes
5x damage from bullet impacts",
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isStunField || tech.oneSuperBall || tech.isCloakStun || tech.isOrbitBotUpgrade || tech.isStun
},
requires: "a stun effect",
effect() {
tech.isCrit = true;
},
remove() {
tech.isCrit = false;
}
},
{
name: "shear stress",
description: "after mobs die
they fire a nail at nearby mobs",
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.sporesOnDeath && !tech.isExplodeMob && !tech.botSpawner && !tech.isMobBlockFling && !tech.iceIXOnDeath
},
requires: "no other mob death tech",
effect() {
tech.nailsDeathMob++
},
remove() {
tech.nailsDeathMob = 0;
}
},
{
name: "thermal runaway",
description: "after mobs die they explode",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.sporesOnDeath && !tech.nailsDeathMob && !tech.botSpawner && !tech.isMobBlockFling && !tech.iceIXOnDeath
},
requires: "no other mob death tech",
effect() {
tech.isExplodeMob = true;
},
remove() {
tech.isExplodeMob = false;
}
},
{
name: "zoospore vector",
link: `zoospore vector`,
descriptionFunction() {
return `after mobs die there is a 10% chance
they grow ${b.guns[6].nameString('s')}`
},
// description: "after mobs die
they have a +10% chance to grow spores",
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.nailsDeathMob && !tech.isExplodeMob && !tech.botSpawner && !tech.isMobBlockFling && !tech.iceIXOnDeath
},
requires: "no other mob death tech",
effect() {
tech.sporesOnDeath += 0.1;
// if (tech.isSporeWorm) {
// for (let i = 0; i < 4; i++) b.worm(m.pos)
// } else {
// for (let i = 0; i < 8; i++) b.spore(m.pos)
// }
},
remove() {
tech.sporesOnDeath = 0;
}
},
{
name: "propagator",
description: "after mobs die advance time 0.5 seconds
1.6x damage",
maxCount: 3,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed: () => true,
requires: "",
effect() {
tech.deathSkipTime++
},
remove() {
tech.deathSkipTime = 0
}
},
{
name: "exciton",
descriptionFunction() {
return `after mobs die they have a 14% chance to
spawn ${powerUps.orb.boost(1)} that give ${(1 + powerUps.boost.damage).toFixed(2)}x damage for ${(powerUps.boost.duration / 60).toFixed(0)} seconds`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed: () => true,
requires: "",
effect() {
tech.isBoostPowerUps = true
},
remove() {
tech.isBoostPowerUps = false
}
},
{
name: "band gap",
descriptionFunction() {
return `${powerUps.orb.boost(1)} give 1.77x damage
but their duration is reduced by 1 second`
},
maxCount: 9,
count: 1,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isBoostPowerUps || tech.isBoostReplaceAmmo
},
requires: "exciton, quasiparticles",
effect() {
powerUps.boost.duration -= 60
powerUps.boost.damage += 0.77
},
remove() {
powerUps.boost.duration = 600
powerUps.boost.damage = 1.25
}
},
{
name: "collider",
descriptionFunction() {
return `after mobs die existing power ups
collide to form new power ups`
// return `after mobs die there is a +33% chance to convert
${powerUps.orb.heal()}, ${powerUps.orb.ammo()}, ${powerUps.orb.research(1)}, tech, field, gun into other types`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed: () => true,
requires: "",
effect() {
tech.collidePowerUps = true
},
remove() {
tech.collidePowerUps = false
}
},
{
name: "bubble fusion",
descriptionFunction() {
return `after destroying a mob's shield
spawn 1-2 ${powerUps.orb.heal()}, ${powerUps.orb.ammo()}, or ${powerUps.orb.research(1)} (once per mob)`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isHealTech: true,
allowed() {
return true
},
requires: "",
effect() {
tech.isShieldAmmo = true;
},
remove() {
tech.isShieldAmmo = false;
}
},
{
name: "enthalpy",
descriptionFunction() {
return `after mobs die
they have an 8% chance to spawn ${powerUps.orb.heal(1)}`
},
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
isHealTech: true,
allowed() {
return true
},
requires: "",
effect() {
tech.healSpawn += 0.08;
},
remove() {
tech.healSpawn = 0;
}
},
{
name: "cascading failure",
description: "3x damage to mobs below 25% durability",
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return tech.mobSpawnWithHealth > 0
},
requires: "reaction inhibitor",
effect() {
tech.isMobLowHealth = true
},
remove() {
tech.isMobLowHealth = false
}
},
{
name: "reaction inhibitor",
description: "mobs spawn with 0.88x initial durability",
maxCount: 3,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isMobFullHealthCloak
},
requires: "not topological defect",
effect() {
tech.mobSpawnWithHealth++
mobs.setMobSpawnHealth()
for (let i = 0; i < mob.length; i++) {
if (mob.health > mobs.mobSpawnWithHealth) mob.health = mobs.mobSpawnWithHealth
}
},
remove() {
tech.mobSpawnWithHealth = 0
mobs.setMobSpawnHealth()
}
},
{
name: "scrap bots",
link: `scrap bots`,
description: "after mobs die you have a 33% chance
to build scrap bots that operate for 15 seconds",
maxCount: 3,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBotTech: true,
allowed() {
return !tech.sporesOnDeath && !tech.nailsDeathMob && !tech.isExplodeMob && !tech.isMobBlockFling && !tech.iceIXOnDeath
},
requires: "no other mob death tech",
effect() {
tech.botSpawner += 0.33;
},
remove() {
tech.botSpawner = 0;
}
},
{
name: "scrap refit",
link: `scrap refit`,
description: "after mobs die
reset scrap bots to 15 seconds of operation",
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
isBotTech: true,
allowed() {
return tech.botSpawner
},
requires: "scrap bots",
effect() {
tech.isBotSpawnerReset = true;
},
remove() {
tech.isBotSpawnerReset = false;
}
},
{
name: "nail-bot",
link: `nail-bot`,
description: "a bot fires nails at mobs in line of sight",
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBot: true,
isBotTech: true,
allowed() {
return true
},
requires: "",
effect() {
tech.nailBotCount++;
b.nailBot();
},
remove() {
if (this.count) {
tech.nailBotCount -= this.count;
b.clearPermanentBots();
b.respawnBots();
}
}
},
{
name: "nail-bot upgrade",
link: `nail-bot upgrade`,
description: "convert your bots to nail-bots
4x fire rate and 1.4x nail velocity",
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
isBotTech: true,
allowed() {
return tech.nailBotCount > 1 && !b.hasBotUpgrade()
},
requires: "2 or more nail bots and no other bot upgrade",
effect() {
tech.isNailBotUpgrade = true
b.convertBotsTo("nail-bot")
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType === 'nail') bullet[i].isUpgraded = true
}
tech.setBotTechFrequency()
tech.setTechFrequency("nail-bot", 5)
},
remove() {
if (this.count) {
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType === 'nail') bullet[i].isUpgraded = false
}
tech.setBotTechFrequency(1)
}
tech.isNailBotUpgrade = false
}
},
{
name: "foam-bot",
link: `foam-bot`,
description: "a bot sprays sticky foam at nearby mobs",
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBot: true,
isBotTech: true,
allowed() {
return true
},
requires: "",
effect() {
tech.foamBotCount++;
b.foamBot();
},
remove() {
if (this.count) {
tech.foamBotCount -= this.count;
b.clearPermanentBots();
b.respawnBots();
}
}
},
{
name: "foam-bot upgrade",
link: `foam-bot upgrade`,
description: "convert your bots to foam-bots
2.5x foam size and fire rate",
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
isBotTech: true,
allowed() {
return tech.foamBotCount > 1 && !b.hasBotUpgrade()
},
requires: "2 or more foam bots and no other bot upgrade",
effect() {
tech.isFoamBotUpgrade = true
b.convertBotsTo("foam-bot")
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType === 'foam') bullet[i].isUpgraded = true
}
tech.setBotTechFrequency()
tech.setTechFrequency("foam-bot", 5)
},
remove() {
if (this.count) {
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType === 'foam') bullet[i].isUpgraded = false
}
tech.setBotTechFrequency(1)
}
tech.isFoamBotUpgrade = false
}
},
{
name: "sound-bot",
link: `sound-bot`,
description: "a bot emits expanding arcs
aimed towards nearby mobs",
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBot: true,
isBotTech: true,
allowed() { return true },
requires: "",
effect() {
tech.soundBotCount++;
b.soundBot();
},
remove() {
if (this.count) {
tech.soundBotCount -= this.count;
b.clearPermanentBots();
b.respawnBots();
}
}
},
{
name: "sound-bot upgrade",
link: `sound-bot upgrade`,
description: "convert your bots to sound-bots
2x wave fire rate, damage, and duration",
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
isBotTech: true,
allowed() {
return tech.soundBotCount > 1 && !b.hasBotUpgrade()
},
requires: "2 or more sound bots and no other bot upgrade",
effect() {
tech.isSoundBotUpgrade = true
b.convertBotsTo("sound-bot")
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType === 'sound') bullet[i].isUpgraded = true
}
tech.setBotTechFrequency()
tech.setTechFrequency("sound-bot", 5)
},
remove() {
if (this.count) {
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType === 'sound') bullet[i].isUpgraded = false
}
tech.setBotTechFrequency(1)
}
tech.isSoundBotUpgrade = false
}
},
{
name: "boom-bot",
link: `boom-bot`,
description: "a bot defends the space around you
ignites an explosion after hitting a mob",
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBot: true,
isBotTech: true,
allowed() {
return true
},
requires: "",
effect() {
tech.boomBotCount++;
b.boomBot();
},
remove() {
if (this.count) {
tech.boomBotCount -= this.count;
b.clearPermanentBots();
b.respawnBots();
}
}
},
{
name: "boom-bot upgrade",
link: `boom-bot upgrade`,
description: "convert your bots to boom-bots
4x explosion damage and size",
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
isBotTech: true,
allowed() {
return tech.boomBotCount > 1 && !b.hasBotUpgrade()
},
requires: "2 or more boom bots and no other bot upgrade",
effect() {
tech.isBoomBotUpgrade = true
b.convertBotsTo("boom-bot")
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType === 'boom') bullet[i].isUpgraded = true
}
tech.setBotTechFrequency()
tech.setTechFrequency("boom-bot", 5)
},
remove() {
if (this.count) {
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType === 'boom') bullet[i].isUpgraded = false
}
tech.setBotTechFrequency(1)
}
tech.isBoomBotUpgrade = false
}
},
{
name: "laser-bot",
link: `laser-bot`,
description: "a bot uses energy to emit a laser beam
that targets nearby mobs",
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBot: true,
isBotTech: true,
allowed() {
return m.maxEnergy > 0.5
},
requires: "maximum energy above 50",
effect() {
tech.laserBotCount++;
b.laserBot();
},
remove() {
if (this.count) {
tech.laserBotCount -= this.count;
b.clearPermanentBots();
b.respawnBots();
}
}
},
{
name: "laser-bot upgrade",
link: `laser-bot upgrade`,
description: "convert your bots to laser-bots
2x damage, efficiency, and range",
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
isBotTech: true,
allowed() {
return tech.laserBotCount > 1 && !b.hasBotUpgrade()
},
requires: "2 or more laser bots and no other bot upgrade",
effect() {
tech.isLaserBotUpgrade = true
b.convertBotsTo("laser-bot")
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType === 'laser') bullet[i].isUpgraded = true
}
tech.setBotTechFrequency()
tech.setTechFrequency("laser-bot", 5)
},
remove() {
if (this.count) {
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType === 'laser') bullet[i].isUpgraded = false
}
tech.setBotTechFrequency(1)
}
tech.isLaserBotUpgrade = false
}
},
{
name: "orbital-bot",
link: `orbital-bot`,
description: "a bot is locked in orbit around you
stuns and damages mobs on contact",
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBot: true,
isBotTech: true,
allowed() {
return true
},
requires: "",
effect() {
b.orbitBot();
tech.orbitBotCount++;
},
remove() {
if (this.count) {
tech.orbitBotCount -= this.count;
b.clearPermanentBots();
b.respawnBots();
}
}
},
{
name: "orbital-bot upgrade",
link: `orbital-bot upgrade`,
description: "convert your bots to orbital-bots
4x orbital damage and 2x radius",
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
isBotTech: true,
allowed() {
return tech.orbitBotCount > 1 && !b.hasBotUpgrade()
},
requires: "2 or more orbital bots and no other bot upgrade",
effect() {
tech.isOrbitBotUpgrade = true
b.convertBotsTo("orbital-bot")
const range = 190 + 120 * tech.isOrbitBotUpgrade
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType === 'orbit') {
bullet[i].isUpgraded = true
bullet[i].range = range
bullet[i].orbitalSpeed = Math.sqrt(0.25 / range)
}
}
tech.setBotTechFrequency()
tech.setTechFrequency("orbital-bot", 5)
},
remove() {
if (this.count) {
const range = 190 + 100 * tech.isOrbitBotUpgrade
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType === 'orbit') {
bullet[i].range = range
bullet[i].orbitalSpeed = Math.sqrt(0.25 / range)
}
}
tech.setBotTechFrequency(1)
}
tech.isOrbitBotUpgrade = false
}
},
{
name: "dynamo-bot",
link: `dynamo-bot`,
description: "a bot damages mobs while it traces your path
+8 energy per second when nearby",
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBot: true,
isBotTech: true,
allowed() {
return true
},
requires: "",
effect() {
tech.dynamoBotCount++;
b.dynamoBot();
},
remove() {
if (this.count) {
tech.dynamoBotCount -= this.count;
b.clearPermanentBots();
b.respawnBots();
}
}
},
{
name: "dynamo-bot upgrade",
link: `dynamo-bot upgrade`,
description: "convert your bots to dynamo-bots
+24 energy per second when nearby",
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
isBotTech: true,
allowed() {
return tech.dynamoBotCount > 1 && !b.hasBotUpgrade()
},
requires: "2 or more dynamo bots and no other bot upgrade",
effect() {
tech.isDynamoBotUpgrade = true
b.convertBotsTo("dynamo-bot")
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType === 'dynamo') bullet[i].isUpgraded = true
}
tech.setBotTechFrequency()
tech.setTechFrequency("dynamo-bot", 5)
},
remove() {
if (this.count) {
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType === 'dynamo') bullet[i].isUpgraded = false
}
tech.setBotTechFrequency(1)
}
tech.isDynamoBotUpgrade = false
}
},
{
name: "perimeter defense",
description: "for each permanent bot
0.96x damage taken",
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
isBotTech: true,
allowed() {
return b.totalBots() > 1
},
requires: "at least 2 bots",
effect() {
tech.isBotArmor = true
},
remove() {
tech.isBotArmor = false
}
},
{
name: "network effect",
description: "for each permanent bot
1.04x damage",
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
isBotTech: true,
allowed() {
return b.totalBots() > 1
},
requires: "at least 2 bots",
effect() {
tech.isBotDamage = true
},
remove() {
tech.isBotDamage = false
}
},
{
name: "bot fabrication",
link: `bot fabrication`,
descriptionFunction() {
return `after you collect ${powerUps.orb.research(2 + Math.floor(0.1666 * b.totalBots()))}use them to build a
random bot (+1 cost every 5 bots)`
},
// description: `if you collect ${powerUps.orb.research(2)}use them to build a
random bot (+1 cost every 5 bots)`,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
isBotTech: true,
allowed() {
return powerUps.research.count > 1 || build.isExperimentSelection
},
requires: "at least 2 research",
effect() {
tech.isRerollBots = true;
powerUps.research.changeRerolls(0)
simulation.makeTextLog(`m.research = 0`)
},
remove() {
tech.isRerollBots = false;
// this.description = `if you collect ${powerUps.orb.research(2 + Math.floor(0.2 * b.totalBots()))}use them to build a
random bot (+1 cost every 5 bots)`
}
},
{
name: "open-source",
description: `tech, fields, and guns have +1 bot choice
3x bottech frequency`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBotTech: true,
allowed() {
return b.totalBots() > 1 && !tech.isDeterminism
},
requires: "at least 2 bots",
effect() {
tech.isExtraBotOption = true
for (let i = 0, len = tech.tech.length; i < len; i++) {
if (tech.tech[i].isBotTech) tech.tech[i].frequency *= 3
}
},
remove() {
if (this.count > 0) {
for (let i = 0, len = tech.tech.length; i < len; i++) {
if (tech.tech[i].isBotTech) tech.tech[i].frequency = Math.ceil(tech.tech[i].frequency / 3)
}
}
tech.isExtraBotOption = false
}
},
{
name: "ersatz bots",
link: `ersatz bots`,
description: "double your current permanent bots
remove all guns in your inventory",
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
isBotTech: true,
isInstant: true,
isBadRandomOption: true,
numberOfGunsLost: 0,
allowed() {
return b.totalBots() > 3 && !build.isExperimentSelection
},
requires: "NOT EXPERIMENT MODE, at least 4 bots",
effect() {
this.numberOfGunsLost = b.inventory.length
b.inventory = []; //removes guns and ammo
for (let i = 0, len = b.guns.length; i < len; ++i) {
b.guns[i].count = 0;
b.guns[i].have = false;
if (b.guns[i].ammo != Infinity) b.guns[i].ammo = 0;
}
tech.buffedGun = 0
b.activeGun = null;
b.inventoryGun = 0;
simulation.drawCursor = simulation.drawCursorBasic //set cross hairs
simulation.makeGunHUD();
//double bots
for (let i = 0; i < tech.nailBotCount; i++) b.nailBot();
tech.nailBotCount *= 2
for (let i = 0; i < tech.laserBotCount; i++) b.laserBot();
tech.laserBotCount *= 2
for (let i = 0; i < tech.foamBotCount; i++) b.foamBot();
tech.foamBotCount *= 2
for (let i = 0; i < tech.boomBotCount; i++) b.boomBot();
tech.boomBotCount *= 2
for (let i = 0; i < tech.orbitBotCount; i++) b.orbitBot();
tech.orbitBotCount *= 2
for (let i = 0; i < tech.dynamoBotCount; i++) b.dynamoBot();
tech.dynamoBotCount *= 2
for (let i = 0; i < tech.soundBotCount; i++) b.soundBot();
tech.soundBotCount *= 2
for (let i = 0; i < tech.plasmaBotCount; i++) b.plasmaBot();
tech.plasmaBotCount *= 2
for (let i = 0; i < tech.missileBotCount; i++) b.missileBot();
tech.missileBotCount *= 2
},
remove() {
// if (this.count) {
// //return guns
// for (let i = 0; i < this.numberOfGunsLost; i++) powerUps.spawn(m.pos.x + 60 * (Math.random() - 0.5), m.pos.y + 60 * (Math.random() - 0.5), "gun");
// this.numberOfGunsLost = 0;
// //half all current bots
// tech.nailBotCount = Math.round(tech.nailBotCount / 2)
// tech.laserBotCount = Math.round(tech.laserBotCount / 2)
// tech.foamBotCount = Math.round(tech.foamBotCount / 2)
// tech.soundBotCount = Math.round(tech.soundBotCount / 2)
// tech.boomBotCount = Math.round(tech.boomBotCount / 2)
// tech.orbitBotCount = Math.round(tech.orbitBotCount / 2)
// tech.dynamoBotCount = Math.round(tech.dynamoBotCount / 2)
// tech.plasmaBotCount = Math.round(tech.plasmaBotCount / 2)
// tech.missileBotCount = Math.round(tech.missileBotCount / 2)
// b.clearPermanentBots();
// b.respawnBots();
// }
}
},
{
name: "robotics",
description: `spawn 2 random bots`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBotTech: true,
isInstant: true,
allowed() {
return b.totalBots() > 2
},
requires: "at least 3 bots",
effect() {
for (let i = 0; i < 2; i++) b.randomBot()
},
remove() {
}
},
{
name: "bot manufacturing",
description: `use ${powerUps.orb.research(2)} to build
3 random bots`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBotTech: true,
isInstant: true,
allowed() {
return b.totalBots() > 3 && powerUps.research.count > 1
},
requires: "at least 4 bots",
effect() {
for (let i = 0; i < 2; i++) {
if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
}
// m.energy = 0.01;
b.randomBot()
b.randomBot()
b.randomBot()
},
remove() { }
},
{
name: "bot prototypes",
description: `use ${powerUps.orb.research(3)}to build 2 random bots
and upgrade all bots to a random type`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBotTech: true,
isInstant: true,
allowed() {
return b.totalBots() > 5 && powerUps.research.count > 2
},
requires: "at least 6 bots",
effect() {
requestAnimationFrame(() => {
for (let i = 0; i < 3; i++) {
if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
}
//fill array of available bots
const notUpgradedBots = []
const num = 2
notUpgradedBots.push(() => {
tech.giveTech("nail-bot upgrade")
for (let i = 0; i < num; i++) {
b.nailBot()
tech.nailBotCount++;
}
simulation.makeTextLog(`tech.isNailBotUpgrade = true`)
})
notUpgradedBots.push(() => {
tech.giveTech("foam-bot upgrade")
for (let i = 0; i < num; i++) {
b.foamBot()
tech.foamBotCount++;
}
simulation.makeTextLog(`tech.isFoamBotUpgrade = true`)
})
notUpgradedBots.push(() => {
tech.giveTech("sound-bot upgrade")
for (let i = 0; i < num; i++) {
b.soundBot()
tech.soundBotCount++;
}
simulation.makeTextLog(`tech.isSoundBotUpgrade = true`)
})
notUpgradedBots.push(() => {
tech.giveTech("boom-bot upgrade")
for (let i = 0; i < num; i++) {
b.boomBot()
tech.boomBotCount++;
}
simulation.makeTextLog(`tech.isBoomBotUpgrade = true`)
})
notUpgradedBots.push(() => {
tech.giveTech("laser-bot upgrade")
for (let i = 0; i < num; i++) {
b.laserBot()
tech.laserBotCount++;
}
simulation.makeTextLog(`tech.isLaserBotUpgrade = true`)
})
notUpgradedBots.push(() => {
tech.giveTech("orbital-bot upgrade")
for (let i = 0; i < num; i++) {
b.orbitBot()
tech.orbitBotCount++;
}
simulation.makeTextLog(`tech.isOrbitalBotUpgrade = true`)
})
notUpgradedBots.push(() => {
tech.giveTech("dynamo-bot upgrade")
for (let i = 0; i < num; i++) {
b.dynamoBot()
tech.dynamoBotCount++;
}
simulation.makeTextLog(`tech.isDynamoBotUpgrade = true`)
})
notUpgradedBots[Math.floor(Math.random() * notUpgradedBots.length)]() //choose random function from the array and run it
})
},
remove() { }
},
{
name: "decorrelation",
description: "if your gun and field are unused for 2 seconds
0.3x damage taken",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isRewindField
},
requires: "not retrocausality",
effect() {
tech.isNoFireDefense = true
},
remove() {
tech.isNoFireDefense = false
}
},
{
name: "anticorrelation",
description: "if your gun and field are unused for 2 seconds
2x damage",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isRewindField
},
requires: "not retrocausality",
effect() {
tech.isNoFireDamage = true
},
remove() {
tech.isNoFireDamage = false
}
},
{
name: "mass driver",
description: "4x block collision damage",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return m.fieldMode !== 9 && !tech.isTokamak && !tech.isReel
},
requires: "not wormhole, reel, tokamak",
effect() {
tech.blockDamage = 0.3
},
remove() {
tech.blockDamage = 0.075
}
},
{
name: "inflation",
link: `inflation`,
description: "if holding a block 0.1x damage taken
after throwing a block it expands 3x",
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return (tech.blockDamage > 0.075 || tech.isPrinter) && m.fieldMode !== 8 && m.fieldMode !== 9 && !tech.isTokamak
},
requires: "mass driver, not pilot wave, tokamak, wormhole",
effect() {
tech.isAddBlockMass = true
},
remove() {
tech.isAddBlockMass = false
}
},
{
name: "restitution",
description: "2.5x block collision damage
after throwing a block it becomes very bouncy",
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return (tech.blockDamage > 0.075 || tech.isPrinter) && m.fieldUpgrades[m.fieldMode].name !== "pilot wave" && m.fieldUpgrades[m.fieldMode].name !== "wormhole" && !tech.isTokamak
},
requires: "mass driver, not pilot wave, tokamak, wormhole",
effect() {
tech.isBlockRestitution = true
},
remove() {
tech.isBlockRestitution = false
}
},
{
name: "flywheel",
description: "2.5x block collision damage
after a mob dies its block is flung at mobs",
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return (tech.blockDamage > 0.075 || tech.isPrinter) && !tech.nailsDeathMob && !tech.sporesOnDeath && !tech.isExplodeMob && !tech.botSpawner && !tech.iceIXOnDeath
},
requires: "mass driver, no other mob death tech",
effect() {
tech.isMobBlockFling = true
},
remove() {
tech.isMobBlockFling = false
}
},
{
name: "buckling",
descriptionFunction() {
return `if a block kills a mob there's a 50% chance
to spawn either ${powerUps.orb.coupling(1)}, ${powerUps.orb.boost(1)}, ${powerUps.orb.heal()}, ${powerUps.orb.ammo()}, or ${powerUps.orb.research(1)}`
},
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return (tech.blockDamage > 0.075 || tech.isPrinter) && m.fieldUpgrades[m.fieldMode].name !== "pilot wave" && !tech.isTokamak
},
requires: "mass driver, not pilot wave, tokamak",
effect() {
tech.isBlockPowerUps = true
},
remove() {
tech.isBlockPowerUps = false
}
},
{
name: "first derivative",
descriptionFunction() {
return `while your first gun is equipped
0.85x damage taken per gun (${(0.85 ** b.inventory.length).toFixed(2)}x)`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isFirstDer = true
},
remove() {
tech.isFirstDer = false;
}
},
{
name: "dark matter",
//a MAssive Compact Halo Object follows you
descriptionFunction() {
return `dark matter slowly gravitates towards you
0.4x damage taken ${tech.isNotDarkMatter ? "outside" : "inside"} dark matter`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isDarkMatter = true; //this harm reduction comes from the particle toggling tech.isHarmDarkMatter
spawn.darkMatter()
},
remove() {
tech.isDarkMatter = false;
tech.isHarmDarkMatter = false;
for (let i = 0, len = mob.length; i < len; i++) {
if (mob[i].isDarkMatter) mob[i].alive = false;
}
}
},
{
name: "axion",
descriptionFunction() {
return `while ${tech.isNotDarkMatter ? "outside" : "inside"} dark matter
2x damage`
},
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isDarkMatter
},
requires: "dark matter",
effect() {
tech.isAxion = true
},
remove() {
tech.isAxion = false
}
},
{
name: "dark energy",
descriptionFunction() {
return `while ${tech.isNotDarkMatter ? "outside" : "inside"} dark matter
generate 10 energy per second`
},
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isDarkMatter
},
requires: "dark matter",
effect() {
tech.isDarkEnergy = true
},
remove() {
tech.isDarkEnergy = false
}
},
{
name: "MACHO",
description: "dark matter's effects are only active outside it's range
1.6x to all dark matter effects",
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isDarkMatter && !tech.isMoveDarkMatter && !tech.isDarkStar
},
requires: "dark matter, not entropic gravity, dark star",
effect() {
tech.isNotDarkMatter = true
},
remove() {
tech.isNotDarkMatter = false
}
},
{
name: "entropic gravity",
description: "crouching pulls dark matter towards you
1.6x to all dark matter effects",
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isDarkMatter && !tech.isNotDarkMatter
},
requires: "dark matter, not MACHO",
effect() {
tech.isMoveDarkMatter = true
},
remove() {
tech.isMoveDarkMatter = false
}
},
{
name: "dark star",
description: `mobs inside dark matter are damaged
1.3x dark matter radius`,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isDarkMatter && !tech.isNotDarkMatter
},
requires: "dark matter, not MACHO",
effect() {
tech.isDarkStar = true
},
remove() {
tech.isDarkStar = false
}
},
{
name: "ablative drones",
description: "after losing health there is a chance
to rebuild your broken parts as drones",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed: () => true,
requires: "",
effect() {
tech.isDroneOnDamage = true;
// for (let i = 0; i < 4; i++) b.drone()
},
remove() {
tech.isDroneOnDamage = false;
}
},
{
name: "non-Newtonian armor",
link: `non-Newtonian armor`,
description: "after mob collisions
0.3x damage taken for 10 seconds",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isHarmArmor = true;
},
remove() {
tech.isHarmArmor = false;
}
},
{
name: "tessellation",
description: `use ${powerUps.orb.research(2)}
0.6x damage taken`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return powerUps.research.count > 1 || build.isExperimentSelection
},
requires: "",
effect() {
tech.isFieldHarmReduction = true
for (let i = 0; i < 2; i++) {
if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
}
},
remove() {
tech.isFieldHarmReduction = false
if (this.count > 0) powerUps.research.changeRerolls(2)
}
},
{
name: "Pauli exclusion",
description: `for 8 seconds after mob collisions
become invulnerable and inhibit energy regen`,
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
m.collisionImmuneCycles += 480;
if (m.immuneCycle < m.cycle + m.collisionImmuneCycles) m.immuneCycle = m.cycle + m.collisionImmuneCycles; //player is immune to damage
},
remove() {
m.collisionImmuneCycles = 30;
}
},
{
name: "spin-statistics theorem",
description: `for 1.9 seconds out of every 7 seconds
become invulnerable and inhibit energy regen`,
maxCount: 3,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true //m.collisionImmuneCycles > 30
},
requires: "",
effect() {
tech.cyclicImmunity += 114;
},
remove() {
tech.cyclicImmunity = 0;
}
},
{
name: "fermion",
description: `for 6 seconds after mobs die
become invulnerable and inhibit energy regen`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isMobDeathImmunity = true;
},
remove() {
tech.isMobDeathImmunity = false;
}
},
{
name: "abelian group",
description: `4x damage while invulnerable`,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isMobDeathImmunity || tech.cyclicImmunity || m.collisionImmuneCycles > 30
},
requires: "invincibility tech",
effect() {
tech.isImmunityDamage = true;
},
remove() {
tech.isImmunityDamage = false;
}
},
{
name: "refrigerant",
descriptionFunction() {
return `after losing at least 5 ${tech.isEnergyHealth ? "energy" : "health"}
freeze all mobs for 7 seconds`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isHarmFreeze = true;
},
remove() {
tech.isHarmFreeze = false;
}
},
{
name: "piezoelectricity",
description: "if you collide with a mob
generate +2048 energy",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isEnergyHealth
},
requires: "not mass-energy",
effect() {
tech.isPiezo = true;
// if (simulation.isTextLogOpen) m.energy += 20.48;
},
remove() {
tech.isPiezo = false;
}
},
{
name: "ground state",
description: "+300 maximum energy
0.66x passive energy generation",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isTimeCrystals
},
requires: "not time crystals",
effect() {
tech.isGroundState = true
m.setFieldRegen()
m.setMaxEnergy()
},
remove() {
tech.isGroundState = false
m.setFieldRegen()
m.setMaxEnergy()
}
},
{
name: "heat engine",
description: `1.4x damage
–50 maximum energy`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed: () => true,
requires: "not CPT",
damage: 1.4,
effect() {
tech.damage *= this.damage
tech.isMaxEnergyTech = true;
m.setMaxEnergy()
},
remove() {
if (this.count && m.alive) tech.damage /= this.damage
tech.isMaxEnergyTech = false;
m.setMaxEnergy()
}
},
{
name: "exothermic process",
description: "1.6x damage
after mobs die –5 energy",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
damage: 1.6,
effect() {
tech.damage *= this.damage
tech.isEnergyLoss = true;
},
remove() {
if (this.count && m.alive) tech.damage /= this.damage
tech.isEnergyLoss = false;
}
},
{
name: "Gibbs free energy",
descriptionFunction() {
return `1.005x damage for each missing energy
(${(1 + 0.5 * Math.max(0, m.maxEnergy - m.energy)).toFixed(2)}x)`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isLowEnergyDamage = true;
},
remove() {
tech.isLowEnergyDamage = false;
}
},
{
name: "overcharge",
description: "+88 maximum energy
+4% JUNKtech chance",
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return tech.junkChance < 1
},
requires: "",
effect() {
tech.bonusEnergy += 0.88
m.setMaxEnergy()
this.refundAmount += tech.addJunkTechToPool(0.04)
},
refundAmount: 0,
remove() {
tech.bonusEnergy = 0;
m.setMaxEnergy()
if (this.count > 0 && this.refundAmount > 0) {
tech.removeJunkTechFromPool(this.refundAmount)
this.refundAmount = 0
}
}
},
{
name: "Maxwells demon",
description: "energy above maximum decays 30x slower
+5% JUNKtech chance",
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (m.energy > m.maxEnergy || build.isExperimentSelection) && tech.junkChance < 1
},
requires: "energy above your max",
effect() {
tech.overfillDrain = 0.99 //70% = 1-(1-0.75)/(1-0.15) //92% = 1-(1-0.75)/(1-0.87)
this.refundAmount += tech.addJunkTechToPool(0.05)
},
refundAmount: 0,
remove() {
tech.overfillDrain = 0.7
if (this.count > 0 && this.refundAmount > 0) {
tech.removeJunkTechFromPool(this.refundAmount)
this.refundAmount = 0
}
}
},
{
name: "inductive charging",
description: "if crouched 7x passive energy generation
otherwise 0x passive energy generation",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isDamageAfterKillNoRegen
},
requires: "not parasitism",
effect() {
tech.isCrouchRegen = true; //only used to check for requirements
m.regenEnergy = function () {
if (m.immuneCycle < m.cycle && m.crouch && m.fieldCDcycle < m.cycle) m.energy += 7 * m.fieldRegen;
if (m.energy < 0) m.energy = 0
}
},
remove() {
tech.isCrouchRegen = false;
m.regenEnergy = m.regenEnergyDefault
}
},
{
name: "energy conservation",
description: "doing damage to mobs generates energy",
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.energySiphon += 0.04;
},
remove() {
tech.energySiphon = 0;
}
},
{
name: "parasitism",
description: "if a mob has died in the last 5 seconds
2x damage, no passive energy generation",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isCrouchRegen
},
requires: "not inductive charging",
effect() {
tech.isDamageAfterKillNoRegen = true;
m.regenEnergy = function () {
if (m.immuneCycle < m.cycle && (m.lastKillCycle + 300 < m.cycle) && m.fieldCDcycle < m.cycle) m.energy += m.fieldRegen;
if (m.energy < 0) m.energy = 0
}
},
remove() {
if (this.count) m.regenEnergy = m.regenEnergyDefault
tech.isDamageAfterKillNoRegen = false;
}
},
{
name: "waste heat recovery",
description: "if a mob has died in the last 5 seconds
generate 0.05x maximum energy every second",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isEnergyRecovery = true;
},
remove() {
tech.isEnergyRecovery = false;
}
},
{
name: "recycling",
descriptionFunction() {
return `if a mob has died in the last 5 seconds
recover 0.005x maximum ${tech.isEnergyHealth ? "energy" : "health"} every second`
},
description: "",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isHealTech: true,
allowed() {
return true
},
requires: "",
effect() {
tech.isHealthRecovery = true;
},
remove() {
tech.isHealthRecovery = false;
}
},
{
name: "torpor",
description: "if a mob has not died in the last 5 seconds
0.3x damage taken",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isHarmReduceNoKill = true;
},
remove() {
tech.isHarmReduceNoKill = false;
}
},
{
name: "stability",
descriptionFunction() {
return `0.3x damage taken
while your health is at maximum`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isMaxHealthDefense = true;
},
remove() {
tech.isMaxHealthDefense = false;
}
},
{
name: "instability",
descriptionFunction() {
return `2x damage while your damage taken is 1.00x
(damage taken = ${(m.defense()).toFixed(2)}x)`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isNoDefenseDamage = true;
},
remove() {
tech.isNoDefenseDamage = false;
}
},
{
name: "control theory",
descriptionFunction() {
return `1.5x damage
while your health is at maximum`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isMaxHealthDamage = true;
},
remove() {
tech.isMaxHealthDamage = false;
}
},
{
name: "homeostasis",
descriptionFunction() {
// return `0.9x damage taken for each ${name} missing
(${(Math.pow(0.1 * max, Math.max(0, max - h))).toFixed(2)}x)`
const scale = 0.2 //adjust this to control the strength of this effect
return `reduce damage taken for each missing ${tech.isEnergyHealth ? "energy" : "health"}
down to a limit of ${scale}x at 0 ${tech.isEnergyHealth ? "energy" : "health"}(${(Math.pow(scale, Math.max(0, 1 - (tech.isEnergyHealth ? m.energy / m.maxEnergy : m.health / m.maxHealth)))).toFixed(2)}x)`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return m.health < 0.9 || build.isExperimentSelection
},
requires: "health below 60",
effect() {
tech.isLowHealthDefense = true;
},
remove() {
tech.isLowHealthDefense = false;
}
},
{
name: "negative feedback",
descriptionFunction() {
return `1.006x damage for each missing ${tech.isEnergyHealth ? "energy" : "health"}
(${(1 + 0.6 * Math.max(0, (tech.isEnergyHealth ? m.maxEnergy - m.energy : m.maxHealth - m.health))).toFixed(2)}x)` //1 + 0.6 * Math.max(0, (tech.isEnergyHealth ? m.maxEnergy - m.energy : m.maxHealth - m.health))
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return m.health < 0.9 || build.isExperimentSelection
},
requires: "health below 90",
effect() {
tech.isLowHealthDmg = true; //used in mob.damage()
},
remove() {
tech.isLowHealthDmg = false;
}
},
{
name: "Zenos paradox",
descriptionFunction() {
return `0.15x damage taken
–5% of current ${tech.isEnergyHealth ? "energy" : "health"} every 5 seconds`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isZeno = true;
},
remove() {
tech.isZeno = false;
}
},
{
name: "antiscience",
descriptionFunction() {
return `–10 ${tech.isEnergyHealth ? "energy" : "health"} after picking up a tech
1.7x damage`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBadRandomOption: true,
allowed() {
return true
},
requires: "",
damage: 1.7,
effect() {
tech.damage *= this.damage
tech.isTechDamage = true;
},
remove() {
if (this.count && m.alive) tech.damage /= this.damage
tech.isTechDamage = false;
}
},
{
name: "ergodicity",
descriptionFunction() {
return `0.50x healing from ${powerUps.orb.heal()}
1.7x damage`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
damage: 1.7,
effect() {
tech.damage *= this.damage
tech.isHalfHeals = true;
for (let i = 0; i < powerUp.length; i++) {
if (powerUp[i].name === "heal") {
const scale = Math.sqrt(0.5)
powerUp[i].size *= scale
Matter.Body.scale(powerUp[i], scale, scale); //grow
}
}
},
remove() {
if (this.count && m.alive) {
tech.damage /= this.damage
for (let i = 0; i < powerUp.length; i++) {
if (powerUp[i].name === "heal") {
const scale = 1 / Math.sqrt(0.5)
powerUp[i].size *= scale
Matter.Body.scale(powerUp[i], scale, scale); //grow
}
}
}
tech.isHalfHeals = false;
}
},
{
name: "fluoroantimonic acid",
description: "if your health is above 100
1.35x damage",
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.maxHealth > 1;
},
requires: "maximum health above 100",
effect() {
tech.isAcidDmg = true;
},
remove() {
tech.isAcidDmg = false;
}
},
{
name: "induction brake",
descriptionFunction() {
return `after using ${powerUps.orb.heal()}
slow nearby mobs for 17 seconds`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isPerfectBrake
},
requires: "not eddy current brake",
effect() {
tech.isHealBrake = true;
},
remove() {
tech.isHealBrake = false;
}
},
{
name: "adiabatic healing",
descriptionFunction() {
return `2x healing from ${powerUps.orb.heal()}
+4% JUNKtech chance`
},
maxCount: 3,
count: 0,
frequency: 1,
frequencyDefault: 1,
isHealTech: true,
allowed() {
return ((m.health / m.maxHealth) < 0.7 || build.isExperimentSelection) && tech.junkChance < 1
},
requires: "under 70% health",
effect() {
tech.largerHeals++;
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
}
}
this.refundAmount += tech.addJunkTechToPool(0.04)
},
refundAmount: 0,
remove() {
tech.largerHeals = 1;
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
}
}
if (this.count > 0 && this.refundAmount > 0) {
tech.removeJunkTechFromPool(this.refundAmount)
this.refundAmount = 0
}
}
},
{
name: "quenching",
descriptionFunction() {
return `${powerUps.orb.heal()} overhealing results in 2x health loss
and 2x maximum health increase`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isHealTech: true,
allowed() {
return !tech.isEnergyHealth
},
requires: "not mass-energy",
effect() {
tech.isOverHeal = true;
},
remove() {
tech.isOverHeal = false;
}
},
{
name: "accretion",
descriptionFunction() {
return `${powerUps.orb.heal(1)} follow you, even between levels
+4% chance to duplicate spawned power ups`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isHealTech: true,
allowed() {
return m.fieldMode !== 9
},
requires: "not wormhole",
effect() {
tech.isHealAttract = true
powerUps.setPowerUpMode();
if (!build.isExperimentSelection && !simulation.isTextLogOpen) simulation.circleFlare(0.04);
},
remove() {
tech.isHealAttract = false
powerUps.setPowerUpMode();
},
},
{
name: "accretion disk",
descriptionFunction() {
return `1.05x damage for each power up on this level
(${(1 + 0.05 * powerUp.length).toFixed(2)}x)`
},
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
isHealTech: true,
allowed() {
return tech.isHealAttract
},
requires: "accretion",
effect() {
tech.isPowerUpDamage = true
},
remove() {
tech.isPowerUpDamage = false
},
},
{
name: "maintenance",
descriptionFunction() {
return `2x healtech frequency
spawn ${powerUps.orb.heal(13)}`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isInstant: true,
isBadRandomOption: true,
allowed() {
return true
},
requires: "",
effect() {
for (let i = 0; i < 13; i++) powerUps.spawn(m.pos.x + 60 * (Math.random() - 0.5), m.pos.y + 60 * (Math.random() - 0.5), "heal");
for (let i = 0, len = tech.tech.length; i < len; i++) {
if (tech.tech[i].isHealTech) tech.tech[i].frequency *= 2
}
},
remove() { }
},
{
name: "self-assembly",
descriptionFunction() {
return `at the start of each level
for every 20 missing ${tech.isEnergyHealth ? "energy" : "health"} spawn ${powerUps.orb.heal()}`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isHealTech: true,
allowed() {
return true
},
requires: "",
effect() {
tech.isHealLowHealth = true;
},
remove() {
tech.isHealLowHealth = false;
}
},
{
name: "interest",
descriptionFunction() {
return `at the start of each level spawn
${(100 * this.rate).toFixed(0)}% of your ${powerUps.orb.research(1)}, ${powerUps.orb.ammo(1)}, ${powerUps.orb.coupling(1)}, and ${powerUps.orb.heal(1)} (rounded up)`
},
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
rate: 0.06,
effect() {
tech.interestRate += this.rate;
},
remove() {
tech.interestRate = 0;
}
},
{
name: "anthropic principle",
// nameInfo: "",
// addNameInfo() {
// setTimeout(function () {
// powerUps.research.changeRerolls(0)
// }, 1000);
// },
descriptionFunction() {
return `once per level, instead of dying
use ${powerUps.orb.research(1)} and spawn ${powerUps.orb.heal(16)}`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isHealTech: true,
allowed() {
return powerUps.research.count > 0 || build.isExperimentSelection
},
requires: "at least 1 research",
effect() {
tech.isDeathAvoid = true;
tech.isDeathAvoidedThisLevel = false;
setTimeout(function () {
powerUps.research.changeRerolls(0)
}, 1000);
},
remove() {
tech.isDeathAvoid = false;
}
},
{
name: "weak anthropic principle",
description: "after anthropic principle prevents your death
+60% duplication chance for that level",
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return tech.isDeathAvoid
},
requires: "anthropic principle",
effect() {
tech.isAnthropicTech = true
powerUps.setPowerUpMode(); //needed after adjusting duplication chance
},
remove() {
tech.isAnthropicTech = false
powerUps.setPowerUpMode(); //needed after adjusting duplication chance
}
},
{
name: "strong anthropic principle",
description: "after anthropic principle prevents your death
2.71828x damage for that level",
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return tech.isDeathAvoid
},
requires: "anthropic principle",
effect() {
tech.isAnthropicDamage = true
},
remove() {
tech.isAnthropicDamage = false
}
},
{
name: "quantum immortality",
description: "0.7x damage taken
after dying, continue in an alternate reality",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return true
},
requires: "",
effect() {
tech.isImmortal = true;
},
remove() {
tech.isImmortal = false;
}
},
{
name: "many-worlds",
// description: "each level is an alternate reality, where you
find a tech at the start of each level",
description: `at the start of each level spawn a tech
and enter an alternate reality`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isAltRealityTech: true,
allowed() {
return !tech.isResearchReality && !tech.isCollisionRealitySwitch
},
requires: "not Ψ(t) collapse, Hilbert space",
effect() {
tech.isSwitchReality = true;
},
remove() {
tech.isSwitchReality = false;
}
},
{
name: "Ψ(t) collapse",
link: `Ψ(t) collapse`,
description: `after a boss dies spawn ${powerUps.orb.research(5)}
if you research enter an alternate reality`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isAltRealityTech: true,
allowed() {
return !tech.isSwitchReality && !tech.isCollisionRealitySwitch && !tech.isJunkResearch
},
requires: "not many-worlds, Hilbert space, pseudoscience",
bonusResearch: 21,
effect() {
tech.isResearchReality = true;
// for (let i = 0; i < this.bonusResearch; i++) powerUps.spawn(m.pos.x + Math.random() * 60, m.pos.y + Math.random() * 60, "research", false);
},
remove() {
tech.isResearchReality = false;
// if (this.count > 0) powerUps.research.changeRerolls(-this.bonusResearch)
}
},
{
name: "decoherence",
description: `after a boss dies spawn ${powerUps.orb.research(2)}
tech options you don't choose won't reoccur`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isSuperDeterminism
},
requires: "not superdeterminism",
bonusResearch: 7,
effect() {
tech.isBanish = true
},
remove() {
if (tech.isBanish) {
tech.isBanish = false
//reset banish list
for (let i = 0; i < tech.tech.length; i++) {
if (tech.tech[i].isBanished) tech.tech[i].isBanished = false
}
}
tech.isBanish = false
}
},
{
name: "peer review",
description: `after you research gain 1.05x damage
and +1% JUNKtech chance`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return (powerUps.research.count > 0 || build.isExperimentSelection) && !tech.isSuperDeterminism
},
requires: "at least 1 research, not superdeterminism",
effect() {
tech.isResearchDamage = true;
},
remove() {
tech.isResearchDamage = false;
}
},
{
name: "pseudoscience",
description: "research 2 times for free, but
add 1% JUNKtech chance each time",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isResearchReality && !tech.isSuperDeterminism
},
requires: "not Ψ(t) collapse, superdeterminism",
effect() {
tech.isJunkResearch = true;
},
remove() {
tech.isJunkResearch = false;
}
},
{
name: "renormalization",
description: `47% chance to spawn ${powerUps.orb.research(1)} after consuming ${powerUps.orb.research(1)}
+5% JUNKtech chance`,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (powerUps.research.count > 3 || build.isExperimentSelection) && !tech.isSuperDeterminism && tech.junkChance < 1
},
requires: "at least 4 research, not superdeterminism",
effect() {
tech.renormalization = true;
this.refundAmount += tech.addJunkTechToPool(0.05)
},
refundAmount: 0,
remove() {
tech.renormalization = false;
if (this.count > 0 && this.refundAmount > 0) {
tech.removeJunkTechFromPool(this.refundAmount)
this.refundAmount = 0
}
}
},
{
name: "perturbation theory",
description: `if you have no ${powerUps.orb.research(1)} in your inventory
2x fire rate`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return powerUps.research.count === 0
},
requires: "no research",
effect() {
tech.isRerollHaste = true;
powerUps.research.changeRerolls(0)
b.setFireCD();
},
remove() {
tech.isRerollHaste = false;
tech.researchHaste = 1;
b.setFireCD();
}
},
{
name: "Bayesian statistics",
descriptionFunction() {
return `1.05x damage per ${powerUps.orb.research(1)} in your inventory
(${(1 + Math.max(0, 0.05 * powerUps.research.count)).toFixed(2)}x)`
},
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return powerUps.research.count > 1 || build.isExperimentSelection
},
requires: "at least 2 research",
effect() {
tech.isRerollDamage = true;
},
remove() {
tech.isRerollDamage = false;
}
},
{
name: "ansatz",
description: `after choosing a field, tech, or gun
if you have no ${powerUps.orb.research(1)} in your inventory spawn ${powerUps.orb.research(3)}`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return powerUps.research.count < 1 && !tech.isSuperDeterminism && !tech.isRerollHaste
},
requires: "no research, not superdeterminism, Ψ(t) collapse, perturbation theory",
effect() {
tech.isAnsatz = true;
},
remove() {
tech.isAnsatz = false;
}
},
{
name: "unified field theory",
description: `when paused you can click to switch fields
2x fieldtech frequency`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isSuperDeterminism && !tech.isNoDraftPause
},
requires: "not superdeterminism, eternalism",
effect() {
tech.isPauseSwitchField = true;
for (let i = 0, len = tech.tech.length; i < len; i++) {
if (tech.tech[i].isFieldTech) tech.tech[i].frequency *= 2
}
},
remove() {
tech.isPauseSwitchField = false;
if (this.count > 1) {
for (let i = 0, len = tech.tech.length; i < len; i++) {
if (tech.tech[i].isFieldTech) tech.tech[i].frequency /= 2
}
}
}
},
{
name: "eternalism",
description: "1.25x damage
time can't be paused (time can be dilated)",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isPauseSwitchField && !tech.isPauseEjectTech && !tech.isWormHolePause
},
requires: "not unified field theory, paradigm shift, invariant",
damage: 1.25,
effect() {
tech.damage *= this.damage
tech.isNoDraftPause = true
},
remove() {
if (this.count && m.alive) tech.damage /= this.damage
tech.isNoDraftPause = false
}
},
{
name: "brainstorming",
description: "tech choices randomize
every 1.5 seconds for 10 seconds",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isSuperDeterminism
},
requires: "not superdeterminism",
effect() {
tech.isBrainstorm = true
tech.isBrainstormActive = false
tech.brainStormDelay = 2000 - simulation.difficultyMode * 100
},
remove() {
tech.isBrainstorm = false
tech.isBrainstormActive = false
}
},
{
name: "cross-disciplinary",
description: "tech have an extra field or gun choice
+5% chance to duplicate spawned power ups",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isDeterminism
},
requires: "not determinism",
effect() {
tech.isExtraGunField = true;
powerUps.setPowerUpMode(); //needed after adjusting duplication chance
if (!build.isExperimentSelection && !simulation.isTextLogOpen) simulation.circleFlare(0.05);
},
remove() {
tech.isExtraGunField = false;
if (this.count) powerUps.setPowerUpMode(); //needed after adjusting duplication chance
}
},
{
name: "emergence",
description: "tech, fields, and guns have +1 choice
1.1x damage",
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isDeterminism
},
requires: "not determinism",
damage: 1.1,
effect() {
tech.extraChoices += 1;
tech.damage *= this.damage
},
refundAmount: 0,
remove() {
tech.extraChoices = 0;
if (this.count && m.alive) tech.damage /= this.damage
}
},
{
name: "path integral",
link: `path integral`,
description: "your next tech choice has all possible options
+4% JUNKtech chance",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isInstant: true,
// isJunk: true,
allowed() {
return !tech.isDeterminism && !tech.isBrainstorm && tech.junkChance < 1
},
requires: "not determinism, brainstorm",
effect() {
tech.tooManyTechChoices = 1
// for (let i = 0; i < this.bonusResearch; i++) powerUps.spawn(m.pos.x + 40 * (Math.random() - 0.5), m.pos.y + 40 * (Math.random() - 0.5), "research", false);
this.refundAmount += tech.addJunkTechToPool(0.04)
},
refundAmount: 0,
remove() {
tech.tooManyTechChoices = 0
if (this.count > 0 && this.refundAmount > 0) {
tech.removeJunkTechFromPool(this.refundAmount)
this.refundAmount = 0
}
}
},
{
name: "determinism",
description: "spawn 5 tech
only 1 choice for tech, fields, and guns",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBadRandomOption: true,
isInstant: true,
allowed() {
return !tech.extraChoices && !tech.isExtraGunField && !tech.isExtraBotOption
},
requires: "not emergence, cross-disciplinary, integrated circuit",
effect() {
tech.isDeterminism = true;
//if you change the number spawned also change it in Born rule
for (let i = 0; i < 5; i++) powerUps.spawn(m.pos.x + 60 * (Math.random() - 0.5), m.pos.y + 60 * (Math.random() - 0.5), "tech");
},
remove() {
tech.isDeterminism = false;
}
},
{
name: "superdeterminism",
description: `spawn 5 tech
you can't cancel and ${powerUps.orb.research(1)} no longer spawn`,
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
isBadRandomOption: true,
isInstant: true,
allowed() {
return tech.isDeterminism && !tech.isAnsatz && !tech.isJunkResearch && !tech.isBrainstorm
},
requires: "determinism, not ansatz, pseudoscience, brainstorming",
effect() {
tech.isSuperDeterminism = true;
//if you change the number spawned also change it in Born rule
for (let i = 0; i < 5; i++) powerUps.spawn(m.pos.x + 60 * (Math.random() - 0.5), m.pos.y + 60 * (Math.random() - 0.5), "tech");
},
remove() {
tech.isSuperDeterminism = false;
}
},
{
name: "technical debt",
descriptionFunction() {
return `4x damage but lose 0.15x damage
for each tech you have learned (${(tech.totalCount > 20 ? (Math.pow(0.85, tech.totalCount - 20)) : (4 - 0.15 * tech.totalCount)).toFixed(2)}x)`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBadRandomOption: true,
allowed() {
return true
},
requires: "",
effect() {
tech.isTechDebt = true;
},
remove() {
tech.isTechDebt = false;
}
},
{
name: "meta-analysis",
description: `if you choose a JUNKtech you instead get a
random normal tech and spawn ${powerUps.orb.research(2)}`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return tech.junkChance > 0
},
requires: "some JUNK tech",
effect() {
tech.isMetaAnalysis = true
},
remove() {
tech.isMetaAnalysis = false
}
},
{
name: "dark patterns",
description: "1.3x damage
+15% JUNKtech chance",
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return tech.junkChance < 1
},
requires: "",
damage: 1.3,
effect() {
tech.damage *= this.damage
this.refundAmount += tech.addJunkTechToPool(0.15)
},
refundAmount: 0,
remove() {
if (this.count && m.alive) {
tech.damage /= this.damage
if (this.refundAmount > 0) tech.removeJunkTechFromPool(this.refundAmount)
}
}
},
{
name: "junk DNA",
descriptionFunction() {
return `increase damage by twice the
JUNKtech chance (${(1 + 2 * tech.junkChance).toFixed(2)}x)`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return tech.junkChance > 0
},
requires: "JUNK in tech pool",
effect() {
tech.isJunkDNA = true
},
remove() {
tech.isJunkDNA = false
}
},
{
name: "mass production",
descriptionFunction() {
return `tech have extra choices to spawn ${powerUps.orb.ammo(1)}, ${powerUps.orb.heal(1)}, or ${powerUps.orb.research(1)}
+3% chance to duplicate spawned power ups`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() { return true },
requires: "",
effect() {
tech.isMassProduction = true
powerUps.setPowerUpMode(); //needed after adjusting duplication chance
if (!build.isExperimentSelection && !simulation.isTextLogOpen) simulation.circleFlare(0.03);
},
remove() {
tech.isMassProduction = false
if (this.count) powerUps.setPowerUpMode(); //needed after adjusting duplication chance
}
},
{
name: "research",
descriptionFunction() {
return `spawn ${this.value > 36 ? this.value + powerUps.orb.research(1) : powerUps.orb.research(this.value)}
next time increase amount spawned by +5${powerUps.orb.research(1)}`
},
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
isInstant: true,
isMassProduction: true,
allowed() { return true },
requires: "",
value: 8,
defaultValue: 8,
effect() {
powerUps.spawnDelay("research", this.value);
this.value += 5
},
remove() { }
},
{
name: "ammo",
descriptionFunction() {
return `spawn ${this.value > 33 ? this.value + powerUps.orb.ammo(1) : powerUps.orb.ammo(this.value)}
next time increase amount spawned by +7${powerUps.orb.ammo(1)}`
},
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
isInstant: true,
isMassProduction: true,
allowed() { return true },
requires: "",
value: 10,
defaultValue: 10,
effect() {
powerUps.spawnDelay("ammo", this.value);
this.value += 7
},
remove() { }
},
{
name: "heals",
descriptionFunction() {
return `spawn ${this.value > 30 ? this.value + powerUps.orb.heal(1) : powerUps.orb.heal(this.value)}
next time increase amount spawned by +7${powerUps.orb.heal(1)}`
},
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
isInstant: true,
isMassProduction: true,
allowed() { return true },
requires: "",
value: 10,
defaultValue: 10,
effect() {
powerUps.spawnDelay("heal", this.value);
this.value += 7
},
remove() { }
},
{
name: "field coupling",
descriptionFunction() {
return `spawn ${powerUps.orb.coupling(10)}
${m.couplingDescription(1)} per ${powerUps.orb.coupling(1)}`
},
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
isInstant: true,
allowed() {
return true
},
requires: "",
effect() {
powerUps.spawnDelay("coupling", 10)
},
remove() {
// if (this.count) m.couplingChange(-this.count * 10)
}
},
{
name: "quintessence",
descriptionFunction() {
if (this.count) {
converted = this.researchUsed * this.couplingToResearch
let orbText
if (converted > 15) {
orbText = `${converted} ${powerUps.orb.coupling()}`
} else {
orbText = powerUps.orb.coupling(converted)
}
return `convert ${this.researchUsed} ${powerUps.orb.research(1)} into ${orbText}
${m.couplingDescription(1)} per ${powerUps.orb.coupling(1)}`
} else {
let converted = powerUps.research.count * this.couplingToResearch
let orbText
if (converted > 15) {
orbText = `${converted} ${powerUps.orb.coupling()}`
} else {
orbText = powerUps.orb.coupling(converted)
}
return `convert ${powerUps.research.count} ${powerUps.orb.research(1)} into ${orbText}
${m.couplingDescription(1)} per ${powerUps.orb.coupling(1)}`
}
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isInstant: true,
allowed() {
return powerUps.research.count > 3
},
requires: "",
researchUsed: 0,
couplingToResearch: 3,
effect() {
// let count = 0
// while (powerUps.research.count > 0 && powerUps.research.count !== Infinity) {
// powerUps.research.changeRerolls(-1)
// count += 2.5
// this.researchUsed++
// }
// powerUps.spawnDelay("coupling", Math.floor(count))
let cycle = () => {
if (powerUps.research.count > 0 && powerUps.research.count !== Infinity) {
if (m.alive) requestAnimationFrame(cycle);
if (!simulation.paused && !simulation.isChoosing) { //&& !(simulation.cycle % 2)
powerUps.research.changeRerolls(-1)
this.researchUsed++
powerUps.spawnDelay("coupling", this.couplingToResearch)
}
} // else exit delay loop
}
requestAnimationFrame(cycle);
},
remove() {
if (this.count) {
m.couplingChange(-this.researchUsed * this.couplingToResearch)
powerUps.research.changeRerolls(this.researchUsed)
this.researchUsed = 0
}
}
},
{
name: "virtual particles",
descriptionFunction() {
return `17% chance after mobs die to spawn ${powerUps.orb.coupling(1)}
${m.couplingDescription(1)} per ${powerUps.orb.coupling(1)}`
// return `17% chance after mobs die to spawn ${powerUps.orb.coupling(1)} that each give +0.1 coupling` //
${m.couplingDescription(1)} ${m.fieldMode === 0 ? "" : "per coupling"}
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed: () => true,
requires: "",
effect() {
tech.isCouplingPowerUps = true //about 20-30 mobs per level so at 16% and 0.1 coupling that's about 25 * 0.16 * 0.1 = 0.4 coupling per level with out duplication
},
remove() {
tech.isCouplingPowerUps = false
}
},
{
name: "fine-structure constant",
descriptionFunction() {
// return `spawn ${this.value} ${powerUps.orb.coupling(1)} that each give +0.1 coupling
-0.5 coupling after mob collisions`//
${m.couplingDescription(1)} ${m.fieldMode === 0 ? "" : "per coupling"}
return `after a boss dies spawn ${powerUps.orb.coupling(9)}
lose ${powerUps.orb.coupling(4)} after mob collisions`//
${m.couplingDescription(1)} per ${powerUps.orb.coupling(1)}
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
// isInstant: true,
allowed: () => true,
requires: "",
value: 60,
effect() {
tech.isCouplingNoHit = true
// powerUps.spawnDelay("coupling", this.value)
},
remove() {
// if (this.count) m.couplingChange(-this.value)
tech.isCouplingNoHit = false
}
},
{
name: "residual dipolar coupling",
descriptionFunction() {
// return `clicking cancel for a field, tech, or gun
spawns ${powerUps.orb.coupling(5)}that each give +0.1 coupling`//
${m.couplingDescription(1)} ${m.fieldMode === 0 ? "" : "per coupling"}
return `clicking cancel spawns ${powerUps.orb.coupling(8)}
${m.couplingDescription(1)} per ${powerUps.orb.coupling(1)}`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isSuperDeterminism
},
requires: "not superdeterminism",
effect() {
tech.isCancelCouple = true
},
remove() {
tech.isCancelCouple = false
}
},
{
name: "commodities exchange",
descriptionFunction() {
return `clicking cancel for a field, tech, or gun
spawns 8-12 ${powerUps.orb.heal()}, ${powerUps.orb.ammo()}, or ${powerUps.orb.research(1)}`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isSuperDeterminism
},
requires: "not superdeterminism",
effect() {
tech.isCancelRerolls = true
},
remove() {
tech.isCancelRerolls = false
}
},
{
name: "options exchange",
link: `options exchange`,
// description: `once per level clicking cancel will randomize
with 2x choices for fields, tech, or guns`,
description: `clicking cancel for fields, tech, or guns
will randomize with 3x choices, once a level`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isSuperDeterminism //&& (tech.isCancelRerolls || tech.isCancelDuplication || tech.isCancelCouple)
},
requires: "not superdeterminism", //residual dipolar coupling, commodities exchange, futures exchange,
effect() {
tech.isCancelTech = true
tech.cancelTechCount = 0
},
remove() {
tech.isCancelTech = false
tech.cancelTechCount = 0
}
},
{
name: "futures exchange",
description: "clicking cancel for a field, tech, or gun
gives +5% power up duplication chance",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return tech.duplicationChance() < 1 && !tech.isSuperDeterminism
},
requires: "below 100% duplication chance, not superdeterminism",
effect() {
tech.isCancelDuplication = true //search for tech.duplication to balance
powerUps.setPowerUpMode(); //needed after adjusting duplication chance
},
remove() {
tech.isCancelDuplication = false
if (this.count) powerUps.setPowerUpMode(); //needed after adjusting duplication chance
}
},
{
name: "replication",
description: "+10% chance to duplicate spawned power ups
+15% JUNKtech chance",
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return tech.duplicationChance() < 1 && tech.junkChance < 1
},
requires: "below 100% duplication chance",
effect() {
tech.duplicateChance += 0.1
powerUps.setPowerUpMode(); //needed after adjusting duplication chance
if (!build.isExperimentSelection && !simulation.isTextLogOpen) simulation.circleFlare(0.1);
this.refundAmount += tech.addJunkTechToPool(0.15)
},
refundAmount: 0,
remove() {
tech.duplicateChance = 0
powerUps.setPowerUpMode(); //needed after adjusting duplication chance
if (this.count > 0 && this.refundAmount > 0) {
tech.removeJunkTechFromPool(this.refundAmount)
this.refundAmount = 0
}
}
},
{
name: "metastability",
description: "+13% chance to duplicate spawned power ups
duplicates explode with a 4 second half-life",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return tech.duplicationChance() < 1
},
requires: "below 100% duplication chance",
effect() {
tech.isPowerUpsVanish = true
powerUps.setPowerUpMode(); //needed after adjusting duplication chance
if (!build.isExperimentSelection && !simulation.isTextLogOpen) simulation.circleFlare(0.11);
},
remove() {
tech.isPowerUpsVanish = false
if (this.count) powerUps.setPowerUpMode(); //needed after adjusting duplication chance }
}
},
{
name: "correlated damage",
descriptionFunction() {
return `duplication increases damage
(${(1 + Math.min(1, tech.duplicationChance())).toFixed(2)}x)`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return tech.duplicationChance() > 0.03
},
requires: "duplication chance > 3%",
effect() {
tech.isDupDamage = true;
},
remove() {
tech.isDupDamage = false;
}
},
{
name: "parthenogenesis",
description: "+8% chance to duplicate spawned power ups
duplication also duplicates mobs",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return tech.duplicationChance() > 0
},
requires: "some duplication chance",
effect() {
tech.isDuplicateMobs = true;
powerUps.setPowerUpMode(); //needed after adjusting duplication chance
if (!build.isExperimentSelection && !simulation.isTextLogOpen) simulation.circleFlare(0.08);
},
remove() {
tech.isDuplicateMobs = false;
if (this.count) powerUps.setPowerUpMode(); //needed after adjusting duplication chance
}
},
{
name: "stimulated emission",
description: "+20% chance to duplicate spawned power ups,
collisions eject a random tech",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return tech.duplicationChance() < 1
},
requires: "below 1% duplication chance",
effect() {
tech.isStimulatedEmission = true
powerUps.setPowerUpMode(); //needed after adjusting duplication chance
if (!build.isExperimentSelection && !simulation.isTextLogOpen) simulation.circleFlare(0.20);
},
remove() {
tech.isStimulatedEmission = false
if (this.count) powerUps.setPowerUpMode(); //needed after adjusting duplication chance
}
},
{
name: "strange attractor",
descriptionFunction() {
return `1.1x damage
removing this increases duplication by +11%`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBadRandomOption: true,
allowed() {
return true
},
requires: "",
damage: 1.1,
effect() {
tech.damage *= this.damage
},
isRemoveBenefit: true,
remove() {
if (this.count > 0 && m.alive) {
tech.duplication += 0.11
powerUps.setPowerUpMode(); //needed after adjusting duplication chance
if (!build.isExperimentSelection && !simulation.isTextLogOpen) simulation.circleFlare(0.11);
tech.damage /= this.damage
this.frequency = 0
}
}
},
{
name: "strange loop",
description: `1.1x damage
removing this gives a random removetech`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBadRandomOption: true,
allowed() {
return true
},
requires: "",
damage: 1.1,
effect() {
tech.damage *= this.damage
},
isRemoveBenefit: true,
remove() {
if (this.count > 0 && m.alive) {
tech.damage /= this.damage
this.frequency = 0
requestAnimationFrame(() => {
const options = []
for (let i = 0, len = tech.tech.length; i < len; i++) if (tech.tech[i].isRemoveBenefit && tech.tech[i].count === 0) options.push(i)
const index = options[Math.floor(Math.random() * options.length)]
tech.giveTech(tech.tech[index].name)
});
}
}
},
{
name: "null hypothesis",
description: `1.1x damage
removing this spawns ${powerUps.orb.research(15)}`,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBadRandomOption: true,
allowed() {
return true
},
requires: "",
damage: 1.1,
effect() {
tech.damage *= this.damage
},
isRemoveBenefit: true,
remove() {
if (this.count > 0 && m.alive) {
tech.damage /= this.damage
requestAnimationFrame(() => { powerUps.spawnDelay("research", 15) });
this.frequency = 0
}
}
},
{
name: "martingale",
descriptionFunction() {
return `${(1 + this.damage).toFixed(1)}x damage. removing this has a 50%
chance return with 2x its damage (${(1 + this.damage).toFixed(1)}x→${(1 + 2 * this.damage).toFixed(1)}x)`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBadRandomOption: true,
allowed() {
return true
},
requires: "",
damage: 0.1,
effect() {
tech.damage *= 1 + this.damage
},
isRemoveBenefit: true,
remove() {
if (this.count > 0 && m.alive) {
tech.damage /= 1 + this.damage
if (Math.random() < 0.5) {
this.damage *= 2
requestAnimationFrame(() => { tech.giveTech("martingale") });
}
this.frequency = 0
}
}
},
{
name: "externality",
descriptionFunction() {
return `1.1x damage
removing this spawns ${this.ammo} ${powerUps.orb.ammo(1)}`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBadRandomOption: true,
allowed() {
return true
},
requires: "",
damage: 1.1,
ammo: 50,
effect() {
tech.damage *= this.damage
},
isRemoveBenefit: true,
remove() {
if (this.count > 0 && m.alive) {
tech.damage /= this.damage
this.frequency = 0
requestAnimationFrame(() => { powerUps.spawnDelay("ammo", this.ammo) });
}
}
},
{
name: "deprecated",
scale: 0.07,
descriptionFunction() {
return `after removing this gain
${1 + this.scale}x damage per removed tech(${(1 + this.scale * ((this.frequency === 0 ? 0 : 1) + tech.removeCount)).toFixed(2)}x)`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBadRandomOption: true,
allowed() {
return true
},
requires: "",
damage: 1.1,
effect() {
},
isRemoveBenefit: true,
remove() {
if (this.count > 0 && m.alive) {
tech.damage *= 1 + this.scale * (1 + tech.removeCount)
this.frequency = 0
}
}
},
{
name: "paradigm shift",
descriptionFunction() {
return `when paused clicking a tech ejects it
–${tech.pauseEjectTech.toFixed(1)} ${tech.isEnergyHealth ? "energy" : "health"} cost (1.3x cost each use)`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isSuperDeterminism && !tech.isNoDraftPause
},
requires: "not superdeterminism, eternalism",
effect() {
tech.isPauseEjectTech = true;
},
remove() {
tech.isPauseEjectTech = false;
}
},
{
name: "Born rule",
description: "eject all your tech",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isInstant: true,
isBadRandomOption: true,
allowed() {
return (tech.totalCount > 6)
},
requires: "more than 6 tech",
effect() {
// //remove active bullets //to get rid of bots
// for (let i = 0; i < bullet.length; ++i) Matter.Composite.remove(engine.world, bullet[i]);
// bullet = [];
// let count = 1 //count tech
// for (let i = 0, len = tech.tech.length; i < len; i++) { // spawn new tech power ups
// if (!tech.tech[i].isInstant) count += tech.tech[i].count
// }
// if (tech.isDeterminism) count -= 4 //remove the bonus tech
// if (tech.isSuperDeterminism) count -= 4 //remove the bonus tech
// const removeCount = tech.removeCount
// tech.setupAllTech(); // remove all tech
// tech.removeCount = removeCount
// if (simulation.isCheating) tech.setCheating();
// lore.techCount = 0;
// // tech.addLoreTechToPool();
// for (let i = 0; i < count; i++) powerUps.spawn(m.pos.x + 100 * (Math.random() - 0.5), m.pos.y + 100 * (Math.random() - 0.5), "tech"); // spawn new tech power ups
// //have state is checked in m.death()
let count = 0 //count tech
for (let i = 0, len = tech.tech.length; i < len; i++) { // spawn new tech power ups
if (!tech.tech[i].isInstant && tech.tech[i].count) {
count += tech.tech[i].count
tech.removeTech(i)
// powerUps.ejectTech(index)
}
}
powerUps.spawnDelay("tech", count);
// for (let i = 0; i < count; i++) powerUps.spawn(m.pos.x + 100 * (Math.random() - 0.5), m.pos.y + 100 * (Math.random() - 0.5), "tech"); // spawn new tech power ups
},
remove() { }
},
{
name: "Occams razor",
descriptionFunction() {
return `randomly remove half your tech
for each removed ${(1 + this.damagePerRemoved).toFixed(2)}x damage (~${((this.count === 0) ? 1 + this.damagePerRemoved * 0.5 * tech.totalCount : this.damage).toFixed(2)}x)`
},
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isInstant: true,
isBadRandomOption: true,
allowed() {
return (tech.totalCount > 6)
},
requires: "more than 6 tech",
damagePerRemoved: 0.5,
damage: null,
effect() {
let pool = []
for (let i = 0, len = tech.tech.length; i < len; i++) { // spawn new tech power ups
if (tech.tech[i].count && !tech.tech[i].isInstant) pool.push(i)
}
pool = shuffle(pool); //shuffles order of maps
let removeCount = 0
for (let i = 0, len = pool.length * 0.5; i < len; i++) removeCount += tech.removeTech(pool[i])
this.damage = this.damagePerRemoved * removeCount
tech.damage *= (1 + this.damage)
},
remove() {
if (this.count && m.alive) tech.damage /= (1 + this.damage)
}
},
{
name: "exchange symmetry",
description: "remove 1 random tech
spawn 2 new guns",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isInstant: true,
isBadRandomOption: true,
allowed() {
return (tech.totalCount > 3) && !tech.isSuperDeterminism
},
requires: "at least 4 tech, not superdeterminism",
effect() {
const have = [] //find which tech you have
for (let i = 0; i < tech.tech.length; i++) {
if (tech.tech[i].count > 0 && !tech.tech[i].isInstant) have.push(i)
}
const choose = have[Math.floor(Math.random() * have.length)]
for (let i = 0; i < tech.tech[choose].count; i++) {
powerUps.spawn(m.pos.x, m.pos.y, "gun");
}
powerUps.spawn(m.pos.x, m.pos.y, "gun");
tech.removeTech(choose)
},
remove() { }
},
{
name: "Monte Carlo method",
description: "remove 1 random tech
spawn 2 tech",
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isInstant: true,
isBadRandomOption: true,
allowed() {
return (tech.totalCount > 3) && tech.duplicationChance() > 0 && !tech.isSuperDeterminism
},
requires: "some duplication, at least 4 tech, not superdeterminism",
effect() {
const removeTotal = tech.removeTech()
for (let i = 0; i < removeTotal + 1; i++) powerUps.spawn(m.pos.x + 60 * (Math.random() - 0.5), m.pos.y + 60 * (Math.random() - 0.5), "tech");
},
remove() { }
},
//**************************************************
//************************************************** gun
//************************************************** tech
//**************************************************
{
name: "needle ice",
description: `after needles impact walls
they chip off 1-2 freezing ice IX crystals`,
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.isNeedles || tech.isNeedles) && !tech.needleTunnel
},
requires: "nail gun, needle gun, not nanowires",
effect() {
tech.isNeedleIce = true
},
remove() {
tech.isNeedleIce = false
}
},
{
name: "nanowires",
description: `needles tunnel through blocks and map
1.2x needle damage`,
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return ((tech.haveGunCheck("nail gun") && tech.isNeedles) || (tech.isNeedles && tech.haveGunCheck("shotgun"))) && !tech.isNeedleIce
},
requires: "nail gun, needle gun, not needle ice",
effect() {
tech.needleTunnel = true
},
remove() {
tech.needleTunnel = false
}
},
{
name: "ceramics",
description: `needles and harpoons pierce shields
directly damaging shielded mobs`,
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (!tech.isLargeHarpoon && tech.haveGunCheck("harpoon")) || tech.isNeedles || tech.isHookDefense
},
requires: "needle gun, harpoon, not Bessemer process",
effect() {
tech.isShieldPierce = true
},
remove() {
tech.isShieldPierce = false
}
},
{
name: "needle gun",
description: "nail gun and shotgun fire mob piercing needles",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return ((tech.haveGunCheck("nail gun") && !tech.nailInstantFireRate && !tech.nailRecoil && !tech.isRicochet) || (tech.haveGunCheck("shotgun") && !tech.isNailShot && !tech.isFoamShot && !tech.isSporeWorm && !tech.isSporeFlea)) && !tech.isRivets && !tech.isIncendiary && !tech.isIceCrystals && !tech.isIceShot
},
requires: "nail gun, shotgun, not ice crystal, rivets, rotary cannon, pneumatic, incendiary, nail-shot, foam-shot, worm-shot, ice-shot",
effect() {
tech.isNeedles = true
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "nail gun") {
b.guns[i].ammo = Math.ceil(b.guns[i].ammo / this.ammoScale);
b.guns[i].ammoPack = b.guns[i].defaultAmmoPack / this.ammoScale;
b.guns[i].chooseFireMethod()
simulation.updateGunHUD();
break
}
}
},
ammoScale: 3,
remove() {
if (tech.isNeedles) {
tech.isNeedles = false
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "nail gun") {
b.guns[i].chooseFireMethod()
b.guns[i].ammo = Math.ceil(b.guns[i].ammo * this.ammoScale);
b.guns[i].ammoPack = b.guns[i].ammo * this.ammoScale;
simulation.updateGunHUD();
break
}
}
}
}
},
{
name: "stress concentration",
description: "mobs below half durability die after you shoot
them near their center with needles or rivets",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.isNeedles || tech.isRivets) && !tech.isNailCrit && !tech.isIncendiary
},
requires: "needles, rivets, not incendiary, supercritical fission",
effect() {
tech.isCritKill = true
},
remove() {
tech.isCritKill = false
}
},
{
name: "rivet gun",
description: "nail gun and shotgun slowly lob a heavy rivet",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return ((tech.haveGunCheck("nail gun") && !tech.nailInstantFireRate && !tech.isRicochet) || (tech.haveGunCheck("shotgun") && !tech.isNailShot && !tech.isFoamShot && !tech.isSporeWorm && !tech.isSporeFlea)) && !tech.isNeedles && !tech.isIceCrystals && !tech.isIceShot
},
requires: "nail gun, shotgun, not ice crystal, needles, or pneumatic actuator",
effect() {
tech.isRivets = true
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "nail gun") {
b.guns[i].chooseFireMethod()
break
}
}
},
remove() {
if (tech.isRivets) {
tech.isRivets = false
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "nail gun") {
b.guns[i].chooseFireMethod()
break
}
}
}
tech.isRivets = false
}
},
{
name: "pneumatic actuator",
description: "nail gun takes no time to ramp up
to its fastest fire rate",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("nail gun") && !tech.isRivets && !tech.isNeedles && !tech.nailRecoil
},
requires: "nail gun, not rotary cannon, rivets, or needles",
effect() {
tech.nailInstantFireRate = true
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "nail gun") b.guns[i].chooseFireMethod()
}
},
remove() {
if (tech.nailInstantFireRate) {
tech.nailInstantFireRate = false
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "nail gun") b.guns[i].chooseFireMethod()
}
}
}
},
{
name: "ice crystal nucleation",
link: `ice crystal nucleation`,
description: "nail gun uses energy to condense
unlimited freezing ice shards",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("nail gun") && !tech.isRivets && !tech.isNeedles // && !tech.isNailRadiation && !tech.isNailCrit
},
requires: "nail gun, not rivets, needles",
effect() {
tech.isIceCrystals = true;
b.guns[0].ammoPack = Infinity
b.guns[0].recordedAmmo = b.guns[i].ammo
b.guns[0].ammo = Infinity
simulation.updateGunHUD();
},
remove() {
if (tech.isIceCrystals) {
tech.isIceCrystals = false;
b.guns[0].ammoPack = b.guns[0].defaultAmmoPack;
if (b.guns[0].recordedAmmo) b.guns[0].ammo = b.guns[0].recordedAmmo
simulation.updateGunHUD();
if (this.count) requestAnimationFrame(() => { simulation.updateGunHUD(); });
}
tech.isIceCrystals = false;
if (b.guns[0].ammo === Infinity) b.guns[0].ammo = 0
}
},
{
name: "rotary cannon",
description: `nail gun has increased muzzle speed,
maximum fire rate, accuracy, and recoil`,
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("nail gun") && !tech.nailInstantFireRate && !tech.isNeedles
},
requires: "nail gun, not pneumatic actuator, needle gun",
effect() {
tech.nailRecoil = true
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "nail gun") b.guns[i].chooseFireMethod()
}
},
remove() {
if (tech.nailRecoil) {
tech.nailRecoil = false
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "nail gun") b.guns[i].chooseFireMethod()
}
}
}
},
{
name: "gauge",
description: `rivets, needles, super balls, and nails
have 1.3x mass and physical damage`,
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.hookNails + tech.isMineDrop + tech.isNailBotUpgrade + tech.fragments + tech.nailsDeathMob + (tech.haveGunCheck("super balls") + (tech.haveGunCheck("mine") && !tech.isFoamMine) + (tech.haveGunCheck("nail gun")) + tech.isNeedles + tech.isNailShot + tech.isRivets) * 2 > 1
},
requires: "nails, nail gun, rivets, shotgun, super balls, mine",
effect() {
tech.bulletSize = 1 + 0.25 * Math.pow(this.count + 1, 0.5)
},
remove() {
tech.bulletSize = 1;
}
},
{
name: "supercritical fission",
description: "if nails, needles, or rivets strike mobs
near their center they can explode",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.isNailShot || tech.isNeedles || tech.isNailBotUpgrade || tech.haveGunCheck("nail gun") || tech.isRivets || ((tech.isMineDrop || tech.haveGunCheck("mine")) && !(tech.isFoamMine || tech.isSuperMine))) && !tech.isIncendiary && !tech.isCritKill
},
requires: "nail gun, mine, needles, nails, rivets, not incendiary, stress concentration",
effect() {
tech.isNailCrit = true
},
remove() {
tech.isNailCrit = false
}
},
{
name: "irradiated nails",
link: `irradiated nails`,
description: "nails, needles, and rivets are radioactive
1.9x radioactive damage over 3 seconds",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isNailBotUpgrade || tech.hookNails || tech.fragments || tech.nailsDeathMob || ((tech.isMineDrop || tech.haveGunCheck("mine")) && !(tech.isFoamMine || tech.isSuperMine)) || (tech.haveGunCheck("nail gun") && !tech.isShieldPierce) || (tech.haveGunCheck("shotgun") && (tech.isNeedles || tech.isNailShot))
},
requires: "nail gun, nails, rivets, mine, not ceramic needles",
effect() {
tech.isNailRadiation = true;
},
remove() {
tech.isNailRadiation = false;
}
},
{
name: "6s half-life",
link: `6s half-life`,
description: "nails, needles, rivets are made of plutonium-238
radioactive damage lasts +3 seconds",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isNailRadiation && !tech.isFastRadiation
},
requires: "nail gun, mine, irradiated nails, not 1s half-life",
effect() {
tech.isSlowRadiation = true;
},
remove() {
tech.isSlowRadiation = false;
}
},
{
name: "1s half-life",
link: `1s half-life`,
description: "nails, needles, rivets are made of lithium-8
4x radioactive damage for 1 second",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isNailRadiation && !tech.isSlowRadiation
},
requires: "nail gun, mine, irradiated nails, not 6s half-life",
effect() {
tech.isFastRadiation = true;
},
remove() {
tech.isFastRadiation = false;
}
},
{
name: "spin-statistics",
link: `spin-statistics`,
description: `after firing the shotgun you are invulnerable
shotgun has 0.7x bullets per ${powerUps.orb.ammo(1)}`,
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("shotgun")
},
requires: "shotgun",
effect() {
tech.isShotgunImmune = true;
//cut current ammo by 1/2
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "shotgun") {
b.guns[i].ammo = Math.ceil(b.guns[i].ammo * 0.7);
b.guns[i].ammoPack *= 0.7
break;
}
}
simulation.updateGunHUD();
},
remove() {
tech.isShotgunImmune = false;
if (this.count > 0) {
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "shotgun") {
b.guns[i].ammoPack /= 0.7
b.guns[i].ammo = Math.ceil(b.guns[i].ammo / 0.7);
simulation.updateGunHUD();
break;
}
}
}
}
},
{
name: "Newtons 3rd law",
description: "1.7x shotgun fire rate and recoil",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("shotgun") && !tech.isShotgunReversed
},
requires: "shotgun, not Noether violation",
effect() {
tech.isShotgunRecoil = true;
},
remove() {
tech.isShotgunRecoil = false;
}
},
{
name: "Noether violation",
link: `Noether violation`,
description: "1.5x shotgun damage
shotgun recoil is reversed",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return (tech.haveGunCheck("shotgun")) && !tech.isShotgunRecoil
},
requires: "shotgun, not Newtons 3rd law",
effect() {
tech.isShotgunReversed = true;
},
remove() {
tech.isShotgunReversed = false;
}
},
{
name: "repeater",
description: "shotgun immediately fires again for no ammo
reduced 0.5x shotgun fire rate",
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.haveGunCheck("shotgun"))
},
requires: "shotgun, not Newtons 3rd law",
effect() {
tech.shotgunExtraShots++;
},
remove() {
tech.shotgunExtraShots = 0
}
},
{
name: "nail-shot",
link: `nail-shot`,
description: "shotgun drives a long clip of nails",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("shotgun") && !tech.isIncendiary && !tech.isRivets && !tech.isIceShot && !tech.isFoamShot && !tech.isSporeWorm && !tech.isSporeFlea && !tech.isNeedles
},
requires: "shotgun, not incendiary, rivets, foam-shot, worm-shot, ice-shot, needles",
effect() {
tech.isNailShot = true;
},
remove() {
tech.isNailShot = false;
}
},
{
name: "foam-shot",
link: `foam-shot`,
description: "shotgun sprays sticky foam bubbles",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("shotgun") && !tech.isNailShot && !tech.isIncendiary && !tech.isRivets && !tech.isIceShot && !tech.isSporeWorm && !tech.isSporeFlea && !tech.isNeedles
},
requires: "shotgun, not incendiary, nail-shot, rivet, worm-shot, ice-shot, needle",
effect() {
tech.isFoamShot = true;
},
remove() {
tech.isFoamShot = false;
}
},
{
name: "ice-shot",
link: `ice-shot`,
description: "shotgun grows freezing ice IX crystals",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("shotgun") && !tech.isNailShot && !tech.isIncendiary && !tech.isRivets && !tech.isFoamShot && !tech.isSporeWorm && !tech.isSporeFlea && !tech.isNeedles
},
requires: "shotgun, not incendiary, nail-shot, rivet, foam-shot, worm-shot",
effect() {
tech.isIceShot = true;
},
remove() {
tech.isIceShot = false;
}
},
{
name: "freezer burn",
description: "mobs frozen while below 33% durability die",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isIceCrystals || tech.isSporeFreeze || (m.fieldMode === 4 && simulation.molecularMode === 2) || tech.isIceShot || tech.isNeedleIce || (m.coupling && (m.fieldMode === 2 || m.fieldMode === 0))
},
requires: "a freeze effect",
effect() {
tech.isIceKill = true
},
remove() {
tech.isIceKill = false
}
},
{
name: "flash freeze",
description: "mobs frozen while above 66% durability
have their durability reduced to 66%",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isIceCrystals || tech.isSporeFreeze || (m.fieldMode === 4 && simulation.molecularMode === 2) || tech.isIceShot || tech.isNeedleIce || (m.coupling && (m.fieldMode === 2 || m.fieldMode === 0))
},
requires: "a freeze effect",
effect() {
tech.isIceMaxHealthLoss = true
},
remove() {
tech.isIceMaxHealthLoss = false
}
},
{
name: "crystallizer",
description: "after frozen mobs die they
shatter into ice IX crystals",
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.isIceCrystals || tech.isSporeFreeze || (m.fieldMode === 4 && simulation.molecularMode === 2) || tech.isIceShot || tech.isNeedleIce || (m.coupling && (m.fieldMode === 2 || m.fieldMode === 0))) && !tech.sporesOnDeath && !tech.isExplodeMob && !tech.botSpawner && !tech.isMobBlockFling && !tech.nailsDeathMob
},
requires: "a localized freeze effect, no other mob death tech",
effect() {
tech.iceIXOnDeath++
},
remove() {
tech.iceIXOnDeath = 0
}
},
{
name: "thermoelectric effect",
description: "after killing mobs with ice IX
+100 energy",
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (m.fieldMode === 4 && simulation.molecularMode === 2) || tech.isNeedleIce || (m.coupling && (m.fieldMode === 2 || m.fieldMode === 0)) || tech.iceIXOnDeath || tech.isIceShot
},
requires: "ice IX",
effect() {
tech.iceEnergy++
},
remove() {
tech.iceEnergy = 0;
}
},
{
name: "superfluidity",
description: "freeze effects are applied
to a small area around the target",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isIceCrystals || tech.isSporeFreeze || (m.fieldMode === 4 && simulation.molecularMode === 2) || tech.isNeedleIce || (m.coupling && (m.fieldMode === 2 || m.fieldMode === 0)) || tech.iceIXOnDeath || tech.isIceShot
},
requires: "a localized freeze effect",
effect() {
tech.isAoESlow = true
},
remove() {
tech.isAoESlow = false
}
},
{
name: "triple point",
descriptionFunction() {
return `+5 second freeze duration`
},
isGunTech: true,
maxCount: 3,
count: 0,
frequency: 1,
frequencyDefault: 1,
// allowed() {
// return (m.fieldMode === 2 && m.coupling > 0) || (tech.haveGunCheck("shotgun") && tech.isIceShot)
// },
// requires: "perfect diamagnetism",
allowed() {
return (tech.isIceCrystals || tech.isSporeFreeze || (m.fieldMode === 4 && simulation.molecularMode === 2) || tech.isIceShot || tech.isNeedleIce || (m.coupling && (m.fieldMode === 2 || m.fieldMode === 0)))
},
requires: "a localized freeze effect",
effect() {
tech.iceIXFreezeTime += 5 * 60
// powerUps.spawnDelay("coupling", 10)
},
remove() {
tech.iceIXFreezeTime = 150
// if (this.count) m.couplingChange(-10 * this.count)
}
},
{
name: "incendiary ammunition",
description: "shotgun, rivets, super balls, and drones
are loaded with explosives",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return (tech.haveGunCheck("shotgun") && !tech.isNailShot && !tech.isIceShot && !tech.isRivets && !tech.isFoamShot && !tech.isSporeWorm && !tech.isSporeFlea && !tech.isNeedles) || ((tech.haveGunCheck("super balls") || tech.isSuperMine) && !tech.isSuperBounce && !tech.isFoamBall && !tech.isSuperHarm) || (tech.isRivets && !tech.isNailCrit) || (m.fieldMode === 4 && simulation.molecularMode === 3) || (tech.haveGunCheck("drones") && !tech.isForeverDrones && !tech.isDroneRadioactive && !tech.isDroneTeleport)
},
requires: "shotgun, super balls, rivets, drones, not irradiated drones, burst drones, polyurethane, Zectron",
effect() {
tech.isIncendiary = true
},
remove() {
tech.isIncendiary = false;
}
},
{
name: "rebound",
description: `after they collide with a mob, super balls
gain speed, duration, and 1.3x damage`,
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.haveGunCheck("super balls") || tech.isSuperMine) && !tech.isIncendiary && !tech.isFoamBall
},
requires: "super balls, not incendiary",
effect() {
tech.isSuperBounce = true
},
remove() {
tech.isSuperBounce = false
}
},
{
name: "Zectron",
description: `2x super ball damage, but
after colliding with super balls -4 energy`,
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.haveGunCheck("super balls") || tech.isSuperMine) && !tech.isIncendiary && !tech.isBulletTeleport
},
requires: "super balls not incendiary ammunition, uncertainty principle",
effect() {
tech.isSuperHarm = true
},
remove() {
tech.isSuperHarm = false
}
},
{
name: "polyurethane foam",
description: "super balls and harpoons colliding with mobs
catalyzes a reaction that yields foam bubbles",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return ((tech.haveGunCheck("super balls") || tech.isSuperMine) && !tech.isSuperBounce) || (tech.haveGunCheck("harpoon") && !tech.fragments) || tech.isHookDefense
},
requires: "super balls, harpoon, not fragmentation",
effect() {
tech.isFoamBall = true;
},
remove() {
tech.isFoamBall = false;
}
},
{
name: "autocannon",
description: "fire +2 super balls in a line
1.4x super ball velocity and gravity",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("super balls") && !tech.oneSuperBall
},
requires: "super balls, but not the tech super ball",
effect() {
tech.superBallDelay = true
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "super balls") b.guns[i].chooseFireMethod()
}
},
remove() {
if (tech.superBallDelay) {
tech.superBallDelay = false;
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "super balls") b.guns[i].chooseFireMethod()
}
}
}
},
{
name: "super duper",
description: `randomly fire +0, +1, +2, or +3 extra super balls`,
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.haveGunCheck("super balls") || tech.isSuperMine) && !tech.oneSuperBall
},
requires: "super balls, not super ball",
effect() {
tech.extraSuperBalls += 4
},
remove() {
tech.extraSuperBalls = 0;
}
},
{
name: "super ball",
description: "fire just 1 large super ball
that stuns mobs for 2 second",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.haveGunCheck("super balls") || tech.isSuperMine) && !tech.extraSuperBalls && !tech.superBallDelay
},
requires: "super balls, not super duper or autocannon",
effect() {
tech.oneSuperBall = true;
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "super balls") b.guns[i].chooseFireMethod()
}
},
remove() {
if (tech.oneSuperBall) {
tech.oneSuperBall = false;
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "super balls") b.guns[i].chooseFireMethod()
}
}
}
},
{
name: "phase velocity",
description: "wave particles propagate faster as solids
1.4x wave damage",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("wave") && !tech.isLongitudinal
},
requires: "wave, not phonon",
effect() {
tech.isPhaseVelocity = true;
},
remove() {
tech.isPhaseVelocity = false;
}
},
{
name: "amplitude",
description: "1.4x wave damage
1.4x wave bullet amplitude",
isGunTech: true,
maxCount: 3,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("wave") || tech.isSoundBotUpgrade
},
requires: "wave",
effect() {
tech.waveFrequency *= 0.66
tech.wavePacketDamage *= 1.4
},
remove() {
tech.waveFrequency = 0.2 //adjust this to make the waves much larger
tech.wavePacketDamage = 1
}
},
{
name: "propagation",
description: `0.75x wave propagation speed
1.4x wave damage`,
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("wave") || tech.isSoundBotUpgrade
},
requires: "wave",
effect() {
tech.waveBeamSpeed *= 0.75;
tech.waveBeamDamage += 0.3 * 0.4 //this sets base wave damage
},
remove() {
tech.waveBeamSpeed = 11;
tech.waveBeamDamage = 0.3 //this sets base wave damage
}
},
{
name: "bound state",
description: "wave packets reflect backwards 2 times
0.7x wave range",
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("wave") && !tech.isLongitudinal
},
requires: "wave, not phonon",
effect() {
tech.waveReflections += 2
},
remove() {
tech.waveReflections = 1
}
},
{
name: "frequency",
description: `wave has unlimited ammo
0.75x wave damage`,
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed: () => tech.haveGunCheck("wave"),
requires: "wave",
effect() {
tech.isInfiniteWaveAmmo = true
b.guns[3].savedAmmo = b.guns[3].ammo
b.guns[3].ammo = Infinity
simulation.updateGunHUD();
},
remove() {
tech.isInfiniteWaveAmmo = false
if (this.count > 0 && b.guns[3].savedAmmo !== undefined) {
b.guns[3].ammo = b.guns[3].savedAmmo
simulation.updateGunHUD();
requestAnimationFrame(() => { simulation.updateGunHUD(); });
} else if (b.guns[3].ammo === Infinity) {
b.guns[3].ammo = 0
}
}
},
{
name: "phonon",
description: "waves are low frequency, high damage
expanding arcs that propagate through solids",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return tech.haveGunCheck("wave") && !tech.isPhaseVelocity && tech.waveReflections === 1
},
requires: "wave, not phase velocity, bound state",
ammoScale: 6,
effect() {
tech.isLongitudinal = true;
b.guns[3].chooseFireMethod()
b.guns[3].ammoPack /= this.ammoScale
if (tech.isInfiniteWaveAmmo) {
b.guns[3].savedAmmo = Math.ceil(b.guns[3].savedAmmo / this.ammoScale); //used with low frequency
} else {
b.guns[3].ammo = Math.ceil(b.guns[3].ammo / this.ammoScale);
}
simulation.updateGunHUD();
},
remove() {
tech.isLongitudinal = false;
if (this.count > 0) {
b.guns[3].chooseFireMethod()
b.guns[3].ammoPack *= this.ammoScale
if (tech.isInfiniteWaveAmmo) {
b.guns[3].savedAmmo = Math.ceil(b.guns[3].savedAmmo * this.ammoScale); //used with low frequency
} else {
b.guns[3].ammo = Math.ceil(b.guns[3].ammo * this.ammoScale);
}
simulation.updateGunHUD();
}
}
},
{
name: "isotropic",
description: "waves expand in all directions
0.6x range and 1.5x damage",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return tech.isLongitudinal && tech.haveGunCheck("wave") && !tech.isBulletTeleport
},
requires: "wave, phonon, not uncertainty principle",
effect() {
tech.is360Longitudinal = true;
b.guns[3].chooseFireMethod()
},
remove() {
tech.is360Longitudinal = false;
b.guns[3].chooseFireMethod()
}
},
{
name: "mechanical resonance",
description: "after a block gets vibrated by a phonon
there is a chance it's flung at nearby mobs",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.isLongitudinal && tech.haveGunCheck("wave")) || tech.isSoundBotUpgrade
},
requires: "wave, phonon",
effect() {
tech.isPhononBlock = true
},
remove() {
tech.isPhononBlock = false
}
},
{
name: "sympathetic resonance",
description: "after a mob gets vibrated by a phonon
a new resonance wave expands",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.isLongitudinal && tech.haveGunCheck("wave")) || tech.isSoundBotUpgrade
},
requires: "wave, phonon",
effect() {
tech.isPhononWave = true
},
remove() {
tech.isPhononWave = false
}
},
{
name: "missile-bot",
link: `missile-bot`,
description: `use ${powerUps.orb.research(1)}to trade your missile gun
for a bot that fires missiles`,
// isGunTech: true,
isRemoveGun: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBot: true,
isBotTech: true,
isInstant: true,
allowed() {
return tech.haveGunCheck("missiles", false) && tech.missileFireCD === 45 && (build.isExperimentSelection || powerUps.research.count > 0)
},
requires: "missiles, not launch system",
effect() {
tech.missileBotCount++;
b.missileBot();
if (tech.haveGunCheck("missiles", false)) b.removeGun("missiles") //remove your last gun
for (let i = 0; i < 1; i++) {
if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
}
},
remove() {
// if (this.count) {
// tech.missileBotCount = 0;
// b.clearPermanentBots();
// b.respawnBots();
// if (!tech.haveGunCheck("missiles", false)) b.giveGuns("missiles")
// powerUps.research.changeRerolls(1)
// }
}
},
{
name: "cruise missile",
description: "2x missile explosive damage, radius
0.5x missile speed",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.haveGunCheck("missiles") && tech.missileFireCD === 45) || (m.fieldMode === 4 && simulation.molecularMode === 1) || tech.missileBotCount
},
requires: "missiles, not launch system",
effect() {
tech.isMissileBig = true
},
remove() {
tech.isMissileBig = false
}
},
{
name: "ICBM",
description: "1.75x missile explosive damage, radius
0.5x missile speed",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.haveGunCheck("missiles") || (m.fieldMode === 4 && simulation.molecularMode === 1)) && tech.isMissileBig
},
requires: "missiles, cruise missile",
effect() {
tech.isMissileBiggest = true
},
remove() {
tech.isMissileBiggest = false
}
},
{
name: "launch system",
description: `5x missile gun fire rate
1.2x missile ammo per ${powerUps.orb.ammo(1)}`,
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("missiles") && !tech.isMissileBig
},
requires: "missiles, not cruise missile",
ammoBonus: 1.2,
effect() {
tech.missileFireCD = 10
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "missiles") {
b.guns[i].ammoPack *= this.ammoBonus;
b.guns[i].ammo = Math.ceil(b.guns[i].ammo * this.ammoBonus);
simulation.updateGunHUD();
break
}
}
},
remove() {
tech.missileFireCD = 45;
if (this.count > 0) {
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "missiles") {
b.guns[i].ammoPack /= this.ammoBonus;
b.guns[i].ammo = Math.ceil(b.guns[i].ammo / this.ammoBonus);
simulation.updateGunHUD();
break
}
}
}
}
},
{
name: "iridium-192",
description: "explosions release gamma radiation
2x explosion damage over 4 seconds",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isImmuneExplosion && tech.explosiveRadius === 1 && !tech.isSmallExplosion && !tech.isBlockExplode && !tech.fragments && (tech.haveGunCheck("missiles") || tech.missileBotCount || tech.isIncendiary || (tech.haveGunCheck("grenades") && !tech.isNeutronBomb) || tech.isPulseLaser || (m.fieldMode === 4 && simulation.molecularMode === 1) || tech.isBoomBotUpgrade || tech.isTokamak)
},
requires: "an explosive damage source, not ammonium nitrate, nitroglycerin, chain reaction, fragmentation, electric armor",
effect() {
tech.isExplodeRadio = true; //iridium-192
},
remove() {
tech.isExplodeRadio = false;
}
},
{
name: "fragmentation",
description: "some detonations and collisions eject nails
blocks, grenades, missiles, rivets, harpoon",
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return !tech.isExplodeRadio && ((tech.haveGunCheck("harpoon") && !tech.isFoamBall) || (tech.haveGunCheck("grenades") && !tech.isNeutronBomb) || tech.haveGunCheck("missiles") || (m.fieldMode === 4 && simulation.molecularMode === 1) || tech.missileBotCount || tech.isRivets || tech.blockDamage > 0.075 || tech.isHookDefense)
},
requires: "grenades, missiles, rivets, harpoon, or mass driver, not iridium-192, not polyurethane foam",
effect() {
tech.fragments++
},
remove() {
tech.fragments = 0
}
},
{
name: "ammonium nitrate",
description: "1.25x explosive damage, radius",
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return !tech.isExplodeRadio && tech.hasExplosiveDamageCheck()
},
requires: "an explosive damage source, not iridium-192",
effect() {
tech.explosiveRadius += 0.25;
},
remove() {
tech.explosiveRadius = 1;
}
},
{
name: "nitroglycerin",
description: "1.7x explosive damage
0.7x smaller explosive radius",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return !tech.isExplodeRadio && tech.hasExplosiveDamageCheck() && !tech.isExplosionHarm
},
requires: "an explosive damage source, not iridium-192, acetone peroxide",
effect() {
tech.isSmallExplosion = true;
},
remove() {
tech.isSmallExplosion = false;
}
},
{
name: "acetone peroxide",
description: "1.7x explosive radius
1.4x explosive damage taken",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
isBadRandomOption: true,
allowed() {
return tech.hasExplosiveDamageCheck() && !tech.isSmallExplosion
},
requires: "an explosive damage source, not nitroglycerin",
effect() {
tech.isExplosionHarm = true;
},
remove() {
tech.isExplosionHarm = false;
}
},
{
name: "shock wave",
description: "mines and sporangium stun for 3-5 seconds
explosions stun for 0.5 seconds",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return tech.haveGunCheck("spores") || tech.haveGunCheck("mine") || (!tech.isExplodeRadio && tech.hasExplosiveDamageCheck())
},
requires: "mine, spores, an explosive damage source, not iridium-192",
effect() {
tech.isStun = true;
},
remove() {
tech.isStun = false;
}
},
{
name: "shaped charge",
description: `use ${powerUps.orb.research(2)} to dynamically reduce
all explosions to prevent health loss`,
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return !tech.isImmuneExplosion && (build.isExperimentSelection || powerUps.research.count > 1) && (tech.haveGunCheck("missiles") || (m.fieldMode === 4 && simulation.molecularMode === 1) || tech.missileBotCount > 0 || tech.isIncendiary || tech.isPulseLaser || tech.isTokamak || (tech.haveGunCheck("grenades") && !tech.isNeutronBomb))
},
requires: "an explosive damage source, not rocket propelled grenade",
effect() {
tech.isSmartRadius = true;
for (let i = 0; i < 2; i++) {
if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
}
},
remove() {
tech.isSmartRadius = false;
if (this.count > 0) powerUps.research.changeRerolls(3)
}
},
{
name: "MIRV",
description: "fire +1 missile or grenade per shot
0.88x explosion damage and radius",
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("missiles") || tech.missileBotCount || tech.haveGunCheck("grenades")
},
requires: "missiles, grenades",
effect() {
tech.missileCount++;
},
remove() {
tech.missileCount = 1;
}
},
{
name: "rocket-propelled grenade",
description: "grenades explode on map collisions
explosions drain energy, not health",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("grenades") && !tech.isVacuumBomb && !tech.isSmartRadius
},
requires: "grenades, not vacuum bomb, shaped charges",
effect() {
tech.isImmuneExplosion = true;
tech.isRPG = true;
b.setGrenadeMode()
},
remove() {
tech.isImmuneExplosion = false;
tech.isRPG = false;
b.setGrenadeMode()
}
},
{
name: "vacuum bomb",
description: "grenades fire slower, explode bigger,
and suck everything towards them",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("grenades") && !tech.isNeutronBomb && !tech.isBlockExplode && !tech.isRPG
},
requires: "grenades, not neutron bomb, chain reaction, RPG",
effect() {
tech.isVacuumBomb = true;
b.setGrenadeMode()
},
remove() {
tech.isVacuumBomb = false;
b.setGrenadeMode()
}
},
{
name: "chain reaction",
description: "1.3x grenade radius and damage
blocks caught in explosions also explode",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("grenades") && !tech.isExplodeRadio && !tech.isNeutronBomb && !tech.isVacuumBomb
},
requires: "grenades, not iridium-192, neutron bomb, vacuum bomb",
effect() {
tech.isBlockExplode = true; //chain reaction
},
remove() {
tech.isBlockExplode = false;
}
},
{
name: "flame test",
description: "after grenades detonate they release
a colorful cluster of small explosions",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("grenades") && !tech.isNeutronBomb && !tech.isCircleExplode && !tech.isPetalsExplode
},
requires: "grenades, not neutron bomb, pyrotechnics, fireworks",
effect() {
tech.isClusterExplode = true;
},
remove() {
tech.isClusterExplode = false;
}
},
{
name: "pyrotechnics",
description: "after grenades detonate they release
a colorful circle of explosions",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("grenades") && !tech.isNeutronBomb && !tech.isClusterExplode && !tech.isPetalsExplode
},
requires: "grenades, not neutron bomb, flame test, fireworks",
effect() {
tech.isCircleExplode = true;
},
remove() {
tech.isCircleExplode = false;
}
},
{
name: "fireworks",
description: "after grenades detonate they release
colorful petals of explosions",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("grenades") && !tech.isNeutronBomb && !tech.isClusterExplode && !tech.isCircleExplode
},
requires: "grenades, not neutron bomb, pyrotechnics, flame test",
effect() {
tech.isPetalsExplode = true;
},
remove() {
tech.isPetalsExplode = false;
}
},
{
name: "neutron bomb",
description: "grenades are irradiated with Cf-252
does radioactive damage over time",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("grenades") && !tech.fragments && !tech.isVacuumBomb && !tech.isExplodeRadio && !tech.isBlockExplode && !tech.isClusterExplode && !tech.isPetalsExplode && !tech.isCircleExplode
},
requires: "grenades, not fragmentation, vacuum bomb, iridium-192, pyrotechnics, fireworks, flame test, chain reaction",
effect() {
tech.isNeutronBomb = true;
b.setGrenadeMode()
},
remove() {
tech.isNeutronBomb = false;
b.setGrenadeMode()
}
},
{
name: "vacuum permittivity",
description: "1.2x radioactive range
objects in range of the bomb are slowed",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isNeutronBomb
},
requires: "grenades, neutron bomb",
effect() {
tech.isNeutronSlow = true
},
remove() {
tech.isNeutronSlow = false
}
},
{
name: "radioactive contamination",
description: "after a mob or shield dies,
leftover radiation spreads to a nearby mob",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isNailRadiation || tech.isWormholeDamage || tech.isNeutronBomb || tech.isExplodeRadio || tech.isBlockRadiation
},
requires: "radiation damage source",
effect() {
tech.isRadioactive = true
},
remove() {
tech.isRadioactive = false
}
},
{
name: "nuclear transmutation",
description: "1.5x radiation damage
nail, drone, neutron bomb, iridium, cosmic string, deflect",
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isNailRadiation || tech.isWormholeDamage || tech.isNeutronBomb || tech.isExplodeRadio || tech.isBlockRadiation || tech.isDroneRadioactive
},
requires: "radiation damage source",
effect() {
tech.radioactiveDamage += 1.5
},
remove() {
tech.radioactiveDamage = 1
}
},
{
name: "water shielding",
link: `water shielding`,
description: "reduce radioactive effects on you by 0.8x
neutron bomb, drones, explosions, slime",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isNeutronBomb || tech.isDroneRadioactive || tech.isExplodeRadio
},
requires: "neutron bomb, irradiated drones, iridium-192",
effect() {
tech.isRadioactiveResistance = true
},
remove() {
tech.isRadioactiveResistance = false
}
},
{
name: "ricochet",
description: "after nails hit a mob they rebound towards
a new mob with 2.8x damage per bounce",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
// return (tech.haveGunCheck("nail gun") && !tech.isRivets && !tech.isNeedles) || (tech.haveGunCheck("mines"))
return tech.isMineDrop || tech.isNailBotUpgrade || tech.hookNails || tech.fragments || tech.nailsDeathMob || (tech.haveGunCheck("mine") && !(tech.isLaserMine || tech.isFoamMine || tech.isSuperMine)) || (tech.haveGunCheck("nail gun") && !tech.isRivets && !tech.isNeedles) || (tech.haveGunCheck("shotgun") && (tech.isNeedles || tech.isNailShot) && !tech.isRivets && !tech.isNeedles)
},
//
requires: "nail gun, not rotary cannon, rivets, or needles",
effect() {
tech.isRicochet = true
},
remove() {
tech.isRicochet = false
}
},
{
name: "booby trap",
description: "50% chance to drop a mine from power ups
+15% JUNKtech chance",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("mine") && tech.junkChance < 1
},
requires: "mines",
effect() {
tech.isMineDrop = true;
if (tech.isMineDrop) b.mine(m.pos, { x: 0, y: 0 }, 0)
this.refundAmount += tech.addJunkTechToPool(0.15)
},
refundAmount: 0,
remove() {
tech.isMineDrop = false;
if (this.count > 0 && this.refundAmount > 0) {
tech.removeJunkTechFromPool(this.refundAmount)
this.refundAmount = 0
}
}
},
{
name: "elephants toothpaste",
description: "instead of nails mines catalyze a reaction
that yields foam bubbles",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("mine") && !tech.isSuperMine && !tech.isRicochet && !tech.isNailRadiation && !tech.isNailCrit
},
requires: "mines, not blast ball, ricochet, irradiated nails, supercritical fission",
effect() {
tech.isFoamMine = true;
},
remove() {
tech.isFoamMine = false;
}
},
{
name: "blast ball",
descriptionFunction() {
return `instead of nails mines fire bouncy balls`
},
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("mine") && !tech.isFoamMine && !tech.isRicochet && !tech.isNailRadiation && !tech.isNailCrit
},
requires: "mines, not elephants toothpaste, ricochet, irradiated nails, supercritical fission",
effect() {
tech.isSuperMine = true;
},
remove() {
tech.isSuperMine = false;
}
},
{
name: "laser-mines",
link: `laser-mines`,
description: "mines laid while you are crouched
use energy to emit 3 unaimed lasers",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("mine")
},
requires: "mines",
effect() {
tech.isLaserMine = true;
},
remove() {
tech.isLaserMine = false;
}
},
{
name: "sentry",
descriptionFunction() {
return `mines fire one ${b.guns[10].nameString()} at a time
mines fire 1.5x more ${b.guns[10].nameString('s')}`
},
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("mine")
},
requires: "mines, not elephants toothpaste",
effect() {
tech.isMineSentry = true;
},
remove() {
tech.isMineSentry = false;
}
},
{
name: "extended magazine",
descriptionFunction() {
return `sentry mines fire 1.5x more ${b.guns[10].nameString('s')}`
},
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("mine") && tech.isMineSentry
},
requires: "mines, sentry",
effect() {
tech.sentryAmmo += 17;
},
remove() {
tech.sentryAmmo = 33;
}
},
{
name: "mycelial fragmentation",
link: `mycelial fragmentation`,
description: "during their growth phase
1.7x sporangium discharge",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("spores")
},
requires: "spores",
effect() {
tech.isSporeGrowth = true
},
remove() {
tech.isSporeGrowth = false
}
},
{
name: "cordyceps",
// descriptionFunction() {
// return `mobs infected by ${b.guns[6].nameString('s')} have a 5% chance
to resurrect and attack other mobs`
// },
description: "sporangium infect mobs they attach to
infected mobs resurrect and attack other mobs",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("spores")
},
requires: "spores",
effect() {
tech.isZombieMobs = true
},
remove() {
tech.isZombieMobs = false
}
},
{
name: "colony",
description: "1.6x sporangium discharge
33% chance to discharge something different",
link: `colony`,
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("spores")
},
requires: "spores",
effect() {
tech.isSporeColony = true
},
remove() {
tech.isSporeColony = false
}
},
{
name: "cryodesiccation",
descriptionFunction() {
return `1.25x sporangium discharge
${b.guns[6].nameString('s')} freeze mobs for 1.5 second`
},
// description: "+25% sporangium discharge
spores freeze mobs for 1.5 second",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("spores") || tech.sporesOnDeath > 0 || (m.fieldMode === 4 && simulation.molecularMode === 0) || tech.isSporeWorm || tech.isSporeFlea
},
requires: "spores",
effect() {
tech.isSporeFreeze = true
},
remove() {
tech.isSporeFreeze = false
}
},
{
name: "flagella",
descriptionFunction() {
return `2x ${b.guns[6].nameString()} acceleration
if they can't find a target ${b.guns[6].nameString('s')} follow you`
},
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("spores") || tech.sporesOnDeath > 0 || (m.fieldMode === 4 && simulation.molecularMode === 0) || tech.isSporeWorm || tech.isSporeFlea
},
requires: "spores",
effect() {
tech.isSporeFollow = true
},
remove() {
tech.isSporeFollow = false
}
},
{
name: "mutualism",
descriptionFunction() {
return `3x ${b.guns[6].nameString()} damage
${b.guns[6].nameString('s')} borrow 1 health until they die`
},
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.haveGunCheck("spores") || tech.sporesOnDeath > 0 || (m.fieldMode === 4 && simulation.molecularMode === 0)) || tech.isSporeWorm || tech.isSporeFlea
},
requires: "spores",
effect() {
tech.isMutualism = true
},
remove() {
tech.isMutualism = false
}
},
{
name: "necrophage",
description: "if foam, fleas, or worms kill their target
they grow 3 copies",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("foam") || tech.isFoamBall || tech.isFoamBotUpgrade || tech.isFoamShot || tech.isSporeWorm || tech.isSporeFlea || tech.isFoamMine
},
requires: "foam, spores, worms, fleas",
effect() {
tech.isSpawnBulletsOnDeath = true
},
remove() {
tech.isSpawnBulletsOnDeath = false;
}
},
{
name: "siphonaptera",
description: "spores metamorphose into fleas
shotgun fires fleas",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return (tech.haveGunCheck("spores") || tech.sporesOnDeath > 0 || (m.fieldMode === 4 && simulation.molecularMode === 0) || (tech.haveGunCheck("shotgun") && !tech.isIncendiary && !tech.isRivets && !tech.isIceShot && !tech.isFoamShot && !tech.isNeedles && !tech.isNailShot)) && !tech.isSporeWorm
},
requires: "spores, not worms",
effect() {
tech.isSporeFlea = true
},
remove() {
tech.isSporeFlea = false
}
},
{
name: "nematodes",
description: "spores metamorphose into worms
shotgun fires worms",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return (tech.haveGunCheck("spores") || tech.sporesOnDeath > 0 || (m.fieldMode === 4 && simulation.molecularMode === 0) || (tech.haveGunCheck("shotgun") && !tech.isIncendiary && !tech.isRivets && !tech.isIceShot && !tech.isFoamShot && !tech.isNeedles && !tech.isNailShot)) && !tech.isSporeFlea
},
requires: "spores, not fleas",
effect() {
tech.isSporeWorm = true
},
remove() {
tech.isSporeWorm = false
}
},
{
name: "K-selection",
description: "1.37x worm and flea damage",
isGunTech: true,
maxCount: 3,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isSporeWorm || tech.isSporeFlea
},
requires: "spores, shotgun, worms, fleas",
effect() {
tech.wormSize++
},
remove() {
tech.wormSize = 0
}
},
{
name: "path integration",
descriptionFunction() {
return `drones and ${b.guns[6].nameString("s")}
travel with you through levels`
},
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.isSporeFollow && (tech.haveGunCheck("spores") || (tech.haveGunCheck("shotgun") && tech.isSporeWorm))) || tech.haveGunCheck("drones") || (m.fieldMode === 4 && (simulation.molecularMode === 0 || simulation.molecularMode === 3))
},
requires: "spores, worms, flagella, drones",
effect() {
tech.isDronesTravel = true
},
remove() {
tech.isDronesTravel = false
}
},
{
name: "fault tolerance",
description: `use ${powerUps.orb.research(2)}to trade your drone gun
for 5 drones that last forever`,
// isGunTech: true,
isRemoveGun: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isInstant: true,
allowed() {
return tech.haveGunCheck("drones", false) && !tech.isDroneRespawn && tech.bulletsLastLonger === 1 && !tech.isDronesTravel && (build.isExperimentSelection || powerUps.research.count > 1)
},
requires: "drones, not drone repair, anti-shear topology, autonomous navigation",
effect() {
const num = 5
tech.isForeverDrones += num
if (tech.haveGunCheck("drones", false)) b.removeGun("drones")
//spawn drones
if (tech.isDroneRadioactive) {
for (let i = 0; i < num * 0.25; i++) {
b.droneRadioactive({
x: m.pos.x + 30 * (Math.random() - 0.5),
y: m.pos.y + 30 * (Math.random() - 0.5)
}, 5)
bullet[bullet.length - 1].endCycle = Infinity
}
} else {
for (let i = 0; i < num; i++) {
b.drone({
x: m.pos.x + 30 * (Math.random() - 0.5),
y: m.pos.y + 30 * (Math.random() - 0.5)
}, 5)
bullet[bullet.length - 1].endCycle = Infinity
}
}
for (let i = 0; i < 2; i++) {
if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
}
},
remove() {
tech.isForeverDrones = 0
// if (this.count && !tech.haveGunCheck("drones", false)) b.giveGuns("drones")
// if (this.count > 0) powerUps.research.changeRerolls(2)
}
},
{
name: "reduced tolerances",
link: `reduced tolerances`,
description: `2x drones per ${powerUps.orb.ammo()} and energy
0.6x drone duration`,
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return !tech.isDroneRadioactive && (tech.haveGunCheck("drones") || (m.fieldMode === 4 && simulation.molecularMode === 3))
},
requires: "drones, not irradiated drones",
effect() {
tech.droneCycleReduction = 0.6
tech.droneEnergyReduction = 0.3
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "drones") b.guns[i].ammoPack *= 2
}
},
remove() {
tech.droneCycleReduction = 1
tech.droneEnergyReduction = 1
if (this.count > 0) {
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "drones") b.guns[i].ammoPack /= 2
}
}
}
},
{
name: "delivery drone",
description: "if a drone picks up a power up,
it becomes larger, faster, and more durable",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("drones") || tech.isForeverDrones || (m.fieldMode === 4 && simulation.molecularMode === 3)
},
requires: "drones",
effect() {
tech.isDroneGrab = true
},
remove() {
tech.isDroneGrab = false
}
},
{
name: "von Neumann probe", //"drone repair",
description: "after a drone expires it will use -4 energy
and a nearby block to replicate itself",
// description: "broken drones repair if the drone gun is active
repairing has a 25% chance to use 1 drone",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("drones") || (m.fieldMode === 4 && simulation.molecularMode === 3)
},
requires: "drones",
effect() {
tech.isDroneRespawn = true
},
remove() {
tech.isDroneRespawn = false
}
},
{
name: "brushless motor",
description: "drones rapidly rush towards their target
1.33x drone collision damage",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return (tech.haveGunCheck("drones") || tech.isForeverDrones || (m.fieldMode === 4 && simulation.molecularMode === 3)) && !tech.isDroneRadioactive && !tech.isIncendiary
},
requires: "drones, molecular assembler, not irradiated drones, incendiary",
effect() {
tech.isDroneTeleport = true
},
remove() {
tech.isDroneTeleport = false
}
},
{
name: "axial flux motor",
description: "1.66x drones rush frequency
1.44x drone collision damage",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isDroneTeleport
},
requires: "drones, brushless motor",
effect() {
tech.isDroneFastLook = true
},
remove() {
tech.isDroneFastLook = false
}
},
{
name: "irradiated drones",
link: `irradiated drones`,
description: `the space around drones is irradiated
0.25x drones per ${powerUps.orb.ammo()} and energy`,
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.droneCycleReduction === 1 && !tech.isIncendiary && !tech.isDroneTeleport && (tech.haveGunCheck("drones") || tech.isForeverDrones || (m.fieldMode === 4 && simulation.molecularMode === 3))
},
requires: "drones, not reduced tolerances, incendiary, torque bursts",
effect() {
tech.isDroneRadioactive = true
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "drones") {
b.guns[i].ammoPack *= 0.25
b.guns[i].ammo = Math.ceil(b.guns[i].ammo * 0.25)
simulation.makeGunHUD();
}
}
},
remove() {
tech.isDroneRadioactive = false
if (this.count > 0) {
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "drones") {
b.guns[i].ammoPack /= 0.25
b.guns[i].ammo = b.guns[i].ammo * 4
simulation.makeGunHUD();
}
}
}
}
},
{
name: "beta radiation", //"control rod ejection",
description: "0.5x drone duration
2x drone radiation damage",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isDroneRadioactive
},
requires: "drones, irradiated drones",
effect() {
tech.droneRadioDamage = 2
},
remove() {
tech.droneRadioDamage = 1
}
},
{
name: "orthocyclic winding",
link: `orthocyclic winding`,
description: "1.66x drone acceleration
1.33x radiation damage",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.isDroneRadioactive
},
requires: "drones, irradiated drones",
effect() {
tech.isFastDrones = true
},
remove() {
tech.isFastDrones = false
}
},
{
name: "electrostatic induction",
description: "foam bubbles are electrically charged
causing attraction to nearby mobs",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return tech.haveGunCheck("foam") || tech.isFoamBotUpgrade || tech.isFoamShot || tech.isFoamBall || tech.isFoamMine
},
requires: "foam",
effect() {
tech.isFoamAttract = true
},
remove() {
tech.isFoamAttract = false
}
},
{
name: "uncertainty principle",
description: "foam, wave, and super ball positions are erratic
1.5x foam, wave, and super ball damage",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return (tech.haveGunCheck("foam") || tech.isFoamBotUpgrade || tech.isFoamShot || tech.isFoamBall || tech.isFoamMine) || (tech.haveGunCheck("wave") && !tech.is360Longitudinal) || (tech.haveGunCheck("super balls") && !tech.isSuperHarm) || tech.isSoundBotUpgrade
},
requires: "foam, wave, super balls, not isotropic, Zectron",
effect() {
tech.isBulletTeleport = true
},
remove() {
tech.isBulletTeleport = false;
}
},
{
name: "surfactant",
description: `use ${powerUps.orb.research(2)}to trade your foam gun
for 2 foam-bots and foam-bot upgrade`,
// isGunTech: true,
isRemoveGun: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
isBot: true,
isBotTech: true,
isInstant: true,
requires: "foam gun, not bot upgrades, fractionation, pressure vessel",
allowed() {
return tech.haveGunCheck("foam", false) && !b.hasBotUpgrade() && !tech.isAmmoFoamSize && !tech.isFoamPressure && (build.isExperimentSelection || powerUps.research.count > 1)
},
effect() {
requestAnimationFrame(() => { tech.giveTech("foam-bot upgrade") })
for (let i = 0; i < 2; i++) {
b.foamBot()
tech.foamBotCount++;
}
simulation.makeTextLog(`tech.isFoamBotUpgrade = true`)
if (tech.haveGunCheck("foam", false)) b.removeGun("foam")
for (let i = 0; i < 2; i++) {
if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
}
},
remove() {
// if (this.count) {
// b.clearPermanentBots();
// b.respawnBots();
// if (!tech.haveGunCheck("foam")) b.giveGuns("foam")
// }
// if (this.count > 0) powerUps.research.changeRerolls(2)
}
},
{
name: "aerogel",
description: "foam bubbles float with 0.5x foam duration
2.8x foam damage",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("foam") || tech.isFoamBotUpgrade || tech.isFoamShot || tech.isFoamBall || tech.isFoamMine
},
requires: "foam",
effect() {
tech.isFastFoam = true
tech.foamGravity = -0.0003
},
remove() {
tech.isFastFoam = false;
tech.foamGravity = 0.00008
}
},
{
name: "surface tension",
description: "1.4x foam damage",
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("foam") || tech.isFoamBotUpgrade || tech.isFoamShot || tech.isFoamBall || tech.isFoamMine
},
requires: "foam",
effect() {
tech.foamDamage += 0.01 * 0.4
},
remove() {
tech.foamDamage = 0.01;
}
},
{
name: "cavitation",
description: "25% chance to discharge a huge foam bubble
2x foam gun recoil",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("foam") || tech.isFoamBotUpgrade || tech.isFoamShot || tech.isFoamBall || tech.isFoamMine
},
requires: "foam",
effect() {
tech.isFoamCavitation = true;
b.guns[8].knockBack = 0.001
},
remove() {
tech.isFoamCavitation = false;
b.guns[8].knockBack = 0.0005
}
},
{
name: "foam fractionation",
description: "if you have below 300 ammo
2x foam gun bubble size",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("foam")
},
requires: "foam",
effect() {
tech.isAmmoFoamSize = true
},
remove() {
tech.isAmmoFoamSize = false;
}
},
{
name: "ideal gas law",
description: `6x foam ammo per ${powerUps.orb.ammo(1)}`, //remove all current foam ammo
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("foam") && !tech.isEnergyNoAmmo
},
requires: "foam, not non-renewables",
// ammoLost: 0,
effect() {
b.guns[8].ammoPack *= 6;
// this.ammoLost = b.guns[8].ammo
// b.guns[8].ammo = 0
simulation.updateGunHUD()
},
remove() {
if (this.count) {
b.guns[8].ammoPack /= 8
// b.guns[8].ammo += this.ammoLost
simulation.updateGunHUD()
}
}
},
{
name: "pressure vessel",
description: "build up charge while firing foam gun
after firing discharge foam bubbles",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("foam")
},
requires: "foam",
effect() {
tech.isFoamPressure = true;
b.guns[8].chooseFireMethod()
},
remove() {
tech.isFoamPressure = false;
b.guns[8].chooseFireMethod()
}
},
{
name: "capacitor bank",
// description: "charge effects build up almost instantly
throwing blocks, foam, railgun, pulse, tokamak",
descriptionFunction() {
return `charge effects build up almost instantly
blocks, ${tech.haveGunCheck("foam", false) ? "foam" : "foam"}, ${tech.isPlasmaBall ? "plasma ball" : "plasma ball"}, ${tech.isRailGun ? "railgun" : "railgun"}, ${tech.isPulseLaser ? "pulse" : "pulse"}, ${tech.isTokamak ? "tokamak" : "tokamak"}`
},
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.blockDamage > 0.075 || tech.isRailGun || (tech.haveGunCheck("foam") && tech.isFoamPressure) || tech.isTokamak || tech.isPulseLaser || tech.isPlasmaBall
},
requires: "mass driver, railgun, foam, pressure vessel, pulse, tokamak, plasma ball",
effect() {
tech.isCapacitor = true;
},
remove() {
tech.isCapacitor = false;
}
},
{
name: "Bitter electromagnet",
descriptionFunction() {
return `0.66x railgun charge rate
2x harpoon density and damage`
},
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.007
},
remove() {
tech.railChargeRate = 0.97;
tech.harpoonDensity = tech.isRailGun ? 0.007 : 0.004
}
},
{
name: "railgun",
description: `hold fire to charge harpoon and release to launch
harpoons can't retract`,
// description: `+900% harpoon ammo, but it can't retract
+50% harpoon density and damage`,
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("harpoon") && !tech.isFilament && !tech.isHarpoonPowerUp && !tech.isBoostReplaceAmmo
},
requires: "harpoon, not UHMWPE, induction furnace, quasiparticles",
ammoBonus: 9,
effect() {
tech.isRailGun = true;
tech.harpoonDensity = tech.isRailGun ? 0.007 : 0.004
b.guns[9].chooseFireMethod()
b.guns[9].ammoPack *= 3;
b.guns[9].ammo = b.guns[9].ammo * 6;
simulation.updateGunHUD();
},
remove() {
tech.isRailGun = false;
if (this.count > 0) {
tech.harpoonDensity = tech.isRailGun ? 0.007 : 0.004
b.guns[9].chooseFireMethod()
b.guns[9].ammoPack /= 3;
b.guns[9].ammo = Math.ceil(b.guns[9].ammo / 6);
simulation.updateGunHUD();
}
}
},
{
name: "alternator",
description: "0.05x harpoon energy cost",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("harpoon")
},
requires: "harpoon",
effect() {
tech.isRailEnergy = true;
},
remove() {
tech.isRailEnergy = false;
}
},
{
name: "autonomous defense",
description: "if you collide with a mob
fire harpoons at nearby mobs",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("harpoon")
},
requires: "harpoon",
effect() {
tech.isHarpoonDefense = true
},
remove() {
tech.isHarpoonDefense = false
}
},
{
name: "Bessemer process",
descriptionFunction() {
return `${(1 + 0.1 * Math.sqrt(b.guns[9].ammo)).toFixed(2)}x harpoon size and damage
(effect scales by 1/10 √ harpoon ammo)`
},
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("harpoon") && !tech.isShieldPierce
},
requires: "harpoon, not ceramics",
effect() {
tech.isLargeHarpoon = true;
},
remove() {
tech.isLargeHarpoon = false;
}
},
{
name: "smelting",
descriptionFunction() {
return `forge ${this.removeAmmo()} ammo into a new harpoon
fire +1 harpoon with each shot`
},
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
ammoRemoved: 0,
removeAmmo() {
return (tech.isRailGun ? 5 : 1) * (2 + 2 * this.count)
},
allowed() {
return tech.haveGunCheck("harpoon") && b.guns[9].ammo >= this.removeAmmo()
},
requires: "harpoon",
effect() {
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "harpoon") {
const removeAmmo = this.removeAmmo()
this.ammoRemoved += removeAmmo
b.guns[i].ammo -= removeAmmo
if (b.guns[i].ammo < 0) b.guns[i].ammo = 0
simulation.updateGunHUD();
tech.extraHarpoons++;
break
}
}
},
remove() {
if (tech.extraHarpoons) {
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "harpoon") {
b.guns[i].ammo += this.ammoRemoved
simulation.updateGunHUD();
break
}
}
}
this.ammoRemoved = 0
tech.extraHarpoons = 0;
}
},
{
name: "UHMWPE",
descriptionFunction() {
return `${(1 + b.guns[9].ammo * 0.0125).toFixed(2)}x harpoon rope length
(effect scales by 1/80 of harpoon ammo)`
},
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("harpoon") && !tech.isRailGun
},
requires: "harpoon, not railgun",
effect() {
tech.isFilament = true;
},
remove() {
tech.isFilament = false;
}
},
{
name: "induction furnace",
description: "after using harpoon/grapple to collect power ups
1.8x harpoon or grapple damage for 8 seconds",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return ((tech.haveGunCheck("harpoon") && !tech.isRailGun) || m.fieldMode === 10) && !tech.isHarpoonFullHealth
},
requires: "harpoon, grappling hook, not railgun, brittle",
effect() {
tech.isHarpoonPowerUp = true
},
remove() {
tech.isHarpoonPowerUp = false
tech.harpoonPowerUpCycle = 0
}
},
{
name: "brittle",
description: "2.2x harpoon/grapple damage
to mobs at maximum durability",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.haveGunCheck("harpoon") || m.fieldMode === 10) && !tech.isHarpoonPowerUp
},
requires: "harpoon, grappling hook, not induction furnace",
effect() {
tech.isHarpoonFullHealth = true
},
remove() {
tech.isHarpoonFullHealth = false
}
},
{
name: "quasiparticles",
descriptionFunction() {
return `convert current and future ${powerUps.orb.ammo(1)} into ${powerUps.orb.boost(1)}
that give ${(1 + powerUps.boost.damage).toFixed(2)}x damage for ${(powerUps.boost.duration / 60).toFixed(0)} seconds`
},
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return ((tech.haveGunCheck("wave") && tech.isInfiniteWaveAmmo) || tech.haveGunCheck("laser") || (tech.haveGunCheck("harpoon") && !tech.isRailGun)) && !tech.isEnergyNoAmmo
},
requires: "harpoon, laser, wave, frequency, not railgun, non-renewables",
effect() {
tech.isBoostReplaceAmmo = true
for (let i = powerUp.length - 1; i > -1; i--) {
if (powerUp[i].name === "ammo") {
powerUps.spawn(powerUp[i].position.x + 50 * (Math.random() - 0.5), powerUp[i].position.y + 50 * (Math.random() - 0.5), "boost");
Matter.Composite.remove(engine.world, powerUp[i]);
powerUp.splice(i, 1);
}
}
},
remove() {
tech.isBoostReplaceAmmo = false
}
},
{
name: "optical amplifier",
description: "gain 3 random laser guntech
laser only turns off if you have no energy",
// isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
isInstant: true,
allowed() {
return tech.haveGunCheck("laser") && !tech.isPulseLaser
},
requires: "laser gun, not pulse",
effect() {
requestAnimationFrame(() => {
let techGiven = 0
for (let j = 0; j < 3; j++) {
const names = ["quasiparticles", "lens", "compound lens", "arc length", "infrared diode", "free-electron laser", "dye laser", "relativistic momentum", "specular reflection", "diffraction grating", "diffuse beam", "output coupler", "slow light", "laser-bot", "laser-bot upgrade"]
//convert names into indexes
const options = []
for (let i = 0; i < names.length; i++) {
for (let k = 0; k < tech.tech.length; k++) {
if (tech.tech[k].name === names[i]) {
options.push(k)
break
}
}
}
//remove options that don't meet requirements
for (let i = options.length - 1; i > -1; i--) {
const index = options[i]
if (!(tech.tech[index].count < tech.tech[index].maxCount) || !tech.tech[index].allowed()) {
options.splice(i, 1);
}
}
//pick one option
if (options.length) {
const index = options[Math.floor(Math.random() * options.length)]
simulation.makeTextLog(`tech.giveTech("${tech.tech[index].name}") //optical amplifier`, 360);
tech.giveTech(index)
techGiven++
}
}
if (techGiven > 0) {
tech.isStuckOn = true
} else { //eject if none found
simulation.makeTextLog(`0 tech found //optical amplifier`);
const loop = () => {
if (!simulation.paused && m.alive) {
for (let i = 0; i < tech.tech.length; i++) {
if (tech.tech[i].name === this.name) powerUps.ejectTech(i)
}
return
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
}
});
},
remove() {
tech.isStuckOn = false
}
},
{
name: "relativistic momentum",
description: "lasers push mobs and blocks",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.haveGunCheck("laser") && !tech.isPulseLaser) || tech.isLaserBotUpgrade || tech.isLaserField
},
requires: "laser, not pulse",
effect() {
tech.isLaserPush = true;
},
remove() {
tech.isLaserPush = false;
}
},
{
name: "iridescence",
description: "if laser beams hit mobs near their center
2x laser damage",
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.haveGunCheck("laser") && !tech.isPulseLaser) || tech.isLaserBotUpgrade || tech.isLaserMine
},
requires: "laser, not pulse",
effect() {
tech.laserCrit += 1;
},
remove() {
tech.laserCrit = 0;
}
},
{
name: "lens",
description: "2.5x laser gun damage if it passes
through a revolving 90° arc circular lens", //π / 2
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("laser")
},
requires: "laser",
effect() {
tech.isLaserLens = true
b.guns[11].chooseFireMethod()
// if (this.count > 0) b.guns[11].lensDamageOn += 20 * Math.PI / 180
// b.guns[11].arcRange = 0.78
},
remove() {
tech.isLaserLens = false
b.guns[11].chooseFireMethod()
// b.guns[11].lensDamageOn = 2.5 // 100% + 150%
// b.guns[11].arcRange = 0
}
},
{
name: "compound lens",
description: "1.4x laser lens damage
+25° lens arc",
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("laser") && tech.isLaserLens
},
requires: "lens",
effect() {
b.guns[11].arcRange += 25 * Math.PI / 180 / 2
b.guns[11].lensDamageOn += 0.4
},
remove() {
b.guns[11].arcRange = 90 * Math.PI / 180 / 2 //0.78 divded by 2 because of how it's drawn
b.guns[11].lensDamageOn = 2.5
}
},
{
name: "specular reflection",
description: "+2 laser beam reflections",
isGunTech: true,
maxCount: 3,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return (tech.haveGunCheck("laser") || tech.isLaserMine || tech.isLaserBotUpgrade || tech.isLaserField) && !tech.isWideLaser && !tech.isPulseLaser && !tech.historyLaser
},
requires: "laser, not diffuse beam, pulse, or slow light",
effect() {
tech.laserReflections += 2;
},
remove() {
tech.laserReflections = 2;
}
},
{
name: "diffraction grating",
description: `+1 diverging laser gun beam`,
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return tech.haveGunCheck("laser") && !tech.isWideLaser && !tech.historyLaser
},
requires: "laser gun, diffuse beam, or slow light",
effect() {
tech.beamSplitter++
b.guns[11].chooseFireMethod()
},
remove() {
if (tech.beamSplitter !== 0) {
tech.beamSplitter = 0
b.guns[11].chooseFireMethod()
}
}
},
{
name: "diffuse beam",
link: `diffuse beam`,
description: "laser gun beam is wider and doesn't reflect
3.2x laser damage",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("laser") && tech.laserReflections < 3 && !tech.beamSplitter && !tech.isPulseLaser && !tech.historyLaser
},
requires: "laser gun, not specular reflection, diffraction grating, slow light, pulse",
effect() {
if (tech.wideLaser === 0) tech.wideLaser = 3
tech.isWideLaser = true;
b.guns[11].chooseFireMethod()
},
remove() {
if (tech.isWideLaser) {
// tech.wideLaser = 0
tech.isWideLaser = false;
b.guns[11].chooseFireMethod()
}
}
},
{
name: "output coupler",
description: "1.3x laser gun beam width
1.3x laser damage",
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("laser") && tech.isWideLaser
},
requires: "laser gun, diffuse beam",
effect() {
tech.wideLaser += 2
b.guns[11].chooseFireMethod()
},
remove() {
if (tech.isWideLaser) {
tech.wideLaser = 3
} else {
tech.wideLaser = 0
}
b.guns[11].chooseFireMethod()
}
},
{
name: "delayed-choice",
description: "laser gun fires a 0.4 second delayed beam
delayed beams do 0.7x damage",
isGunTech: true,
maxCount: 9,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return tech.haveGunCheck("laser") && !tech.beamSplitter && !tech.isWideLaser
},
requires: "laser gun, diffraction grating, diffuse beam",
effect() {
tech.historyLaser++
b.guns[11].chooseFireMethod()
},
remove() {
if (tech.historyLaser) {
tech.historyLaser = 0
b.guns[11].chooseFireMethod()
}
}
},
{
name: "infrared diode",
description: "0.4x laser energy cost
infrared light is outside visual perception",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return (tech.haveGunCheck("laser") || tech.isLaserBotUpgrade || tech.isLaserMine || tech.isLaserField) && !tech.isPulseLaser && tech.laserDrain === 0.003
},
requires: "laser, not free-electron, pulse",
effect() {
tech.laserDrain *= 0.4; //100%-50%
tech.laserColor = "transparent" //"rgb(255,0,20,0.02)"
// tech.laserColorAlpha = "rgba(255,0,20,0.05)"
},
remove() {
tech.laserDrain = 0.003;
tech.laserColor = "#f02"
tech.laserColorAlpha = "rgba(255, 0, 0, 0.5)"
}
},
{
name: "dye laser",
description: "0.75x laser energy cost
1.25x laser damage",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return (tech.haveGunCheck("laser") || tech.isLaserMine || tech.isLaserBotUpgrade || tech.isLaserField) && !tech.isPulseLaser && tech.laserDrain === 0.003
},
requires: "laser, not pulse, infrared diode",
effect() {
tech.laserDrain *= 0.75
tech.laserDamage *= 1.25
tech.laserColor = "rgb(0, 11, 255)"
tech.laserColorAlpha = "rgba(0, 11, 255,0.5)"
},
remove() {
tech.laserDrain = 0.003;
tech.laserDamage = 0.18; //used in check on pulse and diode: tech.laserDamage === 0.18
tech.laserColor = "#f00"
tech.laserColorAlpha = "rgba(255, 0, 0, 0.5)"
}
},
{
name: "free-electron laser",
description: "3x laser energy cost
3x laser damage",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return (tech.haveGunCheck("laser") || tech.isLaserMine || tech.isLaserBotUpgrade || tech.isLaserField) && !tech.isPulseLaser && tech.laserDrain === 0.003
},
requires: "laser, not pulse, infrared diode",
effect() {
tech.laserDrain *= 3
tech.laserDamage *= 3
tech.laserColor = "#83f"
tech.laserColorAlpha = "rgba(136, 51, 255,0.5)"
},
remove() {
tech.laserDrain = 0.003;
tech.laserDamage = 0.18; //used in check on pulse and diode: tech.laserDamage === 0.18
tech.laserColor = "#f00"
tech.laserColorAlpha = "rgba(255, 0, 0, 0.5)"
}
},
{
name: "pulse",
description: "charge your energy and release it as a
laser pulse that initiates an explosion cluster",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("laser") && tech.laserReflections < 3 && !tech.isWideLaser && tech.laserDrain === 0.003 && !tech.isStuckOn
},
requires: "laser gun, not specular reflection, diffuse, free-electron laser, optical amplifier",
effect() {
tech.isPulseLaser = true;
b.guns[11].chooseFireMethod()
},
remove() {
if (tech.isPulseLaser) {
tech.isPulseLaser = false;
b.guns[11].chooseFireMethod()
}
}
},
//**************************************************
//************************************************** field
//************************************************** tech
//**************************************************
{
name: "spherical harmonics",
description: "1.5x standing wave deflection energy efficiency
shield deflection radius is stable", //standing wave oscillates in a 3rd dimension
isFieldTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 1 && !tech.isLaserField
},
requires: "standing wave, not surface plasmons",
effect() {
tech.harmonics++
m.fieldShieldingScale = 1.6 * Math.pow(0.5, (tech.harmonics - 2))
m.harmonicShield = m.harmonicAtomic
},
remove() {
tech.harmonics = 2
m.fieldShieldingScale = 1.6 * Math.pow(0.5, (tech.harmonics - 2))
m.harmonicShield = m.harmonic3Phase
}
},
{
name: "surface plasmons",
description: "if deflecting drains all your energy
emit laser beams that scale with max energy",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 1 && tech.harmonics === 2
},
requires: "standing wave",
effect() {
tech.isLaserField = true
},
remove() {
tech.isLaserField = false
}
},
{
name: "zero point energy",
description: `use ${powerUps.orb.research(2)}
+166 maximum energy`,
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return (m.fieldMode === 1 || m.fieldMode === 8 || m.fieldMode === 6) && (build.isExperimentSelection || powerUps.research.count > 1)
},
requires: "standing wave, pilot wave, time dilation",
effect() {
tech.harmonicEnergy = 1.66
m.setMaxEnergy()
for (let i = 0; i < 2; i++) {
if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
}
},
remove() {
tech.harmonicEnergy = 0;
m.setMaxEnergy()
if (this.count > 0) powerUps.research.changeRerolls(2)
}
},
{
name: "expansion",
description: "using standing wave field expands its radius
+77 maximum energy",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 1
},
requires: "standing wave",
effect() {
tech.isStandingWaveExpand = true
m.setMaxEnergy()
// m.fieldShieldingScale = (tech.isStandingWaveExpand ? 0.9 : 1.6) * Math.pow(0.6, (tech.harmonics - 2))
},
remove() {
tech.isStandingWaveExpand = false
m.setMaxEnergy()
// m.fieldShieldingScale = (tech.isStandingWaveExpand ? 0.9 : 1.6) * Math.pow(0.6, (tech.harmonics - 2))
m.harmonicRadius = 1
}
},
{
// descriptionFunction() {
// return `use ${powerUps.orb.research(2)}
1.01x damage per energy below maximum (${(1 + Math.max(0, m.maxEnergy - m.energy)).toFixed(2)}x)`
// },
name: "electronegativity",
descriptionFunction() {
return `1.0023x damage per energy
(${(1 + 0.23 * m.energy).toFixed(2)} at current energy, ${(1 + 0.23 * m.maxEnergy).toFixed(2)}x at maximum energy)`
},
// description: "+1% damage per 8 stored energy",
isFieldTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 1 || m.fieldMode === 9 || m.fieldMode === 8
},
requires: "standing wave, wormhole, pilot wave",
effect() {
tech.energyDamage++
},
remove() {
tech.energyDamage = 0;
}
},
{
name: "bremsstrahlung",
description: "deflecting and thrown blocks
do braking damage to mobs",
isFieldTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 1 || m.fieldMode === 2 || m.fieldMode === 8
},
requires: "standing wave, perfect diamagnetism, pilot wave",
effect() {
tech.blockDmg += 5 //if you change this value also update the for loop in the electricity graphics in m.pushMass
},
remove() {
tech.blockDmg = 0;
}
},
{
name: "cherenkov radiation", //deflecting and blocks
description: "bremsstrahlung's effects are radioactive
3.5x damage over 3 seconds",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (m.fieldMode === 1 || m.fieldMode === 2 || m.fieldMode === 8) && tech.blockDmg
},
requires: "bremsstrahlung",
effect() {
tech.isBlockRadiation = true
},
remove() {
tech.isBlockRadiation = false;
}
},
{
name: "flux pinning",
description: "after deflecting a mob
it is stunned for up to 4 seconds",
isFieldTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 2 || m.fieldMode === 1 || m.fieldMode === 4
},
requires: "a field that can block",
effect() {
tech.isStunField += 240;
},
remove() {
tech.isStunField = 0;
}
},
{
name: "eddy current brake",
description: "perfect diamagnetism slows nearby mobs
effect radius scales with stored energy",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 2 && !tech.isHealBrake
},
requires: "perfect diamagnetism, not induction brake",
effect() {
tech.isPerfectBrake = true;
},
remove() {
tech.isPerfectBrake = false;
}
},
{
name: "Meissner effect",
description: "1.55x perfect diamagnetism radius
+22° perfect diamagnetism circular arc",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 2
},
requires: "perfect diamagnetism",
effect() {
tech.isBigField = true;
},
remove() {
tech.isBigField = false;
}
},
{
name: "radiative equilibrium",
descriptionFunction() {
return `after losing ${tech.isEnergyHealth ? "energy" : "health"}
3x damage for 8 seconds`
},
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 8 || m.fieldMode === 3
},
requires: "negative mass, pilot wave",
effect() {
tech.isHarmDamage = true;
},
remove() {
tech.isHarmDamage = false;
}
},
{
name: "dynamic equilibrium",
descriptionFunction() {
return `increase damage by your last ${tech.isEnergyHealth ? "energy" : "health"} loss
(${(1 + tech.lastHitDamage * m.lastHit).toFixed(2)}x damage)`
},
isFieldTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 8 || m.fieldMode === 3
},
requires: "negative mass, pilot wave",
effect() {
tech.lastHitDamage += 5;
},
remove() {
tech.lastHitDamage = 0;
}
},
{
name: "neutronium",
description: `0.8x move and jump, but
if your field is active 0.05x damage taken`,
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 3
},
requires: "negative mass",
effect() {
tech.isNeutronium = true
tech.baseFx *= 0.8
tech.baseJumpForce *= 0.8
m.setMovement()
},
//also removed in m.setHoldDefaults() if player switches into a bad field
remove() {
tech.isNeutronium = false
if (!tech.isFreeWormHole) {
tech.baseFx = 0.08
tech.baseJumpForce = 10.5
m.setMovement()
}
}
},
{
name: "aerostat",
description: `2x damage while off the ground
0.9x damage while on the ground`,
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 3 || m.fieldMode === 10
},
requires: "negative mass, grappling hook",
effect() {
tech.isNoGroundDamage = true
},
remove() {
tech.isNoGroundDamage = false
}
},
{
name: "annihilation",
description: "after colliding with non-boss mobs
they are annihilated and –10 energy",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 3 && !tech.isEnergyHealth
},
requires: "negative mass, not mass-energy",
effect() {
tech.isAnnihilation = true
},
remove() {
tech.isAnnihilation = false;
}
},
{
name: "inertial mass",
description: "negative mass is larger and faster", //
blocks also move horizontally with the field
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 3
},
requires: "negative mass",
effect() {
tech.isFlyFaster = true
},
remove() {
tech.isFlyFaster = false;
}
},
// {
// name: "Bose Einstein condensate",
// description: "use energy to freeze mobs in your field
pilot wave, negative mass, time dilation",
// isFieldTech: true,
// maxCount: 1,
// count: 0,
// frequency: 2,
// frequencyDefault: 2,
// allowed() {
// return m.fieldMode === 8 || m.fieldMode === 3 || (m.fieldMode === 6 && !tech.isRewindField)
// },
// requires: "pilot wave, negative mass, time dilation, not retrocausality",
// effect() {
// tech.isFreezeMobs = true
// },
// remove() {
// tech.isFreezeMobs = false
// }
// },
// {
// name: "mycelium manufacturing",
// link: `mycelium manufacturing`,
// // description: `use ${powerUps.orb.research(1)}to repurpose molecular assembler
excess energy used to grow spores`,
// descriptionFunction() { return `use ${powerUps.orb.research(1)}to repurpose molecular assembler
excess energy used to grow ${b.guns[6].nameString('s')}` },
// isFieldTech: true,
// maxCount: 1,
// count: 0,
// frequency: 3,
// frequencyDefault: 3,
// allowed() {
// return (build.isExperimentSelection || powerUps.research.count > 0) && m.fieldMode === 4 && !(tech.isMissileField || tech.isIceField || tech.isFastDrones || tech.isDroneGrab || tech.isDroneRadioactive || tech.isDroneTeleport)
// },
// requires: "molecular assembler, no other manufacturing, no drone tech",
// effect() {
// if (!build.isExperimentSelection) {
// for (let i = 0; i < 1; i++) {
// if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
// }
// }
// tech.isSporeField = true;
// },
// remove() {
// tech.isSporeField = false;
// if (this.count > 0) powerUps.research.changeRerolls(1)
// }
// },
// {
// name: "missile manufacturing",
// link: `missile manufacturing`,
// description: `use ${powerUps.orb.research(1)}to repurpose molecular assembler
excess energy used to construct missiles`,
// // description: "use 3 research to repurpose assembler
excess energy used to construct missiles",
// isFieldTech: true,
// maxCount: 1,
// count: 0,
// frequency: 3,
// frequencyDefault: 3,
// allowed() {
// return (build.isExperimentSelection || powerUps.research.count > 0) && m.maxEnergy > 0.5 && m.fieldMode === 4 && !(tech.isSporeField || tech.isIceField || tech.isFastDrones || tech.isDroneGrab || tech.isDroneRadioactive || tech.isDroneTeleport || tech.isDronesTravel)
// },
// requires: "molecular assembler, no other manufacturing, no drone tech",
// effect() {
// if (!build.isExperimentSelection) {
// for (let i = 0; i < 1; i++) {
// if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
// }
// }
// tech.isMissileField = true;
// },
// remove() {
// tech.isMissileField = false;
// if (this.count > 0) powerUps.research.changeRerolls(1)
// }
// },
// {
// name: "ice IX manufacturing",
// link: `ice IX manufacturing`,
// description: `use ${powerUps.orb.research(1)}to repurpose molecular assembler
excess energy used to condense ice IX`,
// // description: "use 3 research to repurpose assembler
excess energy used to condense ice IX",
// isFieldTech: true,
// maxCount: 1,
// count: 0,
// frequency: 3,
// frequencyDefault: 3,
// allowed() {
// return (build.isExperimentSelection || powerUps.research.count > 0) && m.fieldMode === 4 && !(tech.isSporeField || tech.isMissileField || tech.isFastDrones || tech.isDroneGrab || tech.isDroneRadioactive || tech.isDroneTeleport || tech.isDronesTravel)
// },
// requires: "molecular assembler, no other manufacturing, no drone tech",
// effect() {
// if (!build.isExperimentSelection) {
// for (let i = 0; i < 1; i++) {
// if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
// }
// }
// tech.isIceField = true;
// },
// remove() {
// tech.isIceField = false;
// if (this.count > 0) powerUps.research.changeRerolls(1)
// }
// },
{
name: "additive manufacturing",
description: "hold crouch and use your field to print a block
with 1.8x density, damage, and launch speed",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (m.fieldMode === 4 || m.fieldMode === 8) && !tech.isTokamak
},
requires: "molecular assembler, pilot wave, not tokamak",
effect() {
tech.isPrinter = true;
},
remove() {
if (this.count > 0) m.holdingTarget = null;
tech.isPrinter = false;
}
},
{
name: "working mass",
// description: "molecular assembler prints one block
to jump off while midair",
descriptionFunction() {
const fieldName = m.fieldMode === 8 ? "pilot wave" : "molecular assembler"
return `a second jump in midair
will print a block to jump off`
// return `${fieldName} prints a block
to jump off while midair`
},
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (m.fieldMode === 4 || m.fieldMode === 8)
},
requires: "molecular assembler, pilot wave",
effect() {
simulation.ephemera.push({
name: "blockJump",
blockJumpPhase: 0,
do() {
if (m.onGround && m.buttonCD_jump + 10 < m.cycle && !(m.lastOnGroundCycle + m.coyoteCycles > m.cycle)) this.blockJumpPhase = 0 //reset after touching ground or block
if (this.blockJumpPhase === 0 && !m.onGround && !input.up && m.buttonCD_jump + 10 < m.cycle) { //not pressing jump
this.blockJumpPhase = 1
} else if (this.blockJumpPhase === 1 && input.up && m.buttonCD_jump + 10 < m.cycle) { //2nd jump
this.blockJumpPhase = 2
let horizontalVelocity = 8 * (- input.left + input.right) //ive player and block horizontal momentum
const radius = 25 + Math.floor(15 * Math.random())
body[body.length] = Matter.Bodies.polygon(m.pos.x, m.pos.y + 60 + radius, 4, radius, {
friction: 0.05,
frictionAir: 0.001,
collisionFilter: {
category: cat.body,
mask: cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet
},
classType: "body",
});
const block = body[body.length - 1]
//mess with the block shape (this code is horrible)
Composite.add(engine.world, block); //add to world
const r1 = radius * (1 + 0.4 * Math.random())
const r2 = radius * (1 + 0.4 * Math.random())
let angle = Math.PI / 4
const vertices = []
for (let i = 0, len = block.vertices.length; i < len; i++) {
angle += 2 * Math.PI / len
vertices.push({ x: block.position.x + r1 * Math.cos(angle), y: block.position.y + r2 * Math.sin(angle) })
}
Matter.Body.setVertices(block, vertices)
// Matter.Body.setAngle(block, Math.PI / 4)
Matter.Body.setVelocity(block, { x: 0.9 * player.velocity.x - horizontalVelocity, y: 10 });
Matter.Body.applyForce(block, m.pos, { x: 0, y: m.jumpForce * 0.12 * Math.min(m.standingOn.mass, 5) });
if (tech.isBlockRestitution) {
block.restitution = 0.999 //extra bouncy
block.friction = block.frictionStatic = block.frictionAir = 0.001
}
if (tech.isAddBlockMass) {
const expand = function (that, massLimit) {
if (that.mass < massLimit) {
const scale = 1.04;
Matter.Body.scale(that, scale, scale);
setTimeout(expand, 20, that, massLimit);
}
};
expand(block, Math.min(20, block.mass * 3))
}
//jump
m.buttonCD_jump = m.cycle; //can't jump again until 20 cycles pass
Matter.Body.setVelocity(player, { x: player.velocity.x + horizontalVelocity, y: -7.5 + 0.25 * player.velocity.y });
player.force.y = -m.jumpForce; //player jump force
}
},
})
},
remove() {
if (this.count) simulation.removeEphemera("blockJump")
}
},
{
name: "pair production",
description: "after picking up a power up
+200 energy",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 4 || m.fieldMode === 1 || m.fieldMode === 8
},
requires: "molecular assembler, pilot wave, standing wave",
effect() {
tech.isMassEnergy = true // used in m.grabPowerUp
m.energy += 2
},
remove() {
tech.isMassEnergy = false;
}
},
{
name: "electric generator",
description: "after deflecting mobs
molecular assembler generates +50 energy",
isFieldTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 4
},
requires: "molecular assembler",
effect() {
tech.deflectEnergy += 0.5;
},
remove() {
tech.deflectEnergy = 0;
}
},
{
name: "combinatorial optimization",
description: "1.35x damage
0.7x fire rate",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 6 || m.fieldMode === 7 || m.fieldMode === 8
},
requires: "time dilation, cloaking, pilot wave",
damage: 1.35,
effect() {
tech.damage *= this.damage
tech.aimDamage = 1.42
b.setFireCD();
},
remove() {
if (this.count && m.alive) tech.damage /= this.damage
tech.aimDamage = 1
b.setFireCD();
}
},
{
name: "tokamak",
description: "tokamak converts thrown blocks into energy
and a pulsed fusion explosion",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (m.fieldMode === 5 || m.fieldMode === 4 || m.fieldMode === 10) && !tech.isPrinter && !tech.isReel && !tech.hookNails
},
requires: "plasma torch, molecular assembler, grappling hook, not printer, reel, swarf",
effect() {
tech.isTokamak = true;
},
remove() {
tech.isTokamak = false;
}
},
{
name: "stellarator",
descriptionFunction() {
return `the first 5 blocks detonated by tokamak
spawn ${powerUps.orb.heal(1)} proportional to block size`
},
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return tech.isTokamak && (m.fieldMode === 5 || m.fieldMode === 4 || m.fieldMode === 10)
},
requires: "tokamak",
effect() {
tech.isTokamakHeal = true;
tech.tokamakHealCount = 0
},
remove() {
tech.isTokamakHeal = false;
}
},
{
name: "inertial confinement",
description: "while holding a block charged with tokamak
you can use energy to fly", //and invulnerable?
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return tech.isTokamak && (m.fieldMode === 5 || m.fieldMode === 4 || m.fieldMode === 10)
},
requires: "tokamak",
effect() {
tech.isTokamakFly = true;
},
remove() {
tech.isTokamakFly = false;
}
},
{
name: "degenerate matter",
description: "if your field is active
0.1x damage taken",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (m.fieldMode === 10 || m.fieldMode === 5 || m.fieldMode === 8) //1 standing wave 2 perfect diamagnetism 3 negative mass 4 molecular assembler 5 plasma torch 6 time dilation 7 metamaterial cloaking 8 pilot wave 9 wormhole 10 grappling hook
},
requires: "plasma torch, grappling hook, pilot wave",
effect() {
tech.isHarmReduce = true
},
remove() {
tech.isHarmReduce = false;
}
},
{
name: "plasma-bot",
link: `plasma-bot`,
description: `use ${powerUps.orb.research(2)}to trade your field
for a bot that uses energy to emit plasma`,
// isFieldTech: true,
isInstant: true,
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
isBot: true,
isBotTech: true,
allowed() {
return m.fieldMode === 5 && !tech.isPlasmaBall && !tech.isExtruder && (build.isExperimentSelection || powerUps.research.count > 1)
},
requires: "plasma torch, not extruder, plasma ball",
effect() {
tech.plasmaBotCount++;
b.plasmaBot();
if (build.isExperimentSelection) {
document.getElementById("field-" + m.fieldMode).classList.remove("build-field-selected");
document.getElementById("field-0").classList.add("build-field-selected");
}
m.setField("field emitter")
for (let i = 0; i < 2; i++) {
if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
}
},
remove() {
if (this.count > 0) {
tech.plasmaBotCount = 0;
b.clearPermanentBots();
b.respawnBots();
if (m.fieldMode === 0) {
m.setField("plasma torch")
if (build.isExperimentSelection) {
document.getElementById("field-0").classList.remove("build-field-selected");
document.getElementById("field-" + m.fieldMode).classList.add("build-field-selected");
}
}
powerUps.research.changeRerolls(2)
}
}
},
{
name: "plasma jet",
link: `plasma jet`,
description: `use ${powerUps.orb.research(2)}
1.5x plasma torch range`,
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (tech.plasmaBotCount || m.fieldMode === 5) && (build.isExperimentSelection || powerUps.research.count > 1) && !tech.isPlasmaBall
},
requires: "plasma torch, not plasma ball",
effect() {
tech.isPlasmaRange += 0.5;
for (let i = 0; i < 2; i++) {
if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
}
},
remove() {
tech.isPlasmaRange = 1;
if (this.count > 0) powerUps.research.changeRerolls(this.count * 2)
}
},
{
name: "extruder",
description: "extrude a thin hot wire of plasma
increases damage and energy cost",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 5 && !tech.isPlasmaBall
},
requires: "plasma torch, not plasma ball",
effect() {
tech.isExtruder = true;
m.fieldUpgrades[m.fieldMode].set()
},
remove() {
tech.isExtruder = false;
if (this.count && m.fieldMode === 5) m.fieldUpgrades[m.fieldMode].set()
}
},
{
name: "refractory metal",
description: "extrude metals at a higher temperature
increases effective radius and damage",
isFieldTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 5 && tech.isExtruder
},
requires: "extruder",
effect() {
tech.extruderRange += 55
},
remove() {
tech.extruderRange = 15
}
},
{
name: "plasma ball",
description: "grow an expanding ball of plasma
increases damage and energy cost",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 5 && !tech.isExtruder && tech.isPlasmaRange === 1
},
requires: "plasma torch, not extruder, plasma jet",
effect() {
tech.isPlasmaBall = true;
m.fieldUpgrades[m.fieldMode].set()
},
remove() {
tech.isPlasmaBall = false;
if (this.count && m.fieldMode === 5) m.fieldUpgrades[m.fieldMode].set()
}
},
{
name: "corona discharge",
description: "increase the range and frequency
of plasma ball's electric arc ",
isFieldTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 5 && tech.isPlasmaBall
},
requires: "plasma ball",
effect() {
tech.plasmaDischarge += 0.03
},
remove() {
tech.plasmaDischarge = 0.01 //default chance per cycle of a discharge
}
},
{
name: "retrocausality",
description: "time dilation uses energy to rewind your
health, velocity, and position up to 10 seconds",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return m.fieldMode === 6 && !m.isShipMode && !tech.isRewindAvoidDeath && !tech.isTimeSkip
},
requires: "time dilation, not CPT symmetry",
effect() {
tech.isRewindField = true;
m.fieldUpgrades[6].set()
m.wakeCheck();
},
remove() {
tech.isRewindField = false;
if (this.count) m.fieldUpgrades[6].set()
}
},
{
name: "frame-dragging", //"non-inertial frame",
description: "when not moving time dilation stops time
0.6x damage taken",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return m.fieldMode === 6
},
requires: "time dilation",
effect() {
tech.isTimeStop = true;
m.fieldHarmReduction = 0.66; //33% reduction
},
remove() {
tech.isTimeStop = false;
if (m.fieldMode === 6) m.fieldHarmReduction = 1;
}
},
{
name: "Lorentz transformation",
description: `use ${powerUps.orb.research(3)}
1.5x movement, jumping, and fire rate`,
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return (m.fieldMode === 6 || m.fieldMode === 8) && (build.isExperimentSelection || powerUps.research.count > 2)
},
requires: "time dilation or pilot wave",
effect() {
tech.isFastTime = true
m.setMovement();
b.setFireCD();
for (let i = 0; i < 3; i++) {
if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
}
},
remove() {
tech.isFastTime = false
m.setMovement();
b.setFireCD();
if (this.count > 0) powerUps.research.changeRerolls(3)
}
},
{
name: "time crystals",
descriptionFunction() {
return `2.5x passive energy generation
(+${(150 * m.fieldRegen * 60).toFixed(1)} energy per second)`
},
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return !tech.isGroundState && (m.fieldMode === 6 || m.fieldMode === 8)
},
requires: "time dilation or pilot wave, not ground state",
effect() {
tech.isTimeCrystals = true
m.setFieldRegen()
this.descriptionFunction = function () {
return `2.5x passive energy generation
(+${(60 * m.fieldRegen * 60).toFixed(1)} energy per second)`
}
},
remove() {
tech.isTimeCrystals = false
m.setFieldRegen()
this.descriptionFunction = function () {
return `2.5x passive energy generation
(+${(150 * m.fieldRegen * 60).toFixed(1)} energy per second)`
}
}
},
{
name: "no-cloning theorem",
description: `+40% chance to duplicate spawned power ups
after a mob dies –1% duplication`,
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (m.fieldMode === 6 || m.fieldMode === 7)
},
requires: "cloaking, time dilation",
effect() {
tech.cloakDuplication = 0.4
powerUps.setPowerUpMode(); //needed after adjusting duplication chance
if (!build.isExperimentSelection && !simulation.isTextLogOpen) simulation.circleFlare(0.4);
},
remove() {
tech.cloakDuplication = 0
if (this.count) powerUps.setPowerUpMode(); //needed after adjusting duplication chance
}
},
{
name: "metamaterial absorber", //quantum eraser
descriptionFunction() {
return `for each mob left alive after you exit a level
there is a 30% chance to spawn a random power up`
},
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return (m.fieldMode === 7) && !tech.cloakDuplication
},
requires: "cloaking",
effect() {
tech.isQuantumEraser = true
},
remove() {
tech.isQuantumEraser = false
}
},
{
name: "symbiosis",
descriptionFunction() {
return `after a boss dies spawn ${powerUps.orb.research(4)}${powerUps.orb.heal(3)} and a tech
after a mob dies –0.25 maximum ${tech.isEnergyHealth ? "energy" : "health"}`
},
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 7 //|| m.fieldMode === 6
},
requires: "cloaking",
effect() {
tech.isAddRemoveMaxHealth = true
},
remove() {
tech.isAddRemoveMaxHealth = false
}
},
{
name: "boson composite",
link: `boson composite`,
description: "while cloaked you are intangible
to blocks and mobs, but mobs drain energy",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 7
},
requires: "metamaterial cloaking",
effect() {
tech.isIntangible = true;
},
remove() {
if (tech.isIntangible) {
tech.isIntangible = false;
player.collisionFilter.mask = cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield //normal collisions
}
}
},
{
name: "patch",
link: `patch`,
description: "after cloaking recover 0.75x
of your last health lost",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 7 && !tech.isEnergyHealth
},
requires: "metamaterial cloaking, not mass-energy",
effect() {
tech.isCloakHealLastHit = true;
},
remove() {
tech.isCloakHealLastHit = false;
}
},
{
name: "dazzler",
link: `dazzler`,
description: "after decloaking
stun nearby mobs for 2 second",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 7
},
requires: "metamaterial cloaking",
effect() {
tech.isCloakStun = true;
},
remove() {
tech.isCloakStun = false;
}
},
{
name: "topological defect",
description: "2.1x damage
to mobs at maximum durability",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return (m.fieldMode === 8 || m.fieldMode === 7) && tech.mobSpawnWithHealth === 0
},
requires: "cloaking, pilot wave, not reaction inhibitor",
effect() {
tech.isMobFullHealthCloak = true
},
remove() {
tech.isMobFullHealthCloak = false
}
},
{
name: "WIMPs",
description: `at the end of each level spawn ${powerUps.orb.research(4)}
and a dangerous particle that slowly chases you`,
isFieldTech: true,
maxCount: 9,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 9 || m.fieldMode === 8
},
requires: "wormhole, pilot wave",
effect() {
tech.wimpCount++
spawn.WIMP()
for (let j = 0, len = 4; j < len; j++) powerUps.spawn(level.exit.x + 100 * (Math.random() - 0.5), level.exit.y - 100 + 100 * (Math.random() - 0.5), "research", false)
},
remove() {
tech.wimpCount = 0
}
},
{
name: "vacuum fluctuation",
description: `use ${powerUps.orb.research(3)}
+11% chance to duplicate spawned power ups`,
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
return (m.fieldMode === 8 || m.fieldMode === 6 || m.fieldMode === 9) && (build.isExperimentSelection || powerUps.research.count > 2)
},
requires: "wormhole, time dilation, negative mass, pilot wave",
effect() {
tech.fieldDuplicate = 0.11
powerUps.setPowerUpMode(); //needed after adjusting duplication chance
if (!build.isExperimentSelection && !simulation.isTextLogOpen) simulation.circleFlare(0.11);
for (let i = 0; i < 3; i++) {
if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
}
},
remove() {
tech.fieldDuplicate = 0
if (this.count) powerUps.setPowerUpMode(); //needed after adjusting duplication chance
if (this.count > 0) powerUps.research.changeRerolls(3)
}
},
{
name: "transdimensional worms",
link: `transdimensional worms`,
description: "after a block falls into a wormhole
spawn a worm",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 9
},
requires: "wormhole",
effect() {
tech.isWormholeWorms = true
},
remove() {
tech.isWormholeWorms = false
}
},
{
name: "anyon",
descriptionFunction() {
return `2x energy after duplicating a power up
+6% chance to duplicate spawned power ups`
},
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return m.fieldMode === 9 || m.fieldMode === 1
},
requires: "wormhole, standing wave",
effect() {
tech.isDupEnergy = true;
powerUps.setPowerUpMode(); //needed after adjusting duplication chance
if (!build.isExperimentSelection && !simulation.isTextLogOpen) simulation.circleFlare(0.06);
},
remove() {
tech.isDupEnergy = false;
if (this.count) powerUps.setPowerUpMode(); //needed after adjusting duplication chance }
}
},
{
name: "geodesics",
description: `your bullets can traverse wormholes
1.5x damage`,
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 9
},
requires: "wormhole",
damage: 1.5,
effect() {
tech.damage *= this.damage
tech.isWormHoleBullets = true
// for (let i = 0; i < 2; i++) powerUps.spawn(m.pos.x + 200 * (Math.random() - 0.5), m.pos.y + 200 * (Math.random() - 0.5), "gun");
// for (let i = 0; i < 4; i++) powerUps.spawn(m.pos.x + 200 * (Math.random() - 0.5), m.pos.y + 200 * (Math.random() - 0.5), "ammo");
},
remove() {
// if (tech.isWormHoleBullets) {
// for (let i = 0; i < 2; i++) {
// if (b.inventory.length) b.removeGun(b.guns[b.inventory[b.inventory.length - 1]].name) //remove your last gun
// }
// }
if (this.count && m.alive) tech.damage /= this.damage
tech.isWormHoleBullets = false;
}
},
{
name: "cosmic string",
description: "after tunneling through mobs with a wormhole
stun them and do radioactive damage",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 9
},
requires: "wormhole",
effect() {
tech.isWormholeDamage = true
},
remove() {
tech.isWormholeDamage = false
}
},
{
name: "invariant",
description: "while placing your wormhole
use energy to pause time",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 9 && !tech.isNoDraftPause
},
requires: "wormhole, not eternalism",
effect() {
tech.isWormHolePause = true
},
remove() {
if (tech.isWormHolePause && m.isBodiesAsleep) m.wakeCheck();
tech.isWormHolePause = false
}
},
{
name: "charmed baryons",
description: `0.8x movement and jumping
wormholes cost zero energy`,
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 9 && !tech.isWormholeMapIgnore
},
requires: "wormhole, not affine connection",
effect() {
tech.isFreeWormHole = true
tech.baseFx *= 0.8
tech.baseJumpForce *= 0.8
m.setMovement()
},
//also removed in m.setHoldDefaults() if player switches into a bad field
remove() {
tech.isFreeWormHole = false
if (!tech.isNeutronium) {
tech.baseFx = 0.08
tech.baseJumpForce = 10.5
m.setMovement()
}
}
},
{
name: "affine connection",
description: "wormholes can tunnel through anything
for 2x energy cost",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 9 && !tech.isFreeWormHole
},
requires: "wormhole, not charmed baryons",
effect() {
tech.isWormholeMapIgnore = true
},
remove() {
tech.isWormholeMapIgnore = false
}
},
{
name: "CIWS",
description: "grappling hook uses 10 energy
to fire harpoons at nearby mobs",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 10
},
requires: "grappling hook",
effect() {
tech.isHookDefense = true
},
remove() {
tech.isHookDefense = false
}
},
{
name: "swarf",
// description: "after grappling hook impacts solid objects generate an explosion and become briefly invulnerable",
description: "after grappling hook impacts something
eject nails splinters towards nearby mobs",
isFieldTech: true,
maxCount: 3,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return m.fieldMode === 10 && !tech.isReel && !tech.isTokamak
},
requires: "grappling hook, not reel, tokamak",
effect() {
tech.hookNails += 4
},
remove() {
tech.hookNails = 0
}
},
{
name: "reel",
description: "5x block collision damage
up to +100 energy after reeling in blocks",
isFieldTech: true,
maxCount: 1,
count: 0,
frequency: 1,
frequencyDefault: 1,
allowed() {
return m.fieldMode === 10 && !tech.isTokamak && tech.blockDamage === 0.075 && !tech.hookNails
},
requires: "grappling hook, not mass driver, swarf, tokamak",
effect() {
tech.blockDamage = 0.375
tech.isReel = true
},
remove() {
tech.blockDamage = 0.075
tech.isReel = false
}
},
// {
// name: "CIWS",
// description: "if you collide with a mob
fire harpoons at nearby mobs",
// isFieldTech: true,
// maxCount: 1,
// count: 0,
// frequency: 2,
// frequencyDefault: 2,
// allowed() {
// return m.fieldMode === 10 && !tech.isHookDefense
// },
// requires: "grappling hook, not automatic offense",
// effect() {
// tech.isHookDefense = true
// },
// remove() {
// tech.isHookDefense = false
// }
// },
// {
// name: "wire",
// description: "",
// isFieldTech: true,
// maxCount: 1,
// count: 0,
// frequency: 2,
// frequencyDefault: 2,
// allowed() {
// return m.fieldMode === 10
// },
// requires: "grappling hook",
// effect() {
// tech.isHookWire = true
// },
// remove() {
// tech.isHookWire = false
// }
// },
//**************************************************
//************************************************** experimental
//************************************************** modes
//**************************************************
// {
// name: "-ship-",
// description: "experiment: fly around with no legs
aim with the keyboard",
// maxCount: 1,
// count: 0,
// frequency: 0,
// isInstant: true,
// isBadRandomOption: true,
// isExperimentalMode: true,
// allowed() {
// return build.isExperimentSelection && !m.isShipMode && m.fieldUpgrades[m.fieldMode].name !== "negative mass"
// },
// requires: "",
// effect() {
// m.shipMode()
// },
// remove() {}
// },
// {
// name: "-quantum leap-",
// description: "experiment: every 20 seconds
become an alternate version of yourself",
// maxCount: 1,
// count: 0,
// frequency: 0,
// isBadRandomOption: true,
// isExperimentalMode: true,
// allowed() {
// return build.isExperimentSelection
// },
// requires: "",
// interval: undefined,
// effect() {
// this.interval = setInterval(() => {
// if (!build.isExperimentSelection) {
// m.switchWorlds()
// simulation.trails()
// }
// }, 20000); //every 20 seconds
// },
// remove() {
// if (this.count > 0) clearTimeout(this.interval);
// }
// },
// {
// name: "-shields-",
// description: "experiment: every 5 seconds
all mobs gain a shield",
// maxCount: 1,
// count: 0,
// frequency: 0,
// isBadRandomOption: true,
// isExperimentalMode: true,
// allowed() {
// return build.isExperimentSelection
// },
// requires: "",
// effect() {
// this.interval = setInterval(() => {
// if (!build.isExperimentSelection) {
// for (let i = 0; i < mob.length; i++) {
// if (!mob[i].isShielded && !mob[i].shield && mob[i].isDropPowerUp) spawn.shield(mob[i], mob[i].position.x, mob[i].position.y, 1, true);
// }
// }
// }, 5000); //every 5 seconds
// },
// interval: undefined,
// remove() {
// if (this.count > 0) clearTimeout(this.interval);
// }
// },
// {
// name: "-Fourier analysis-",
// description: "experiment: your aiming is random",
// maxCount: 1,
// count: 0,
// frequency: 0,
// isBadRandomOption: true,
// isExperimentalMode: true,
// allowed() {
// return build.isExperimentSelection && !m.isShipMode
// },
// requires: "not ship",
// effect() {
// m.look = () => {
// m.angle = 2 * Math.sin(m.cycle * 0.0133) + Math.sin(m.cycle * 0.013) + 0.5 * Math.sin(m.cycle * 0.031) + 0.33 * Math.sin(m.cycle * 0.03)
// const scale = 0.8;
// m.transSmoothX = canvas.width2 - m.pos.x - (simulation.mouse.x - canvas.width2) * scale;
// m.transSmoothY = canvas.height2 - m.pos.y - (simulation.mouse.y - canvas.height2) * scale;
// m.transX += (m.transSmoothX - m.transX) * 0.07;
// m.transY += (m.transSmoothY - m.transY) * 0.07;
// }
// },
// remove() {
// if (this.count > 0) m.look = m.lookDefault()
// }
// },
// {
// name: "-panopticon-",
// description: "experiment: mobs can always see you",
// maxCount: 1,
// count: 0,
// frequency: 0,
// isBadRandomOption: true,
// isExperimentalMode: true,
// allowed() {
// return build.isExperimentSelection
// },
// requires: "",
// effect() {
// this.interval = setInterval(() => {
// if (!build.isExperimentSelection) {
// for (let i = 0; i < mob.length; i++) {
// if (!mob[i].shield && mob[i].isDropPowerUp) {
// mob[i].locatePlayer()
// mob[i].seePlayer.yes = true;
// }
// }
// }
// }, 1000); //every 1 seconds
// },
// interval: undefined,
// remove() {
// if (this.count > 0) clearTimeout(this.interval);
// }
// },
// {
// name: "-decomposers-",
// description: "experiment: after they die
mobs leave behind spawns",
// maxCount: 1,
// count: 0,
// frequency: 0,
// isBadRandomOption: true,
// isExperimentalMode: true,
// allowed() {
// return build.isExperimentSelection
// },
// requires: "",
// effect() {
// tech.deathSpawns = 0.2
// },
// remove() {
// tech.deathSpawns = 0
// }
// },
//**************************************************
//************************************************** JUNK
//************************************************** tech
//**************************************************
// {
// name: "junk",
// description: "",
// maxCount: 9,
// count: 0,
// frequency: 0,
// isInstant: true,
// isJunk: true,
// allowed() {
// return true
// },
// requires: "",
// effect() {
// },
// remove() {}
// },
{
name: "swap meet",
description: "normal tech become JUNK
and JUNK become normal tech",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
isInstant: true,
allowed() {
return true
},
requires: "",
effect() {
for (let i = 0, len = tech.tech.length; i < len; i++) {
tech.tech[i].isJunk = !tech.tech[i].isJunk
if (tech.tech[i].isJunk) { } else { }
if (tech.tech[i].frequency > 0) {
tech.tech[i].frequency = 0
} else {
tech.tech[i].frequency = 2
}
}
},
remove() { }
},
// {
// name: "pocket dimension",
// description: "rotate tech descriptions into a higher spacial dimension",
// maxCount: 1,
// count: 0,
// frequency: 0,
// isJunk: true,
// isInstant: true,
// allowed() {
// return true
// },
// requires: "",
// effect() {
// document.getElementById("choose-grid").classList.add("flipX");
// },
// remove() {}
// },
{
name: "random",
link: `random`,
delay: 333,
descriptionFunction() {
const delay = 333
const loop = () => {
if ((simulation.isChoosing) && m.alive && !build.isExperimentSelection) {
const dmg = Math.floor(27 * Math.random()) * 0.01
this.text = `+${(1 + dmg).toFixed(2).padStart(2, '0')}x damage`
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 `${this.text}`
},
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 && m.alive) tech.damage /= this.damage
}
},
{
name: "boost",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
isInstant: true,
allowed() {
return !build.isExperimentSelection
},
requires: "NOT EXPERIMENT MODE",
effect() {
powerUps.spawnDelay("boost", this.spawnCount)
},
remove() { },
id: 0,
text: "",
delay: 100,
spawnCount: 0,
descriptionFunction() {
let count = 9999 * Math.random()
const loop = () => {
if ((simulation.isChoosing) && m.alive && !build.isExperimentSelection) { //&& (!simulation.isChoosing || this.count === 0) //simulation.paused ||
count += 4.5
const waves = 2 * Math.sin(count * 0.0133) + Math.sin(count * 0.013) + 0.5 * Math.sin(count * 0.031) + 0.33 * Math.sin(count * 0.03)
this.spawnCount = Math.floor(100 * Math.abs(waves))
this.text = `spawn ${this.spawnCount.toLocaleString(undefined, { minimumIntegerDigits: 3 })} ${powerUps.orb.boost(1)}
that give ${(1 + powerUps.boost.damage).toFixed(2)}x damage for ${(powerUps.boost.duration / 60).toFixed(0)} seconds`
if (document.getElementById(`boost-JUNK-id${this.id}`)) document.getElementById(`boost-JUNK-id${this.id}`).innerHTML = this.text
setTimeout(() => {
loop()
}, this.delay);
}
}
setTimeout(() => {
loop()
}, this.delay);
this.id++
return `${this.text}`
},
},
{
name: "placebo",
description: "7.77x damage",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
allowed: () => true,
requires: "",
effect() {
if (Math.random() < 0.07) tech.damage *= 7.77
},
remove() { }
},
{
name: "universal healthcare",
description: "make your damage negative",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
allowed: () => true,
requires: "",
effect() {
tech.damage *= -1
},
remove() { }
},
{
name: "planned obsolescence",
description: "build 100 scrap bots
bots might last for 30 seconds",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
allowed: () => true,
requires: "",
effect() {
for (let i = 0; i < 100; i++) {
b.randomBot(m.pos, false)
bullet[bullet.length - 1].endCycle = simulation.cycle + 800 + 1000 * Math.random() //15 seconds
}
},
remove() { }
},
// {
// name: "synchrotron",
// descriptionFunction() {
// return `power ups change into a different flavor after a boss dies`
// },
// maxCount: 3,
// count: 0,
// frequency: 1,
// frequencyDefault: 1,
// allowed: () => true,
// requires: "",
// effect() {
// },
// remove() {
// }
// },
{
name: "return",
description: "return to the start of the game",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
isInstant: true,
allowed: () => true,
requires: "",
effect() {
level.onLevel = 0
simulation.clearNow = true //end current level
},
remove() { }
},
{
name: "panpsychism",
description: "awaken all blocks
blocks have a chance to spawn power ups",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
isInstant: true,
allowed: () => true,
requires: "",
effect() {
setInterval(() => {
for (let i = body.length - 1; i > -1; i--) {
if (!body[i].isNotHoldable) {
Matter.Composite.remove(engine.world, body[i]);
spawn.blockMob(body[i].position.x, body[i].position.y, body[i], 0);
if (!body[i].isAboutToBeRemoved) mob[mob.length - 1].isDropPowerUp = true
body.splice(i, 1);
}
}
}, 6000);
},
remove() { }
},
{
name: "meteor shower",
description: "take a shower, but meteors instead of water",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
isInstant: true,
allowed: () => true,
requires: "",
effect() {
setInterval(() => {
fireBlock = function (xPos, yPos) {
const index = body.length
spawn.bodyRect(xPos, yPos, 20 + 50 * Math.random(), 20 + 50 * Math.random());
const bodyBullet = body[index]
Matter.Body.setVelocity(bodyBullet, {
x: 5 * (Math.random() - 0.5),
y: 10 * (Math.random() - 0.5)
});
bodyBullet.isAboutToBeRemoved = true
setTimeout(() => { //remove block
for (let i = 0; i < body.length; i++) {
if (body[i] === bodyBullet) {
Matter.Composite.remove(engine.world, body[i]);
body.splice(i, 1);
}
}
}, 4000 + Math.floor(9000 * Math.random()));
}
fireBlock(player.position.x + 600 * (Math.random() - 0.5), player.position.y - 500 - 500 * Math.random());
// for (let i = 0, len = Math.random(); i < len; i++) {
// }
}, 1000);
},
remove() { }
},
{
name: "reinforcement learning",
description: "10x current tech frequency",
maxCount: 1,
count: 0,
frequency: 1,
isJunk: true,
allowed() {
return tech.totalCount > 9
},
requires: "at least 10 tech",
effect() {
for (let i = 0, len = tech.tech.length; i < len; i++) {
if (tech.tech[i].count > 0) tech.tech[i].frequency *= 10
}
},
remove() {
if (this.count) {
for (let i = 0, len = tech.tech.length; i < len; i++) {
if (tech.tech[i].count > 0 && tech.tech[i].frequency > 1) tech.tech[i].frequency /= 10
}
}
}
},
{
name: "startle response",
description: `if a threat is nearby, activate a ${powerUps.orb.boost(1)}
and lock your mouse until you press escape`,
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
isInstant: true,
allowed: () => true,
requires: "",
effect() {
// tech.damage *= 1.33
setInterval(() => {
if (powerUps.boost.endCycle < m.cycle && !simulation.paused && m.alive) {
for (let i = 0; i < mob.length; i++) {
if (mob[i].distanceToPlayer2() < 400000) { //650
canvas.requestPointerLock();
powerUps.boost.effect();
break
}
}
}
}, 2000);
},
remove() { }
},
{
name: "closed timelike curve",
description: "spawn 5 field power ups, but every 12 seconds
teleport a second into your future or past",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
isInstant: true,
allowed: () => true,
requires: "",
effect() {
for (let i = 0; i < 5; i++) powerUps.spawn(m.pos.x + 10 * Math.random(), m.pos.y + 10 * Math.random(), "field");
function loop() {
if (!simulation.paused && m.alive) {
if (!(simulation.cycle % 720)) {
requestAnimationFrame(() => {
if ((simulation.cycle % 1440) > 720) { //kinda alternate between each option
m.rewind(60)
m.energy += 0.4 //to make up for lost energy
} else {
simulation.timePlayerSkip(60)
}
}); //wrapping in animation frame prevents errors, probably
}
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
},
remove() { }
},
// {
// name: "translate",
// description: "translate n-gon into a random language",
// maxCount: 1,
// count: 0,
// frequency: 0,
// isJunk: true,
// isInstant: true,
// allowed() {
// return true
// },
// requires: "",
// effect() {
// // generate a container
// const gtElem = document.createElement('div')
// gtElem.id = "gtElem"
// gtElem.style.visibility = 'hidden' // make it invisible
// document.body.append(gtElem)
// // generate a script to run after creation
// function initGT() {
// // create a new translate element
// new google.translate.TranslateElement({ pageLanguage: 'en', layout: google.translate.TranslateElement.InlineLayout.HORIZONTAL }, 'gtElem')
// // ok now since it's loaded perform a funny hack to make it work
// const langSelect = document.getElementsByClassName("goog-te-combo")[0]
// // select a random language. It takes a second for all langauges to load, so wait a second.
// setTimeout(() => {
// langSelect.selectedIndex = Math.round(langSelect.options.length * Math.random())
// // simulate a click
// langSelect.dispatchEvent(new Event('change'))
// // now make it go away
// const bar = document.getElementById(':1.container')
// bar.style.display = 'none'
// bar.style.visibility = 'hidden'
// }, 1000)
// }
// // add the google translate script
// const translateScript = document.createElement('script')
// translateScript.src = '//translate.google.com/translate_a/element.js?cb=initGT'
// document.body.append(translateScript)
// },
// remove() {}
// },
{
name: "discount",
description: "get 3 random JUNKtech for the price of 1!",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
isInstant: true,
allowed: () => true,
requires: "",
effect() {
for (let i = 0; i < 3; i++) {
const list = []
for (let i = 0; i < tech.tech.length; i++) {
if (tech.tech[i].isJunk) list.push(tech.tech[i].name)
}
let name = list[Math.floor(Math.random() * list.length)]
simulation.makeTextLog(`tech.giveTech("${name}")`);
tech.giveTech(name)
}
},
remove() { }
},
// {
// name: "hi",
// description: `spawn to seed 616 `,
// maxCount: 1,
// count: 0,
// frequency: 0,
// isInstant: true,
// isJunk: true,
// allowed() {
// return true
// },
// requires: "",
// effect() {
// document.getElementById("seed").placeholder = Math.initialSeed = String(616)
// Math.seed = Math.abs(Math.hash(Math.initialSeed)) //update randomizer seed in case the player changed it
// },
// remove() {}
// },
{
name: "Higgs phase transition",
description: "instantly spawn 5 tech, but add a chance to
remove everything with a 5 minute half-life",
maxCount: 1,
count: 0,
frequency: 0,
frequencyDefault: 0,
isJunk: true,
isInstant: true,
allowed: () => true,
requires: "",
effect() {
powerUps.spawn(m.pos.x, m.pos.y, "tech");
powerUps.spawn(m.pos.x + 30, m.pos.y, "tech");
powerUps.spawn(m.pos.x + 60, m.pos.y, "tech");
powerUps.spawn(m.pos.x, m.pos.y - 30, "tech");
powerUps.spawn(m.pos.x + 30, m.pos.y - 60, "tech");
function loop() {
// (1-X)^cycles = chance to be removed //Math.random() < 0.000019 10 min
if (!simulation.paused && m.alive) {
if (Math.random() < 0.000038) {
// m.death();
simulation.clearMap();
simulation.draw.setPaths();
return
}
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
},
remove() { }
},
{
name: "harvest",
description: "convert all the mobs on this level into ammo",
maxCount: 1,
count: 0,
frequency: 0,
frequencyDefault: 0,
isJunk: true,
isInstant: true,
allowed: () => true,
requires: "",
effect() {
for (let i = 0, len = mob.length; i < len; i++) {
if (mob[i].isDropPowerUp) {
powerUps.directSpawn(mob[i].position.x, mob[i].position.y, "ammo");
mob[i].death();
}
}
// for (let i = powerUp.length - 1; i > -1; i--) {
// if (powerUp[i].name !== "ammo") {
// Matter.Composite.remove(engine.world, powerUp[i]);
// powerUp.splice(i, 1);
// }
// }
},
remove() { }
},
{
name: "brainstorm",
description: "the tech choice menu randomizes
every 0.5 seconds for 10 seconds",
maxCount: 1,
count: 0,
frequency: 0,
frequencyDefault: 0,
isJunk: true,
allowed: () => true,
requires: "",
effect() {
tech.isBrainstorm = true
tech.isBrainstormActive = false
tech.brainStormDelay = 500 //show each option for 0.5 seconds
},
remove() {
tech.isBrainstorm = false
tech.isBrainstormActive = false
}
},
{
name: "catabolysis",
description: `set your maximum health to 1
double your current ammo 10 times`,
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return !tech.isFallingDamage && !tech.isOverHeal && !tech.isEnergyHealth
},
requires: "not quenching, tungsten carbide, mass-energy",
effect() {
m.baseHealth = 0.01
m.setMaxHealth();
for (let i = 0; i < b.guns.length; i++) b.guns[i].ammo = b.guns[i].ammo * Math.pow(2, 10)
simulation.updateGunHUD();
},
remove() { }
},
{
name: "palantír",
description: `see far away lands`,
maxCount: 1,
count: 0,
frequency: 0,
// isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
m.look = () => {
//always on mouse look
m.angle = Math.atan2(
simulation.mouseInGame.y - m.pos.y,
simulation.mouseInGame.x - m.pos.x
);
//smoothed mouse look translations
const scale = 2;
m.transSmoothX = canvas.width2 - m.pos.x - (simulation.mouse.x - canvas.width2) * scale;
m.transSmoothY = canvas.height2 - m.pos.y - (simulation.mouse.y - canvas.height2) * scale;
m.transX += (m.transSmoothX - m.transX) * m.lookSmoothing;
m.transY += (m.transSmoothY - m.transY) * m.lookSmoothing;
}
},
remove() {
if (this.count) m.look = m.lookDefault
}
},
{
name: "motion sickness",
description: `disable camera smoothing`,
maxCount: 1,
count: 0,
frequency: 0,
// isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
m.look = () => {
//always on mouse look
m.angle = Math.atan2(
simulation.mouseInGame.y - m.pos.y,
simulation.mouseInGame.x - m.pos.x
);
//smoothed mouse look translations
const scale = 1.2;
m.transSmoothX = canvas.width2 - m.pos.x - (simulation.mouse.x - canvas.width2) * scale;
m.transSmoothY = canvas.height2 - m.pos.y - (simulation.mouse.y - canvas.height2) * scale;
m.transX = canvas.width2 - m.pos.x - (simulation.mouse.x - canvas.width2) * scale;
m.transY = canvas.height2 - m.pos.y - (simulation.mouse.y - canvas.height2) * scale;
// m.transX += (m.transSmoothX - m.transX) * m.lookSmoothing;
// m.transY += (m.transSmoothY - m.transY) * m.lookSmoothing;
}
},
remove() {
if (this.count) m.look = m.lookDefault
}
},
{
name: "facsimile",
description: `inserts a copy of your current level into the level list`,
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
const index = Math.min(level.levels.length - 1, level.onLevel)
level.levels.splice(index, 0, level.levels[index]);
},
remove() { }
},
{
name: "negative friction",
description: "when you touch walls you speed up instead of slowing down. It's kinda fun.",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
player.friction = -0.4
},
remove() {
if (this.count) player.friction = 0.002
}
},
{
name: "bounce",
description: "you bounce off things. It's annoying, but not that bad.",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
player.restitution = 0.9
},
remove() {
if (this.count) player.restitution = 0
}
},
{
name: "mouth",
description: "mobs have a non functional mouth",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
mobs.draw = () => {
ctx.lineWidth = 2;
let i = mob.length;
while (i--) {
ctx.beginPath();
const vertices = mob[i].vertices;
ctx.moveTo(vertices[0].x, vertices[0].y);
for (let j = 1, len = vertices.length; j < len; ++j) ctx.lineTo(vertices[j].x, vertices[j].y);
ctx.quadraticCurveTo(mob[i].position.x, mob[i].position.y, vertices[0].x, vertices[0].y);
ctx.fillStyle = mob[i].fill;
ctx.strokeStyle = mob[i].stroke;
ctx.fill();
ctx.stroke();
}
}
},
remove() {
mobs.draw = mobs.drawDefault
}
},
{
name: "all-stars",
description: "make all mobs look like stars",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
mobs.draw = () => {
ctx.lineWidth = 2;
let i = mob.length;
while (i--) {
ctx.beginPath();
const vertices = mob[i].vertices;
ctx.moveTo(vertices[0].x, vertices[0].y);
for (let j = 1, len = vertices.length; j < len; ++j) ctx.quadraticCurveTo(mob[i].position.x, mob[i].position.y, vertices[j].x, vertices[j].y);
ctx.quadraticCurveTo(mob[i].position.x, mob[i].position.y, vertices[0].x, vertices[0].y);
ctx.fillStyle = mob[i].fill;
ctx.strokeStyle = mob[i].stroke;
ctx.fill();
ctx.stroke();
}
}
},
remove() {
mobs.draw = mobs.drawDefault
}
},
// draw() {
// ctx.lineWidth = 2;
// let i = mob.length;
// while (i--) {
// ctx.beginPath();
// const vertices = mob[i].vertices;
// ctx.moveTo(vertices[0].x, vertices[0].y);
// for (let j = 1, len = vertices.length; j < len; ++j) ctx.lineTo(vertices[j].x, vertices[j].y);
// ctx.lineTo(vertices[0].x, vertices[0].y);
// ctx.fillStyle = mob[i].fill;
// ctx.strokeStyle = mob[i].stroke;
// ctx.fill();
// ctx.stroke();
// }
// },
{
name: "true colors",
description: `set all power ups to their real world colors`,
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
isInstant: true,
allowed() {
return true
},
requires: "",
effect() {
// const colors = shuffle(["#f7b", "#0eb", "#467", "#0cf", "hsl(246,100%,77%)", "#26a"])
const colors = shuffle([powerUps.research.color, powerUps.heal.color, powerUps.ammo.color, powerUps.ammo.color, powerUps.field.color, powerUps.gun.color])
powerUps.research.color = colors[0]
powerUps.heal.color = colors[1]
powerUps.ammo.color = colors[2]
powerUps.field.color = colors[3]
powerUps.tech.color = colors[4]
powerUps.gun.color = colors[5]
for (let i = 0; i < powerUp.length; i++) {
switch (powerUp[i].name) {
case "research":
powerUp[i].color = colors[0]
break;
case "heal":
powerUp[i].color = colors[1]
break;
case "ammo":
powerUp[i].color = colors[2]
break;
case "field":
powerUp[i].color = colors[3]
break;
case "tech":
powerUp[i].color = colors[4]
break;
case "gun":
powerUp[i].color = colors[5]
break;
}
}
},
remove() {
// const colors = ["#f7b", "#0eb", "#467", "#0cf", "hsl(246,100%,77%)", "#26a"] //no shuffle
// powerUps.research.color = colors[0]
// powerUps.heal.color = colors[1]
// powerUps.ammo.color = colors[2]
// powerUps.field.color = colors[3]
// powerUps.tech.color = colors[4]
// powerUps.gun.color = colors[5]
// for (let i = 0; i < powerUp.length; i++) {
// switch (powerUp[i].name) {
// case "research":
// powerUp[i].color = colors[0]
// break;
// case "heal":
// powerUp[i].color = colors[1]
// break;
// case "ammo":
// powerUp[i].color = colors[2]
// break;
// case "field":
// powerUp[i].color = colors[3]
// break;
// case "tech":
// powerUp[i].color = colors[4]
// break;
// case "gun":
// powerUp[i].color = colors[5]
// break;
// }
// }
}
},
{
name: "emergency broadcasting",
description: "emit 2 sine waveforms at 853 Hz and 960 Hz
lower your volume",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
isInstant: true,
allowed() {
return true
},
requires: "",
effect: () => {
//setup audio context
function tone(frequency) {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const oscillator1 = audioCtx.createOscillator();
const gainNode1 = audioCtx.createGain();
gainNode1.gain.value = 0.5; //controls volume
oscillator1.connect(gainNode1);
gainNode1.connect(audioCtx.destination);
oscillator1.type = "sine"; // 'sine' 'square', 'sawtooth', 'triangle' and 'custom'
oscillator1.frequency.value = frequency; // value in hertz
oscillator1.start();
return audioCtx
}
// let sound = tone(1050)
function EBS() {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const oscillator1 = audioCtx.createOscillator();
const gainNode1 = audioCtx.createGain();
gainNode1.gain.value = 0.3; //controls volume
oscillator1.connect(gainNode1);
gainNode1.connect(audioCtx.destination);
oscillator1.type = "sine"; // 'sine' 'square', 'sawtooth', 'triangle' and 'custom'
oscillator1.frequency.value = 850; // value in hertz
oscillator1.start();
const oscillator2 = audioCtx.createOscillator();
const gainNode2 = audioCtx.createGain();
gainNode2.gain.value = 0.3; //controls volume
oscillator2.connect(gainNode2);
gainNode2.connect(audioCtx.destination);
oscillator2.type = "sine"; // 'sine' 'square', 'sawtooth', 'triangle' and 'custom'
oscillator2.frequency.value = 957; // value in hertz
oscillator2.start();
return audioCtx
}
let sound = EBS()
delay = 1000
setTimeout(() => {
sound.suspend()
powerUps.spawn(m.pos.x + 160 * (Math.random() - 0.5), m.pos.y + 160 * (Math.random() - 0.5), "heal");
setTimeout(() => {
sound.resume()
setTimeout(() => {
sound.suspend()
powerUps.spawn(m.pos.x + 160 * (Math.random() - 0.5), m.pos.y + 160 * (Math.random() - 0.5), "heal");
setTimeout(() => {
sound.resume()
setTimeout(() => {
sound.suspend()
powerUps.spawn(m.pos.x + 160 * (Math.random() - 0.5), m.pos.y + 160 * (Math.random() - 0.5), "heal");
setTimeout(() => {
sound.resume()
setTimeout(() => {
sound.suspend()
powerUps.spawn(m.pos.x + 160 * (Math.random() - 0.5), m.pos.y + 160 * (Math.random() - 0.5), "heal");
setTimeout(() => {
sound.resume()
setTimeout(() => {
sound.suspend()
powerUps.spawn(m.pos.x + 160 * (Math.random() - 0.5), m.pos.y + 160 * (Math.random() - 0.5), "heal");
setTimeout(() => {
sound.resume()
setTimeout(() => {
sound.suspend()
sound.close()
powerUps.spawn(m.pos.x + 160 * (Math.random() - 0.5), m.pos.y + 160 * (Math.random() - 0.5), "heal");
}, delay);
}, delay);
}, delay);
}, delay);
}, delay);
}, delay);
}, delay);
}, delay);
}, delay);
}, delay);
}, delay);
},
remove() { }
},
{
name: "automatic",
description: "you can't fire when moving
always fire when at rest",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
allowed() {
return !tech.isFireMoveLock
},
requires: "not Higgs mechanism",
effect() {
tech.isAlwaysFire = true;
b.setFireMethod();
},
remove() {
if (tech.isAlwaysFire) {
tech.isAlwaysFire = false
b.setFireMethod();
}
}
},
{
name: "hidden variable",
descriptionFunction() {
return `spawn ${powerUps.orb.heal(20)}
but hide your health bar`
},
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
document.getElementById("health").style.display = "none"
document.getElementById("health-bg").style.display = "none"
document.getElementById("defense-bar").style.display = "none"
for (let i = 0; i < 20; i++) powerUps.spawn(m.pos.x + 160 * (Math.random() - 0.5), m.pos.y + 160 * (Math.random() - 0.5), "heal");
},
remove() { }
},
{
name: "not a bug",
description: "initiate a totally safe game crash for 10 seconds",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
const savedfunction = simulation.drawCircle
simulation.drawCircle = () => {
const a = mob[Infinity].position //crashed the game in a visually interesting way, because of the ctx.translate command is never reverted in the main game loop
}
setTimeout(() => {
simulation.drawCircle = savedfunction
canvas.width = canvas.width //clears the canvas // works on chrome at least
powerUps.spawn(m.pos.x, m.pos.y, "tech");
}, 10000);
// for (;;) {} //freezes the tab
},
remove() { }
},
{
name: "what the block?",
description: "throwing a block throws you instead",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return m.fieldMode !== 8 && m.fieldMode !== 9 && !tech.isTokamak
},
requires: "not pilot wave, tokamak, wormhole",
effect() {
},
remove() {
m.throwBlock = m.throwBlockDefault
}
},
{
name: "spinor",
description: "the direction you aim is determined by your position",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return !m.isShipMode
},
requires: "",
effect() {
m.look = function () {
//always on mouse look
m.angle = (((m.pos.x + m.pos.y) / 100 + Math.PI) % Math.PI * 2) - Math.PI
//smoothed mouse look translations
const scale = 0.8;
m.transSmoothX = canvas.width2 - m.pos.x - (simulation.mouse.x - canvas.width2) * scale;
m.transSmoothY = canvas.height2 - m.pos.y - (simulation.mouse.y - canvas.height2) * scale;
m.transX += (m.transSmoothX - m.transX) * 0.07;
m.transY += (m.transSmoothY - m.transY) * 0.07;
}
},
remove() {
if (this.count) m.look = m.lookDefault
}
},
{
name: "p-zombie",
description: "set your health to 1
all mobs, not bosses, die and resurrect as zombies",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() { return true },
requires: "",
effect() {
m.health = 0.01 //set health to 1
m.displayHealth();
for (let i = mob.length - 1; i > -1; i--) { //replace mobs with zombies
if (mob[i].isDropPowerUp && !mob[i].isBoss && mob[i].alive) {
mob[i].isSoonZombie = true
mob[i].death()
}
}
},
remove() { }
},
{
name: "decomposers",
description: "after they die mobs leave behind spawns",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return tech.deathSpawns === 0
},
requires: "",
effect() {
tech.deathSpawns = 0.2
},
remove() {
tech.deathSpawns = 0
}
},
{
name: "panopticon",
description: "mobs can always see you",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
setInterval(() => {
for (let i = 0; i < mob.length; i++) {
if (!mob[i].shield && mob[i].isDropPowerUp) {
mob[i].locatePlayer()
mob[i].seePlayer.yes = true;
}
}
}, 1000); //every 1 seconds
},
remove() { }
},
// {
// name: "inverted mouse",
// description: "your mouse is scrambled
it's fine, just rotate it 90 degrees",
// maxCount: 1,
// count: 0,
// frequency: 0,
// isExperimentHide: true,
// isInstant: true,
// isJunk: true,
// allowed() {
// return !m.isShipMode
// },
// requires: "not ship",
// effect() {
// document.body.addEventListener("mousemove", (e) => {
// const ratio = window.innerWidth / window.innerHeight
// simulation.mouse.x = e.clientY * ratio
// simulation.mouse.y = e.clientX / ratio;
// });
// },
// remove() {
// // m.look = m.lookDefault
// }
// },
{
name: "Fourier analysis",
description: "your aiming is now controlled by this equation:
2sin(0.0133t) + sin(0.013t) + 0.5sin(0.031t)+ 0.33sin(0.03t)",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
allowed() {
return !m.isShipMode
},
requires: "not ship",
effect() {
m.look = () => {
m.angle = 2 * Math.sin(m.cycle * 0.0133) + Math.sin(m.cycle * 0.013) + 0.5 * Math.sin(m.cycle * 0.031) + 0.33 * Math.sin(m.cycle * 0.03)
const scale = 0.8;
simulation.mouse.y
m.transSmoothX = canvas.width2 - m.pos.x - (simulation.mouse.x - canvas.width2) * scale;
m.transSmoothY = canvas.height2 - m.pos.y - (simulation.mouse.y - canvas.height2) * scale;
m.transX += (m.transSmoothX - m.transX) * 0.07;
m.transY += (m.transSmoothY - m.transY) * 0.07;
}
},
remove() {
if (this.count) m.look = m.lookDefault
}
},
{
name: "disintegrated armament",
description: "spawn a gun
remove your active gun",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return b.inventory.length > 0
},
requires: "at least 1 gun",
effect() {
if (b.inventory.length > 0) b.removeGun(b.guns[b.activeGun].name)
simulation.makeGunHUD()
powerUps.spawn(m.pos.x + 60 * (Math.random() - 0.5), m.pos.y + 60 * (Math.random() - 0.5), "gun");
},
remove() { }
},
{
name: "probability",
description: "100x frequency
of one random tech",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
let options = []; //find what tech I could get
for (let i = 0, len = tech.tech.length; i < len; i++) {
if (
tech.tech[i].count < tech.tech[i].maxCount &&
tech.tech[i].allowed() &&
!tech.tech[i].isJunk &&
!tech.tech.isLore
) {
options.push(i);
}
}
if (options.length) {
const index = options[Math.floor(Math.random() * options.length)]
tech.tech[index].frequency = 100
}
},
remove() { }
},
{
name: "encryption",
description: "secure tech information",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
String.prototype.shuffle = function () {
var a = this.split(""),
n = a.length;
for (var i = n - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
return a.join("");
}
for (let i = 0, len = tech.tech.length; i < len; i++) tech.tech[i].name = tech.tech[i].name.shuffle()
},
remove() { }
},
{
name: "quantum leap",
description: "become an alternate version of yourself
every 20 seconds",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
setInterval(() => {
m.switchWorlds()
simulation.trails()
}, 20000); //every 30 seconds
},
remove() { }
},
{
name: "score",
description: "Add a score to n-gon!",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
setInterval(() => {
let score = Math.ceil(1000 * Math.random() * Math.random() * Math.random() * Math.random() * Math.random())
simulation.makeTextLog(`simulation.score = ${score.toFixed(0)}`);
}, 10000); //every 10 seconds
},
remove() { }
},
{
name: "pop-ups",
description: "sign up to learn endless easy ways to win n-gon
that Landgreen doesn't want you to know!!!1!!",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
setInterval(() => {
alert(`The best combo is ${tech.tech[Math.floor(Math.random() * tech.tech.length)].name} with ${tech.tech[Math.floor(Math.random() * tech.tech.length)].name}!`);
}, 30000); //every 30 seconds
},
remove() { }
},
{
name: "music",
description: "add music to n-gon",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
window.open('https://www.youtube.com/watch?v=lEbHeSdmS-k&list=PL9Z5wjoBiPKEDhwCW2RN-VZoCpmhIojdn', '_blank')
},
remove() { }
},
{
name: "performance",
description: "display performance stats to n-gon",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
(function () {
var script = document.createElement('script');
script.onload = function () {
var stats = new Stats();
document.body.appendChild(stats.dom);
requestAnimationFrame(function loop() {
stats.update();
requestAnimationFrame(loop)
});
};
script.src = 'https://unpkg.com/stats.js@0.17.0/build/stats.min.js';
document.head.appendChild(script);
})()
//move health to the right
document.getElementById("health").style.left = "86px"
document.getElementById("health-bg").style.left = "86px"
document.getElementById("defense-bar").style.left = "86px"
document.getElementById("damage-bar").style.left = "86px"
},
remove() { }
},
{
name: "repartitioning",
description: "set the frequency of finding normal tech to 0
spawn 5 tech",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
for (let i = 0, len = tech.tech.length; i < len; i++) {
if (tech.tech[i].isJunk) {
tech.tech[i].frequency = 2
} else {
tech.tech[i].frequency = 0
}
}
for (let i = 0; i < 5; i++) powerUps.spawn(m.pos.x, m.pos.y, "tech");
},
remove() { }
},
{
name: "defragment",
description: "set the frequency of finding JUNKtech to zero",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
tech.junkChance = 0;
},
remove() { }
},
// {
// name: "lubrication",
// description: "reduce block density and friction for this level",
// maxCount: 9,
// count: 0,
// frequency: 0,
// isInstant: true,
// isExperimentHide: true,
// isJunk: true,
// allowed() {
// return true
// },
// requires: "",
// effect() {
// for (let i = 0; i < body.length; i++) {
// Matter.Body.setDensity(body[i], 0.0001) // 0.001 is normal
// body[i].friction = 0.01
// }
// },
// remove() {}
// },
{
name: "pitch",
description: "oscillate the pitch of your world",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
setInterval(() => {
if (!simulation.paused) ctx.rotate(0.001 * Math.sin(simulation.cycle * 0.01))
}, 16);
},
remove() { }
},
// {
// name: "flatland",
// description: "map blocks line of sight",
// maxCount: 1,
// count: 0,
// frequency: 0,
// isInstant: true,
// isJunk: true,
// allowed() { return true },
// requires: "",
// effect() {
// simulation.draw.lineOfSightPrecalculation() //required precalculation for line of sight
// simulation.draw.drawMapPath = simulation.draw.drawMapSight
// simulation.ephemera.push({
// name: "LoS", count: 0, do() {
// const pos = m.pos
// const radius = 3000
// if (!simulation.isTimeSkipping) {
// const vertices = simulation.sight.circleLoS(pos, radius);
// if (vertices.length) {
// ctx.beginPath();
// ctx.moveTo(vertices[0].x, vertices[0].y);
// for (var i = 1; i < vertices.length; i++) {
// var currentDistance = Math.sqrt((vertices[i - 1].x - pos.x) ** 2 + (vertices[i - 1].y - pos.y) ** 2);
// var newDistance = Math.sqrt((vertices[i].x - pos.x) ** 2 + (vertices[i].y - pos.y) ** 2);
// if (Math.abs(currentDistance - radius) < 1 && Math.abs(newDistance - radius) < 1) {
// const currentAngle = Math.atan2(vertices[i - 1].y - pos.y, vertices[i - 1].x - pos.x);
// const newAngle = Math.atan2(vertices[i].y - pos.y, vertices[i].x - pos.x);
// ctx.arc(pos.x, pos.y, radius, currentAngle, newAngle);
// } else {
// ctx.lineTo(vertices[i].x, vertices[i].y)
// }
// }
// newDistance = Math.sqrt((vertices[0].x - pos.x) ** 2 + (vertices[0].y - pos.y) ** 2);
// currentDistance = Math.sqrt((vertices[vertices.length - 1].x - pos.x) ** 2 + (vertices[vertices.length - 1].y - pos.y) ** 2);
// if (Math.abs(currentDistance - radius) < 1 && Math.abs(newDistance - radius) < 1) {
// const currentAngle = Math.atan2(vertices[vertices.length - 1].y - pos.y, vertices[vertices.length - 1].x - pos.x);
// const newAngle = Math.atan2(vertices[0].y - pos.y, vertices[0].x - pos.x);
// ctx.arc(pos.x, pos.y, radius, currentAngle, newAngle);
// } else {
// ctx.lineTo(vertices[0].x, vertices[0].y)
// }
// //stroke the map, so it looks different form the line of sight
// ctx.strokeStyle = "#234";
// ctx.lineWidth = 9;
// ctx.stroke(simulation.draw.mapPath); //this has a pretty large impact on performance, maybe 5% worse performance
// ctx.globalCompositeOperation = "destination-in";
// ctx.fillStyle = "#000";
// ctx.fill();
// ctx.globalCompositeOperation = "source-over";
// // also see the map
// // ctx.fill(simulation.draw.mapPath);
// // ctx.fillStyle = "#000";
// ctx.clip();
// }
// }
// },
// })
// },
// remove() { }
// },
{
name: "umbra",
description: "produce a blue glow around everything
and probably some simulation lag",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
ctx.shadowColor = '#06f';
ctx.shadowBlur = 25;
},
remove() { }
},
{
name: "lighter",
description: `ctx.globalCompositeOperation = "lighter"`,
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
ctx.globalCompositeOperation = "lighter";
},
remove() { }
},
{
name: "rewind",
description: "every 10 seconds rewind 2 seconds",
maxCount: 9,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
setInterval(() => {
m.rewind(120)
m.energy += 0.4
}, 10000);
// for (let i = 0; i < 24; i++) {
// setTimeout(() => { m.rewind(120) }, i * 5000);
// }
},
remove() { }
},
{
name: "undo",
description: "every 4 seconds rewind 1/2 a second",
maxCount: 9,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
setInterval(() => {
m.rewind(30)
m.energy += 0.2
}, 4000);
},
remove() { }
},
{
name: "energy to mass conversion",
description: "convert your energy into blocks",
maxCount: 9,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
for (let i = 0, len = 40; i < len; i++) {
setTimeout(() => {
m.energy -= 1 / len
where = Vector.add(m.pos, { x: 400 * (Math.random() - 0.5), y: 400 * (Math.random() - 0.5) })
spawn.bodyRect(where.x, where.y, Math.floor(15 + 100 * Math.random()), Math.floor(15 + 100 * Math.random()));
}, i * 100);
}
},
remove() { }
},
{
name: "level.nextLevel()",
description: "advance to the next level",
maxCount: 9,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
level.nextLevel();
},
remove() { }
},
{
name: "reincarnation",
description: "kill all mobs and spawn new ones
(also spawn a few extra mobs for fun)",
maxCount: 3,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
spawn.setSpawnList();
spawn.setSpawnList();
for (let i = 0, len = mob.length; i < len; i++) {
if (mob[i].alive && !mob[i].shield && !mob[i].isBadTarget) {
const pick = spawn.pickList[Math.floor(Math.random() * spawn.pickList.length)];
spawn[pick](mob[i].position.x, mob[i].position.y);
if (Math.random() < 0.5) spawn[pick](mob[i].position.x, mob[i].position.y);
mob[i].death();
}
}
},
remove() { }
},
{
name: "expert system",
description: "spawn a tech power up
+50% JUNKtech chance",
maxCount: 9,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return tech.junkChance < 1
},
requires: "",
effect() {
powerUps.spawn(m.pos.x, m.pos.y, "tech");
tech.addJunkTechToPool(0.5)
},
remove() { }
},
{
name: "energy investment",
description: "every 10 seconds drain your energy
return it doubled 5 seconds later",
maxCount: 9,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
setInterval(() => {
if (!simulation.paused) {
const energy = m.energy
m.energy = 0
setTimeout(() => { //return energy
m.energy += 2 * energy
}, 5000);
}
}, 10000);
},
remove() { }
},
{
name: "missile launching system",
description: "fire missiles for the next 120 seconds",
maxCount: 9,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
for (let i = 0; i < 120; i++) {
setTimeout(() => {
const where = {
x: m.pos.x,
y: m.pos.y - 40
}
b.missile(where, -Math.PI / 2 + 0.2 * (Math.random() - 0.5) * Math.sqrt(tech.missileCount), -2)
}, i * 1000);
}
},
remove() { }
},
{
name: "grenade production",
description: "drop a grenade every 2 seconds",
maxCount: 9,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
setInterval(() => {
if (!simulation.paused && document.visibilityState !== "hidden") {
b.grenade(Vector.add(m.pos, {
x: 10 * (Math.random() - 0.5),
y: 10 * (Math.random() - 0.5)
}), -Math.PI / 2) //fire different angles for each grenade
const who = bullet[bullet.length - 1]
Matter.Body.setVelocity(who, {
x: who.velocity.x * 0.1,
y: who.velocity.y * 0.1
});
}
}, 2000);
},
remove() { }
},
{
name: "wall jump",
description: "no knees or toes are drawn on the player
you can wall climb though",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
isInstant: true,
allowed() {
return !m.isShipMode
},
requires: "",
effect() {
m.skin.stubs()
jumpSensor.vertices[0].x += -22
jumpSensor.vertices[3].x += -22
jumpSensor.vertices[1].x += 22
jumpSensor.vertices[2].x += 22
},
remove() { }
},
{
name: "Sleipnir",
description: "grow more legs",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
allowed() {
return !m.isShipMode
},
requires: "",
effect() {
m.skin.Sleipnir()
},
remove() {
if (this.count) m.resetSkin();
}
},
{
name: "diegesis",
description: "indicate fire cooldown
through a rotation of your head",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
allowed() {
return !m.isShipMode
},
requires: "",
effect() {
m.skin.diegesis()
},
remove() {
if (this.count) m.resetSkin();
}
},
{
name: "🐱",
description: "🐈",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
allowed() {
return !m.isShipMode
},
requires: "",
effect() {
m.skin.cat();
},
remove() {
if (this.count) m.resetSkin();
}
},
{
name: "n-gone",
description: "become invisible to yourself
mobs can still see you",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
m.draw = () => { }
},
remove() {
if (this.count) m.resetSkin();
}
},
{
name: "pareidolia",
description: "don't",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return !m.isShipMode
},
requires: "",
effect() {
m.skin.pareidolia()
},
remove() {
if (this.count) m.resetSkin();
}
},
{
name: "posture",
description: "stand a bit taller",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
allowed() {
return !m.isShipMode
},
requires: "",
effect() {
m.yOffWhen.stand = 70
},
remove() {
m.yOffWhen.stand = 49
}
},
{
name: "rhythm",
description: "you oscillate up and down",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
isInstant: true,
allowed() {
return !m.isShipMode
},
requires: "",
effect() {
setInterval(() => {
m.yOffWhen.stand = 53 + 28 * Math.sin(simulation.cycle * 0.2)
if (m.onGround && !m.crouch) m.yOffGoal = m.yOffWhen.stand
}, 100);
},
remove() { }
},
{
name: "prism",
description: "you cycle through different colors",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
m.color = {
hue: 0,
sat: 100,
light: 50
}
setInterval(function () {
m.color.hue++
m.setFillColors()
}, 10);
},
remove() { }
},
// {
// name: "microtransactions",
// description: `when you choose a tech you can
use ${powerUps.orb.research(1)} to buy a free in game skin`,
// maxCount: 1,
// count: 0,
// frequency: 0,
// isJunk: true,
// allowed() {
// return true
// },
// requires: "",
// effect() {
// tech.isMicroTransactions = true
// },
// remove() {
// tech.isMicroTransactions = false
// }
// },
{
name: "ship",
description: "fly around with no legs",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return !m.isShipMode && !m.isAltSkin && m.fieldUpgrades[m.fieldMode].name !== "negative mass"
},
requires: "",
effect() {
m.isAltSkin = true
m.shipMode()
//unlock relativistic rotation
for (let i = 0; i < tech.tech.length; i++) {
if (tech.tech[i].name === "relativistic rotation") tech.tech[i].frequency = 10
}
},
remove() { }
},
{
name: "circular symmetry",
description: "turning the ship rotates the universe instead
2x damage",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return m.isShipMode
},
requires: "",
effect() {
tech.damage *= 3
m.look = () => {
// const scale = 0;
m.transSmoothX = canvas.width2 - m.pos.x // - (simulation.mouse.x - canvas.width2) * scale;
m.transSmoothY = canvas.height2 - m.pos.y // - (simulation.mouse.y - canvas.height2) * scale;
m.transX += (m.transSmoothX - m.transX) * m.lookSmoothing;
m.transY += (m.transSmoothY - m.transY) * m.lookSmoothing;
ctx.restore();
ctx.save();
ctx.translate(canvas.width2, canvas.height2); //center
ctx.rotate(-m.angle)
ctx.translate(-canvas.width2, -canvas.height2); //center
}
},
remove() { }
},
{
name: "assimilation",
description: "all your bots are converted to the same random model",
maxCount: 1,
count: 0,
frequency: 0,
isBotTech: true,
isInstant: true,
isJunk: true,
allowed() {
return b.totalBots() > 2
},
requires: "at least 3 bots",
effect() {
const total = b.totalBots();
tech.dynamoBotCount = 0;
tech.nailBotCount = 0;
tech.laserBotCount = 0;
tech.orbitBotCount = 0;
tech.foamBotCount = 0;
tech.soundBotCount = 0;
tech.boomBotCount = 0;
tech.plasmaBotCount = 0;
tech.missileBotCount = 0;
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType) bullet[i].endCycle = 0
}
const bots = [
() => {
b.nailBot();
tech.nailBotCount++;
},
() => {
b.foamBot();
tech.foamBotCount++;
},
() => {
b.soundBot();
tech.soundBotCount++;
},
() => {
b.boomBot();
tech.boomBotCount++;
},
() => {
b.laserBot();
tech.laserBotCount++;
},
() => {
b.orbitBot();
tech.orbitBotCount++
},
() => {
b.dynamoBot();
tech.dynamoBotCount++
}
]
const index = Math.floor(Math.random() * bots.length)
for (let i = 0; i < total; i++) bots[index]()
},
remove() { }
},
{
name: "stun",
description: "stun all mobs for up to 8 seconds",
maxCount: 9,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
for (let i = 0; i < mob.length; i++) mobs.statusStun(mob[i], 480)
},
remove() { }
},
{
name: "translucent",
description: "spawn 3 gun power ups
your bullets and bots are transparent",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
for (let i = 0; i < 3; i++) powerUps.spawn(m.pos.x + 60 * (Math.random() - 0.5), m.pos.y + 60 * (Math.random() - 0.5), "gun");
// //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;
// }
// simulation.makeGunHUD(); //update gun HUD
b.bulletDraw = () => { }; //make bullets invisible
},
remove() { }
},
{
name: "difficulty",
description: "spawn a power up that lets you
adjust the simulation difficulty parameters",
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return (level.levelsCleared < 5)
},
requires: "before level 5",
effect() {
powerUps.spawn(m.pos.x, m.pos.y, "difficulty");
},
remove() { }
},
{
name: "re-research",
description: `eject all your ${powerUps.orb.research(1)}`,
maxCount: 9,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return powerUps.research.count > 3
},
requires: "at least 4 research",
effect() {
powerUps.spawnDelay("research", powerUps.research.count);
powerUps.research.count = 0
},
remove() { }
},
{
name: "black hole",
description: `use your energy and ${powerUps.orb.research(4)} to spawn
inside the event horizon of a huge black hole`,
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return powerUps.research.count > 3
},
requires: "at least 4 research",
effect() {
m.energy = 0
spawn.suckerBoss(m.pos.x, m.pos.y - 700)
powerUps.research.changeRerolls(-4)
simulation.makeTextLog(`m.research --
${powerUps.research.count}`)
},
remove() { }
},
{
name: "apomixis",
description: `spawn 11 bosses`,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
isInstant: true,
isJunk: true,
allowed() {
return tech.duplicationChance() > 0.99
},
requires: "duplication chance above 99%",
effect() {
const range = 1300
for (let i = 0, len = 9; i < len; i++) {
const angle = 2 * Math.PI * i / len
spawn.randomLevelBoss(m.pos.x + range * Math.cos(angle), m.pos.y + range * Math.sin(angle), spawn.nonCollideBossList);
}
spawn.historyBoss(0, 0)
spawn.pulsarBoss(level.exit.x, level.exit.y, 70, true)
spawn.blockBoss(level.enter.x, level.enter.y)
},
remove() { }
},
{
name: "mobs!",
descriptionFunction() {
if (this.mobType === "") this.mobType = spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]
return `spawn 20 ${this.mobType} mobs`
},
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() { return true },
requires: "",
mobType: "",
effect() {
if (this.mobType === "") this.mobType = spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]
for (let i = 0; i < 20; i++) {
spawn[this.mobType](m.pos.x, m.pos.y - 700)
}
simulation.makeTextLog(`spawn.${this.mobType}(x,y)`)
},
remove() { }
},
{
name: "black hole cluster",
description: `spawn 30 nearby black holes`,
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
const unit = {
x: 1,
y: 0
}
for (let i = 0; i < 30; i++) {
const where = Vector.add(m.pos, Vector.mult(Vector.rotate(unit, Math.random() * 2 * Math.PI), 2000 + 1200 * Math.random()))
spawn.sucker(where.x, where.y, 140)
const who = mob[mob.length - 1]
who.locatePlayer()
// who.damageReduction = 0.2
}
},
remove() { }
},
{
name: "rule 30",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
allowed() {
return !build.isExperimentSelection
},
requires: "NOT EXPERIMENT MODE",
effect() { },
remove() { },
state: [
[false, false, false, Math.random() > 0.8, false, false, false, Math.random() > 0.8, false, false, false, false, false, false, false, false, false, true, false, false, false, Math.random() > 0.8, false, false, false, Math.random() > 0.8, false, false, false, false, Math.random() > 0.8, false, Math.random() > 0.8, false, false, false, Math.random() > 0.8, false, false, false, false, false, false, false, false, false]
],
rule(state, a, b, c) {
//30
if (state[a] && state[b] && state[c]) return false; // TTT => F
if (state[a] && state[b] && !state[c]) return false; // TTF => F
if (state[a] && !state[b] && state[c]) return false; //TFT => F
if (state[a] && !state[b] && !state[c]) return true; //TFF => T
if (!state[a] && state[b] && state[c]) return true; //FTT => T
if (!state[a] && state[b] && !state[c]) return true; //FTF => T
if (!state[a] && !state[b] && state[c]) return true; //FFT => T
if (!state[a] && !state[b] && !state[c]) return false; //FFF => F
},
id: 0,
researchSpawned: 0,
descriptionFunction() {
const loop = () => {
if ((simulation.paused || simulation.isChoosing) && m.alive && !build.isExperimentSelection) { //&& (!simulation.isChoosing || this.count === 0)
let b = []; //produce next row
b.push(this.rule(this.state[this.state.length - 1], this.state[this.state.length - 1].length - 1, 0, 1)); //left edge wrap around
for (let i = 1; i < this.state[this.state.length - 1].length - 1; i++) { //apply rule to the rest of the array
b.push(this.rule(this.state[this.state.length - 1], i - 1, i, i + 1));
}
b.push(this.rule(this.state[this.state.length - 1], this.state[this.state.length - 1].length - 2, this.state[this.state.length - 1].length - 1, 0)); //right edge wrap around
this.state.push(b)
if (document.getElementById(`cellular-rule-id${this.id}`)) document.getElementById(`cellular-rule-id${this.id}`).innerHTML = this.outputText() //convert to squares and send HTML
if (this.count && this.researchSpawned < 12 && !(this.state.length % 10)) {
this.researchSpawned++
powerUps.spawn(m.pos.x - 50 + 100 * (Math.random() - 0.5), m.pos.y + 100 * (Math.random() - 0.5), "research");
}
setTimeout(() => {
loop()
}, 300 + 5 * this.state.length);
}
}
setTimeout(() => {
loop()
}, 300);
this.id++
return `${this.outputText()}`
},
outputText() {
let text = ""
for (let j = 0; j < this.state.length; j++) {
// text += ""
text += "
"
for (let i = 0; i < this.state[j].length; i++) {
if (this.state[j][i]) {
text += "■" //"☻" //"⬛" //"█" //"■"
} else {
text += " " //"□" //"☺" //"⬜" //" " //"□"
}
}
text += "
"
}
text += ""
return text
},
},
{
name: "rule 90",
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
allowed() {
return !build.isExperimentSelection
},
requires: "NOT EXPERIMENT MODE",
effect() { },
remove() { },
state: [
[false, false, false, Math.random() > 0.8, false, false, false, Math.random() > 0.8, false, false, false, false, false, false, false, false, false, true, true, false, false, false, Math.random() > 0.8, false, false, false, Math.random() > 0.8, false, false, false, false, Math.random() > 0.8, false, Math.random() > 0.8, false, false, false, Math.random() > 0.8, false, false, false, false, false, false, false, false]
],
rule(state, a, b, c) { //90
if (state[a] && state[b] && state[c]) return false; // TTT => F
if (state[a] && state[b] && !state[c]) return true; // TTF => T
if (state[a] && !state[b] && state[c]) return false; //TFT => F
if (state[a] && !state[b] && !state[c]) return true; //TFF => T
if (!state[a] && state[b] && state[c]) return true; //FTT => T
if (!state[a] && state[b] && !state[c]) return false; //FTF => F
if (!state[a] && !state[b] && state[c]) return true; //FFT => T
if (!state[a] && !state[b] && !state[c]) return false; //FFF => F
},
id: 90,
researchSpawned: 0,
descriptionFunction() {
const loop = () => {
if ((simulation.paused || simulation.isChoosing) && m.alive && !build.isExperimentSelection) { //&& (!simulation.isChoosing || this.count === 0)
let b = []; //produce next row
b.push(this.rule(this.state[this.state.length - 1], this.state[this.state.length - 1].length - 1, 0, 1)); //left edge wrap around
for (let i = 1; i < this.state[this.state.length - 1].length - 1; i++) { //apply rule to the rest of the array
b.push(this.rule(this.state[this.state.length - 1], i - 1, i, i + 1));
}
b.push(this.rule(this.state[this.state.length - 1], this.state[this.state.length - 1].length - 2, this.state[this.state.length - 1].length - 1, 0)); //right edge wrap around
this.state.push(b)
if (document.getElementById(`cellular-rule-id${this.id}`)) document.getElementById(`cellular-rule-id${this.id}`).innerHTML = this.outputText() //convert to squares and send HTML
if (this.count && this.researchSpawned < 12 && !(this.state.length % 10)) {
this.researchSpawned++
powerUps.spawn(m.pos.x - 50 + 100 * (Math.random() - 0.5), m.pos.y + 100 * (Math.random() - 0.5), "research");
}
setTimeout(() => {
loop()
}, 300 + 5 * this.state.length);
}
}
setTimeout(() => {
loop()
}, 300);
this.id++
return `${this.outputText()}`
},
outputText() {
let text = ""
for (let j = 0; j < this.state.length; j++) {
// text += ""
text += "
"
for (let i = 0; i < this.state[j].length; i++) {
if (this.state[j][i]) {
text += "■" //"☻" //"⬛" //"█" //"■"
} else {
text += " " //"□" //"☺" //"⬜" //" " //"□"
}
}
text += "
"
}
text += ""
return text
},
},
{
name: "cosmogonic myth",
description: `open a portal to a primordial version of reality
in 5 minutes close the portal, spawn 1 of each power up`,
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
const urls = ["https://scratch.mit.edu/projects/14005697/fullscreen/", "https://scratch.mit.edu/projects/22573757/fullscreen/", "https://scratch.mit.edu/projects/41429974/fullscreen/", "https://scratch.mit.edu/projects/43690666/fullscreen/", "https://codepen.io/lilgreenland/full/ozXNWZ", "https://codepen.io/lilgreenland/full/wzARJY", "classic/7-1-2017/", "classic/4-15-2018/", "classic/7-11-2019/", "classic/9-8-2019/", "classic/7-15-2020/", "classic/6-1-2021/"]
const choose = urls[Math.floor(Math.random() * urls.length)]
console.log(`opening new tab" ${choose}`)
let tab = window.open(choose, "_blank");
setTimeout(() => {
tab.close();
powerUps.spawn(m.pos.x, m.pos.y, "gun");
setTimeout(() => {
powerUps.spawn(m.pos.x, m.pos.y - 50, "ammo")
}, 250);
setTimeout(() => {
powerUps.spawn(m.pos.x + 50, m.pos.y, "field");
}, 500);
setTimeout(() => {
powerUps.spawn(m.pos.x + 50, m.pos.y - 50, "heal");
}, 750);
setTimeout(() => {
powerUps.spawn(m.pos.x - 50, m.pos.y, "tech");
}, 1000);
setTimeout(() => {
powerUps.spawn(m.pos.x - 50, m.pos.y - 50, "research");
}, 1250);
}, 1000 * 5 * 60);
},
remove() { }
},
{
name: "beforeunload",
description: "75% of the time when you attempt to exit n-gon
you are prompted to cancel or continue.
Each time you cancel gain 1.25x damage.",
maxCount: 1,
count: 0,
frequency: 1,
isJunk: true,
allowed() {
return tech.totalCount > 9
},
requires: "at least 10 tech",
effect() {
tech.isExitPrompt = true
addEventListener('beforeunload', beforeUnloadEventListener);
},
remove() {
tech.isExitPrompt = false
if (this.count > 0) removeEventListener('beforeunload', beforeUnloadEventListener);
}
},
{
name: "planetesimals",
description: `play planetesimals (an asteroids-like game)
clear levels in planetesimals to spawn tech
if you die in planetesimals you die in n-gon`,
maxCount: 1,
count: 0,
frequency: 0,
isInstant: true,
isJunk: true,
allowed() {
return true
},
requires: "",
effect() {
window.open('../../planetesimals/index.html', '_blank')
// powerUps.spawn(m.pos.x, m.pos.y, "tech");
// for communicating to other tabs, like planetesimals
// Connection to a broadcast channel
const bc = new BroadcastChannel('planetesimals');
bc.activated = false
bc.onmessage = function (ev) {
if (ev.data === 'tech') powerUps.directSpawn(m.pos.x, m.pos.y, "tech");
if (ev.data === 'death') {
m.death()
bc.close(); //end session
}
if (ev.data === 'ready' && !bc.activated) {
bc.activated = true //prevents n-gon from activating multiple copies of planetesimals
bc.postMessage("activate");
}
}
},
remove() { }
},
{
name: "tinker",
description: "permanently unlock JUNKtech in experiment mode
this effect is stored for future visits",
maxCount: 1,
count: 0,
frequency: 0,
frequencyDefault: 0,
isJunk: true,
isInstant: true,
allowed() {
return !localSettings.isJunkExperiment
},
requires: "",
effect() {
localSettings.isJunkExperiment = true
if (localSettings.isAllowed) localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage
},
remove() { }
},
{
name: "NFT",
descriptionFunction() {
return `buy your current game seed: ${Math.initialSeed}
no one is allowed to use your seeds
if they use them they are gonna get in trouble
your seeds: ${localSettings.personalSeeds.join(", ")}`
},
maxCount: 1,
count: 0,
frequency: 0,
isJunk: true,
isInstant: true,
allowed: () => true,
requires: "",
effect() {
localSettings.personalSeeds.push(Math.initialSeed)
if (localSettings.isAllowed) localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage
},
remove() { }
},
// {
// name: "rule 90",
// maxCount: 1,
// count: 0,
// frequency: 0,
// isJunk: true,
// allowed() {
// return true
// },
// requires: "",
// effect() {},
// remove() {},
// state: [
// [false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false]
// ],
// rule(state, a, b, c) {
// if (state[a] && state[b] && state[c]) return false; // TTT => F
// if (state[a] && state[b] && !state[c]) return true; // TTF => T
// if (state[a] && !state[b] && state[c]) return false; //TFT => F
// if (state[a] && !state[b] && !state[c]) return true; //TFF => T
// if (!state[a] && state[b] && state[c]) return true; //FTT => T
// if (!state[a] && state[b] && !state[c]) return false; //FTF => F
// if (!state[a] && !state[b] && state[c]) return true; //FFT => T
// if (!state[a] && !state[b] && !state[c]) return false; //FFF => F
// },
// id: 0,
// descriptionFunction() {
// const loop = () => {
// if ((simulation.paused || simulation.isChoosing) && m.alive && !build.isExperimentSelection) { //&& (!simulation.isChoosing || this.count === 0)
// let b = []; //produce next row
// b.push(this.rule(this.state[this.state.length - 1], this.state[this.state.length - 1].length - 1, 0, 1)); //left edge wrap around
// for (let i = 1; i < this.state[this.state.length - 1].length - 1; i++) { //apply rule to the rest of the array
// b.push(this.rule(this.state[this.state.length - 1], i - 1, i, i + 1));
// }
// b.push(this.rule(this.state[this.state.length - 1], this.state[this.state.length - 1].length - 2, this.state[this.state.length - 1].length - 1, 0)); //right edge wrap around
// this.state.push(b)
// if (document.getElementById(`cellular-rule-id${this.id}`)) document.getElementById(`cellular-rule-id${this.id}`).innerHTML = this.outputText() //convert to squares and send HTML
// if (this.count && this.state.length < 120 && !(this.state.length % 10)) powerUps.spawn(m.pos.x - 50 + 100 * (Math.random() - 0.5), m.pos.y + 100 * (Math.random() - 0.5), "research");
// setTimeout(() => { loop() }, 400);
// }
// }
// setTimeout(() => { loop() }, 400);
// // if (this.id === 0) {
// // for (let i = 0; i < 29; i++) this.state[0][i] = Math.random() < 0.5 //randomize seed
// // }
// this.id++
// return `${this.outputText()}`
// },
// outputText() {
// let text = ""
// for (let j = 0; j < this.state.length; j++) {
// text += ""
// for (let i = 0; i < this.state[j].length; i++) {
// if (this.state[j][i]) {
// text += "⬛" //"█" //"■"
// } else {
// text += "⬜" //" " //"□"
// }
// }
// text += "
"
// }
// return text
// },
// },
//**************************************************
//************************************************** undefined / lore
//************************************************** tech
//**************************************************
{
name: `undefined`,
description: `this
`,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
isLore: true,
// isExperimentHide: true,
allowed() { return !build.isExperimentSelection },
requires: "NOT EXPERIMENT MODE",
effect() {
if (localSettings.loreCount > lore.conversation.length - 1) { //reward for people done with lore chapters (or on the final chapter)
for (let i = mob.length - 1; i > -1; i--) { //replace mobs with starters
if (!mob[i].isBoss && mob[i].isDropPowerUp && mob[i].alive) {
spawn.starter(mob[i].position.x, mob[i].position.y)
mob[i].leaveBody = false
mob[i].isDropPowerUp = false
mob[i].death()
//spawn a random power up
// if (Math.random() < 1 / 5) {
// powerUps.spawn(mob[i].position.x, mob[i].position.y, "research")
// } else
if (Math.random() < 1 / 4) {
powerUps.spawn(mob[i].position.x, mob[i].position.y, "ammo")
} else if (Math.random() < 1 / 3) {
powerUps.spawn(mob[i].position.x, mob[i].position.y, "heal")
} else if (Math.random() < 1 / 2) {
powerUps.spawn(mob[i].position.x, mob[i].position.y, "boost")
} else {
powerUps.spawn(mob[i].position.x, mob[i].position.y, "coupling")
}
}
}
}
setTimeout(() => { //a short delay, I can't remember why
lore.techCount++
if (lore.techCount === lore.techGoal) {
// tech.removeLoreTechFromPool();
this.frequency = 0;
this.description = `null is open at level.final()
`
} else {
this.frequency += lore.techGoal * 2
// for (let i = 0; i < tech.tech.length; i++) { //set name for all unchosen copies of this tech
// if (tech.tech[i].isLore && tech.tech[i].count === 0) tech.tech[i].description = `${lore.techCount+1}/${lore.techGoal}
add copies of this to the potential tech pool`
// }
// for (let i = 0, len = 10; i < len; i++) tech.addLoreTechToPool()
this.description = `uncaught error:
${Math.max(0, lore.techGoal - lore.techCount)} more required for access to null`
}
}, 1);
},
remove() {
lore.techCount = 0;
this.maxCount = lore.techGoal;
this.description = `this
`
}
}
],
// addLoreTechToPool() { //adds lore tech to tech pool
// if (!simulation.isCheating) {
// tech.tech.push({
// name: `undefined`,
// description: `${lore.techCount+1}/${lore.techGoal}
add copies of this to the potential tech pool`,
// maxCount: 1,
// count: 0,
// frequency: 2,
// isLore: true,
// isInstant: true,
// isExperimentHide: true,
// allowed() {
// return true
// },
// requires: "",
// effect() {
// setTimeout(() => { //a short delay, I can't remember why
// lore.techCount++
// if (lore.techCount > lore.techGoal - 1) {
// // tech.removeLoreTechFromPool();
// for (let i = tech.tech.length - 1; i > 0; i--) {
// if (tech.tech[i].isLore && tech.tech[i].count === 0) tech.tech.splice(i, 1)
// }
// } else {
// for (let i = 0; i < tech.tech.length; i++) { //set name for all unchosen copies of this tech
// if (tech.tech[i].isLore && tech.tech[i].count === 0) tech.tech[i].description = `${lore.techCount+1}/${lore.techGoal}
add copies of this to the potential tech pool`
// }
// for (let i = 0, len = 10; i < len; i++) tech.addLoreTechToPool()
// }
// }, 1);
// },
// remove() {}
// })
// }
// },
// junk: [
// ],
//variables use for gun tech upgrades
fireRate: null,
bulletSize: null,
energySiphon: null,
healSpawn: null,
crouchAmmoCount: null,
bulletsLastLonger: null,
isImmortal: null,
sporesOnDeath: null,
isImmuneExplosion: null,
isExplodeMob: null,
isDroneOnDamage: null,
isAcidDmg: null,
isAnnihilation: null,
largerHeals: null,
isCrit: null,
isLowHealthDmg: null,
isLowHealthDefense: null,
isLowHealthFireRate: null,
isFarAwayDmg: null,
isFirstDer: null,
isMassEnergy: null,
extraChoices: null,
laserBotCount: null,
dynamoBotCount: null,
nailBotCount: null,
foamBotCount: null,
soundBotCount: null,
boomBotCount: null,
plasmaBotCount: null,
missileBotCount: null,
orbitBotCount: null,
blockDmg: null,
isBlockRadiation: null,
isPiezo: null,
isFastDrones: null,
oneSuperBall: null,
laserReflections: null,
laserDamage: null,
isAmmoFromHealth: null,
mobSpawnWithHealth: null,
isEnergyRecovery: null,
isHealthRecovery: null,
isEnergyLoss: null,
isDeathAvoid: null,
isDeathAvoidedThisLevel: null,
isPlasmaRange: null,
isFreezeMobs: null,
isIceCrystals: null,
blockDamage: null,
isBlockStun: null,
isStunField: null,
isHarmDamage: null,
isVacuumBomb: null,
renormalization: null,
fragments: null,
energyDamage: null,
botSpawner: null,
isBotSpawnerReset: null,
isSporeFollow: null,
isNailRadiation: null,
isEnergyHealth: null,
isStun: null,
restDamage: null,
isRPG: null,
missileCount: null,
isDeterminism: null,
isSuperDeterminism: null,
isHarmReduce: null,
nailsDeathMob: null,
isSlowFPS: null,
isNeutronStun: null,
isAnsatz: null,
isDamageFromBulletCount: null,
laserDrain: null,
isNailShot: null,
slowFire: null,
fastTime: null,
isFastRadiation: null,
isAmmoForGun: null,
isRapidPulse: null,
isSporeFreeze: null,
isShotgunRecoil: null,
isHealLowHealth: null,
isAoESlow: null,
isHarmArmor: null,
isTurret: null,
isRerollDamage: null,
isHarmFreeze: null,
isBotArmor: null,
isRerollHaste: null,
researchHaste: null,
isMineDrop: null,
isRerollBots: null,
isNailBotUpgrade: null,
isFoamBotUpgrade: null,
isSoundBotUpgrade: null,
isLaserBotUpgrade: null,
isBoomBotUpgrade: null,
isOrbitBotUpgrade: null,
isDroneGrab: null,
isOneGun: null,
isDamageForGuns: null,
isGunCycle: null,
isFastFoam: null,
isSporeGrowth: null,
isStimulatedEmission: null,
// nailGun: null,
nailInstantFireRate: null,
isCapacitor: null,
isEnergyNoAmmo: null,
// isFreezeHarmImmune: null,
isSmallExplosion: null,
isExplosionHarm: null,
extraMaxHealth: null,
// bonusHealth: null,
isIntangible: null,
isCloakStun: null,
bonusEnergy: null,
// healGiveMaxEnergy: null,
healMaxEnergyBonus: 0, //not null
aimDamage: null,
isNoFireDefense: null,
isNoFireDamage: null,
duplicateChance: null,
beamSplitter: null,
iceEnergy: null,
isPerfectBrake: null,
explosiveRadius: null,
// isWormholeEnergy: null,
isWormholeDamage: null,
isNailCrit: null,
isFlechetteExplode: null,
isWormholeWorms: null,
isWormHoleBullets: null,
isWideLaser: null,
wideLaser: null,
isPulseLaser: null,
isRadioactive: null,
radioactiveDamage: null,
isRailEnergy: null,
isMineSentry: null,
isIncendiary: null,
overfillDrain: null,
isNeutronSlow: null,
// isRailAreaDamage: null,
historyLaser: null,
isSpeedHarm: null,
isSpeedDamage: null,
speedAdded: null,
isTimeSkip: null,
isCancelDuplication: null,
duplication: null,
isCancelRerolls: null,
isCancelTech: null,
cancelTechCount: null,
isBotDamage: null,
isBanish: null,
isMaxEnergyTech: null,
isLowEnergyDamage: null,
isRewindBot: null,
isRewindGrenade: null,
isExtruder: null,
isEndLevelPowerUp: null,
isMissileBig: null,
isMissileBiggest: null,
isLaserMine: null,
isFoamMine: null,
isAmmoFoamSize: null,
isIceIX: null,
isDupDamage: null,
isDupEnergy: null,
isFireRateForGuns: null,
cyclicImmunity: null,
isTechDamage: null,
isRestHarm: null,
isFireMoveLock: null,
isRivets: null,
isNeedles: null,
isExplodeRadio: null,
isPauseSwitchField: null,
isPauseEjectTech: null,
pauseEjectTech: null,
isShieldPierce: null,
isDuplicateMobs: null,
isDynamoBotUpgrade: null,
isBlockPowerUps: null,
isDamageAfterKillNoRegen: null,
isHarmReduceNoKill: null,
isSwitchReality: null,
isResearchReality: null,
isAnthropicDamage: null,
isMetaAnalysis: null,
isFoamAttract: null,
droneCycleReduction: null,
droneEnergyReduction: null,
isHalfHeals: null,
isAlwaysFire: null,
isDroneRespawn: null,
deathSpawns: null,
isMobBlockFling: null,
isPhaseVelocity: null,
waveBeamSpeed: null,
wavePacketAmplitude: null,
isCollisionRealitySwitch: null,
iceIXOnDeath: null,
wimpCount: null,
isAddBlockMass: null,
isDarkMatter: null,
isHarmDarkMatter: null,
isMoveDarkMatter: null,
isNotDarkMatter: null,
isSneakAttack: null,
isFallingDamage: null,
harmonics: null,
isStandingWaveExpand: null,
isTokamak: null,
isTokamakHeal: null,
tokamakHealCount: null,
isTokamakFly: null,
deflectEnergy: null,
superBallDelay: null,
isBlockExplode: null,
isOverHeal: null,
isDroneRadioactive: null,
droneRadioDamage: null,
isDroneTeleport: null,
isDroneFastLook: null,
isBulletTeleport: null,
isJunkResearch: null,
laserColor: null,
laserColorAlpha: null,
isLongitudinal: null,
is360Longitudinal: null,
isShotgunReversed: null,
fieldDuplicate: null,
isCloakingDamage: null,
harmonicEnergy: null,
isFieldHarmReduction: null,
isFastTime: null,
isAnthropicTech: null,
isSporeWorm: null,
isSporeFlea: null,
isFoamShot: null,
isIceShot: null,
isBlockRestitution: null,
isZeno: null,
isFieldFree: null,
isExtraGunField: null,
isBigField: null,
isSmartRadius: null,
isFilament: null,
isLargeHarpoon: null,
extraHarpoons: null,
ammoCap: null,
isHarpoonPowerUp: null,
harpoonDensity: null,
isAddRemoveMaxHealth: null,
cloakDuplication: null,
extruderRange: null,
isForeverDrones: null,
nailRecoil: null,
baseJumpForce: null,
baseFx: null,
isNeutronium: null,
isFreeWormHole: null,
isRewindField: null,
isCrouchRegen: null,
isAxion: null,
isDarkEnergy: null,
isDarkStar: null,
isWormholeMapIgnore: null,
isLessDamageReduction: null,
needleTunnel: null,
isBrainstorm: null,
isBrainstormActive: null,
brainStormDelay: null,
wormSize: null,
extraSuperBalls: null,
isTimeCrystals: null,
isGroundState: null,
isRailGun: null,
isDronesTravel: null,
isTechDebt: null,
isPlasmaBall: null,
plasmaDischarge: null,
missileFireCD: null,
isBotField: null,
isFoamBall: null,
isNoDraftPause: null,
isFoamPressure: null,
foamDamage: null,
isClusterExplode: null,
isCircleExplode: null,
isPetalsExplode: null,
deathSkipTime: null,
isIceMaxHealthLoss: null,
isIceKill: null,
isCritKill: null,
isQuantumEraser: null,
isPhononBlock: null,
isPhononWave: null,
isLaserLens: null,
laserCrit: null,
isSporeColony: null,
isExtraBotOption: null,
isLastHitDamage: null,
isCloakHealLastHit: null,
isRicochet: null,
isCancelCouple: null,
isCouplingPowerUps: null,
isBoostPowerUps: null,
isBoostReplaceAmmo: null,
isInfiniteWaveAmmo: null,
isJunkDNA: null,
buffedGun: 0,
isGunChoice: null,
railChargeRate: null,
isSuperHarm: null,
isZombieMobs: null,
isSuperMine: null,
sentryAmmo: null,
collidePowerUps: null,
isDilate: null,
isDiaphragm: null,
isNoGroundDamage: null,
isSuperBounce: null,
isDivisor: null,
isFoamCavitation: null,
isHealAttract: null,
isLaserField: null,
isHealBrake: null,
isMassProduction: null,
isPrinter: null,
isHookDefense: null,
hookNails: null,
isHarpoonDefense: null,
isReel: null,
harpoonPowerUpCycle: null,
isHarpoonFullHealth: null,
isMobFullHealthCloak: null,
isMobLowHealth: null,
isDamageCooldown: null,
isDamageCooldownTime: null,
isPowerUpDamage: null,
isExitPrompt: null,
isResearchDamage: null,
interestRate: null,
isImmunityDamage: null,
isMobDeathImmunity: null,
isMaxHealthDefense: null,
isNoDefenseDamage: null,
isMaxHealthDamage: null,
}