diff --git a/img/additive manufacturing.webp b/img/additive manufacturing.webp
new file mode 100644
index 0000000..3a7b15a
Binary files /dev/null and b/img/additive manufacturing.webp differ
diff --git a/js/engine.js b/js/engine.js
index 80b94bc..7a0b708 100644
--- a/js/engine.js
+++ b/js/engine.js
@@ -286,13 +286,9 @@ function collisionChecks(event) {
mob[k].damage(dmg, true);
if (tech.isBlockPowerUps && !mob[k].alive && mob[k].isDropPowerUp && m.throwCycle > m.cycle) {
- let type = tech.isEnergyNoAmmo ? "heal" : "ammo"
- if (Math.random() < 0.4) {
- type = "heal"
- } else if (Math.random() < 0.4 && !tech.isSuperDeterminism) {
- type = "research"
- }
- powerUps.spawn(mob[k].position.x, mob[k].position.y, type);
+ options = ["coupling", "boost", "heal", "research"]
+ if (!tech.isEnergyNoAmmo) options.push("ammo")
+ powerUps.spawn(mob[k].position.x, mob[k].position.y, options[Math.floor(Math.random() * options.length)]);
}
const stunTime = dmg / Math.sqrt(obj.mass)
diff --git a/js/level.js b/js/level.js
index 9b6f7b4..264868f 100644
--- a/js/level.js
+++ b/js/level.js
@@ -10,7 +10,7 @@ const level = {
// playableLevels: ["pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion"],
//see level.populateLevels: (intro, ... , reservoir or factory, reactor, ... , gauntlet, final) added later
playableLevels: ["labs", "rooftops", "skyscrapers", "warehouse", "highrise", "office", "aerie", "satellite", "sewers", "testChamber", "pavilion", "lock"],
- communityLevels: ["gauntlet", "stronghold", "basement", "crossfire", "vats", "run", "ngon", "house", "perplex", "coliseum", "tunnel", "islands", "temple", "dripp", "biohazard", "stereoMadness", "yingYang", "staircase", "fortress", "commandeer", "clock", "buttonbutton", "downpour", "superNgonBros", "underpass", "cantilever", "dojo", "tlinat", "ruins"],
+ communityLevels: ["gauntlet", "stronghold", "basement", "crossfire", "vats", "run", "ngon", "house", "perplex", "coliseum", "tunnel", "islands", "temple", "dripp", "biohazard", "stereoMadness", "yingYang", "staircase", "fortress", "commandeer", "clock", "buttonbutton", "downpour", "superNgonBros", "underpass", "cantilever", "dojo", "tlinat", "ruins", "ace"],
trainingLevels: ["walk", "crouch", "jump", "hold", "throw", "throwAt", "deflect", "heal", "fire", "nailGun", "shotGun", "superBall", "matterWave", "missile", "stack", "mine", "grenades", "harpoon", "diamagnetism"],
levels: [],
start() {
@@ -18,7 +18,7 @@ const level = {
// simulation.enableConstructMode() //tech.giveTech('motion sickness') //used to build maps in testing mode
// simulation.isHorizontalFlipped = true
// tech.giveTech("performance")
- // level.difficultyIncrease(1 * 4) //30 is near max on hard //60 is near max on why
+ // level.difficultyIncrease(3 * 4) //30 is near max on hard //60 is near max on why
// spawn.setSpawnList();
// spawn.setSpawnList();
// m.maxHealth = m.health = 100
@@ -27,7 +27,7 @@ const level = {
// m.immuneCycle = Infinity //you can't take damage
// tech.tech[297].frequency = 100
// m.couplingChange(10)
- // m.setField("time dilation") //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
+ // m.setField("molecular assembler") //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
// m.energy = 0
// simulation.molecularMode = 2
// m.damage(0.1);
@@ -35,7 +35,7 @@ const level = {
// b.giveGuns("drones") //0 nail gun 1 shotgun 2 super balls 3 wave 4 missiles 5 grenades 6 spores 7 drones 8 foam 9 harpoon 10 mine 11 laser
// b.guns[3].ammo = 100000000
// tech.giveTech("von Neumann probe")
- // for (let i = 0; i < 1; ++i) tech.giveTech("mass production")
+ // for (let i = 0; i < 1; ++i) tech.giveTech("additive manufacturing")
// for (let i = 0; i < 2; ++i) tech.giveTech("sound-bot")
// for (let i = 0; i < 1; ++i) tech.giveTech("foam-bot")
// for (let i = 0; i < 1; ++i) tech.giveTech("nail-bot")
@@ -48,7 +48,7 @@ const level = {
// for (let i = 0; i < 3; i++) powerUps.directSpawn(450, -50, "tech");
// for (let i = 0; i < 10; i++) powerUps.directSpawn(1750, -500, "research");
// for (let i = 0; i < 10; i++) powerUps.directSpawn(1750, -500, "coupling");
- // level.diamagnetism();
+ // level.ace();
// for (let i = 0; i < 1; ++i) spawn.slasher(1900, -500)
// for (let i = 0; i < 1; ++i) spawn.slasher2(1900, -500)
// for (let i = 0; i < 1; ++i) spawn.shooterBoss(1900, -2500)
@@ -27352,6 +27352,1275 @@ const level = {
spawn.mapRect(-100, 0, 1000, 100);
powerUps.addResearchToLevel() //needs to run after mobs are spawned
},
+ ace() { //join us at discord.gg/Q8gY4WeUcm
+ simulation.makeTextLog(`ace by Richard0820`);
+ let isDestroyed = false;
+ const ace = {
+ spawnOrbitals(who, radius, chance = Math.min(0.25 + simulation.difficulty * 0.005)) {
+ if (Math.random() < chance) {
+ // simulation.difficulty = 50
+ const len = Math.floor(Math.min(15, 3 + Math.sqrt(simulation.difficulty))) // simulation.difficulty = 40 on hard mode level 10
+ const speed = (0.003 + 0.004 * Math.random() + 0.002 * Math.sqrt(simulation.difficulty)) * ((Math.random() < 0.5) ? 1 : -1)
+ const offSet = 6.28 * Math.random()
+ for (let i = 0; i < len; i++) ace.orbital(who, radius, i / len * 2 * Math.PI + offSet, speed)
+ }
+ },
+ orbital(who, radius, phase, speed) {
+ // for (let i = 0, len = 7; i < len; i++) spawn.orbital(me, radius + 250, 2 * Math.PI / len * i)
+ mobs.spawn(who.position.x, who.position.y, 8, 12, "rgb(0,0,0)");
+ let me = mob[mob.length - 1];
+ me.stroke = "transparent";
+ Matter.Body.setDensity(me, 0.01); //normal is 0.001
+ me.leaveBody = false;
+ me.isDropPowerUp = false;
+ me.isBadTarget = true;
+ me.isUnstable = true; //dies when blocked
+ me.showHealthBar = false;
+ me.isOrbital = true;
+ // me.isShielded = true
+ me.collisionFilter.category = cat.mobBullet;
+ me.collisionFilter.mask = cat.bullet; //cat.player | cat.map | cat.body
+ me.do = function () {
+ //if host is gone
+ if (!who || !who.alive) {
+ this.death();
+ return
+ }
+ //set orbit
+ const time = simulation.cycle * speed + phase
+ const orbit = {
+ x: Math.cos(time),
+ y: Math.sin(time)
+ }
+ Matter.Body.setPosition(this, Vector.add(Vector.add(who.position, who.velocity), Vector.mult(orbit, radius)))
+ //damage player
+ if (Matter.Query.collides(this, [player]).length > 0 && !(m.isCloak && tech.isIntangible) && m.immuneCycle < m.cycle) {
+ m.immuneCycle = m.cycle + m.collisionImmuneCycles; //player is immune to damage for 30 cycles
+ const dmg = 0.03 * simulation.dmgScale
+ m.damage(dmg);
+ simulation.drawList.push({ //add dmg to draw queue
+ x: this.position.x,
+ y: this.position.y,
+ radius: Math.sqrt(dmg) * 200,
+ color: simulation.mobDmgColor,
+ time: simulation.drawTime
+ });
+ this.death();
+ }
+ };
+ },
+ shield(target, x, y, chance = Math.min(0.02 + simulation.difficulty * 0.005, 0.2) + tech.duplicationChance(), isExtraShield = false) {
+ if (this.allowShields && Math.random() < chance) {
+ mobs.spawn(x, y, 9, target.radius + 30, "rgba(255,255,255,0.9)");
+ let me = mob[mob.length - 1];
+ me.stroke = "rgb(0,0,0)";
+ Matter.Body.setDensity(me, 0.00001) //very low density to not mess with the original mob's motion
+ me.shield = true;
+ me.damageReduction = 0.05 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1)
+ me.isUnblockable = true
+ me.isExtraShield = isExtraShield //this prevents spamming with tech.isShieldAmmo
+ me.collisionFilter.category = cat.mobShield
+ me.collisionFilter.mask = cat.bullet;
+ consBB[consBB.length] = Constraint.create({
+ bodyA: me,
+ bodyB: target, //attach shield to target
+ stiffness: 0.4,
+ damping: 0.1
+ });
+ Composite.add(engine.world, consBB[consBB.length - 1]);
+
+ me.onDamage = function () {
+ //make sure the mob that owns the shield can tell when damage is done
+ this.alertNearByMobs();
+ this.fill = `rgba(255,255,255,${0.3 + 0.6 * this.health})`
+ };
+ me.leaveBody = false;
+ me.isDropPowerUp = false;
+ me.showHealthBar = false;
+
+ me.shieldTargetID = target.id
+ target.isShielded = true;
+ target.shieldID = me.id
+ me.onDeath = function () {
+ //clear isShielded status from target
+ for (let i = 0, len = mob.length; i < len; i++) {
+ if (mob[i].id === this.shieldTargetID) mob[i].isShielded = false;
+ }
+ };
+ me.do = function () {
+ this.checkStatus();
+ };
+
+ mob.unshift(me); //move shield to the front of the array, so that mob is behind shield graphically
+
+ //swap order of shield and mob, so that mob is behind shield graphically
+ // mob[mob.length - 1] = mob[mob.length - 2];
+ // mob[mob.length - 2] = me;
+ }
+ },
+ groupShield(targets, x, y, radius, stiffness = 0.4) {
+ const nodes = targets.length
+ mobs.spawn(x, y, 9, radius, "rgba(255,255,255,0.9)");
+ let me = mob[mob.length - 1];
+ me.stroke = "rgb(0,0,0)";
+ Matter.Body.setDensity(me, 0.00001) //very low density to not mess with the original mob's motion
+ me.frictionAir = 0;
+ me.shield = true;
+ me.damageReduction = 0.075 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1)
+ me.collisionFilter.category = cat.mobShield
+ me.collisionFilter.mask = cat.bullet;
+ for (let i = 0; i < nodes; ++i) {
+ mob[mob.length - i - 2].isShielded = true;
+ //constrain to all mob nodes in group
+ consBB[consBB.length] = Constraint.create({
+ bodyA: me,
+ bodyB: mob[mob.length - i - 2],
+ stiffness: stiffness,
+ damping: 0.1
+ });
+ Composite.add(engine.world, consBB[consBB.length - 1]);
+ }
+ me.onDamage = function () {
+ this.alertNearByMobs(); //makes sure the mob that owns the shield can tell when damage is done
+ this.fill = `rgba(255,255,255,${0.3 + 0.6 * this.health})`
+ };
+ me.onDeath = function () {
+ //clear isShielded status from target
+ for (let j = 0; j < targets.length; j++) {
+ for (let i = 0, len = mob.length; i < len; i++) {
+ if (mob[i].id === targets[j]) mob[i].isShielded = false;
+ }
+ }
+ };
+ me.leaveBody = false;
+ me.isDropPowerUp = false;
+ me.showHealthBar = false;
+ mob[mob.length - 1] = mob[mob.length - 1 - nodes];
+ mob[mob.length - 1 - nodes] = me;
+ me.do = function () {
+ this.checkStatus();
+ };
+ },
+ slasher2(x, y, radius = 33 + Math.ceil(Math.random() * 30)) {
+ mobs.spawn(x, y, 6, radius, "rgb(0,0,0)");
+ let me = mob[mob.length - 1];
+ Matter.Body.rotate(me, 2 * Math.PI * Math.random());
+ me.accelMag = 0.0009 * simulation.accelScale;
+ me.torqueMagnitude = 0.000012 * me.inertia //* (Math.random() > 0.5 ? -1 : 1);
+ me.frictionStatic = 0;
+ me.friction = 0;
+ me.frictionAir = 0.035;
+ me.delay = 140 * simulation.CDScale;
+ me.cd = 0;
+ me.swordRadius = 0;
+ me.swordVertex = 1
+ me.swordRadiusMax = 275 + 3.5 * simulation.difficulty;
+ me.swordRadiusGrowRate = me.swordRadiusMax * (0.011 + 0.0002 * simulation.difficulty)
+ me.isSlashing = false;
+ me.swordDamage = 0.03 * simulation.dmgScale
+ me.laserAngle = 3 * Math.PI / 5
+ const seeDistance2 = 200000
+ ace.shield(me, x, y);
+ me.onDamage = function () { };
+ me.do = function () {
+ this.checkStatus();
+ this.seePlayerByHistory(15);
+ this.attraction();
+ this.sword() //does various things depending on what stage of the sword swing
+ };
+ me.swordWaiting = function () {
+ if (
+ this.seePlayer.recall &&
+ this.cd < simulation.cycle &&
+ this.distanceToPlayer2() < seeDistance2 &&
+ Matter.Query.ray(map, this.position, this.playerPosRandomY()).length === 0 &&
+ Matter.Query.ray(body, this.position, this.playerPosRandomY()).length === 0
+ ) {
+ this.laserAngle = -Math.PI / 6
+ this.sword = this.swordGrow
+ this.accelMag = 0
+ }
+ }
+ me.sword = me.swordWaiting //base function that changes during different aspects of the sword swing
+ me.swordGrow = function () {
+ this.laserSword(this.vertices[0], this.angle + this.laserAngle);
+ this.laserSword(this.vertices[1], this.angle + this.laserAngle + (Math.PI / 3));
+ this.laserSword(this.vertices[2], this.angle + this.laserAngle + (Math.PI * 2 / 3));
+ this.laserSword(this.vertices[3], this.angle + this.laserAngle + Math.PI);
+ this.laserSword(this.vertices[4], this.angle + this.laserAngle + (Math.PI * 4 / 3));
+ this.laserSword(this.vertices[5], this.angle + this.laserAngle + (Math.PI * 5 / 3));
+ this.swordRadius += this.swordRadiusGrowRate
+ if (this.swordRadius > this.swordRadiusMax || this.isStunned) {
+ this.sword = this.swordSlash
+ this.spinCount = 0
+ }
+ }
+ me.swordSlash = function () {
+ this.laserSword(this.vertices[0], this.angle + this.laserAngle);
+ this.laserSword(this.vertices[1], this.angle + this.laserAngle + (Math.PI / 3));
+ this.laserSword(this.vertices[2], this.angle + this.laserAngle + (Math.PI * 2 / 3));
+ this.laserSword(this.vertices[3], this.angle + this.laserAngle + Math.PI);
+ this.laserSword(this.vertices[4], this.angle + this.laserAngle + (Math.PI * 4 / 3));
+ this.laserSword(this.vertices[5], this.angle + this.laserAngle + (Math.PI * 5 / 3));
+
+ this.torque += this.torqueMagnitude;
+ this.spinCount++
+ if (this.spinCount > 100 || this.isStunned) {
+ this.sword = this.swordWaiting
+ this.swordRadius = 0
+ this.accelMag = 0.001 * simulation.accelScale;
+ this.cd = simulation.cycle + this.delay;
+ }
+ }
+ me.laserSword = function (where, angle) {
+ const vertexCollision = function (v1, v1End, domain) {
+ for (let i = 0; i < domain.length; ++i) {
+ let v = domain[i].vertices;
+ const len = v.length - 1;
+ for (let j = 0; j < len; j++) {
+ results = simulation.checkLineIntersection(v1, v1End, v[j], v[j + 1]);
+ if (results.onLine1 && results.onLine2) {
+ const dx = v1.x - results.x;
+ const dy = v1.y - results.y;
+ const dist2 = dx * dx + dy * dy;
+ if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) best = { x: results.x, y: results.y, dist2: dist2, who: domain[i], v1: v[j], v2: v[j + 1] };
+ }
+ }
+ results = simulation.checkLineIntersection(v1, v1End, v[0], v[len]);
+ if (results.onLine1 && results.onLine2) {
+ const dx = v1.x - results.x;
+ const dy = v1.y - results.y;
+ const dist2 = dx * dx + dy * dy;
+ if (dist2 < best.dist2) best = { x: results.x, y: results.y, dist2: dist2, who: domain[i], v1: v[0], v2: v[len] };
+ }
+ }
+ };
+ best = { x: null, y: null, dist2: Infinity, who: null, v1: null, v2: null };
+ const look = { x: where.x + this.swordRadius * Math.cos(angle), y: where.y + this.swordRadius * Math.sin(angle) };
+ vertexCollision(where, look, body); // vertexCollision(where, look, mob);
+ vertexCollision(where, look, map);
+ if (!m.isCloak) vertexCollision(where, look, [playerBody, playerHead]);
+ if (best.who && (best.who === playerBody || best.who === playerHead) && m.immuneCycle < m.cycle) {
+ m.immuneCycle = m.cycle + m.collisionImmuneCycles + 60; //player is immune to damage for an extra second
+ m.damage(this.swordDamage);
+ simulation.drawList.push({ //add dmg to draw queue
+ x: best.x,
+ y: best.y,
+ radius: this.swordDamage * 1500,
+ color: "rgba(80,0,255,0.5)",
+ time: 20
+ });
+ }
+ if (best.dist2 === Infinity) best = look;
+ ctx.beginPath(); //draw beam
+ ctx.moveTo(where.x, where.y);
+ ctx.lineTo(best.x, best.y);
+ ctx.strokeStyle = "rgba(0,0,0,0.1)"; // 0 path
+ ctx.lineWidth = 15;
+ ctx.stroke();
+ ctx.strokeStyle = "rgba(0,0,0,0.5)"; // 0 path
+ ctx.lineWidth = 4;
+ ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]);
+ ctx.stroke(); // Draw it
+ ctx.setLineDash([]);
+ }
+ },
+ slasher3(x, y, radius = 33 + Math.ceil(Math.random() * 30)) {
+ const sides = 6
+ mobs.spawn(x, y, sides, radius, "rgb(0,0,0)");
+ let me = mob[mob.length - 1];
+ Matter.Body.rotate(me, 2 * Math.PI * Math.random());
+ me.accelMag = 0.0005 * simulation.accelScale;
+ me.frictionStatic = 0;
+ me.friction = 0;
+ me.frictionAir = 0.02;
+ me.delay = 150 * simulation.CDScale;
+ me.cd = 0;
+ me.cycle = 0;
+ me.swordVertex = 1
+ me.swordRadiusInitial = radius / 2;
+ me.swordRadius = me.swordRadiusInitial;
+ me.swordRadiusMax = 750 + 6 * simulation.difficulty;
+ me.swordRadiusGrowRateInitial = 1.08
+ me.swordRadiusGrowRate = me.swordRadiusGrowRateInitial//me.swordRadiusMax * (0.009 + 0.0002 * simulation.difficulty)
+ me.isSlashing = false;
+ me.swordDamage = 0.04 * simulation.dmgScale
+ me.laserAngle = 3 * Math.PI / 5
+ const seeDistance2 = me.swordRadiusMax * me.swordRadiusMax
+ ace.shield(me, x, y);
+ me.onDamage = function () { };
+ me.do = function () {
+ this.checkStatus();
+ this.seePlayerByHistory(15);
+ this.sword() //does various things depending on what stage of the sword swing
+ };
+ me.swordWaiting = function () {
+ this.attraction();
+ if (
+ this.seePlayer.recall &&
+ this.cd < simulation.cycle &&
+ this.distanceToPlayer2() < seeDistance2 &&
+ Matter.Query.ray(map, this.position, this.playerPosRandomY()).length === 0 &&
+ Matter.Query.ray(body, this.position, this.playerPosRandomY()).length === 0
+ ) {
+ //find vertex closest to the player
+ let dist = Infinity
+ for (let i = 0, len = this.vertices.length; i < len; i++) {
+ const D = Vector.magnitudeSquared(Vector.sub({ x: this.vertices[i].x, y: this.vertices[i].y }, m.pos))
+ if (D < dist) {
+ dist = D
+ this.swordVertex = i
+ }
+ }
+ this.laserAngle = this.swordVertex / sides * 2 * Math.PI + Math.PI / sides
+ this.sword = this.swordGrow
+ this.cycle = 0
+ this.swordRadius = this.swordRadiusInitial
+ //slow velocity but don't stop
+ Matter.Body.setVelocity(this, Vector.mult(this.velocity, 0.5))
+ //set angular velocity to 50%
+ // Matter.Body.setAngularVelocity(this, this.angularVelocity * 0.5)
+ //gently rotate towards the player with a torque, use cross product to decided clockwise or counterclockwise
+ const laserStartVector = Vector.sub(this.position, this.vertices[this.swordVertex])
+ const playerVector = Vector.sub(this.position, m.pos)
+ const cross = Matter.Vector.cross(laserStartVector, playerVector)
+ this.torque = 0.00002 * this.inertia * (cross > 0 ? 1 : -1)
+ }
+ }
+ me.sword = me.swordWaiting //base function that changes during different aspects of the sword swing
+ me.swordGrow = function () {
+ const angle = this.angle + this.laserAngle;
+ const end = {
+ x: this.vertices[this.swordVertex].x + this.swordRadiusMax * Math.cos(angle),
+ y: this.vertices[this.swordVertex].y + this.swordRadiusMax * Math.sin(angle)
+ };
+
+ const dx = end.x - this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1].x;
+ const dy = end.y - this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1].y;
+ const angle1 = Math.atan2(dy, dx) * (180 / Math.PI);
+
+ const dx1 = end.x - this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1].x;
+ const dy1 = end.y - this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1].y;
+ const angle2 = Math.atan2(dy1, dx1) * (180 / Math.PI);
+
+ this.laserSpear(this.vertices[this.swordVertex], this.angle + this.laserAngle);
+ this.laserSpear(this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1], angle1 * (Math.PI / 180))
+ this.laserSpear(this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1], angle2 * (Math.PI / 180))
+
+ Matter.Body.setVelocity(this, Vector.mult(this.velocity, 0.9))
+ // this.swordRadius += this.swordRadiusGrowRate
+ this.cycle++
+ // this.swordRadius = this.swordRadiusMax * Math.sin(this.cycle * 0.03)
+ this.swordRadius *= this.swordRadiusGrowRate
+
+ if (this.swordRadius > this.swordRadiusMax) this.swordRadiusGrowRate = 1 / this.swordRadiusGrowRateInitial
+ // if (this.swordRadius > this.swordRadiusMax) this.swordRadiusGrowRate = -Math.abs(this.swordRadiusGrowRate)
+ if (this.swordRadius < this.swordRadiusInitial || this.isStunned) {
+ // this.swordRadiusGrowRate = Math.abs(this.swordRadiusGrowRate)
+ this.swordRadiusGrowRate = this.swordRadiusGrowRateInitial
+ this.sword = this.swordWaiting
+ this.swordRadius = 0
+ this.cd = simulation.cycle + this.delay;
+ }
+ }
+ me.laserSpear = function (where, angle) {
+ const vertexCollision = function (v1, v1End, domain) {
+ for (let i = 0; i < domain.length; ++i) {
+ let v = domain[i].vertices;
+ const len = v.length - 1;
+ for (let j = 0; j < len; j++) {
+ results = simulation.checkLineIntersection(v1, v1End, v[j], v[j + 1]);
+ if (results.onLine1 && results.onLine2) {
+ const dx = v1.x - results.x;
+ const dy = v1.y - results.y;
+ const dist2 = dx * dx + dy * dy;
+ if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) best = { x: results.x, y: results.y, dist2: dist2, who: domain[i], v1: v[j], v2: v[j + 1] };
+ }
+ }
+ results = simulation.checkLineIntersection(v1, v1End, v[0], v[len]);
+ if (results.onLine1 && results.onLine2) {
+ const dx = v1.x - results.x;
+ const dy = v1.y - results.y;
+ const dist2 = dx * dx + dy * dy;
+ if (dist2 < best.dist2) best = { x: results.x, y: results.y, dist2: dist2, who: domain[i], v1: v[0], v2: v[len] };
+ }
+ }
+ };
+ best = { x: null, y: null, dist2: Infinity, who: null, v1: null, v2: null };
+ const look = { x: where.x + this.swordRadius * Math.cos(angle), y: where.y + this.swordRadius * Math.sin(angle) };
+ vertexCollision(where, look, body); // vertexCollision(where, look, mob);
+ vertexCollision(where, look, map);
+ if (!m.isCloak) vertexCollision(where, look, [playerBody, playerHead]);
+ if (best.who && (best.who === playerBody || best.who === playerHead)) {
+ this.swordRadiusGrowRate = 1 / this.swordRadiusGrowRateInitial //!!!! this retracts the sword if it hits the player
+
+ if (m.immuneCycle < m.cycle) {
+ m.immuneCycle = m.cycle + m.collisionImmuneCycles + 60; //player is immune to damage for an extra second
+ m.damage(this.swordDamage);
+ simulation.drawList.push({ //add dmg to draw queue
+ x: best.x,
+ y: best.y,
+ radius: this.swordDamage * 1500,
+ color: "rgba(80,0,255,0.5)",
+ time: 20
+ });
+ }
+ }
+ if (best.dist2 === Infinity) best = look;
+ ctx.beginPath(); //draw beam
+ ctx.moveTo(where.x, where.y);
+ ctx.lineTo(best.x, best.y);
+ ctx.strokeStyle = "rgba(0,0,0,0.1)"; // 0 path
+ ctx.lineWidth = 15;
+ ctx.stroke();
+ ctx.strokeStyle = "rgba(0,0,0,0.5)"; // 0 path
+ ctx.lineWidth = 4;
+ ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]);
+ ctx.stroke(); // Draw it
+ ctx.setLineDash([]);
+ }
+ },
+ stabber(x, y, radius = 25 + Math.ceil(Math.random() * 12), spikeMax = 7) {
+ if (radius > 80) radius = 65;
+ mobs.spawn(x, y, 6, radius, "rgb(0,0,0)"); //can't have sides above 6 or collision events don't work (probably because of a convex problem)
+ let me = mob[mob.length - 1];
+ me.isVerticesChange = true
+ me.accelMag = 0.0006 * simulation.accelScale;
+ // me.g = 0.0002; //required if using this.gravity
+ me.isInvulnerable = false
+ me.delay = 360 * simulation.CDScale;
+ me.spikeVertex = 0;
+ me.spikeLength = 0;
+ me.isSpikeGrowing = false;
+ me.spikeGrowth = 0;
+ me.isSpikeReset = true;
+ me.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.player //can't touch other mobs
+ Matter.Body.rotate(me, Math.PI * 0.1);
+ ace.shield(me, x, y);
+ // me.onDamage = function () {};
+ // me.onHit = function() { //run this function on hitting player
+ // };
+ me.onDeath = function () {
+ if (this.spikeLength > 4) {
+ this.spikeLength = 4
+ const spike = Vector.mult(Vector.normalise(Vector.sub(this.vertices[this.spikeVertex], this.position)), this.radius * this.spikeLength)
+ this.vertices[this.spikeVertex].x = this.position.x + spike.x
+ this.vertices[this.spikeVertex].y = this.position.y + spike.y
+ // this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices))
+ }
+ };
+ me.do = function () {
+ this.seePlayerByLookingAt();
+ this.checkStatus();
+ this.attraction();
+ if (this.isSpikeReset) {
+ if (this.seePlayer.recall) {
+ const dist = Vector.sub(this.seePlayer.position, this.position);
+ const distMag = Vector.magnitude(dist);
+ if (distMag < radius * spikeMax) {
+ //find nearest vertex
+ let nearestDistance = Infinity
+ for (let i = 0, len = this.vertices.length; i < len; i++) {
+ //find distance to player for each vertex
+ const dist = Vector.sub(this.seePlayer.position, this.vertices[i]);
+ const distMag = Vector.magnitude(dist);
+ //save the closest distance
+ if (distMag < nearestDistance) {
+ this.spikeVertex = i
+ nearestDistance = distMag
+ }
+ }
+ this.spikeLength = 1
+ this.isSpikeGrowing = true;
+ this.isSpikeReset = false;
+ Matter.Body.setAngularVelocity(this, 0)
+ }
+ me.isInvulnerable = false
+ }
+ } else {
+ if (this.isSpikeGrowing) {
+ this.spikeLength += Math.pow(this.spikeGrowth += 0.02, 8)
+ // if (this.spikeLength < 2) {
+ // this.spikeLength += 0.035
+ // } else {
+ // this.spikeLength += 1
+ // }
+ if (this.spikeLength > spikeMax) {
+ this.isSpikeGrowing = false;
+ this.spikeGrowth = 0
+ }
+ } else {
+ Matter.Body.setAngularVelocity(this, this.angularVelocity * 0.8) //reduce rotation
+ this.spikeLength -= 0.3
+ if (this.spikeLength < 1) {
+ this.spikeLength = 1
+ this.isSpikeReset = true
+ this.radius = radius
+ }
+ }
+ const spike = Vector.mult(Vector.normalise(Vector.sub(this.vertices[this.spikeVertex], this.position)), radius * this.spikeLength)
+ this.vertices[this.spikeVertex].x = this.position.x + spike.x
+ this.vertices[this.spikeVertex].y = this.position.y + spike.y
+ me.isInvulnerable = true
+ // this.radius = radius * this.spikeLength;
+ }
+ if (this.isInvulnerable) {
+ ctx.beginPath();
+ let vertices = this.vertices;
+ ctx.moveTo(vertices[0].x, vertices[0].y);
+ for (let j = 1; j < vertices.length; j++) ctx.lineTo(vertices[j].x, vertices[j].y);
+ ctx.lineTo(vertices[0].x, vertices[0].y);
+ ctx.lineWidth = 13 + 5 * Math.random();
+ ctx.strokeStyle = `rgba(255,255,255,${0.5 + 0.2 * Math.random()})`;
+ ctx.stroke();
+ me.damageReduction = 0;
+ } else {
+ me.damageReduction = 1;
+ }
+ };
+ },
+ slash(x, y, radius = 80) {
+ let targets = []
+ const sides = 6;
+ mobs.spawn(x, y, 6, radius, "#000000");
+ let me = mob[mob.length - 1];
+ Matter.Body.rotate(me, 2 * Math.PI * Math.random());
+ targets.push(me.id) //add to shield protection
+ const nodeBalance = Math.random()
+ const nodes2 = Math.min(15, Math.floor(2 + 4 * nodeBalance + 0.75 * Math.sqrt(simulation.difficulty)))
+ me.isBoss = true;
+ me.isSlashBoss = true;
+ me.showHealthBar = false;
+ me.damageReduction = 0.1 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1)
+ me.startingDamageReduction = me.damageReduction
+ me.isInvulnerable = false
+ me.frictionAir = 0.02
+ me.seeAtDistance2 = 1000000;
+ me.accelMag = 0.0004 + 0.00015 * simulation.accelScale;
+ Matter.Body.setDensity(me, 0.0005); //normal is 0.001
+ me.collisionFilter.mask = cat.bullet | cat.player | cat.body | cat.map
+ me.memory = Infinity;
+ me.seePlayerFreq = 20
+ me.lockedOn = null;
+ me.laserRange = 500;
+ me.torqueMagnitude = 0.00024 * me.inertia * (Math.random() > 0.5 ? -1 : 1);
+ me.delay = 70 + 70 * simulation.CDScale;
+ me.cd = 0;
+ me.swordRadius = 0;
+ me.swordVertex = 1
+ me.swordRadiusMax = 1100 + 20 * simulation.difficulty;
+ me.swordRadiusGrowRate = me.swordRadiusMax * (0.005 + 0.0003 * simulation.difficulty)
+ me.isSlashing = false;
+ me.swordDamage = 0.07 * simulation.dmgScale
+ me.laserAngle = 3 * Math.PI / 5
+ me.eventHorizon = 550;
+ const seeDistance2 = 200000
+ ace.shield(me, x, y);
+ const rangeInnerVsOuter = Math.random()
+ let speed = (0.006 + 0.001 * Math.sqrt(simulation.difficulty)) * ((Math.random() < 0.5) ? 1 : -1)
+ let range = radius + 350 + 200 * rangeInnerVsOuter + nodes2 * 7
+ for (let i = 0; i < nodes2; i++) ace.orbital(me, range, i / nodes2 * 2 * Math.PI, speed)
+ const orbitalIndexes = [] //find indexes for all the current nodes2
+ for (let i = 0; i < nodes2; i++) orbitalIndexes.push(mob.length - 1 - i)
+ // add orbitals for each orbital
+ range = Math.max(60, 100 + 100 * Math.random() - nodes2 * 3 - rangeInnerVsOuter * 80)
+ speed = speed * (1.25 + 2 * Math.random())
+ const subNodes = Math.max(2, Math.floor(6 - 5 * nodeBalance + 0.5 * Math.sqrt(simulation.difficulty)))
+ for (let j = 0; j < nodes2; j++) {
+ for (let i = 0, len = subNodes; i < len; i++) ace.orbital(mob[orbitalIndexes[j]], range, i / len * 2 * Math.PI, speed)
+ }
+ for (let i = 0, len = 3 + 0.5 * Math.sqrt(simulation.difficulty); i < len; i++) ace.spawnOrbitals(me, radius + 40 + 10 * i, 1);
+
+ const springStiffness = 0.00014;
+ const springDampening = 0.0005;
+
+ me.springTarget = {
+ x: me.position.x,
+ y: me.position.y
+ };
+ const len = cons.length;
+ cons[len] = Constraint.create({
+ pointA: me.springTarget,
+ bodyB: me,
+ stiffness: springStiffness,
+ damping: springDampening
+ });
+ Composite.add(engine.world, cons[cons.length - 1]);
+ cons[len].length = 100 + 1.5 * radius;
+ me.cons = cons[len];
+
+ me.springTarget2 = {
+ x: me.position.x,
+ y: me.position.y
+ };
+ const len2 = cons.length;
+ cons[len2] = Constraint.create({
+ pointA: me.springTarget2,
+ bodyB: me,
+ stiffness: springStiffness,
+ damping: springDampening,
+ length: 0
+ });
+ Composite.add(engine.world, cons[cons.length - 1]);
+ cons[len2].length = 100 + 1.5 * radius;
+ me.cons2 = cons[len2];
+ me.onDamage = function () { };
+ me.onDeath = function () {
+ isDestroyed = true;
+ this.removeCons();
+ powerUps.spawnBossPowerUp(this.position.x, this.position.y);
+ };
+ me.do = function () {
+ for (let i = 0; i < this.vertices.length; i++) {
+ this.harmField(this.vertices[i].x, this.vertices[i].y);
+ }
+ this.seePlayerByHistory(40);
+ this.springAttack();
+ this.checkStatus();
+ this.sword() //does various things depending on what stage of the sword swing
+ const eventHorizon = this.eventHorizon * (1 + 0.2 * Math.sin(simulation.cycle * 0.008))
+ me.laserRange = eventHorizon;
+ };
+ me.swordWaiting = function () {
+ if (
+ this.seePlayer.recall &&
+ this.cd < simulation.cycle &&
+ this.distanceToPlayer2() < seeDistance2 &&
+ !m.isCloak &&
+ Matter.Query.ray(map, this.position, this.playerPosRandomY()).length === 0 &&
+ Matter.Query.ray(body, this.position, this.playerPosRandomY()).length === 0
+ ) {
+ //find vertex farthest away from player
+ let dist = 0
+ for (let i = 0, len = this.vertices.length; i < len; i++) {
+ const D = Vector.magnitudeSquared(Vector.sub({ x: this.vertices[i].x, y: this.vertices[i].y }, m.pos))
+ if (D > dist) {
+ dist = D
+ this.swordVertex = i
+ }
+ }
+ this.laserAngle = this.swordVertex / 6 * 2 * Math.PI + 0.6283
+ this.sword = this.swordGrow
+ Matter.Body.setAngularVelocity(this, 0)
+ this.accelMag = 0.0004 + 0.00015 * simulation.accelScale;
+ this.damageReduction = 0
+ this.isInvulnerable = true
+ this.frictionAir = 1
+ }
+ }
+ me.sword = me.swordWaiting //base function that changes during different aspects of the sword swing
+ me.swordGrow = function () {
+ const angle = this.angle + this.laserAngle;
+ const end = {
+ x: this.vertices[this.swordVertex].x + this.swordRadiusMax * Math.cos(angle),
+ y: this.vertices[this.swordVertex].y + this.swordRadiusMax * Math.sin(angle)
+ };
+
+ const dx = end.x - this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1].x;
+ const dy = end.y - this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1].y;
+ const angle1 = Math.atan2(dy, dx) * (180 / Math.PI);
+
+ const dx1 = end.x - this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1].x;
+ const dy1 = end.y - this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1].y;
+ const angle2 = Math.atan2(dy1, dx1) * (180 / Math.PI);
+
+ this.laserSword(this.vertices[this.swordVertex], this.angle + this.laserAngle);
+ this.laserSword(this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1], angle1 * (Math.PI / 180))
+ this.laserSword(this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1], angle2 * (Math.PI / 180))
+ this.swordRadius += this.swordRadiusGrowRate
+ if (this.swordRadius > this.swordRadiusMax) {
+ this.sword = this.swordSlash
+ this.spinCount = 0
+ }
+
+ ctx.beginPath();
+ let vertices = this.vertices;
+ ctx.moveTo(vertices[0].x, vertices[0].y);
+ for (let j = 1; j < vertices.length; j++) ctx.lineTo(vertices[j].x, vertices[j].y);
+ ctx.lineTo(vertices[0].x, vertices[0].y);
+ ctx.lineWidth = 13 + 5 * Math.random();
+ ctx.strokeStyle = `rgba(255,255,255,${0.5 + 0.2 * Math.random()})`;
+ ctx.stroke();
+ }
+ me.swordSlash = function () {
+ const angle = this.angle + this.laserAngle;
+ const end = {
+ x: this.vertices[this.swordVertex].x + this.swordRadiusMax * Math.cos(angle),
+ y: this.vertices[this.swordVertex].y + this.swordRadiusMax * Math.sin(angle)
+ };
+
+ const dx = end.x - this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1].x;
+ const dy = end.y - this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1].y;
+ const angle1 = Math.atan2(dy, dx) * (180 / Math.PI);
+
+ const dx1 = end.x - this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1].x;
+ const dy1 = end.y - this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1].y;
+ const angle2 = Math.atan2(dy1, dx1) * (180 / Math.PI);
+
+ this.laserSword(this.vertices[this.swordVertex], this.angle + this.laserAngle);
+ this.laserSword(this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1], angle1 * (Math.PI / 180))
+ this.laserSword(this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1], angle2 * (Math.PI / 180))
+ this.torque += this.torqueMagnitude;
+ this.spinCount++
+ if (this.spinCount > 80) {
+ this.sword = this.swordWaiting
+ this.swordRadius = 0
+ this.accelMag = 0.0004 + 0.00015 * simulation.accelScale;
+ this.cd = simulation.cycle + this.delay;
+ this.damageReduction = this.startingDamageReduction
+ this.isInvulnerable = false
+ this.frictionAir = 0.01
+ }
+ ctx.beginPath();
+ let vertices = this.vertices;
+ ctx.moveTo(vertices[0].x, vertices[0].y);
+ for (let j = 1; j < vertices.length; j++) ctx.lineTo(vertices[j].x, vertices[j].y);
+ ctx.lineTo(vertices[0].x, vertices[0].y);
+ ctx.lineWidth = 13 + 5 * Math.random();
+ ctx.strokeStyle = `rgba(255,255,255,${0.5 + 0.2 * Math.random()})`;
+ ctx.stroke();
+ }
+ me.laserSword = function (where, angle) {
+ const vertexCollision = function (v1, v1End, domain) {
+ for (let i = 0; i < domain.length; ++i) {
+ let v = domain[i].vertices;
+ const len = v.length - 1;
+ for (let j = 0; j < len; j++) {
+ results = simulation.checkLineIntersection(v1, v1End, v[j], v[j + 1]);
+ if (results.onLine1 && results.onLine2) {
+ const dx = v1.x - results.x;
+ const dy = v1.y - results.y;
+ const dist2 = dx * dx + dy * dy;
+ if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) best = { x: results.x, y: results.y, dist2: dist2, who: domain[i], v1: v[j], v2: v[j + 1] };
+ }
+ }
+ results = simulation.checkLineIntersection(v1, v1End, v[0], v[len]);
+ if (results.onLine1 && results.onLine2) {
+ const dx = v1.x - results.x;
+ const dy = v1.y - results.y;
+ const dist2 = dx * dx + dy * dy;
+ if (dist2 < best.dist2) best = { x: results.x, y: results.y, dist2: dist2, who: domain[i], v1: v[0], v2: v[len] };
+ }
+ }
+ };
+ best = { x: null, y: null, dist2: Infinity, who: null, v1: null, v2: null };
+ const look = { x: where.x + this.swordRadius * Math.cos(angle), y: where.y + this.swordRadius * Math.sin(angle) };
+ vertexCollision(where, look, body); // vertexCollision(where, look, mob);
+ vertexCollision(where, look, map);
+ if (!m.isCloak) vertexCollision(where, look, [playerBody, playerHead]);
+ if (best.who && (best.who === playerBody || best.who === playerHead) && m.immuneCycle < m.cycle) {
+ m.immuneCycle = m.cycle + m.collisionImmuneCycles + 60; //player is immune to damage for an extra second
+ m.damage(this.swordDamage);
+ simulation.drawList.push({ //add dmg to draw queue
+ x: best.x,
+ y: best.y,
+ radius: this.swordDamage * 1500,
+ color: "rgba(0,0,0,0.5)",
+ time: 20
+ });
+ }
+ if (best.dist2 === Infinity) best = look;
+ ctx.beginPath(); //draw beam
+ ctx.moveTo(where.x, where.y);
+ ctx.lineTo(best.x, best.y);
+ ctx.strokeStyle = "rgba(0,0,0,0.1)"; // Black path
+ ctx.lineWidth = 25;
+ ctx.stroke();
+ ctx.strokeStyle = "rgba(0,0,0,0.5)"; // Black path
+ ctx.lineWidth = 5;
+ ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]);
+ ctx.stroke(); // Draw it
+ ctx.setLineDash([]);
+ }
+ me.harmField = function (x, y) {
+ ctx.setLineDash([125 * Math.random(), 125 * Math.random()]);
+ // ctx.lineDashOffset = 6*(simulation.cycle % 215);
+ if (this.distanceToPlayer3(x, y) < this.laserRange) {
+ if (m.immuneCycle < m.cycle) {
+ m.damage(0.0003 * simulation.dmgScale);
+ if (m.energy > 0.1) m.energy -= 0.003
+ }
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ ctx.lineTo(m.pos.x, m.pos.y);
+ ctx.lineTo(m.pos.x + (Math.random() - 0.5) * 3000, m.pos.y + (Math.random() - 0.5) * 3000);
+ ctx.lineWidth = 2;
+ ctx.strokeStyle = "rgb(0,0,0)";
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.arc(m.pos.x, m.pos.y, 40, 0, 2 * Math.PI);
+ ctx.fillStyle = "rgba(0,0,0,0.15)";
+ ctx.fill();
+ }
+ ctx.beginPath();
+ ctx.arc(x, y, this.laserRange * 0.9, 0, 2 * Math.PI);
+ ctx.strokeStyle = "rgba(0,0,0,0.5)";
+ ctx.lineWidth = 1;
+ ctx.stroke();
+ ctx.setLineDash([]);
+ ctx.fillStyle = "rgba(0,0,0,0.03)";
+ ctx.fill();
+ }
+ me.distanceToPlayer3 = function (x, y) {
+ const dx = x - player.position.x;
+ const dy = y - player.position.y;
+ return Math.sqrt(dx * dx + dy * dy);
+ }
+ radius = 22 // radius of each node mob
+ const sideLength = 100 // distance between each node mob
+ const nodes = 6
+ const angle = 2 * Math.PI / nodes
+
+ spawn.allowShields = false; //don't want shields on individual mobs
+
+ for (let i = 0; i < nodes; ++i) {
+ ace.stabber(x + sideLength * Math.sin(i * angle), y + sideLength * Math.cos(i * angle), radius, 12);
+ Matter.Body.setDensity(mob[mob.length - 1], 0.003); //extra dense //normal is 0.001 //makes effective life much larger
+ mob[mob.length - 1].damageReduction = 0.12
+ mob[mob.length - 1].showHealthBar = false;
+ mob[mob.length - 1].isBoss = true;
+ targets.push(mob[mob.length - 1].id) //track who is in the node boss, for shields
+ }
+
+ const attachmentStiffness = 0.02
+ spawn.constrain2AdjacentMobs(nodes, attachmentStiffness, true); //loop mobs together
+
+ for (let i = 0; i < nodes; ++i) { //attach to center mob
+ consBB[consBB.length] = Constraint.create({
+ bodyA: me,
+ bodyB: mob[mob.length - i - 1],
+ stiffness: attachmentStiffness,
+ damping: 0.03
+ });
+ Composite.add(engine.world, consBB[consBB.length - 1]);
+ }
+ //spawn shield around all nodes
+ ace.groupShield(targets, x, y, sideLength + 1 * radius + nodes * 5 - 25);
+ spawn.allowShields = true;
+ },
+ }
+ level.setPosToSpawn(-625, -100); //normal spawn
+ level.exit.x = -23650;
+ level.exit.y = 11100;
+ simulation.fallHeight = 20000;
+ const door = level.door(350, -200, 25, 225, 225, 10)
+ const door2 = level.door(6325, -200, 25, 225, 225, 10);
+ // spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); //bump for level entrance
+ spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 20); //bump for level exit
+ level.defaultZoom = 1800
+ simulation.zoomTransition(level.defaultZoom)
+ document.body.style.backgroundColor = "#d8dadf";
+ ace.stabber(425, -100);
+ ace.stabber(725, -100);
+ ace.stabber(1000, -100);
+ ace.stabber(1300, -100);
+ ace.stabber(1550, -100);
+ ace.stabber(1850, -100);
+ ace.stabber(2125, -100);
+ ace.stabber(2400, -100);
+ ace.stabber(2675, -100);
+ ace.stabber(2975, -100);
+ ace.stabber(3225, -100);
+ ace.stabber(3525, -100);
+ ace.stabber(3800, -100);
+ ace.stabber(4100, -100);
+ ace.stabber(4375, -100);
+ ace.stabber(4650, -100);
+ ace.stabber(4925, -100);
+ ace.stabber(5200, -100);
+ ace.stabber(5500, -100);
+ ace.stabber(5775, -100);
+ spawn.mapRect(-200, -450, 2825, 75);
+ spawn.mapRect(-200, 0, 2825, 75);
+ spawn.mapRect(-300, -400, 150, 50);
+ spawn.mapRect(-575, -375, 325, 50);
+ spawn.mapRect(-1175, 0, 700, 75);
+ spawn.mapRect(-1100, 50, 675, 75);
+ spawn.mapRect(-1100, -50, 225, 75);
+ spawn.mapRect(-1025, -75, 200, 50);
+ spawn.mapRect(-875, -50, 150, 75);
+ spawn.mapRect(-700, -350, 325, 50);
+ spawn.mapRect(-950, -100, 150, 50);
+ spawn.mapRect(-675, -50, 125, 25);
+ spawn.mapRect(-575, -150, 25, 125);
+ spawn.mapRect(-650, -50, 25, 75);
+ spawn.mapRect(-600, -50, 25, 75);
+ spawn.mapRect(-800, -325, 250, 50);
+ spawn.mapRect(-1450, 50, 500, 75);
+ spawn.mapRect(-1550, 100, 475, 100);
+ spawn.mapRect(-1650, 175, 525, 375);
+ spawn.mapRect(-1700, 275, 200, 175);
+ spawn.mapRect(-1550, 525, 475, 100);
+ spawn.mapRect(-1475, 600, 4125, 100);
+ spawn.mapRect(-50, 50, 75, 75);
+ level.chain(-450, 75, 0, false, 13)
+ level.chain(7475, 600, -0.5498531827, false, 200)
+ spawn.mapRect(325, -425, 75, 275);
+ spawn.mapRect(-850, -300, 100, 50);
+ spawn.mapRect(-900, -125, 75, 50);
+ spawn.mapRect(-875, -275, 50, 50);
+ spawn.mapRect(425, -400, 125, 100);
+ spawn.mapRect(600, -400, 125, 100);
+ spawn.mapRect(775, -400, 125, 100);
+ spawn.mapRect(950, -400, 125, 100);
+ spawn.mapRect(375, -350, 2250, 25);
+ spawn.mapRect(1125, -400, 125, 100);
+ spawn.mapRect(1300, -400, 125, 100);
+ spawn.mapRect(1475, -400, 125, 100);
+ spawn.mapRect(1650, -400, 125, 100);
+ spawn.mapRect(1825, -400, 125, 100);
+ spawn.mapRect(2000, -400, 125, 100);
+ spawn.mapRect(2175, -400, 125, 100);
+ spawn.mapRect(2350, -400, 125, 100);
+ spawn.mapRect(2525, -400, 125, 100);
+ spawn.mapRect(-1350, 650, 4000, 150);
+ spawn.mapRect(-1400, 675, 125, 75);
+ spawn.mapRect(-1325, 25, 200, 50);
+ spawn.mapRect(2600, 600, 4350, 200);
+ spawn.mapRect(2550, 0, 4400, 75);
+ spawn.mapRect(6875, 0, 875, 75);
+ spawn.mapRect(2500, -450, 5300, 50);
+ spawn.mapRect(7575, -425, 575, 475);
+ spawn.mapRect(6825, 600, 650, 125);
+ spawn.mapRect(6875, 675, 475, 100);
+ spawn.mapRect(7050, -525, 1175, 175);
+ spawn.mapRect(7650, -25, 575, 150);
+ spawn.mapRect(6075, -500, 1125, 75);
+ spawn.mapRect(2550, -350, 3450, 25);
+ spawn.mapRect(2700, -400, 125, 100);
+ spawn.mapRect(2875, -400, 125, 100);
+ spawn.mapRect(3050, -400, 125, 100);
+ spawn.mapRect(3225, -400, 125, 100);
+ spawn.mapRect(3400, -400, 125, 100);
+ spawn.mapRect(3575, -400, 125, 100);
+ spawn.mapRect(3750, -400, 125, 100);
+ spawn.mapRect(3925, -400, 125, 100);
+ spawn.mapRect(4100, -400, 125, 100);
+ spawn.mapRect(4275, -400, 125, 100);
+ spawn.mapRect(4450, -400, 125, 100);
+ spawn.mapRect(4625, -400, 125, 100);
+ spawn.mapRect(4800, -400, 125, 100);
+ spawn.mapRect(4975, -400, 125, 100);
+ spawn.mapRect(5150, -400, 125, 100);
+ spawn.mapRect(5325, -400, 125, 100);
+ spawn.mapRect(5500, -400, 125, 100);
+ spawn.mapRect(5675, -400, 125, 100);
+ spawn.mapRect(5850, -400, 125, 100);
+ spawn.mapRect(6000, -400, 125, 100);
+ spawn.mapRect(6100, -425, 125, 100);
+ spawn.mapRect(6150, -450, 200, 75);
+ spawn.mapRect(2575, -400, 3575, 25);
+ spawn.mapRect(6300, -425, 75, 250);
+ spawn.mapRect(-200, -175, 50, 50);
+ spawn.bodyRect(-950, 475, 150, 125);
+ spawn.bodyRect(-650, 475, 150, 125);
+ spawn.bodyRect(-1000, 350, 550, 125);
+ spawn.bodyRect(-650, 150, 225, 200);
+ spawn.bodyRect(-1050, 150, 400, 200);
+ spawn.bodyRect(-1125, 225, 25, 275);
+ spawn.bodyRect(-1100, 350, 125, 200);
+ spawn.bodyRect(-800, 475, 150, 125);
+ spawn.bodyRect(-500, 475, 125, 125);
+ spawn.mapRect(-3325, -50, 600, 75);
+ spawn.mapRect(-3250, 0, 450, 75);
+ spawn.mapRect(-2950, -100, 350, 75);
+ spawn.mapRect(-2975, -150, 325, 75);
+ spawn.mapRect(-3150, -250, 125, 25);
+ spawn.mapRect(-3050, -225, 100, 25);
+ spawn.mapRect(-3000, -200, 125, 25);
+ spawn.mapRect(-3425, -75, 325, 50);
+ spawn.mapRect(-3250, -225, 125, 25);
+ spawn.mapRect(-3325, -200, 100, 25);
+ spawn.mapRect(-3100, -300, 25, 50);
+ spawn.mapRect(-3725, -325, 1300, 25);
+ spawn.mapRect(-2925, -175, 125, 25);
+ spawn.mapRect(-3375, -175, 100, 25);
+ spawn.mapRect(-3550, -150, 250, 75);
+ spawn.mapRect(-3725, -150, 250, 50);
+ spawn.mapRect(-3725, -200, 125, 75);
+ spawn.mapRect(-3625, -175, 50, 25);
+ spawn.mapRect(-3750, -125, 50, 25);
+ spawn.mapRect(-3750, -50, 125, 50);
+ spawn.mapRect(-3650, -125, 75, 100);
+ spawn.mapRect(-2750, 0, 100, 75);
+ spawn.mapRect(-2675, 25, 175, 25);
+ spawn.mapRect(-2850, 0, 150, 50);
+ spawn.mapRect(-3150, 50, 25, 75);
+ spawn.mapRect(-2900, 50, 25, 75);
+ spawn.mapRect(-3300, 100, 575, 25);
+ spawn.mapRect(-24300, 11125, 51525, 6250);
+ spawn.mapVertex(7900, -675, "0 0 500 -200 500 300 -450 300")
+ spawn.mapRect(-24300, 9575, 475, 1750);
+ spawn.mapRect(26800, 9575, 425, 1750);
+ spawn.hopper(-3100, -150);
+ spawn.mapRect(11625, 8375, 11200, 225);
+ spawn.mapRect(22600, 8375, 225, 3225);
+ spawn.mapRect(11625, 8375, 225, 1800);
+ spawn.mapRect(12425, 9825, 225, 875);
+ spawn.mapRect(13150, 10725, 225, 575);
+ spawn.mapRect(14125, 10450, 5025, 200);
+ spawn.mapRect(21775, 10625, 1050, 225);
+ spawn.mapRect(20325, 10925, 1300, 200);
+ spawn.mapRect(20825, 10250, 750, 225);
+ spawn.mapRect(19500, 10000, 1000, 225);
+
+
+ ace.slasher2(-22725, 10325);
+ ace.slasher3(-23425, 10250);
+ ace.slasher2(-23350, 10700);
+ ace.slasher3(-21725, 11075);
+ ace.slasher2(-21525, 10025);
+ ace.slasher3(-20950, 9750);
+ ace.slasher2(-19975, 9700);
+ ace.slasher3(-18850, 9650);
+ ace.slasher2(-18675, 9700);
+ ace.slasher3(-18250, 9125);
+ ace.slasher2(-17775, 8925);
+ ace.slasher3(-16975, 8875);
+ ace.slasher2(-16475, 9125);
+ ace.slasher3(-16125, 9275);
+ ace.slasher2(-15650, 9225);
+ ace.slasher3(-15200, 9175);
+ ace.slasher2(-16800, 9325);
+ ace.slasher3(-17450, 9525);
+ ace.slasher2(-18375, 9625);
+ ace.slasher3(-19650, 9375);
+ ace.slasher2(-20600, 9225);
+ ace.slasher3(-21625, 9400);
+ ace.slasher2(-22450, 9775);
+ ace.slasher3(-22900, 10000);
+ ace.slasher2(-23275, 9300);
+ ace.slasher3(-23125, 9150);
+ ace.slasher2(2800, 9350);
+ ace.slasher3(4925, 9825);
+ ace.slasher2(3725, 10525);
+ ace.slasher3(1850, 10450);
+ ace.slash(16850, 10075);
+ spawn.mapVertex(-14325, 11000, "0 0 4000 -2000 10000 -3000 16000 -2000 20000 0");
+
+ let q = Matter.Bodies.rectangle(7525 + (1100 / 2), 10675 + (150 / 2), 1100, 150, {
+ density: 0.05,
+ isNotHoldable: false,
+ restitution: 1.05,
+ isStatic: false
+ }, true, [true], 0);
+ let qq = Matter.Bodies.rectangle(7375 + (150 / 2), 10550 + (200 / 2), 150, 200, {
+ density: 0.05,
+ isNotHoldable: true,
+ restitution: 1.05,
+ isStatic: false
+ }, true, [true], 0);
+ let qqq = Matter.Bodies.rectangle(7450 + (1250 / 2), 10500 + (100 / 2), 1250, 100, {
+ density: 0.05,
+ isNotHoldable: true,
+ restitution: 1.05,
+ isStatic: false
+ }, true, [true], 0);
+ let qqqq = Matter.Bodies.rectangle(8625 + (150 / 2), 10550 + (200 / 2), 150, 200, {
+ density: 0.05,
+ isNotHoldable: true,
+ restitution: 1.05,
+ isStatic: false
+ }, true, [true], 0);
+ let qqqqq = Matter.Bodies.rectangle(7600 + (100 / 2), 10350 + (200 / 2), 100, 200, {
+ density: 0.05,
+ isNotHoldable: true,
+ restitution: 1.05,
+ isStatic: false
+ }, true, [true], 0);
+ let qqqqqq = Matter.Bodies.rectangle(8475 + (100 / 2), 10350 + (200 / 2), 100, 200, {
+ density: 0.05,
+ isNotHoldable: true,
+ restitution: 1.05,
+ isStatic: false
+ }, true, [true], 0);
+ let qqqqqqq = Matter.Bodies.rectangle(7650 + (725 / 2), 10325 + (75 / 2), 725, 75, {
+ density: 0.05,
+ isNotHoldable: true,
+ restitution: 1.05,
+ isStatic: false
+ }, true, [true], 0);
+ let qqqqqqqq = Matter.Bodies.rectangle(8000 + 100, 10200 + (150 / 2), 200, 150, {
+ density: 0.05,
+ isNotHoldable: true,
+ restitution: 1.05,
+ isStatic: false
+ }, true, [true], 0);
+ let qqqqqqqqq = Matter.Bodies.rectangle(6975 + (1125 / 2), 10250 + 25, 1125, 50, {
+ density: 0.05,
+ isNotHoldable: true,
+ restitution: 1.05,
+ isStatic: false
+ }, true, [true], 0);
+ let qqqqqqqqqq = Matter.Bodies.rectangle(7600 + 50, 10575 + (125 / 2), 100, 125, {
+ density: 0.05,
+ isNotHoldable: true,
+ restitution: 1.05,
+ isStatic: false
+ }, true, [true], 0);
+ let qqqqqqqqqqq = Matter.Bodies.rectangle(8475 + 50, 10575 + (125 / 2), 100, 125, {
+ density: 0.05,
+ isNotHoldable: true,
+ restitution: 1.05,
+ isStatic: false
+ }, true, [true], 0);
+ let qqqqqqqqqqqq = Matter.Bodies.rectangle(8025 + 50, 10575 + (125 / 2), 100, 125, {
+ density: 0.05,
+ isNotHoldable: true,
+ restitution: 1.05,
+ isStatic: false
+ }, true, [true], 0);
+
+
+ wasd = Matter.Body.create({
+ parts: [q, qq, qqq, qqqq, qqqqq, qqqqqq, qqqqqqq, qqqqqqqq, qqqqqqqqq, qqqqqqqqqq, qqqqqqqqqqq, qqqqqqqqqqqq]
+ });
+
+ body[body.length] = q;
+ body[body.length] = qq;
+ body[body.length] = qqq;
+ body[body.length] = qqqq;
+ body[body.length] = qqqqq;
+ body[body.length] = qqqqqq;
+ body[body.length] = qqqqqqq;
+ body[body.length] = qqqqqqqq;
+ body[body.length] = qqqqqqqqq;
+ body[body.length] = qqqqqqqqqq;
+ body[body.length] = qqqqqqqqqqq;
+ body[body.length] = qqqqqqqqqqqq;
+ // body[body.length] = wasd;
+
+ Matter.Composite.add(engine.world, wasd)
+ composite[composite.length] = wasd;
+ // wasd.friction -= 0.5
+ setTimeout(function () {
+ wasd.collisionFilter.category = cat.map;
+ wasd.collisionFilter.mask = cat.body | cat.player | cat.bullet | cat.mobBullet | cat.mob | cat.map
+ }, 100);
+ let Vx = 0;
+ var gradient = ctx.createLinearGradient(0, 0, 10975 / 2, 0);
+ gradient.addColorStop(0, "#00000000");
+ gradient.addColorStop(1, "#686868");
+ level.custom = () => {
+ wasd.force.y += simulation.g * wasd.mass;
+ if (Matter.Query.collides(wasd, [player]).length > 0 && !(m.isCloak && tech.isIntangible) && input.down && isDestroyed) {
+ wasd.force.x += Math.cos(m.angle) * 75;
+ Matter.Body.setVelocity(player, wasd.velocity)
+ m.Vx = player.velocity.x - wasd.velocity.x;
+ }
+ for (let i = 0; i < mob.length; i++) {
+ if (Matter.Query.collides(wasd, [mob[i]]).length > 0 && !mob[i].isBoss && isDestroyed) {
+ const dmg = 1;
+ mob[i].damage(dmg, true);
+ simulation.drawList.push({ //add dmg to draw queue
+ x: mob[i].position.x,
+ y: mob[i].position.y,
+ radius: Math.sqrt(dmg) * 50,
+ color: simulation.mobDmgColor,
+ time: simulation.drawTime
+ });
+ break
+ }
+ }
+ Vx = wasd.velocity.x / 5;
+ level.exit.drawAndCheck();
+ drawSeats(475, -50, 5600, 125, 20, "darkgray");
+ door.openClose()
+ door2.openClose()
+ if (player.position.y < 25) {
+ door.isClosing = false;
+ door2.isClosing = false;
+ } else {
+ door.isClosing = true;
+ door2.isClosing = true;
+ }
+ ctx.fillStyle = "red";
+ ctx.fillRect(-825, -75, 50, 50);
+
+ b.pulse(30, 0, { x: -2500, y: (25 + (25 / 2)) });
+ ctx.save()
+ ctx.translate(11750, 8475)
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, 0, 10975, 2800);
+ ctx.restore()
+ drawHead(7400, 0, Math.PI * 0.1);
+ drawHead(7460, 0, Math.PI * 0.5);
+ drawHead(7520, 0, Math.PI * 0.3);
+ drawHead(22400, 11125, Math.PI * 0.3);
+ drawHead(21925, 10625, Math.PI * 0.5);
+ drawHead(21175, 10250, Math.PI * 0.1);
+ drawHead(22525, 10625, Math.PI * 0.7);
+ drawHead(22525, 11125, Math.PI * 0.9);
+ drawHead(22225, 11125, Math.PI * 1.5);
+ };
+ level.customTopLayer = () => {
+ drawSeats(500, -50, 5600, 125);
+
+ ctx.strokeStyle = 'red';
+ ctx.lineWidth = 20;
+ ctx.beginPath();
+ ctx.setLineDash([40, 40]);
+ ctx.lineDashOffset = (-simulation.cycle * Vx) % 80;
+
+ ctx.moveTo(q.vertices[0].x, q.vertices[0].y);
+ for (let i = 1; i < q.vertices.length; i++) {
+ ctx.lineTo(q.vertices[i].x, q.vertices[i].y);
+ }
+ ctx.lineTo(q.vertices[0].x, q.vertices[0].y);
+ ctx.closePath();
+ ctx.stroke();
+ ctx.setLineDash([0, 0]);
+ };
+ function drawSeats(x, y, w, h, num = 20, c = "gray") {
+ const seatWidth = w / num;
+ const seatHeight = h / num;
+
+ for (let i = 0; i < num; i++) {
+ const seatX = x + i * seatWidth;
+ const seatY = y;
+
+ // Draw the seat parts
+ ctx.fillStyle = c;
+ ctx.fillRect(seatX - 100, seatY, 125, 25);
+ ctx.fillRect(seatX, seatY - 125, 25, 150);
+ ctx.fillRect(seatX - 75, seatY, 25, 75);
+ ctx.fillRect(seatX - 25, seatY, 25, 75);
+ }
+ }
+ function drawHead(x, y, angle) {
+ ctx.save();
+ ctx.translate(x, y - 30);
+ ctx.rotate(angle);
+ ctx.beginPath();
+ ctx.arc(0, 0, 30, 0, 2 * Math.PI);
+ ctx.fillStyle = m.bodyGradient
+ ctx.fill();
+ ctx.arc(15, 0, 4, 0, 2 * Math.PI);
+ ctx.strokeStyle = "#333";
+ ctx.lineWidth = 2;
+ ctx.stroke();
+ ctx.restore();
+ }
+ for (let i = 0, len = mob.length; i < len; i++) {
+ if (mob[i].isSlashBoss) {
+ simulation.ephemera.push({
+ name: "bossBar",
+ do() {
+ if (level.levels[level.onLevel] == "ace" && !isDestroyed) {
+ ctx.save();
+ ctx.setTransform(1, 0, -0.5, 1, 0, 0); //slanted
+ ctx.fillStyle = "rgba(100, 100, 100, 0.3)";
+ ctx.fillRect(canvas.width2 / 2, canvas.height2 / 10, canvas.width2, 30);
+ ctx.fillStyle = "rgba(0,0,0,0.7)";
+ ctx.fillRect(canvas.width2 / 2, canvas.height2 / 10, canvas.width2 * mob[i].health, 30);
+ ctx.restore();
+ }
+ },
+ })
+ }
+ }
+ },
// ********************************************************************************************************
// ********************************************************************************************************
// ***************************************** training levels **********************************************
diff --git a/js/player.js b/js/player.js
index 1c79fd5..2045371 100644
--- a/js/player.js
+++ b/js/player.js
@@ -307,6 +307,23 @@ const m = {
if (player.velocity.x < m.airSpeedLimit / player.mass / player.mass) player.force.x += m.FxAir; //move player right / d
}
},
+ printBlock() {
+ const sides = Math.floor(4 + 6 * Math.random() * Math.random())
+ body[body.length] = Matter.Bodies.polygon(m.pos.x, m.pos.y, sides, 8, {
+ friction: 0.05,
+ frictionAir: 0.001,
+ collisionFilter: { category: 0, mask: 0 }, //no collision because player is holding
+ classType: "body",
+ isPrinted: true,
+ radius: 10, //used to grow and warp the shape of the block
+ density: 0.002, //double density for 2x damage
+ });
+ const who = body[body.length - 1]
+ Composite.add(engine.world, who); //add to world
+ m.throwCharge = 4;
+ m.holdingTarget = who
+ m.isHolding = true;
+ },
alive: false,
switchWorlds() {
powerUps.boost.endCycle = 0
@@ -552,7 +569,7 @@ const m = {
if (tech.isLowHealthDefense) dmg *= 1 - Math.max(0, 1 - m.health) * 0.8
if (tech.isHarmReduceNoKill && m.lastKillCycle + 300 < m.cycle) dmg *= 0.33
if (tech.squirrelFx !== 1) dmg *= 0.78//Math.pow(0.78, (tech.squirrelFx - 1) / 0.4)
- if (tech.isAddBlockMass && m.isHolding) dmg *= 0.15
+ if (tech.isAddBlockMass && m.isHolding) dmg *= 0.1
if (tech.isSpeedHarm && player.speed > 0.1) dmg *= 1 - Math.min(player.speed * 0.0165, 0.66)
if (tech.isHarmReduce && input.field && m.fieldCDcycle < m.cycle) dmg *= 0.25
if (tech.isNeutronium && input.field && m.fieldCDcycle < m.cycle) dmg *= 0.1
@@ -1972,11 +1989,11 @@ const m = {
m.fieldRegen *= 0.66
}
},
- regenEnergy: function () { //used in drawRegenEnergy // rewritten by some tech
+ regenEnergy() { //used in drawRegenEnergy // rewritten by some tech
if (m.immuneCycle < m.cycle && m.fieldCDcycle < m.cycle) m.energy += m.fieldRegen;
if (m.energy < 0) m.energy = 0
},
- regenEnergyDefault: function () {
+ regenEnergyDefault() {
if (m.immuneCycle < m.cycle && m.fieldCDcycle < m.cycle) m.energy += m.fieldRegen;
if (m.energy < 0) m.energy = 0
},
@@ -2068,7 +2085,6 @@ const m = {
if (m.fireCDcycle < m.cycle) m.fireCDcycle = m.cycle
if (tech.isCapacitor && m.throwCharge < 4) m.throwCharge = 4
m.throwCharge += 0.5 / m.holdingTarget.mass / b.fireCDscale
-
if (m.throwCharge < 6) m.energy -= 0.001 / b.fireCDscale; // m.throwCharge caps at 5
//trajectory path prediction
@@ -2116,11 +2132,8 @@ const m = {
//trajectory prediction
const cycles = 30
const charge = Math.min(m.throwCharge / 5, 1)
- const speed = 80 * charge * Math.min(0.85, 0.8 / Math.pow(m.holdingTarget.mass, 0.25));
- const v = {
- x: speed * Math.cos(m.angle),
- y: speed * Math.sin(m.angle)
- } //m.Vy / 2 + removed to make the path less jerky
+ const speed = (tech.isPrinter ? 15 + 80 * charge * Math.min(0.85, 0.8 / Math.pow(m.holdingTarget.mass, 0.1)) : 80 * charge * Math.min(0.85, 0.8 / Math.pow(m.holdingTarget.mass, 0.25)))
+ const v = { x: speed * Math.cos(m.angle), y: speed * Math.sin(m.angle) }
ctx.beginPath()
for (let i = 1, len = 10; i < len + 1; i++) {
const time = cycles * i / len
@@ -2134,8 +2147,11 @@ const m = {
m.drop()
}
} else if (m.throwCharge > 0) { //Matter.Query.region(mob, player.bounds)
+ if (m.holdingTarget.isPrinted) m.holdingTarget.isPrinted = undefined
//throw the body
- m.fieldCDcycle = m.cycle + 15;
+ m.fieldCDcycle = m.cycle + 20;
+ m.fireCDcycle = m.cycle + 20;
+
m.isHolding = false;
if (tech.isTokamak && m.throwCharge > 4) { //remove the block body and pulse in the direction you are facing
@@ -2176,7 +2192,9 @@ const m = {
const charge = Math.min(m.throwCharge / 5, 1)
//***** scale throw speed with the first number, 80 *****
- let speed = 80 * charge * Math.min(0.85, 0.8 / Math.pow(m.holdingTarget.mass, 0.25));
+ // let speed = 80 * charge * Math.min(0.85, 0.8 / Math.pow(m.holdingTarget.mass, 0.25));
+ let speed = (tech.isPrinter ? 15 + 80 * charge * Math.min(0.85, 0.8 / Math.pow(m.holdingTarget.mass, 0.1)) : 80 * charge * Math.min(0.85, 0.8 / Math.pow(m.holdingTarget.mass, 0.25)))
+
if (Matter.Query.collides(m.holdingTarget, map).length !== 0) {
speed *= 0.7 //drop speed by 30% if touching map
if (Matter.Query.ray(map, m.holdingTarget.position, m.pos).length !== 0) speed = 0 //drop to zero if the center of the block can't see the center of the player through the map
@@ -2196,7 +2214,7 @@ const m = {
if (tech.isAddBlockMass) {
const expand = function (that, massLimit) {
if (that.mass < massLimit) {
- const scale = 1.05;
+ const scale = 1.04;
Matter.Body.scale(that, scale, scale);
setTimeout(expand, 20, that, massLimit);
}
@@ -3105,10 +3123,10 @@ const m = {
},
{
name: "molecular assembler",
- description: `excess energy used to build ${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}
use energy to deflect mobs
generate 12 energy per second`,
+ description: `excess energy used to print ${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}
use energy to deflect mobs
generate 12 energy per second`,
// simulation.molecularMode: Math.floor(4 * Math.random()), //0 spores, 1 missile, 2 ice IX, 3 drones
setDescription() {
- return `excess energy used to build ${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}
use energy to deflect mobs
generate 12 energy per second`
+ return `excess energy used to print ${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}
use energy to deflect mobs
generate 12 energy per second`
},
effect: () => {
m.fieldMeterColor = "#ff0"
@@ -3195,12 +3213,28 @@ const m = {
if (m.isHolding) {
m.drawHold(m.holdingTarget);
m.holding();
+ if (tech.isPrinter && m.holdingTarget.isPrinted && input.field) {
+ // if (Math.random() < 0.004 && m.holdingTarget.vertices.length < 12) m.holdingTarget.vertices.push({ x: 0, y: 0 }) //small chance to increase the number of vertices
+ m.holdingTarget.radius += Math.min(1.1, 1.3 / m.holdingTarget.mass) //grow up to a limit
+ const r1 = m.holdingTarget.radius * (1 + 0.12 * Math.sin(m.cycle * 0.11))
+ const r2 = m.holdingTarget.radius * (1 + 0.12 * Math.cos(m.cycle * 0.11))
+ let angle = (m.cycle * 0.01) % (2 * Math.PI) //rotate the object
+ let vertices = []
+ for (let i = 0, len = m.holdingTarget.vertices.length; i < len; i++) {
+ angle += 2 * Math.PI / len
+ vertices.push({ x: m.holdingTarget.position.x + r1 * Math.cos(angle), y: m.holdingTarget.position.y + r2 * Math.sin(angle) })
+ }
+ Matter.Body.setVertices(m.holdingTarget, vertices)
+ m.definePlayerMass(m.defaultMass + m.holdingTarget.mass * m.holdingMassScale)
+ }
m.throwBlock();
} else if ((input.field && m.fieldCDcycle < m.cycle)) { //not hold but field button is pressed
if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen
m.grabPowerUp();
m.lookForPickUp();
- if (m.energy > m.minEnergyToDeflect) {
+ if (tech.isPrinter && input.down) {
+ m.printBlock();
+ } else if (m.energy > m.minEnergyToDeflect) {
m.drawField();
m.pushMobsFacing();
}
@@ -3993,6 +4027,36 @@ const m = {
m.fieldRadius = 0;
m.drop();
m.hold = function () {
+ if (tech.isPrinter) {
+ //spawn blocks if field and crouch
+ if (input.field && m.fieldCDcycle < m.cycle && input.down && !m.isHolding) {
+ m.printBlock()
+ }
+ //if holding block grow it
+ if (m.isHolding) {
+ m.drawHold(m.holdingTarget);
+ m.holding();
+ if (tech.isPrinter && m.holdingTarget.isPrinted && input.field) {
+ // if (Math.random() < 0.004 && m.holdingTarget.vertices.length < 12) m.holdingTarget.vertices.push({ x: 0, y: 0 }) //small chance to increase the number of vertices
+ m.holdingTarget.radius += Math.min(1.1, 1.3 / m.holdingTarget.mass) //grow up to a limit
+ const r1 = m.holdingTarget.radius * (1 + 0.12 * Math.sin(m.cycle * 0.11))
+ const r2 = m.holdingTarget.radius * (1 + 0.12 * Math.cos(m.cycle * 0.11))
+ let angle = (m.cycle * 0.01) % (2 * Math.PI) //rotate the object
+ let vertices = []
+ for (let i = 0, len = m.holdingTarget.vertices.length; i < len; i++) {
+ angle += 2 * Math.PI / len
+ vertices.push({ x: m.holdingTarget.position.x + r1 * Math.cos(angle), y: m.holdingTarget.position.y + r2 * Math.sin(angle) })
+ }
+ Matter.Body.setVertices(m.holdingTarget, vertices)
+ m.definePlayerMass(m.defaultMass + m.holdingTarget.mass * m.holdingMassScale)
+ }
+ m.throwBlock()
+ } else {
+ m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists)
+ }
+ //if releasing field throw it
+
+ }
if (input.field) {
if (m.fieldCDcycle < m.cycle) {
const scale = 25
diff --git a/js/spawn.js b/js/spawn.js
index c7dbe55..0dabc8c 100644
--- a/js/spawn.js
+++ b/js/spawn.js
@@ -8371,7 +8371,7 @@ const spawn = {
Composite.add(engine.world, who); //add to world
if (isRedrawMap) simulation.draw.setPaths()
},
- mapVertexNow(x, y, vector, properties) { //adds shape to map array in the middle of a level
+ mapVertexNow(x, y, vector, properties, isRedrawMap = true) { //adds shape to map array in the middle of a level
map[map.length] = Matter.Bodies.fromVertices(x, y, Vertices.fromPath(vector), properties);
const who = map[map.length - 1]
who.collisionFilter.category = cat.map;
diff --git a/js/tech.js b/js/tech.js
index a23769c..c173744 100644
--- a/js/tech.js
+++ b/js/tech.js
@@ -2044,9 +2044,9 @@ const tech = {
frequency: 1,
frequencyDefault: 1,
allowed() {
- return m.fieldMode !== 9
+ return m.fieldMode !== 9 && !tech.isTokamak
},
- requires: "not wormhole",
+ requires: "not wormhole, tokamak",
effect() {
tech.blockDamage = 0.3
},
@@ -2057,13 +2057,13 @@ const tech = {
{
name: "inflation",
link: `inflation`,
- description: "if holding a block +85% defense
after throwing a block it expands 300%",
+ description: "if holding a block +90% defense
after throwing a block it expands 200%",
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
- return tech.blockDamage > 0.075 && m.fieldMode !== 8 && m.fieldMode !== 9 && !tech.isTokamak
+ return (tech.blockDamage > 0.075 || tech.isPrinter) && m.fieldMode !== 8 && m.fieldMode !== 9 && !tech.isTokamak
},
requires: "mass driver, not pilot wave, tokamak, wormhole",
effect() {
@@ -2081,7 +2081,7 @@ const tech = {
frequency: 3,
frequencyDefault: 3,
allowed() {
- return tech.blockDamage > 0.075 && m.fieldUpgrades[m.fieldMode].name !== "pilot wave" && m.fieldUpgrades[m.fieldMode].name !== "wormhole" && !tech.isTokamak
+ 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() {
@@ -2099,7 +2099,7 @@ const tech = {
frequency: 3,
frequencyDefault: 3,
allowed() {
- return tech.blockDamage > 0.075 && !tech.nailsDeathMob && !tech.sporesOnDeath && !tech.isExplodeMob && !tech.botSpawner && !tech.iceIXOnDeath
+ 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() {
@@ -2130,14 +2130,14 @@ const tech = {
{
name: "buckling",
descriptionFunction() {
- return `if a block you threw kills a mob
spawn either ${powerUps.orb.heal()}, ${powerUps.orb.ammo()}, or ${powerUps.orb.research(1)}`
+ return `if a block you threw kills a mob
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 && m.fieldUpgrades[m.fieldMode].name !== "pilot wave" && !tech.isTokamak
+ return (tech.blockDamage > 0.075 || tech.isPrinter) && m.fieldUpgrades[m.fieldMode].name !== "pilot wave" && !tech.isTokamak
},
requires: "mass driver, not pilot wave, tokamak",
effect() {
@@ -7796,32 +7796,7 @@ const tech = {
// },
{
name: "bot manufacturing",
- description: `use ${powerUps.orb.research(1)} to build
3 random bots`,
- isFieldTech: true,
- maxCount: 1,
- count: 0,
- frequency: 1,
- frequencyDefault: 1,
- isBotTech: true,
- isNonRefundable: true,
- allowed() {
- return powerUps.research.count > 0 && (m.fieldMode === 4 || m.fieldMode === 8)
- },
- requires: "molecular assembler, pilot wave",
- effect() {
- for (let i = 0; i < 1; 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(2)}to build 2 random bots
and upgrade all bots to that type`,
+ description: `use ${powerUps.orb.research(2)} to build
3 random bots`,
isFieldTech: true,
maxCount: 1,
count: 0,
@@ -7837,6 +7812,31 @@ const tech = {
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 that type`,
+ isFieldTech: true,
+ maxCount: 1,
+ count: 0,
+ frequency: 1,
+ frequencyDefault: 1,
+ isBotTech: true,
+ isNonRefundable: true,
+ allowed() {
+ return powerUps.research.count > 2 && (m.fieldMode === 4 || m.fieldMode === 8)
+ },
+ requires: "molecular assembler, pilot wave",
+ effect() {
+ 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
@@ -7983,6 +7983,27 @@ const tech = {
// if (this.count > 0) powerUps.research.changeRerolls(1)
// }
// },
+ {
+ name: "additive manufacturing",
+ description: "hold crouch and use your field to print a block
with +80% density, damage, and launch speed",
+ // description: "simultaneously fire and activate your field to make
molecular assembler print a throwable block
+80% block throwing 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: "pair production",
description: "after picking up a power up
+200 energy",
@@ -8055,9 +8076,9 @@ const tech = {
frequency: 2,
frequencyDefault: 2,
allowed() {
- return m.fieldMode === 5 || m.fieldMode === 4
+ return (m.fieldMode === 5 || m.fieldMode === 4) && !tech.isPrinter
},
- requires: "plasma torch, molecular assembler",
+ requires: "plasma torch, molecular assembler, not printer",
effect() {
tech.isTokamak = true;
},
@@ -11724,4 +11745,5 @@ const tech = {
isLaserField: null,
isHealBrake: null,
isMassProduction: null,
+ isPrinter: null,
}
\ No newline at end of file
diff --git a/todo.txt b/todo.txt
index c95e84f..8de3a67 100644
--- a/todo.txt
+++ b/todo.txt
@@ -1,23 +1,21 @@
******************************************************** NEXT PATCH **************************************************
+community map ace by Richard0820
-new community training level diamagnetism by Richard0820
- it's at the end of the training levels
- start training by click the top right button at the load screen
+field tech: additive manufacturing - crouch while activating your field to print a throwable block
+ blocks 80% faster and 80% more dense/more damage
+ molecular assembler, pilot wave
-slasher mob variant with 2 laser swords
-slasher mob variant with a laser spear
-
-suckerBoss pulls in powerUps and makes them orbit better
-shooterBoss fires faster and larger bullets
-
-wormhole 4 -> 5% duplication, and a 10% reduction in energy cost
-drones fire faster and aim more accurately
-quenching gives 10% more max health and harm
-snake tail mobs have 15% less health
-tungsten carbide properly gives 222 health in addition to the bonus max health
+inflation: 85->90% defense, 300->200% larger blocks
+buckling: can spawn boosts or coupling in addition to heals, ammo, and research
*********************************************************** TODO *****************************************************
+add a player to the load screen
+ static SVG image of player
+ canvas image
+ interactable?
+ full matter.js level?
+
use cross product rotation for other mobs?
snipers, shooters?
//gently rotate towards the player with a torque, use cross product to decided clockwise or counterclockwise
@@ -26,9 +24,6 @@ use cross product rotation for other mobs?
const cross = Matter.Vector.cross(laserStartVector, playerVector)
this.torque = 0.00002 * this.inertia * (cross > 0 ? 1 : -1)
-block manufacturing - molecular assembler tech
-Holding r-click will create a slowly increasing in size block, which will be thrown on release
-
super-bot: fires super balls
tech - only allow 1,2 turrets at time. spawning a new mine removes the oldest mine
@@ -1074,6 +1069,7 @@ possible names for tech
eternal inflation
hypergraph
SQUID (for superconducting quantum interference device) is a very sensitive magnetometer used to measure extremely subtle magnetic fields, based on superconducting loops containing Josephson junctions.
+ Josephson junction - superconducting junction used in SQUIDS and quantum computers
nuclear pasta - hard matter in neutron star
nonlocal, nonlocality: maybe use for pilot wave
fine-tuned universe
@@ -1100,6 +1096,7 @@ possible names for tech
negative entropy
memetics
magnetorquers - produce spin by pushing on earth's magnetic field
+ Josephson junction - superconducting junction
******************************************************** CARS IMAGES ********************************************************