incendiary ammunition
bug - spider boss move at full speed again (watch out) bots that uses energy stop when you hit 50% energy if you have mass-energy mod you get a warning before you overwrite your current gun due to integrated armament gun - flak is removed mod: High-explosive incendiary, or incendiary rounds shotgun: several large explosions nail gun: rapid fire explosions drones: drones explode on impact super balls: explode on contact
This commit is contained in:
275
js/bullet.js
275
js/bullet.js
@@ -928,17 +928,22 @@ const b = {
|
||||
deathCycles: 110 + RADIUS * 5,
|
||||
isImproved: false,
|
||||
beforeDmg(who) {
|
||||
//move away from target after hitting
|
||||
const unit = Vector.mult(Vector.normalise(Vector.sub(this.position, who.position)), -20)
|
||||
Matter.Body.setVelocity(this, {
|
||||
x: unit.x,
|
||||
y: unit.y
|
||||
});
|
||||
if (mod.isIncendiary) {
|
||||
b.explosion(this.position, 120 + (Math.random() - 0.5) * 60); //makes bullet do explosive damage at end
|
||||
this.endCycle = 0
|
||||
} else {
|
||||
//move away from target after hitting
|
||||
const unit = Vector.mult(Vector.normalise(Vector.sub(this.position, who.position)), -20)
|
||||
Matter.Body.setVelocity(this, {
|
||||
x: unit.x,
|
||||
y: unit.y
|
||||
});
|
||||
|
||||
this.lockedOn = null
|
||||
if (this.endCycle > game.cycle + this.deathCycles) {
|
||||
this.endCycle -= 60
|
||||
if (game.cycle + this.deathCycles > this.endCycle) this.endCycle = game.cycle + this.deathCycles
|
||||
this.lockedOn = null
|
||||
if (this.endCycle > game.cycle + this.deathCycles) {
|
||||
this.endCycle -= 60
|
||||
if (game.cycle + this.deathCycles > this.endCycle) this.endCycle = game.cycle + this.deathCycles
|
||||
}
|
||||
}
|
||||
},
|
||||
onEnd() {},
|
||||
@@ -1377,6 +1382,7 @@ const b = {
|
||||
dmg: 0, // 0.14 //damage done in addition to the damage from momentum
|
||||
minDmgSpeed: 2,
|
||||
lookFrequency: 40 + Math.floor(7 * Math.random()),
|
||||
drainThreshold: mod.isEnergyHealth ? 0.5 : 0.15,
|
||||
acceleration: 0.0015 * (1 + 0.3 * Math.random()),
|
||||
range: 700 * (1 + 0.1 * Math.random()) + 250 * mod.isLaserBotUpgrade,
|
||||
followRange: 150 + Math.floor(30 * Math.random()),
|
||||
@@ -1430,47 +1436,9 @@ const b = {
|
||||
}
|
||||
}
|
||||
//hit target with laser
|
||||
if (this.lockedOn && this.lockedOn.alive && mech.energy > 0.15) {
|
||||
if (this.lockedOn && this.lockedOn.alive && mech.energy > this.drainThreshold) {
|
||||
mech.energy -= 0.0012 * mod.isLaserDiode
|
||||
// const sub = Vector.sub(this.lockedOn.position, this.vertices[0])
|
||||
// const angle = Math.atan2(sub.y, sub.x);
|
||||
b.laser(this.vertices[0], this.lockedOn.position, b.dmgScale * (0.06 + 0.1 * this.isUpgraded))
|
||||
|
||||
// //make sure you can still see vertex
|
||||
// const DIST = Vector.magnitude(Vector.sub(this.vertices[0], this.lockedOn.position));
|
||||
// if (DIST - this.lockedOn.radius < this.range + 150 &&
|
||||
// Matter.Query.ray(map, this.vertices[0], this.lockedOn.position).length === 0 &&
|
||||
// Matter.Query.ray(body, this.vertices[0], this.lockedOn.position).length === 0) {
|
||||
// //move towards the target
|
||||
// this.force = Vector.add(this.force, Vector.mult(Vector.normalise(Vector.sub(this.lockedOn.position, this.position)), 0.0013))
|
||||
// //find the closest vertex
|
||||
// let bestVertexDistance = Infinity
|
||||
// let bestVertex = null
|
||||
// for (let i = 0; i < this.lockedOn.vertices.length; i++) {
|
||||
// const dist = Vector.magnitude(Vector.sub(this.vertices[0], this.lockedOn.vertices[i]));
|
||||
// if (dist < bestVertexDistance) {
|
||||
// bestVertex = i
|
||||
// bestVertexDistance = dist
|
||||
// }
|
||||
// }
|
||||
// const dmg = b.dmgScale * (0.06 + 0.08 * this.isUpgraded);
|
||||
// this.lockedOn.damage(dmg);
|
||||
// this.lockedOn.locatePlayer();
|
||||
|
||||
// ctx.beginPath(); //draw laser
|
||||
// ctx.moveTo(this.vertices[0].x, this.vertices[0].y);
|
||||
// ctx.lineTo(this.lockedOn.vertices[bestVertex].x, this.lockedOn.vertices[bestVertex].y);
|
||||
// ctx.strokeStyle = "#f00";
|
||||
// ctx.lineWidth = "2"
|
||||
// ctx.lineDashOffset = 300 * Math.random()
|
||||
// ctx.setLineDash([50 + 100 * Math.random(), 100 * Math.random()]);
|
||||
// ctx.stroke();
|
||||
// ctx.setLineDash([0, 0]);
|
||||
// ctx.beginPath();
|
||||
// ctx.arc(this.lockedOn.vertices[bestVertex].x, this.lockedOn.vertices[bestVertex].y, Math.sqrt(dmg) * 100, 0, 2 * Math.PI);
|
||||
// ctx.fillStyle = "#f00";
|
||||
// ctx.fill();
|
||||
// }
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1571,6 +1539,7 @@ const b = {
|
||||
cd: 0,
|
||||
acceleration: 0.009,
|
||||
endCycle: Infinity,
|
||||
drainThreshold: mod.isEnergyHealth ? 0.5 : 0.15,
|
||||
classType: "bullet",
|
||||
collisionFilter: {
|
||||
category: cat.bullet,
|
||||
@@ -1608,9 +1577,8 @@ const b = {
|
||||
const sub = Vector.sub(this.lockedOn.position, this.position)
|
||||
const DIST = Vector.magnitude(sub);
|
||||
const unit = Vector.normalise(sub)
|
||||
const DRAIN = 0.0012
|
||||
if (DIST < mod.isPlasmaRange * 450 && mech.energy > DRAIN) {
|
||||
mech.energy -= DRAIN;
|
||||
if (DIST < mod.isPlasmaRange * 450 && mech.energy > this.drainThreshold) {
|
||||
mech.energy -= 0.0012;
|
||||
if (mech.energy < 0) {
|
||||
mech.fieldCDcycle = mech.cycle + 120;
|
||||
mech.energy = 0;
|
||||
@@ -1904,27 +1872,58 @@ const b = {
|
||||
mech.fireCDcycle = mech.cycle + Math.floor(CD * b.fireCD); // cool down
|
||||
const speed = 30 + 6 * Math.random() + 9 * mod.nailInstantFireRate
|
||||
const angle = mech.angle + (Math.random() - 0.5) * (Math.random() - 0.5) * (mech.crouch ? 1.35 : 3.2) / CD
|
||||
const dmg = 0.9
|
||||
b.nail({
|
||||
x: mech.pos.x + 30 * Math.cos(mech.angle),
|
||||
y: mech.pos.y + 30 * Math.sin(mech.angle)
|
||||
}, {
|
||||
x: mech.Vx / 2 + speed * Math.cos(angle),
|
||||
y: mech.Vy / 2 + speed * Math.sin(angle)
|
||||
}, dmg) //position, velocity, damage
|
||||
if (mod.isIceCrystals) {
|
||||
bullet[bullet.length - 1].beforeDmg = function(who) {
|
||||
mobs.statusSlow(who, 30)
|
||||
if (mod.isNailPoison) mobs.statusDoT(who, dmg * 0.22, 120) // one tick every 30 cycles
|
||||
if (mod.isNailCrit && !who.shield && Vector.dot(Vector.normalise(Vector.sub(who.position, this.position)), Vector.normalise(this.velocity)) > 0.99) this.dmg *= 5 //crit if hit near center
|
||||
};
|
||||
if (mod.isIncendiary) {
|
||||
const me = bullet.length;
|
||||
bullet[me] = Bodies.rectangle(mech.pos.x + 50 * Math.cos(mech.angle), mech.pos.y + 50 * Math.sin(mech.angle), 25, 2, {
|
||||
density: 0.0001, //frictionAir: 0.01, //restitution: 0,
|
||||
angle: angle,
|
||||
friction: 0.5,
|
||||
frictionAir: 0,
|
||||
dmg: 0, //damage done in addition to the damage from momentum
|
||||
endCycle: Math.floor(mech.crouch ? 28 : 13) + Math.random() * 5 + game.cycle,
|
||||
classType: "bullet",
|
||||
collisionFilter: {
|
||||
category: cat.bullet,
|
||||
mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield
|
||||
},
|
||||
minDmgSpeed: 10,
|
||||
beforeDmg() {
|
||||
this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion
|
||||
},
|
||||
onEnd() {
|
||||
b.explosion(this.position, 72 + (Math.random() - 0.5) * 30); //makes bullet do explosive damage at end
|
||||
},
|
||||
do() {}
|
||||
});
|
||||
Matter.Body.setVelocity(bullet[me], {
|
||||
x: speed * Math.cos(angle),
|
||||
y: speed * Math.sin(angle)
|
||||
});
|
||||
World.add(engine.world, bullet[me]); //add bullet to world
|
||||
} else {
|
||||
const dmg = 0.9
|
||||
b.nail({
|
||||
x: mech.pos.x + 30 * Math.cos(mech.angle),
|
||||
y: mech.pos.y + 30 * Math.sin(mech.angle)
|
||||
}, {
|
||||
x: mech.Vx / 2 + speed * Math.cos(angle),
|
||||
y: mech.Vy / 2 + speed * Math.sin(angle)
|
||||
}, dmg) //position, velocity, damage
|
||||
if (mod.isIceCrystals) {
|
||||
bullet[bullet.length - 1].beforeDmg = function(who) {
|
||||
mobs.statusSlow(who, 30)
|
||||
if (mod.isNailPoison) mobs.statusDoT(who, dmg * 0.22, 120) // one tick every 30 cycles
|
||||
if (mod.isNailCrit && !who.shield && Vector.dot(Vector.normalise(Vector.sub(who.position, this.position)), Vector.normalise(this.velocity)) > 0.99) this.dmg *= 5 //crit if hit near center
|
||||
};
|
||||
|
||||
if (mech.energy < 0.01) {
|
||||
mech.fireCDcycle = mech.cycle + 60; // cool down
|
||||
} else {
|
||||
mech.energy -= mech.fieldRegen + 0.009
|
||||
if (mech.energy < 0.01) {
|
||||
mech.fireCDcycle = mech.cycle + 60; // cool down
|
||||
} else {
|
||||
mech.energy -= mech.fieldRegen + 0.009
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1958,7 +1957,57 @@ const b = {
|
||||
}
|
||||
|
||||
b.muzzleFlash(35);
|
||||
if (mod.isNailShot) {
|
||||
if (mod.isIncendiary) {
|
||||
const SPEED = mech.crouch ? 35 : 25
|
||||
const END = Math.floor(mech.crouch ? 9 : 6);
|
||||
const totalBullets = 8
|
||||
const angleStep = (mech.crouch ? 0.1 : 0.33) / totalBullets
|
||||
let dir = mech.angle - angleStep * totalBullets / 2;
|
||||
for (let i = 0; i < totalBullets; i++) { //5 -> 7
|
||||
dir += angleStep
|
||||
const me = bullet.length;
|
||||
bullet[me] = Bodies.rectangle(mech.pos.x + 50 * Math.cos(mech.angle), mech.pos.y + 50 * Math.sin(mech.angle), 17, 4, b.fireAttributes(dir));
|
||||
const end = END + Math.random() * 3
|
||||
bullet[me].endCycle = 2 * end + game.cycle
|
||||
const speed = SPEED * end / END
|
||||
const dirOff = dir + 0.15 * (Math.random() - 0.5)
|
||||
Matter.Body.setVelocity(bullet[me], {
|
||||
x: speed * Math.cos(dirOff),
|
||||
y: speed * Math.sin(dirOff)
|
||||
});
|
||||
bullet[me].onEnd = function() {
|
||||
b.explosion(this.position, 60 + (Math.random() - 0.5) * 40); //makes bullet do explosive damage at end
|
||||
}
|
||||
bullet[me].beforeDmg = function() {
|
||||
this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion
|
||||
};
|
||||
bullet[me].do = function() {}
|
||||
World.add(engine.world, bullet[me]); //add bullet to world
|
||||
}
|
||||
// for (let i = 0; i < totalBullets; i++) { //5 -> 7
|
||||
// dir += angleStep
|
||||
// const me = bullet.length;
|
||||
// bullet[me] = Bodies.rectangle(mech.pos.x + 50 * Math.cos(mech.angle), mech.pos.y + 50 * Math.sin(mech.angle), 17, 4, b.fireAttributes(dir));
|
||||
// World.add(engine.world, bullet[me]); //add bullet to world
|
||||
// Matter.Body.setVelocity(bullet[me], {
|
||||
// x: (SPEED + 15 * Math.random() - 2 * i) * Math.cos(dir),
|
||||
// y: (SPEED + 15 * Math.random() - 2 * i) * Math.sin(dir)
|
||||
// });
|
||||
// bullet[me].endCycle = 2 * i + END
|
||||
// bullet[me].restitution = 0;
|
||||
// bullet[me].friction = 1;
|
||||
// bullet[me].onEnd = function() {
|
||||
// b.explosion(this.position, (mech.crouch ? 95 : 75) + (Math.random() - 0.5) * 50); //makes bullet do explosive damage at end
|
||||
// }
|
||||
// bullet[me].beforeDmg = function() {
|
||||
// this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion
|
||||
// };
|
||||
// bullet[me].do = function() {
|
||||
// // this.force.y += this.mass * 0.0004;
|
||||
// }
|
||||
// }
|
||||
|
||||
} else if (mod.isNailShot) {
|
||||
for (let i = 0; i < 14; i++) {
|
||||
const dir = mech.angle + (Math.random() - 0.5) * spread * 0.2
|
||||
const pos = {
|
||||
@@ -2349,48 +2398,48 @@ const b = {
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "flak",
|
||||
description: "fire a <strong>cluster</strong> of short range <strong>projectiles</strong><br><strong class='color-e'>explodes</strong> on <strong>contact</strong> or after half a second",
|
||||
ammo: 0,
|
||||
ammoPack: 4,
|
||||
defaultAmmoPack: 4, //use to revert ammoPack after mod changes drop rate
|
||||
have: false,
|
||||
fire() {
|
||||
mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 25 : 10) * b.fireCD); // cool down
|
||||
b.muzzleFlash(30);
|
||||
const SPEED = mech.crouch ? 29 : 25
|
||||
const END = Math.floor(mech.crouch ? 30 : 18);
|
||||
const side1 = 17
|
||||
const side2 = 4
|
||||
const totalBullets = 6
|
||||
const angleStep = (mech.crouch ? 0.06 : 0.25) / totalBullets
|
||||
let dir = mech.angle - angleStep * totalBullets / 2;
|
||||
for (let i = 0; i < totalBullets; i++) { //5 -> 7
|
||||
dir += angleStep
|
||||
const me = bullet.length;
|
||||
bullet[me] = Bodies.rectangle(mech.pos.x + 50 * Math.cos(mech.angle), mech.pos.y + 50 * Math.sin(mech.angle), side1, side2, b.fireAttributes(dir));
|
||||
World.add(engine.world, bullet[me]); //add bullet to world
|
||||
Matter.Body.setVelocity(bullet[me], {
|
||||
x: (SPEED + 15 * Math.random() - 2 * i) * Math.cos(dir),
|
||||
y: (SPEED + 15 * Math.random() - 2 * i) * Math.sin(dir)
|
||||
});
|
||||
bullet[me].endCycle = 2 * i + game.cycle + END
|
||||
bullet[me].restitution = 0;
|
||||
bullet[me].friction = 1;
|
||||
bullet[me].explodeRad = (mech.crouch ? 95 : 75) + (Math.random() - 0.5) * 50;
|
||||
bullet[me].onEnd = function() {
|
||||
b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end
|
||||
}
|
||||
bullet[me].beforeDmg = function() {
|
||||
this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion
|
||||
};
|
||||
bullet[me].do = function() {
|
||||
// this.force.y += this.mass * 0.0004;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// {
|
||||
// name: "flak",
|
||||
// description: "fire a <strong>cluster</strong> of short range <strong>projectiles</strong><br><strong class='color-e'>explodes</strong> on <strong>contact</strong> or after half a second",
|
||||
// ammo: 0,
|
||||
// ammoPack: 4,
|
||||
// defaultAmmoPack: 4, //use to revert ammoPack after mod changes drop rate
|
||||
// have: false,
|
||||
// fire() {
|
||||
// mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 25 : 10) * b.fireCD); // cool down
|
||||
// b.muzzleFlash(30);
|
||||
// const SPEED = mech.crouch ? 29 : 25
|
||||
// const END = Math.floor(mech.crouch ? 30 : 18);
|
||||
// const side1 = 17
|
||||
// const side2 = 4
|
||||
// const totalBullets = 6
|
||||
// const angleStep = (mech.crouch ? 0.06 : 0.25) / totalBullets
|
||||
// let dir = mech.angle - angleStep * totalBullets / 2;
|
||||
// for (let i = 0; i < totalBullets; i++) { //5 -> 7
|
||||
// dir += angleStep
|
||||
// const me = bullet.length;
|
||||
// bullet[me] = Bodies.rectangle(mech.pos.x + 50 * Math.cos(mech.angle), mech.pos.y + 50 * Math.sin(mech.angle), side1, side2, b.fireAttributes(dir));
|
||||
// World.add(engine.world, bullet[me]); //add bullet to world
|
||||
// Matter.Body.setVelocity(bullet[me], {
|
||||
// x: (SPEED + 15 * Math.random() - 2 * i) * Math.cos(dir),
|
||||
// y: (SPEED + 15 * Math.random() - 2 * i) * Math.sin(dir)
|
||||
// });
|
||||
// bullet[me].endCycle = 2 * i + game.cycle + END
|
||||
// bullet[me].restitution = 0;
|
||||
// bullet[me].friction = 1;
|
||||
// bullet[me].explodeRad = (mech.crouch ? 95 : 75) + (Math.random() - 0.5) * 50;
|
||||
// bullet[me].onEnd = function() {
|
||||
// b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end
|
||||
// }
|
||||
// bullet[me].beforeDmg = function() {
|
||||
// this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion
|
||||
// };
|
||||
// bullet[me].do = function() {
|
||||
// // this.force.y += this.mass * 0.0004;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
{
|
||||
name: "grenades",
|
||||
description: "lob a single <strong>bouncy</strong> projectile<br><strong class='color-e'>explodes</strong> on <strong>contact</strong> or after one second",
|
||||
|
||||
95
js/engine.js
95
js/engine.js
@@ -24,8 +24,29 @@ function playerOnGroundCheck(event) {
|
||||
//runs on collisions events
|
||||
function enter() {
|
||||
mech.numTouching++;
|
||||
if (!mech.onGround) mech.enterLand();
|
||||
if (!mech.onGround) {
|
||||
mech.onGround = true;
|
||||
if (mech.crouch) {
|
||||
if (mech.checkHeadClear()) {
|
||||
mech.undoCrouch();
|
||||
} else {
|
||||
mech.yOffGoal = mech.yOffWhen.crouch;
|
||||
}
|
||||
} else {
|
||||
//sets a hard land where player stays in a crouch for a bit and can't jump
|
||||
//crouch is forced in groundControl below
|
||||
const momentum = player.velocity.y * player.mass //player mass is 5 so this triggers at 26 down velocity, unless the player is holding something
|
||||
if (momentum > 130) {
|
||||
mech.doCrouch();
|
||||
mech.yOff = mech.yOffWhen.jump;
|
||||
mech.hardLandCD = mech.cycle + Math.min(momentum / 6.5 - 6, 40)
|
||||
} else {
|
||||
mech.yOffGoal = mech.yOffWhen.stand;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const pairs = event.pairs;
|
||||
for (let i = 0, j = pairs.length; i != j; ++i) {
|
||||
let pair = pairs[i];
|
||||
@@ -42,76 +63,26 @@ function playerOnGroundCheck(event) {
|
||||
|
||||
function playerOffGroundCheck(event) {
|
||||
//runs on collisions events
|
||||
function enter() {
|
||||
if (mech.onGround && mech.numTouching === 0) mech.enterAir();
|
||||
}
|
||||
const pairs = event.pairs;
|
||||
for (let i = 0, j = pairs.length; i != j; ++i) {
|
||||
if (pairs[i].bodyA === jumpSensor) {
|
||||
enter();
|
||||
} else if (pairs[i].bodyB === jumpSensor) {
|
||||
enter();
|
||||
if (pairs[i].bodyA === jumpSensor || pairs[i].bodyB === jumpSensor) {
|
||||
if (mech.onGround && mech.numTouching === 0) {
|
||||
mech.onGround = false;
|
||||
mech.hardLandCD = 0 // disable hard landing
|
||||
if (mech.checkHeadClear()) {
|
||||
if (mech.crouch) {
|
||||
mech.undoCrouch();
|
||||
}
|
||||
mech.yOffGoal = mech.yOffWhen.jump;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// function playerHeadCheck(event) {
|
||||
// //runs on collisions events
|
||||
// if (mech.crouch) {
|
||||
// mech.isHeadClear = true;
|
||||
// const pairs = event.pairs;
|
||||
// for (let i = 0, j = pairs.length; i != j; ++i) {
|
||||
// if (pairs[i].bodyA === headSensor) {
|
||||
// mech.isHeadClear = false;
|
||||
// } else if (pairs[i].bodyB === headSensor) {
|
||||
// mech.isHeadClear = false;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
function collisionChecks(event) {
|
||||
const pairs = event.pairs;
|
||||
for (let i = 0, j = pairs.length; i != j; i++) {
|
||||
|
||||
// //map + bullet collisions
|
||||
// if (pairs[i].bodyA.collisionFilter.category === cat.map && pairs[i].bodyB.collisionFilter.category === cat.bullet) {
|
||||
// collideBulletStatic(pairs[i].bodyB)
|
||||
// } else if (pairs[i].bodyB.collisionFilter.category === cat.map && pairs[i].bodyA.collisionFilter.category === cat.bullet) {
|
||||
// collideBulletStatic(pairs[i].bodyA)
|
||||
// }
|
||||
// //triggers when the bullets hits something static
|
||||
// function collideBulletStatic(obj, speedThreshold = 12, massThreshold = 2) {
|
||||
// if (obj.onWallHit) obj.onWallHit();
|
||||
// }
|
||||
|
||||
// function collidePlayer(obj) {
|
||||
// //player dmg from hitting a body
|
||||
// if (obj.classType === "body" && obj.speed > 10 && mech.immuneCycle < mech.cycle) {
|
||||
// const velocityThreshold = 30 //keep this lines up with player.enterLand numbers (130/5 = 26)
|
||||
// if (player.position.y > obj.position.y) { //block is above the player look at total momentum difference
|
||||
// const velocityDiffMag = Vector.magnitude(Vector.sub(player.velocity, obj.velocity))
|
||||
// if (velocityDiffMag > velocityThreshold) hit(velocityDiffMag - velocityThreshold)
|
||||
// } else { //block is below player only look at horizontal momentum difference
|
||||
// const velocityDiffMagX = Math.abs(obj.velocity.x - player.velocity.x)
|
||||
// if (velocityDiffMagX > velocityThreshold) hit(velocityDiffMagX - velocityThreshold)
|
||||
// }
|
||||
|
||||
// function hit(dmg) {
|
||||
// mech.immuneCycle = mech.cycle + mod.collisionImmuneCycles; //player is immune to collision damage for 30 cycles
|
||||
// dmg = Math.min(Math.max(Math.sqrt(dmg) * obj.mass * 0.01, 0.02), 0.15);
|
||||
// mech.damage(dmg);
|
||||
// game.drawList.push({ //add dmg to draw queue
|
||||
// x: pairs[i].activeContacts[0].vertex.x,
|
||||
// y: pairs[i].activeContacts[0].vertex.y,
|
||||
// radius: dmg * 500,
|
||||
// color: game.mobDmgColor,
|
||||
// time: game.drawTime
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
//mob + (player,bullet,body) collisions
|
||||
for (let k = 0; k < mob.length; k++) {
|
||||
if (mob[k].alive && mech.alive) {
|
||||
|
||||
@@ -987,8 +987,8 @@ document.getElementById("updates").addEventListener("toggle", function() {
|
||||
loadJSON('https://api.github.com/repos/landgreen/n-gon/commits',
|
||||
function(data) {
|
||||
// console.log(data)
|
||||
for (let i = 0, len = 4; i < len; i++) {
|
||||
text += data[i].commit.author.date.substr(0, 10) + ": "; //+ "<br>"
|
||||
for (let i = 0, len = 3; i < len; i++) {
|
||||
text += "<strong>" + data[i].commit.author.date.substr(0, 10) + "</strong> - "; //+ "<br>"
|
||||
text += data[i].commit.message
|
||||
if (i < len - 1) text += "<hr>"
|
||||
}
|
||||
|
||||
17
js/level.js
17
js/level.js
@@ -12,20 +12,19 @@ const level = {
|
||||
levels: [],
|
||||
start() {
|
||||
if (level.levelsCleared === 0) { //this code only runs on the first level
|
||||
// level.difficultyIncrease(8)
|
||||
level.difficultyIncrease(8)
|
||||
// game.enableConstructMode() //used to build maps in testing mode
|
||||
// game.zoomScale = 1000;
|
||||
// game.setZoom();
|
||||
// mech.setField("wormhole")
|
||||
// b.giveGuns("mine")
|
||||
// mod.giveMod("sentry")
|
||||
// b.giveGuns("drones")
|
||||
// mod.giveMod("incendiary ammunition")
|
||||
|
||||
|
||||
level.intro(); //starting level
|
||||
// level.intro(); //starting level
|
||||
level.testing(); //not in rotation
|
||||
// level.finalBoss() //final boss level
|
||||
// level.gauntlet(); //before final boss level
|
||||
// level.testing(); //not in rotation
|
||||
// level.template() //not in rotation
|
||||
// level.testChamber() //less mobs, more puzzle
|
||||
// level.sewers();
|
||||
// level.satellite();
|
||||
@@ -141,16 +140,16 @@ const level = {
|
||||
spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 100); //exit bump
|
||||
// spawn.boost(1500, 0, 900);
|
||||
|
||||
// spawn.starter(1600, -500)
|
||||
spawn.starter(1600, -500, 200)
|
||||
// spawn.bomberBoss(2900, -500)
|
||||
// spawn.launcherBoss(1200, -500)
|
||||
// spawn.laserTargetingBoss(1600, -400)
|
||||
// spawn.spawner(1600, -500)
|
||||
// spawn.sniper(1700, -120, 50)
|
||||
// spawn.bomberBoss(1400, -500)
|
||||
spawn.sucker(1800, -120)
|
||||
// spawn.sucker(1800, -120)
|
||||
// spawn.cellBossCulture(1600, -500)
|
||||
// spawn.powerUpBoss(1600, -500)
|
||||
// spawn.spiderBoss(1600, -500)
|
||||
// spawn.sniper(1200, -500)
|
||||
// spawn.shield(mob[mob.length - 1], 1800, -120, 1);
|
||||
|
||||
|
||||
71
js/mods.js
71
js/mods.js
@@ -197,7 +197,7 @@ const mod = {
|
||||
},
|
||||
{
|
||||
name: "fluoroantimonic acid",
|
||||
description: "increase <strong class='color-d'>damage</strong> by <strong>40%</strong><br>when your base <strong>health</strong> is above <strong>100%</strong>",
|
||||
description: "increase <strong class='color-d'>damage</strong> by <strong>40%</strong><br>when your <strong>health</strong> is above <strong>100%</strong>",
|
||||
maxCount: 1,
|
||||
count: 0,
|
||||
allowed() {
|
||||
@@ -408,7 +408,7 @@ const mod = {
|
||||
maxCount: 9,
|
||||
count: 0,
|
||||
allowed() {
|
||||
return mod.haveGunCheck("missiles") || mod.haveGunCheck("flak") || mod.haveGunCheck("grenades") || mod.haveGunCheck("vacuum bomb") || mod.isPulseLaser || mod.isMissileField || mod.boomBotCount > 1 || mod.isFlechetteExplode
|
||||
return mod.haveGunCheck("missiles") || mod.isIncendiary || mod.haveGunCheck("grenades") || mod.haveGunCheck("vacuum bomb") || mod.isPulseLaser || mod.isMissileField || mod.boomBotCount > 1 || mod.isFlechetteExplode
|
||||
},
|
||||
requires: "an explosive damage source",
|
||||
effect: () => {
|
||||
@@ -424,7 +424,7 @@ const mod = {
|
||||
maxCount: 1,
|
||||
count: 0,
|
||||
allowed() {
|
||||
return mod.haveGunCheck("missiles") || mod.haveGunCheck("flak") || mod.haveGunCheck("grenades") || mod.haveGunCheck("vacuum bomb") || mod.isPulseLaser || mod.isMissileField || mod.boomBotCount > 1 || mod.isFlechetteExplode
|
||||
return mod.haveGunCheck("missiles") || mod.isIncendiary || mod.haveGunCheck("grenades") || mod.haveGunCheck("vacuum bomb") || mod.isPulseLaser || mod.isMissileField || mod.boomBotCount > 1 || mod.isFlechetteExplode
|
||||
},
|
||||
requires: "an explosive damage source",
|
||||
effect: () => {
|
||||
@@ -440,7 +440,7 @@ const mod = {
|
||||
maxCount: 1,
|
||||
count: 0,
|
||||
allowed() {
|
||||
return mod.haveGunCheck("missiles") || mod.haveGunCheck("flak") || mod.haveGunCheck("grenades") || mod.haveGunCheck("vacuum bomb") || mod.isPulseLaser || mod.isMissileField || mod.isFlechetteExplode
|
||||
return mod.haveGunCheck("missiles") || mod.isIncendiary || mod.haveGunCheck("grenades") || mod.haveGunCheck("vacuum bomb") || mod.isPulseLaser || mod.isMissileField || mod.isFlechetteExplode
|
||||
},
|
||||
requires: "an explosive damage source",
|
||||
effect: () => {
|
||||
@@ -457,7 +457,7 @@ const mod = {
|
||||
maxCount: 1,
|
||||
count: 0,
|
||||
allowed() {
|
||||
return mod.haveGunCheck("missiles") || mod.haveGunCheck("flak") || mod.haveGunCheck("grenades") || mod.haveGunCheck("vacuum bomb") || mod.isMissileField || mod.isExplodeMob || mod.isFlechetteExplode || mod.isPulseLaser
|
||||
return mod.haveGunCheck("missiles") || mod.isIncendiary || mod.haveGunCheck("grenades") || mod.haveGunCheck("vacuum bomb") || mod.isMissileField || mod.isExplodeMob || mod.isFlechetteExplode || mod.isPulseLaser
|
||||
},
|
||||
requires: "an explosive damage source",
|
||||
effect: () => {
|
||||
@@ -469,7 +469,7 @@ const mod = {
|
||||
},
|
||||
{
|
||||
name: "scrap bots",
|
||||
description: "<strong>20%</strong> chance to build a <strong>bot</strong> after killing a mob<br>the bot last for about <strong>20</strong> seconds",
|
||||
description: "<strong>20%</strong> chance to build a <strong>bot</strong> after killing a mob<br>the bot lasts for about <strong>20</strong> seconds",
|
||||
maxCount: 3,
|
||||
count: 0,
|
||||
allowed() {
|
||||
@@ -1574,9 +1574,25 @@ const mod = {
|
||||
//************************************************** gun
|
||||
//************************************************** mods
|
||||
//**************************************************
|
||||
{
|
||||
name: "incendiary ammunition",
|
||||
description: "your <strong>bullets</strong> <strong class='color-e'>explode</strong> after a short time<br>(nail gun, shotgun, super balls, drones)",
|
||||
maxCount: 1,
|
||||
count: 0,
|
||||
allowed() {
|
||||
return mod.haveGunCheck("drones") || mod.haveGunCheck("super balls") || (mod.haveGunCheck("nail gun") && !mod.isIceCrystals && !mod.isNailCrit) || (mod.haveGunCheck("shotgun") && !mod.isNailShot)
|
||||
},
|
||||
requires: "drones, super balls, nail gun, shotgun",
|
||||
effect() {
|
||||
mod.isIncendiary = true
|
||||
},
|
||||
remove() {
|
||||
mod.isIncendiary = false;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Lorentzian topology",
|
||||
description: "your <strong>bullets</strong> last <strong>33% longer</strong>",
|
||||
description: "your <strong>bullets</strong> last <strong>33% longer</strong><br><span style = 'font-size: 85%'>drones, spores, super balls, foam, wave, ice IX, neutron</span>",
|
||||
maxCount: 3,
|
||||
count: 0,
|
||||
allowed() {
|
||||
@@ -1612,9 +1628,9 @@ const mod = {
|
||||
maxCount: 1,
|
||||
count: 0,
|
||||
allowed() {
|
||||
return mod.haveGunCheck("nail gun") && !mod.nailInstantFireRate
|
||||
return mod.haveGunCheck("nail gun") && !mod.nailInstantFireRate && !mod.isIncendiary
|
||||
},
|
||||
requires: "nail gun",
|
||||
requires: "nail gun, not incendiary, not powder-actuated",
|
||||
effect() {
|
||||
mod.isIceCrystals = true;
|
||||
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
|
||||
@@ -1645,9 +1661,9 @@ const mod = {
|
||||
maxCount: 1,
|
||||
count: 0,
|
||||
allowed() {
|
||||
return mod.haveGunCheck("nail gun")
|
||||
return mod.haveGunCheck("nail gun") && !mod.isIncendiary
|
||||
},
|
||||
requires: "nail gun",
|
||||
requires: "nail gun, not incendiary",
|
||||
effect() {
|
||||
mod.isNailCrit = true
|
||||
},
|
||||
@@ -1731,7 +1747,7 @@ const mod = {
|
||||
maxCount: 1,
|
||||
count: 0,
|
||||
allowed() {
|
||||
return mod.haveGunCheck("shotgun")
|
||||
return mod.haveGunCheck("shotgun") && !mod.isIncendiary
|
||||
},
|
||||
requires: "shotgun",
|
||||
effect() {
|
||||
@@ -1765,7 +1781,7 @@ const mod = {
|
||||
allowed() {
|
||||
return mod.haveGunCheck("super balls") && !mod.oneSuperBall
|
||||
},
|
||||
requires: "super balls",
|
||||
requires: "super balls, but not the mod super ball",
|
||||
effect() {
|
||||
mod.superBallNumber += 2
|
||||
},
|
||||
@@ -1781,7 +1797,7 @@ const mod = {
|
||||
allowed() {
|
||||
return mod.haveGunCheck("super balls") && mod.superBallNumber === 4
|
||||
},
|
||||
requires: "super balls",
|
||||
requires: "super balls, but not super duper",
|
||||
effect() {
|
||||
mod.oneSuperBall = true;
|
||||
},
|
||||
@@ -1999,26 +2015,6 @@ const mod = {
|
||||
mod.is3Missiles = false;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "optimized shell packing",
|
||||
description: "<strong>flak</strong> <strong class='color-g'>ammo</strong> drops contain <strong>2x</strong> more shells",
|
||||
maxCount: 3,
|
||||
count: 0,
|
||||
allowed() {
|
||||
return mod.haveGunCheck("flak")
|
||||
},
|
||||
requires: "flak",
|
||||
effect() {
|
||||
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
|
||||
if (b.guns[i].name === "flak") b.guns[i].ammoPack = b.guns[i].defaultAmmoPack * (2 * (1 + this.count));
|
||||
}
|
||||
},
|
||||
remove() {
|
||||
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
|
||||
if (b.guns[i].name === "flak") b.guns[i].ammoPack = b.guns[i].defaultAmmoPack;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "fragmentation grenade",
|
||||
description: "<strong>grenades</strong> are loaded with <strong>5</strong> nails<br>on detonation <strong>nails</strong> are ejected towards mobs",
|
||||
@@ -2143,7 +2139,7 @@ const mod = {
|
||||
maxCount: 1,
|
||||
count: 0,
|
||||
allowed() {
|
||||
return mod.nailBotCount + mod.grenadeFragments + mod.nailsDeathMob / 2 + (mod.haveGunCheck("mine") + mod.isRailNails + mod.isNailShot + mod.haveGunCheck("nail gun")) * 2 > 1
|
||||
return mod.nailBotCount + mod.grenadeFragments + mod.nailsDeathMob / 2 + (mod.haveGunCheck("mine") + mod.isRailNails + mod.isNailShot + (mod.haveGunCheck("nail gun") && !mod.isIncendiary)) * 2 > 1
|
||||
},
|
||||
requires: "nails",
|
||||
effect() {
|
||||
@@ -2159,7 +2155,7 @@ const mod = {
|
||||
maxCount: 1,
|
||||
count: 0,
|
||||
allowed() {
|
||||
return mod.nailBotCount + mod.grenadeFragments + mod.nailsDeathMob / 2 + (mod.haveGunCheck("mine") + mod.isRailNails + mod.isNailShot + mod.haveGunCheck("nail gun")) * 2 > 1
|
||||
return mod.nailBotCount + mod.grenadeFragments + mod.nailsDeathMob / 2 + (mod.haveGunCheck("mine") + mod.isRailNails + mod.isNailShot + (mod.haveGunCheck("nail gun") && !mod.isIncendiary)) * 2 > 1
|
||||
},
|
||||
requires: "nails",
|
||||
effect() {
|
||||
@@ -3259,5 +3255,6 @@ const mod = {
|
||||
timeEnergyRegen: null,
|
||||
isRadioactive: null,
|
||||
isRailEnergyGain: null,
|
||||
isMineSentry: null
|
||||
isMineSentry: null,
|
||||
isIncendiary: null
|
||||
}
|
||||
35
js/player.js
35
js/player.js
@@ -186,39 +186,6 @@ const mech = {
|
||||
return true
|
||||
}
|
||||
},
|
||||
enterAir() {
|
||||
//triggered in engine.js on collision
|
||||
mech.onGround = false;
|
||||
mech.hardLandCD = 0 // disable hard landing
|
||||
if (mech.checkHeadClear()) {
|
||||
if (mech.crouch) {
|
||||
mech.undoCrouch();
|
||||
}
|
||||
mech.yOffGoal = mech.yOffWhen.jump;
|
||||
}
|
||||
},
|
||||
//triggered in engine.js on collision
|
||||
enterLand() {
|
||||
mech.onGround = true;
|
||||
if (mech.crouch) {
|
||||
if (mech.checkHeadClear()) {
|
||||
mech.undoCrouch();
|
||||
} else {
|
||||
mech.yOffGoal = mech.yOffWhen.crouch;
|
||||
}
|
||||
} else {
|
||||
//sets a hard land where player stays in a crouch for a bit and can't jump
|
||||
//crouch is forced in groundControl below
|
||||
const momentum = player.velocity.y * player.mass //player mass is 5 so this triggers at 26 down velocity, unless the player is holding something
|
||||
if (momentum > 130) {
|
||||
mech.doCrouch();
|
||||
mech.yOff = mech.yOffWhen.jump;
|
||||
mech.hardLandCD = mech.cycle + Math.min(momentum / 6.5 - 6, 40)
|
||||
} else {
|
||||
mech.yOffGoal = mech.yOffWhen.stand;
|
||||
}
|
||||
}
|
||||
},
|
||||
buttonCD_jump: 0, //cool down for player buttons
|
||||
groundControl() {
|
||||
//check for crouch or jump
|
||||
@@ -516,7 +483,6 @@ const mech = {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
return;
|
||||
} else { //death
|
||||
mech.health = 0;
|
||||
@@ -2410,6 +2376,7 @@ const mech = {
|
||||
mech.hole.isReady = false;
|
||||
mech.fieldRange = 0
|
||||
Matter.Body.setPosition(player, game.mouseInGame);
|
||||
mech.buttonCD_jump = 0 //this might fix a bug with jumping
|
||||
const velocity = Vector.mult(Vector.normalise(sub), 18)
|
||||
Matter.Body.setVelocity(player, {
|
||||
x: velocity.x,
|
||||
|
||||
1152
js/powerup.js
1152
js/powerup.js
File diff suppressed because it is too large
Load Diff
34
js/spawn.js
34
js/spawn.js
@@ -159,7 +159,7 @@ const spawn = {
|
||||
}
|
||||
this.mode = 3
|
||||
this.fill = "#000";
|
||||
this.eventHorizon = 700
|
||||
this.eventHorizon = 750
|
||||
this.spawnInterval = 600
|
||||
this.rotateVelocity = 0.001 * (player.position.x > this.position.x ? 1 : -1) //rotate so that the player can get away
|
||||
if (!this.isShielded) spawn.shield(this, x, y, 1); //regen shield here ?
|
||||
@@ -200,7 +200,7 @@ const spawn = {
|
||||
me.eventHorizonCycleRate = 4 * Math.PI / me.endCycle
|
||||
me.modeSuck = function() {
|
||||
//eventHorizon waves in and out
|
||||
if (!mech.isBodiesAsleep) eventHorizon = this.eventHorizon * (1 - 0.25 * Math.cos(this.cycle * this.eventHorizonCycleRate)) //0.014
|
||||
const eventHorizon = this.eventHorizon * (1 - 0.25 * Math.cos(game.cycle * this.eventHorizonCycleRate)) //0.014
|
||||
//draw darkness
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.position.x, this.position.y, eventHorizon * 0.2, 0, 2 * Math.PI);
|
||||
@@ -763,7 +763,7 @@ const spawn = {
|
||||
this.checkStatus();
|
||||
if (this.seePlayer.recall) {
|
||||
//eventHorizon waves in and out
|
||||
eventHorizon = this.eventHorizon * (0.93 + 0.17 * Math.sin(game.cycle * 0.011))
|
||||
const eventHorizon = this.eventHorizon * (0.93 + 0.17 * Math.sin(game.cycle * 0.011))
|
||||
|
||||
//accelerate towards the player
|
||||
const forceMag = this.accelMag * this.mass;
|
||||
@@ -860,7 +860,7 @@ const spawn = {
|
||||
this.force.y += forceMag * dy / mag;
|
||||
|
||||
//eventHorizon waves in and out
|
||||
eventHorizon = this.eventHorizon * (1 + 0.2 * Math.sin(game.cycle * 0.008))
|
||||
const eventHorizon = this.eventHorizon * (1 + 0.2 * Math.sin(game.cycle * 0.008))
|
||||
// zoom camera in and out with the event horizon
|
||||
|
||||
//draw darkness
|
||||
@@ -910,7 +910,6 @@ const spawn = {
|
||||
}
|
||||
},
|
||||
spiderBoss(x, y, radius = 60 + Math.ceil(Math.random() * 10)) {
|
||||
const isDaddyLongLegs = Math.random() < 0.25
|
||||
let targets = [] //track who is in the node boss, for shields
|
||||
mobs.spawn(x, y, 6, radius, "#b386e8");
|
||||
let me = mob[mob.length - 1];
|
||||
@@ -919,10 +918,10 @@ const spawn = {
|
||||
me.friction = 0;
|
||||
me.frictionAir = 0.0065;
|
||||
me.lookTorque = 0.0000008; //controls spin while looking for player
|
||||
me.g = 0.00025; //required if using 'gravity'
|
||||
me.g = 0.0002; //required if using 'gravity'
|
||||
me.seePlayerFreq = Math.round((30 + 20 * Math.random()) * game.lookFreqScale);
|
||||
const springStiffness = isDaddyLongLegs ? 0.0001 : 0.000065;
|
||||
const springDampening = isDaddyLongLegs ? 0 : 0.0006;
|
||||
const springStiffness = 0.00014;
|
||||
const springDampening = 0.0005;
|
||||
|
||||
me.springTarget = {
|
||||
x: me.position.x,
|
||||
@@ -935,8 +934,8 @@ const spawn = {
|
||||
stiffness: springStiffness,
|
||||
damping: springDampening
|
||||
});
|
||||
World.add(engine.world, cons[cons.length - 1]);
|
||||
cons[len].length = 100 + 1.5 * radius;
|
||||
|
||||
me.cons = cons[len];
|
||||
|
||||
me.springTarget2 = {
|
||||
@@ -948,11 +947,12 @@ const spawn = {
|
||||
pointA: me.springTarget2,
|
||||
bodyB: me,
|
||||
stiffness: springStiffness,
|
||||
damping: springDampening
|
||||
damping: springDampening,
|
||||
length: 0
|
||||
});
|
||||
World.add(engine.world, cons[cons.length - 1]);
|
||||
cons[len2].length = 100 + 1.5 * radius;
|
||||
me.cons2 = cons[len2];
|
||||
if (isDaddyLongLegs) Matter.Body.setDensity(me, 0.017); //extra dense //normal is 0.001 //makes effective life much larger
|
||||
|
||||
me.onDeath = function() {
|
||||
this.removeCons();
|
||||
@@ -974,15 +974,12 @@ const spawn = {
|
||||
|
||||
for (let i = 0; i < nodes; ++i) {
|
||||
spawn.stabber(x + sideLength * Math.sin(i * angle), y + sideLength * Math.cos(i * angle), radius, 12);
|
||||
if (isDaddyLongLegs) Matter.Body.setDensity(mob[mob.length - 1], 0.01); //extra dense //normal is 0.001 //makes effective life much larger
|
||||
targets.push(mob[mob.length - 1].id) //track who is in the node boss, for shields
|
||||
}
|
||||
//spawn shield around all nodes
|
||||
if (!isDaddyLongLegs) spawn.bossShield(targets, x, y, sideLength + 1 * radius + nodes * 5 - 25);
|
||||
spawn.allowShields = true;
|
||||
|
||||
const attachmentStiffness = isDaddyLongLegs ? 0.0003 : 0.05
|
||||
if (!isDaddyLongLegs) spawn.constrain2AdjacentMobs(nodes + 2, attachmentStiffness, true); //loop mobs together
|
||||
const attachmentStiffness = 0.05
|
||||
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,
|
||||
@@ -992,6 +989,9 @@ const spawn = {
|
||||
});
|
||||
World.add(engine.world, consBB[consBB.length - 1]);
|
||||
}
|
||||
//spawn shield around all nodes
|
||||
spawn.bossShield(targets, x, y, sideLength + 1 * radius + nodes * 5 - 25);
|
||||
spawn.allowShields = true;
|
||||
},
|
||||
timeSkipBoss(x, y, radius = 55) {
|
||||
mobs.spawn(x, y, 6, radius, '#000');
|
||||
|
||||
Reference in New Issue
Block a user