new level sewers

This commit is contained in:
landgreen
2020-07-11 13:51:09 -07:00
parent 31c16b1c71
commit 4af6e26975
8 changed files with 274 additions and 100 deletions

View File

@@ -1965,8 +1965,8 @@ const b = {
name: "flak", 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", 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, ammo: 0,
ammoPack: 6, ammoPack: 8,
defaultAmmoPack: 6, //use to revert ammoPack after mod changes drop rate defaultAmmoPack: 8, //use to revert ammoPack after mod changes drop rate
have: false, have: false,
isEasyToAim: false, isEasyToAim: false,
fire() { fire() {
@@ -2264,10 +2264,16 @@ const b = {
} else { } else {
const bodyCollisions = Matter.Query.collides(this, body) const bodyCollisions = Matter.Query.collides(this, body)
if (bodyCollisions.length) { if (bodyCollisions.length) {
onCollide(this)
this.stuckTo = bodyCollisions[0].bodyA
//find the relative position for when the mob is at angle zero by undoing the mobs rotation if (!bodyCollisions[0].bodyA.isNotSticky) {
this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle) onCollide(this)
this.stuckTo = bodyCollisions[0].bodyA
//find the relative position for when the mob is at angle zero by undoing the mobs rotation
this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle)
} else {
this.do = this.radiationMode;
}
this.stuck = function () { this.stuck = function () {
if (this.stuckTo) { if (this.stuckTo) {
const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector
@@ -2419,10 +2425,14 @@ const b = {
} else { } else {
const bodyCollisions = Matter.Query.collides(this, body) const bodyCollisions = Matter.Query.collides(this, body)
if (bodyCollisions.length) { if (bodyCollisions.length) {
onCollide(this) if (!bodyCollisions[0].bodyA.isNotSticky) {
this.stuckTo = bodyCollisions[0].bodyA onCollide(this)
//find the relative position for when the mob is at angle zero by undoing the mobs rotation this.stuckTo = bodyCollisions[0].bodyA
this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle) //find the relative position for when the mob is at angle zero by undoing the mobs rotation
this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle)
} else {
this.do = this.grow;
}
this.stuck = function () { this.stuck = function () {
if (this.stuckTo) { if (this.stuckTo) {
const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector

View File

@@ -606,23 +606,6 @@ const game = {
if (game.firstRun) { if (game.firstRun) {
mech.spawn(); //spawns the player mech.spawn(); //spawns the player
mod.setupAllMods(); //doesn't run on reset so that gun mods carry over to new runs mod.setupAllMods(); //doesn't run on reset so that gun mods carry over to new runs
function shuffle(array) {
var currentIndex = array.length,
temporaryValue,
randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
if (game.isCommunityMaps) level.levels.push("stronghold"); if (game.isCommunityMaps) level.levels.push("stronghold");
level.levels = shuffle(level.levels); //shuffles order of maps level.levels = shuffle(level.levels); //shuffles order of maps
level.levels.unshift("bosses"); //add bosses level to the end of the randomized levels list level.levels.unshift("bosses"); //add bosses level to the end of the randomized levels list
@@ -806,9 +789,9 @@ const game = {
} }
}, },
testingOutput() { testingOutput() {
ctx.fillStyle = "#000";
if (!game.isConstructionMode) { if (!game.isConstructionMode) {
ctx.textAlign = "right"; ctx.textAlign = "right";
ctx.fillStyle = "#000";
let line = 500; let line = 500;
const x = canvas.width - 5; const x = canvas.width - 5;
ctx.fillText("T: exit testing mode", x, line); ctx.fillText("T: exit testing mode", x, line);
@@ -1083,7 +1066,7 @@ const game = {
enableConstructMode() { enableConstructMode() {
game.isConstructionMode = true; game.isConstructionMode = true;
game.isAutoZoom = false; game.isAutoZoom = false;
game.zoomScale = 2200; game.zoomScale = 2600;
game.setZoom(); game.setZoom();
document.body.addEventListener("mouseup", (e) => { document.body.addEventListener("mouseup", (e) => {

View File

@@ -13,6 +13,23 @@ const cat = {
phased: 0x100000000, phased: 0x100000000,
} }
function shuffle(array) {
var currentIndex = array.length,
temporaryValue,
randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
//example https://landgreen.github.io/sidescroller/index.html? //example https://landgreen.github.io/sidescroller/index.html?
// &gun1=minigun&gun2=laser // &gun1=minigun&gun2=laser
// &mod1=laser-bot&mod2=mass%20driver&mod3=overcharge&mod4=laser-bot&mod5=laser-bot&field=phase%20decoherence%20field&difficulty=2 // &mod1=laser-bot&mod2=mass%20driver&mod3=overcharge&mod4=laser-bot&mod5=laser-bot&field=phase%20decoherence%20field&difficulty=2

View File

@@ -6,12 +6,12 @@ const level = {
defaultZoom: 1400, defaultZoom: 1400,
onLevel: 0, onLevel: 0,
levelsCleared: 0, levelsCleared: 0,
levels: ["skyscrapers", "rooftops", "warehouse", "highrise", "office", "aerie", "satellite"], levels: ["skyscrapers", "rooftops", "warehouse", "highrise", "office", "aerie", "satellite", "sewers"],
start() { start() {
if (build.isURLBuild && level.levelsCleared === 0) build.onLoadPowerUps(); if (build.isURLBuild && level.levelsCleared === 0) build.onLoadPowerUps();
if (level.levelsCleared === 0) { //this code only runs on the first level if (level.levelsCleared === 0) { //this code only runs on the first level
// game.enableConstructMode() //used to build maps in testing mode // game.enableConstructMode() //used to build maps in testing mode
// level.difficultyIncrease(9) // level.difficultyIncrease(4)
// mech.isStealth = true; // mech.isStealth = true;
// mod.giveMod("superfluidity"); // mod.giveMod("superfluidity");
// b.giveGuns("ice IX") // b.giveGuns("ice IX")
@@ -68,20 +68,19 @@ const level = {
//****************************************************************************************************************** //******************************************************************************************************************
//****************************************************************************************************************** //******************************************************************************************************************
//****************************************************************************************************************** //******************************************************************************************************************
rotor(x, y, radius = 900, width = 50, density = 0.0002) { rotor(x, y, rotate = 0, radius = 900, width = 50, density = 0.001) {
const rotor1 = Matter.Bodies.rectangle(x, y, width, radius, { const rotor1 = Matter.Bodies.rectangle(x, y, width, radius, {
density: density, density: density,
isNotSticky: true
}); });
const rotor2 = Matter.Bodies.rectangle(x, y, width, radius, { const rotor2 = Matter.Bodies.rectangle(x, y, width, radius, {
angle: Math.PI / 2, angle: Math.PI / 2,
density: density, density: density,
isNotSticky: true
}); });
const rotor = Body.create({ //combine rotor1 and rotor2 const rotor = Body.create({ //combine rotor1 and rotor2
parts: [rotor1, rotor2], parts: [rotor1, rotor2],
// friction: 0, restitution: 0,
// frictionAir: 0,
// frictionStatic: 0,
// restitution: 0,
collisionFilter: { collisionFilter: {
category: cat.body, category: cat.body,
mask: cat.body | cat.mob | cat.mobBullet | cat.mobShield | cat.powerUp | cat.player | cat.bullet mask: cat.body | cat.mob | cat.mobBullet | cat.mobShield | cat.powerUp | cat.player | cat.bullet
@@ -108,83 +107,215 @@ const level = {
bodyB: rotor bodyB: rotor
}); });
World.add(engine.world, constraint); World.add(engine.world, constraint);
if (rotate) {
rotor.rotate = function () {
if (!mech.isBodiesAsleep) {
Matter.Body.applyForce(rotor, {
x: rotor.position.x + 100,
y: rotor.position.y + 100
}, {
x: rotate * rotor.mass,
y: 0
})
} else {
Matter.Body.setAngularVelocity(rotor, 0);
}
}
}
return rotor return rotor
}, },
sewer() { button(x, y, width = 70, height = 20) {
const rotor = level.rotor(3050, 1850) spawn.mapVertex(x + 35, y + 27, "70 10 -70 10 -40 -10 40 -10");
// const rotor2 = level.rotor(3050, 1200) // map[map.length - 1].friction = 1;
return {
isUp: false,
min: {
x: x,
y: y
},
max: {
x: x + width,
y: y + height
},
width: width,
height: height,
query() {
if (Matter.Query.region(body, this).length === 0 && Matter.Query.region([player], this).length === 0) {
this.isUp = true;
} else {
this.isUp = false;
}
},
draw() {
ctx.fillStyle = "hsl(0, 100%, 70%)"
// ctx.fillStyle = "hsl(287, 30%, 65%)"
if (this.isUp) {
ctx.fillRect(this.min.x, this.min.y, this.width, 20)
} else {
ctx.fillRect(this.min.x, this.min.y + 10, this.width, 25)
}
}
}
},
hazard(x, y, width, height, damage = 0.0005, color = "hsl(160, 100%, 35%)") {
return {
min: {
x: x,
y: y
},
max: {
x: x + width,
y: y + height
},
width: width,
height: height,
maxHeight: height,
query() {
if (this.height > 0 && Matter.Query.region([player], this).length) {
mech.damage(damage)
const drain = 0.005
if (mech.energy > drain) mech.energy -= drain
}
},
draw() {
ctx.fillStyle = color
ctx.fillRect(this.min.x, this.min.y, this.width, this.height)
},
level(isFill) {
const growSpeed = 1
if (isFill) {
if (this.height < this.maxHeight) {
this.height += growSpeed
this.min.y -= growSpeed
this.max.y = this.min.y + this.height
}
} else if (this.height > 0) {
this.height -= growSpeed
this.min.y += growSpeed
this.max.y = this.min.y + this.height
}
}
}
},
sewers() {
const rotor = level.rotor(5100, 2425, -0.001)
const button = level.button(6600, 2650)
const hazard = level.hazard(4550, 2750, 4550, 150)
level.custom = () => { level.custom = () => {
Matter.Body.applyForce(rotor, { button.query();
x: rotor.position.x + 100, button.draw();
y: rotor.position.y + 100 hazard.draw();
}, { hazard.query();
x: 0.001 * rotor.mass, hazard.level(button.isUp)
y: 0 rotor.rotate();
})
// Matter.Body.applyForce(rotor2, {
// x: rotor2.position.x + 100,
// y: rotor2.position.y + 100
// }, {
// x: -0.001 * rotor2.mass,
// y: 0
// })
level.playerExitCheck(); level.playerExitCheck();
}; };
level.setPosToSpawn(3775, 425); //normal spawn
// level.setPosToSpawn(0, -50); //normal spawn level.setPosToSpawn(0, -50); //normal spawn
spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20);
level.exit.x = 1500; level.exit.x = 9700;
level.exit.y = -1875; level.exit.y = 2560;
level.defaultZoom = 1800 level.defaultZoom = 1800
game.zoomTransition(level.defaultZoom) game.zoomTransition(level.defaultZoom)
document.body.style.backgroundColor = "hsl(138, 3%, 74%)"; document.body.style.backgroundColor = "hsl(138, 3%, 74%)";
// powerUps.spawnStartingPowerUps(1475, -1175); powerUps.spawnStartingPowerUps(3475, 1775);
// spawn.debris(750, -2200, 3700, 16); //16 debris per level spawn.debris(4575, 2550, 1600, 9); //16 debris per level
// level.fill.push({ //foreground spawn.debris(7000, 2550, 2000, 7); //16 debris per level
// x: 2500,
// y: -1100, level.fill.push({
// width: 450, x: 9300,
// height: 250, y: 2200,
// color: "rgba(0,0,0,0.1)" width: 600,
// }); height: 400,
// level.fillBG.push({ //background color: "rgba(0,255,255,0.1)"
// x: 1300, });
// y: -1800, level.fillBG.push({
// width: 750, x: 9300,
// height: 1800, y: 2200,
// color: "#d4d4d7" width: 600,
// }); height: 400,
color: "hsl(138, 10%, 80%)" //c4f4f4
});
spawn.mapRect(-400, -500, 100, 600); //left entrance wall spawn.mapRect(-400, -500, 100, 600); //left entrance wall
spawn.mapRect(-400, -500, 3800, 100); //ceiling spawn.mapRect(-400, -500, 3550, 100); //ceiling
spawn.mapRect(-400, 0, 3000, 100); //floor spawn.mapRect(-400, 0, 3000, 100); //floor
spawn.mapRect(300, -500, 100, 400); //right entrance wall spawn.mapRect(300, -500, 100, 400); //right entrance wall
spawn.bodyRect(340, -100, 25, 100);
spawn.bodyRect(1450, -300, 150, 50);
const xPos = shuffle([600, 1250, 2000]);
spawn.mapRect(xPos[0], -200, 400, 100);
spawn.mapRect(xPos[1], -250, 300, 300);
spawn.mapRect(xPos[2], -150, 300, 200);
spawn.mapRect(600, -200, 400, 100); spawn.bodyRect(3100, 410, 75, 100);
spawn.mapRect(1250, -250, 300, 300); spawn.bodyRect(2450, -25, 250, 25);
spawn.mapRect(2000, -150, 300, 200);
spawn.mapRect(3300, -500, 100, 700); //right down tube wall spawn.mapRect(3050, -500, 100, 700); //right down tube wall
spawn.mapRect(3000, 500, 100, 1400); //right down tube wall spawn.mapRect(3050, 100, 1250, 100); //tube right exit ceiling
spawn.mapRect(3000, 500, 1000, 100); //tube right exit floor spawn.mapRect(4200, 100, 100, 1800);
spawn.mapRect(3300, 100, 1000, 100); //tube right exit ceiling spawn.mapRect(3000, 400, 1000, 1250);
spawn.mapRect(3000, 1925, 1000, 150);
spawn.mapRect(2500, 0, 100, 1900); //left down tube wall spawn.mapRect(3100, 1875, 800, 100);
spawn.mapRect(850, 2300, 2800, 100); spawn.mapRect(3100, 1600, 800, 100);
spawn.mapRect(3000, 1800, 600, 100); spawn.mapRect(3100, 350, 800, 100);
spawn.mapRect(3100, 2025, 800, 100);
spawn.mapRect(2500, 0, 100, 1950); //left down tube wall
spawn.mapRect(600, 2300, 3750, 100);
spawn.bodyRect(3800, 275, 125, 125);
spawn.mapRect(4200, 1800, 5000, 100);
spawn.mapRect(4250, 2300, 100, 400);
spawn.mapRect(4250, 2300, 100, 400);
// spawn.bodyRect(1540, -1110, 300, 25, 0.9); spawn.mapRect(600, 1800, 2000, 100); //bottom left room ceiling
// spawn.boost(4150, 0, 1300); spawn.mapRect(600, 1800, 100, 600); //left wall
// spawn.randomSmallMob(1300, -70); spawn.mapRect(1775, 2225, 550, 125);
// spawn.randomMob(2650, -975, 0.8); spawn.mapRect(675, 1875, 325, 150);
// spawn.randomBoss(1700, -900, 0.4);
// if (game.difficulty > 3) spawn.randomLevelBoss(2200, -1300); spawn.mapRect(4450, 2900, 4900, 100); //boss room floor
spawn.mapRect(4250, 2600, 300, 400);
spawn.mapRect(6250, 2675, 700, 325);
spawn.mapRect(8000, 2600, 600, 400);
spawn.bodyRect(5875, 2725, 200, 200);
spawn.bodyRect(6800, 2490, 50, 50);
spawn.bodyRect(6800, 2540, 50, 50);
spawn.bodyRect(6800, 2590, 50, 50);
spawn.bodyRect(8225, 2200, 100, 400);
spawn.mapRect(6250, 1875, 700, 150);
spawn.mapRect(8000, 1875, 600, 150);
spawn.mapRect(9100, 1800, 900, 400); //exit
spawn.mapRect(9100, 2600, 900, 400);
spawn.mapRect(9900, 2125, 100, 575); //back wall
spawn.mapRect(9300, 2150, 50, 250);
spawn.mapRect(9300, 2590, 650, 25);
spawn.mapRect(9700, 2580, 100, 50);
spawn.randomBoss(1300, 2100, 0.7);
spawn.randomMob(8300, 2225, 0.5);
spawn.randomSmallMob(2575, -75, 0.5); //entrance
spawn.randomMob(8125, 2450, 0.5);
spawn.randomSmallMob(3200, 250, 0.5);
spawn.randomMob(2425, 2150, 0.5);
spawn.randomSmallMob(3825, 300, 0.5);
spawn.randomMob(3800, 2175, 0.5);
spawn.randomSmallMob(1100, -300, 0.5); //entrance
spawn.randomMob(4450, 2500, 0.5);
spawn.randomMob(6350, 2525, 0.5);
spawn.randomSmallMob(1900, -250, 0.5); //entrance
spawn.randomMob(1500, 2100, 0.8);
spawn.randomSmallMob(1700, -150, 0.5); //entrance
spawn.randomMob(8800, 2725, 0.5);
spawn.randomBoss(8650, 2275, 0.5);
if (game.difficulty > 3) spawn.randomLevelBoss(6000, 2300, ["shooterBoss", "spiderBoss", "launcherBoss", "laserTargetingBoss"]);
}, },
template() { template() {
level.custom = () => { level.custom = () => {
@@ -611,8 +742,8 @@ const level = {
height: 1500, height: 1500,
color: "rgba(0,20,40,0.13)" color: "rgba(0,20,40,0.13)"
}); });
spawn.mapRect(3950, -350, 375, 50); spawn.mapRect(3925, -300, 425, 50);
spawn.mapRect(4725, -350, 375, 50); spawn.mapRect(4700, -375, 425, 50);
spawn.mapRect(4000, -1300, 1050, 100); spawn.mapRect(4000, -1300, 1050, 100);
//steep stairs //steep stairs

View File

@@ -1482,7 +1482,7 @@ const mod = {
requires: "flak", requires: "flak",
effect() { effect() {
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun 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 * (3 + this.count); if (b.guns[i].name === "flak") b.guns[i].ammoPack = b.guns[i].defaultAmmoPack * (3 * this.count);
} }
}, },
remove() { remove() {

View File

@@ -451,6 +451,7 @@ const mech = {
return dmg return dmg
}, },
damage(dmg) { damage(dmg) {
// , noTransition = false
mech.lastHarmCycle = mech.cycle mech.lastHarmCycle = mech.cycle
if (mod.isDroneOnDamage) { //chance to build a drone on damage from mod if (mod.isDroneOnDamage) { //chance to build a drone on damage from mod
@@ -549,6 +550,11 @@ const mech = {
} }
mech.defaultFPSCycle = mech.cycle mech.defaultFPSCycle = mech.cycle
} }
// if (!noTransition) {
// document.getElementById("health").style.transition = "width 0s ease-out"
// } else {
// document.getElementById("health").style.transition = "width 1s ease-out"
// }
}, },
hitMob(i, dmg) { hitMob(i, dmg) {
//prevents damage happening too quick //prevents damage happening too quick

View File

@@ -81,9 +81,8 @@ const spawn = {
} }
} }
}, },
randomLevelBoss(x, y) { randomLevelBoss(x, y, options = ["shooterBoss", "cellBossCulture", "bomberBoss", "spiderBoss", "launcherBoss", "laserTargetingBoss"]) {
// other bosses: suckerBoss, laserBoss, tetherBoss, snakeBoss //all need a particular level to work so they are not included // other bosses: suckerBoss, laserBoss, tetherBoss, snakeBoss //all need a particular level to work so they are not included
const options = ["shooterBoss", "cellBossCulture", "bomberBoss", "spiderBoss", "launcherBoss", "laserTargetingBoss"] // , "timeSkipBoss"
spawn[options[Math.floor(Math.random() * options.length)]](x, y) spawn[options[Math.floor(Math.random() * options.length)]](x, y)
}, },
//mob templates ********************************************************************************************* //mob templates *********************************************************************************************
@@ -897,18 +896,18 @@ const spawn = {
}; };
} }
}, },
laserTargetingBoss(x, y, radius = 65) { laserTargetingBoss(x, y, radius = 80) {
const color = "#05f" const color = "#05f"
mobs.spawn(x, y, 3, radius, color); mobs.spawn(x, y, 3, radius, color);
let me = mob[mob.length - 1]; let me = mob[mob.length - 1];
me.isBoss = true; me.isBoss = true;
me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front
Matter.Body.rotate(me, Math.random() * Math.PI * 2); Matter.Body.rotate(me, Math.random() * Math.PI * 2);
me.accelMag = 0.0005 * game.accelScale; me.accelMag = 0.00065 * game.accelScale;
me.seePlayerFreq = Math.floor(25 * game.lookFreqScale); me.seePlayerFreq = Math.floor(25 * game.lookFreqScale);
me.memory = 420; me.memory = 420;
me.restitution = 1; me.restitution = 1;
me.frictionAir = 0.05; me.frictionAir = 0.035;
me.frictionStatic = 0; me.frictionStatic = 0;
me.friction = 0; me.friction = 0;
@@ -1015,7 +1014,7 @@ const spawn = {
// hitting player // hitting player
if (best.who === player) { if (best.who === player) {
if (mech.immuneCycle < mech.cycle) { if (mech.immuneCycle < mech.cycle) {
const dmg = 0.002 * game.dmgScale; const dmg = 0.001 * game.dmgScale;
mech.damage(dmg); mech.damage(dmg);
//draw damage //draw damage
ctx.fillStyle = color; ctx.fillStyle = color;

View File

@@ -1,7 +1,35 @@
new level, sewers
flak ammo buff
************** TODO - n-gon ************** ************** TODO - n-gon **************
mod: do more damage for each reroll you are holding
do more damage for the ammo/ammoPack ratio?
mod: taking damage slows (or stuns) all mobs on the map
requires the mod that slows time, overclock
button: blocks that are on the button at an angle will slowly slide off the button
maybe just avoid long blocks near the button?
maybe actively hold the button in place?
slime should affect blocks, bullets, mobs
and do mob damage?
rotor doesn't work with
plasma torch
seems to have no effect
pilot wave
maybe rewrite pilot wave to apply a force from an angle, like plasma torch
this would be probably too much work, but worth a try
time dilation field (mostly fixed, but it would be nice if it started up faster after a pause)
sporangium and neutron bomb (mostly fixed, but it would be nice if it sticked instead of bounced)
new gun - deploy a turret that last for 20 seconds
fire nails at nearby targets once a second.
use mine code and bot code
field that pushes blocks and mobs away field that pushes blocks and mobs away
charges up on mouse down and triggers on mouse up charges up on mouse down and triggers on mouse up
drain mana while charging up drain mana while charging up