diff --git a/mods/creepers.js b/mods/creepers.js index 1f7b9869..2ecc0883 100644 --- a/mods/creepers.js +++ b/mods/creepers.js @@ -15,7 +15,8 @@ function slBound(number) { //SL bounding function (not hue), same use as above return Math.min(100,Math.max(0,number)); }; -function upVelocity(pixel,x,y,radius,fire,smoke,power) { //Angelic Creeper's effect, "compatible" with velocity.js by including the modified version of its code in itself +function angelicUpwardVelocity(pixel,x,y,radius,fire,smoke,power) { //Angelic Creeper's effect, "compatible" with velocity.js by including the modified version of its code in itself + var info = elements[pixel.element] if(enabledMods.includes("mods/velocity.js")) { //console.log("yeet"); // set the pixel.vx and pixel.vy depending on the angle and power @@ -28,12 +29,44 @@ function upVelocity(pixel,x,y,radius,fire,smoke,power) { //Angelic Creeper's eff pixel.vy = 0 - Math.abs(Math.round((pixel.vy|0) + Math.sin(angle) * (radius * power/4)) + 4); //massively increased Y velocities even for objects below //pixel.color = "rgb(255,0,0)"; //console.log(`vy calculated (${pixel.vy}) for pixel (${pixel.x},${pixel.y})`); - };5 + }; //console.log(`Velocities set`); }; //console.log(`end`); }; +//afterFunction(pixel,x,y,radius,fire,smoke,power,damage); +function hellExplosionFire(pixel,x,y,radius,fire,smoke,power,damage) { //Angelic Creeper's effect, "compatible" with velocity.js by including the modified version of its code in itself + var coords = circleCoords(pixel.x,pixel.y,radius); + for (var i = 0; i < coords.length; i++) { + var x = coords[i].x; + var y = coords[i].y; + if(!isEmpty(x,y,true)) { + var pixel = pixelMap[x][y]; + var info = elements[pixel.element] + if (info.burn) { //Light everything on fire + pixel.burning = true; + pixel.burnStart = pixelTicks; + pixel.temp += 10; //smoke prevention + } else if(Math.random() < 0.05) { //5%/px cursed burning + pixel.burning = true; + pixel.burnStart = pixelTicks; + pixel.temp += 10; + }; + } else if(isEmpty(x,y)) { //if there's space for fire + if (Array.isArray(fire)) { //this should remain "fire" + var newfire = fire[Math.floor(Math.random() * fire.length)]; + } else { + var newfire = fire; + }; + createPixel(newfire,x,y); //add fire + var firePixel = pixelMap[x][y]; + firePixel.temp = Math.max(elements[newfire].temp,firePixel.temp); + firePixel.burning = true; + }; + }; +}; + function explodeAtPlus(x,y,radius,fire="fire",smoke="smoke",beforeFunction=null,afterFunction=null) { //explodeAt with additional arguments, used to allow implementation of Angelic Creeper's effect // if fire contains , split it into an array if (fire.indexOf(",") !== -1) { @@ -301,7 +334,7 @@ elements.creeper_body = { }; if(pixel.charged) { - var explosionRadius = 10; + var explosionRadius = 7; if(!pixel.didChargeBlueTinted) { //do once, on initial charge //console.log("something something halsey lyric"); var color = pixel.color; @@ -340,7 +373,7 @@ elements.creeper_body = { pixel.didChargeBlueTinted = true; }; } else { - var explosionRadius = 7; + var explosionRadius = 5; }; if(pixel.burning) { @@ -917,7 +950,7 @@ elements.angelic_creeper_body = { }; if(pixelTicks - pixel.burnStart > 30) { //console.log("GOTTA YEET YEET YEET!"); - explodeAtPlus(pixel.x,pixel.y,explosionRadius,"fire","smoke",null,upVelocity); //Special effect: Flings you upwards (extended to all movable tiles because it's easier). + explodeAtPlus(pixel.x,pixel.y,explosionRadius,"fire","smoke",null,angelicUpwardVelocity); //Special effect: Flings you upwards (extended to all movable tiles because it's easier). //It also floats when hissing, but that will come soon. //console.log("Yes, Rico, kaboom."); }; @@ -1236,7 +1269,7 @@ elements.angelic_creeper_head = { if(pixelTicks - pixel.hissStart > 30) { //console.log("GOTTA YEET YEET YEET!"); //console.log(`Exploding with radius ${explosionRadius} (charged: ${pixel.charged})`); - explodeAtPlus(body.x,body.y,explosionRadius,"fire","smoke",null,upVelocity); + explodeAtPlus(body.x,body.y,explosionRadius,"fire","smoke",null,angelicUpwardVelocity); //console.log("Yes, Rico, kaboom."); }; }; @@ -1775,6 +1808,530 @@ elements.bombing_creeper_head = { }; }; + if(Math.random() < 0.01) { //1% chance each tick to lose interest + pixel.following = false; + //console.log("Meh."); + }; + }, +}; + + //Hell Creeper + +elements.hell_creeper = { + color: ["#D2D2D2", "#ff141e", "#fc3232", "#DFAFAF", "#e84a4a", "#ce7979", "#d95555", "#d53c3c", "#c53636", "#b13333", "#913535", "#954242", "#872828", "#8b4949", "#2b0304"], + category: "life", + properties: { + dead: false, + dir: 1, + panic: 0, + following: false, + }, + tick: function(pixel) { + if (isEmpty(pixel.x, pixel.y+1)) { + createPixel("hell_creeper_body", pixel.x, pixel.y+1); + pixel.element = "hell_creeper_head"; + pixel.color = pixelColorPick(pixel) + } + else if (isEmpty(pixel.x, pixel.y-1)) { + createPixel("hell_creeper_head", pixel.x, pixel.y-1); + pixelMap[pixel.x][pixel.y-1].color = pixel.color; + pixel.element = "hell_creeper_body"; + pixel.color = pixelColorPick(pixel) + } + else { + deletePixel(pixel.x, pixel.y); + } + }, + related: ["hell_creeper_body","hell_creeper_head"], + desc: 'A creeper type from Extra Creeper Types (CF). It has a small explosion radius, but spawns a lot of fire around its explosion.' +}; + +elements.hell_creeper_body = { + color: ["#D2D2D2", "#ff141e", "#fc3232", "#DFAFAF", "#e84a4a", "#ce7979", "#d95555", "#d53c3c", "#c53636", "#b13333", "#913535", "#954242", "#872828", "#8b4949", "#2b0304"], + category: "life", + hidden: true, + density: 1500, + state: "solid", + conduct: 25, + tempHigh: 2000, //they are immune to lava, and minecraft's lava is presumably mafic, so at least 1200*C + stateHigh: "ash", + breakInto: ["blood","gunpowder","fire"], + reactions: { + "cancer": { "elem1":"cancer", "chance":0.005 }, + "radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.4 }, + "plague": { "elem1":"plague", "chance":0.05 }, + }, + properties: { + dead: false, + dir: 1, + panic: 0, + charged: false, + didChargeBlueTinted: false, + }, + tick: function(pixel) { + if (tryMove(pixel, pixel.x, pixel.y+1)) { // Fall + if (!isEmpty(pixel.x, pixel.y-2, true)) { // Drag head down + var headPixel = pixelMap[pixel.x][pixel.y-2]; + if (headPixel.element == "hell_creeper_head") { + if (isEmpty(pixel.x, pixel.y-1)) { + movePixel(pixelMap[pixel.x][pixel.y-2], pixel.x, pixel.y-1); + } + else { + swapPixels(pixelMap[pixel.x][pixel.y-2], pixelMap[pixel.x][pixel.y-1]); + } + } + } + } + doHeat(pixel); + doBurning(pixel); + doElectricity(pixel); + if (pixel.dead) { + // Turn into rotten_meat if pixelTicks-dead > 500 + if (pixelTicks-pixel.dead > 200) { + Math.random() < 0.1 ? changePixel(pixel,"gunpowder") : changePixel(pixel,"rotten_meat"); + } + return + } + + // Find the head + if (!isEmpty(pixel.x, pixel.y-1, true) && pixelMap[pixel.x][pixel.y-1].element == "hell_creeper_head") { + var head = pixelMap[pixel.x][pixel.y-1]; + if (head.dead) { // If head is dead, kill body + pixel.dead = head.dead; + } + } + else { var head = null } + + if (isEmpty(pixel.x, pixel.y-1)) { + // create blood if decapitated 10% chance + if (Math.random() < 0.1) { + createPixel("blood", pixel.x, pixel.y-1); + // set dead to true 15% chance + if (Math.random() < 0.15) { + pixel.dead = pixelTicks; + } + } + } + else if (head == null) { return } + else if (Math.random() < 0.1) { // Move 10% chance + var movesToTry = [ + [1*pixel.dir,0], + [1*pixel.dir,-1], + ]; + // While movesToTry is not empty, tryMove(pixel, x, y) with a random move, then remove it. if tryMove returns true, break. + while (movesToTry.length > 0) { + var move = movesToTry.splice(Math.floor(Math.random() * movesToTry.length), 1)[0]; + if (isEmpty(pixel.x+move[0], pixel.y+move[1]-1)) { + if (tryMove(pixel, pixel.x+move[0], pixel.y+move[1])) { + movePixel(head, head.x+move[0], head.y+move[1]); + break; + }; + }; + }; + // 15% chance to change direction while not chasing a human + if(!head.following) { + if (Math.random() < 0.15) { + pixel.dir *= -1; + //console.log("*turns around cutely to face ${pixel.dir < 0 ? 'left' : 'right'}*"); + }; + }/* else { + //console.log("*chases cutely*"); + };*/ + }; + + if(pixel.charge) { + pixel.charged = true; + }; + + if(head) { + if(typeof(head.charge) !== "undefined") { + if(head.charge) { + pixel.charged = true; + }; + }; + if(typeof(head.charged) !== "undefined") { + if(head.charged) { + pixel.charged = true; + }; + }; + }; + + if(typeof(pixel.charged) === "undefined") { + pixel.charged = false; + }; + + if(pixel.charged) { + var explosionRadius = 10; + if(!pixel.didChargeBlueTinted) { //do once, on initial charge + //console.log("something something halsey lyric"); + var color = pixel.color; + if(color.startsWith("rgb")) { + //console.log("rgb detected"); + color = color.split(","); //split color for addition + var red = parseFloat(color[0].substring(4)); + var green = parseFloat(color[1]); + var blue = parseFloat(color[2].slice(0,-1)); + red = rgbColorBound(red + 51); + green = rgbColorBound(green + 51); + blue = rgbColorBound(blue + 102); + color = `rgb(${red},${green},${blue})`; + pixel.color = color; + //console.log("color set"); + } else if(color.startsWith("hsl")) { + //console.log("hsl detected"); + color = color.split(","); //split color for addition + var hue = parseFloat(color[0].substring(4)); + var saturation = parseFloat(color[1].slice(0,-1)); + var luminance = parseFloat(color[2].slice(0,-2)); + hue = hue % 360; //piecewise hue shift + if(hue <= 235 && hue >= 135) { + hue = 185; + } else if(hue < 135) { + hue += 50; + } else if(hue > 235 && hue < 360) { + hue -= 50; + }; + saturation = slBound (saturation + 10); + luminance = slBound(luminance + 20); + color = `hsl(${hue},${saturation}%,${luminance}%)`; + pixel.color = color; + //console.log("color set"); + }; + pixel.didChargeBlueTinted = true; + }; + } else { + var explosionRadius = 7; + }; + + if(pixel.burning) { + pixel.hissing = true; + if(!pixel.burnStart) { //I don't like errors. + pixel.burnStart = pixel.ticks; + }; + if(pixelTicks - pixel.burnStart > 30) { + //console.log("Kaboom?"); + explodeAtPlus(pixel.x,pixel.y,explosionRadius,"fire","fire",null,hellExplosionFire); + //console.log("Yes, Rico, kaboom."); + }; + }; + + //Head hissing color handler: keeps track of head's hissing for coloring purposes + for(i = 0; i < 1; i++) { //dummy for loop + if(pixel.dead || !head || head.dead) { //can't hiss without a head according to the classic creeper anatomy + //console.log("ss-- oof"); + pixel.hissing = false; + break; + }; + if(head.hissing) { + //console.log("Ssssssss"); + if(!head.hissStart) { + //console.log("t-30 ticks or whatever it was"); + head.hissStart = pixelTicks; + }; + + //Color code { + var ticksHissing = pixelTicks - head.hissStart; + var color = pixel.color; //do on each hissing tick + if(color.startsWith("rgb")) { + //console.log("rgb detected"); + color = color.split(","); //split color for addition + var red = parseFloat(color[0].substring(4)); + var green = parseFloat(color[1]); + var blue = parseFloat(color[2].slice(0,-1)); + red = rgbColorBound(red + (ticksHissing * 3)); + green = rgbColorBound(green + (ticksHissing * 3)); + blue = rgbColorBound(blue + (ticksHissing * 3)); + color = `rgb(${red},${green},${blue})`; + pixel.color = color; + //console.log("color set"); + } else if(color.startsWith("hsl")) { + //console.log("hsl detected"); + color = color.split(","); //split color for addition + var hue = parseFloat(color[0].substring(4)); + var saturation = parseFloat(color[1].slice(0,-1)); + var luminance = parseFloat(color[2].slice(0,-2)); + //console.log("the j"); + luminance = slBound(luminance + 1.176); + //console.log(luminance); + color = `hsl(${hue},${saturation}%,${luminance}%)`; + pixel.color = color; + //console.log("color set"); + }; + //} + }; + }; + }, +}; + +elements.hell_creeper_head = { + color: ["#D2D2D2", "#ff141e", "#fc3232", "#e84a4a", "#b13333", "#913535", "#954242", "#872828", "#8b4949", "#2b0304", "#111111", "#faae3c", "#f5e131"], + category: "life", + hidden: true, + density: 1080, + state: "solid", + conduct: 25, + tempHigh: 2000, + stateHigh: "ash", + tempLow: -30, + stateLow: "frozen_meat", + breakInto: ["blood","fire"], + reactions: { + "cancer": { "elem1":"cancer", "chance":0.005 }, + "radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.4 }, + "plague": { "elem1":"plague", "chance":0.05 }, + "oxygen": { "elem2":"carbon_dioxide", "chance":0.5 }, + }, + properties: { + dead: false, + following: false, + hissing: false, + charged: false, + didChargeBlueTinted: false, + }, + tick: function(pixel) { + doHeat(pixel); + doBurning(pixel); + doElectricity(pixel); + if (pixel.dead) { + // Turn into rotten_meat if pixelTicks-dead > 500 + if (pixelTicks-pixel.dead > 200) { + Math.random() < 0.1 ? changePixel(pixel,"gunpowder") : changePixel(pixel,"rotten_meat"); + return + } + } + + // Find the body + if (!isEmpty(pixel.x, pixel.y+1, true) && pixelMap[pixel.x][pixel.y+1].element == "hell_creeper_body") { + var body = pixelMap[pixel.x][pixel.y+1]; + if (body.dead) { // If body is dead, kill head + pixel.dead = body.dead; + } + } + else { var body = null } + + if(body) { + if(body.dir !== pixel.dir) { //hacky workaround: lock head dir to body dir + pixel.dir = body.dir; + }; + }; + + if (isEmpty(pixel.x, pixel.y+1)) { + tryMove(pixel, pixel.x, pixel.y+1); + // create blood if severed 10% chance + if (isEmpty(pixel.x, pixel.y+1) && !pixel.dead && Math.random() < 0.1) { + createPixel("blood", pixel.x, pixel.y+1); + // set dead to true 15% chance + if (Math.random() < 0.15) { + pixel.dead = pixelTicks; + } + } + } + + //start of most new code + var pX = pixel.x; + var pY = pixel.y; + + if(pixel.charge) { + pixel.charged = true; + }; + + if(body) { + if(typeof(body.charge) !== "undefined") { + if(body.charge) { + pixel.charged = true; + }; + }; + if(typeof(body.charged) !== "undefined") { + if(body.charged) { + pixel.charged = true; + }; + }; + }; + + if(typeof(pixel.charged) === "undefined") { + pixel.charged = false; + }; + + if(pixel.charged) { + var explosionRadius = 10; + if(!pixel.didChargeBlueTinted) { //do once, on initial charge + //console.log("something something halsey lyric"); + var color = pixel.color; + if(color.startsWith("rgb")) { + //console.log("rgb detected"); + color = color.split(","); //split color for addition + var red = parseFloat(color[0].substring(4)); + var green = parseFloat(color[1]); + var blue = parseFloat(color[2].slice(0,-1)); + red = rgbColorBound(red + 51); + green = rgbColorBound(green + 51); + blue = rgbColorBound(blue + 102); + color = `rgb(${red},${green},${blue})`; + pixel.color = color; + //console.log("color set"); + } else if(color.startsWith("hsl")) { + //console.log("hsl detected"); + color = color.split(","); //split color for addition + var hue = parseFloat(color[0].substring(4)); + var saturation = parseFloat(color[1].slice(0,-1)); + var luminance = parseFloat(color[2].slice(0,-2)); + hue = hue % 360; //piecewise hue shift + if(hue <= 235 && hue >= 135) { + hue = 185; + } else if(hue < 135) { + hue += 50; + } else if(hue > 235 && hue < 360) { + hue -= 50; + }; + saturation = slBound (saturation + 10); + luminance = slBound(luminance + 20); + color = `hsl(${hue},${saturation}%,${luminance}%)`; + pixel.color = color; + //console.log("color set"); + }; + pixel.didChargeBlueTinted = true; + }; + } else { + var explosionRadius = 7; + }; + + //Human detection loop (looks ahead according to direction and sets the "following" variable to true, telling the body to lock the direction) + var directionAdverb = "left"; + if(pixel.dir > 0) { + directionAdverb = "right"; + }; + //console.log(`Looking ${directionAdverb}`) + if(pixel.dir === -1) { + for(i = -4; i < 4+1; i++) { + var oY = i; + //console.log(`Starting row look at row ${pY+oY}`) + for(j = (-1); j > (-16 - 1); j--) { + var oX = j; + var nX = pX+oX; + var nY = pY+oY; + if(outOfBounds(nX,nY)) { + //console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`) + break; + }; + if(isEmpty(nX,nY)) { + ////console.log(`Skipping pixel (${nX},${nY}) (empty)`) + continue; + }; + if(!isEmpty(nX,nY,true)) { + var newPixel = pixelMap[nX][nY]; + var newElement = newPixel.element; + if(enemyHumanoidArray.includes(newElement)) { + //console.log(`Human part found at (${nX},${nY})`) + if(!newPixel.dead) { + pixel.following = true; + //console.log(`Human detected at (${nX},${nY})`) + //Start "hissing" if a human is close enough + if(pyth(pX,pY,nX,nY) <= 3.15) { //probably misapplying the tolerance from the MC Wiki line: "Creepers will chase after any player, as long as it is within a 16 block (±5%) radius" + pixel.hissing = true; + if(!pixel.hissStart) { + pixel.hissStart = pixelTicks; + }; + }; + }; + } else { + //console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`) + break; //can't see through humans + }; + }; + }; + }; + } else if(pixel.dir === 1) { + for(i = -4; i < 4+1; i++) { + var oY = i; + //console.log(`Starting row look at row ${pY+oY}`) + for(j = 1; j < 16 + 1; j++) { + var oX = j; + var nX = pX+oX; + var nY = pY+oY; + if(outOfBounds(nX,nY)) { + //console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`) + break; + }; + if(isEmpty(nX,nY)) { + ////console.log(`Skipping pixel (${nX},${nY}) (empty)`) + continue; + }; + if(!isEmpty(nX,nY,true)) { + var newPixel = pixelMap[nX][nY]; + var newElement = newPixel.element; + if(enemyHumanoidArray.includes(newElement)) { + //console.log(`Human part found at (${nX},${nY})`) + if(!newPixel.dead) { + pixel.following = true; + //console.log(`Human detected at (${nX},${nY})`) + //Start "hissing" if a human is close enough + if(pyth(pX,pY,nX,nY) <= 3.15) { + pixel.hissing = true; + if(!pixel.hissStart) { + pixel.hissStart = pixelTicks; + }; + }; + break; + }; + } else { + //console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`) + break; + }; + }; + }; + }; + }; + + //Pre-explosion handler: keeps track of time before the kaboom + for(i = 0; i < 1; i++) { //dummy for loop + if(pixel.hissing) { + //console.log("Ssssssss"); + if(pixel.dead || !body || body.dead) { //can't explode without a body according to the classic creeper anatomy + //console.log("ss-- oof"); + pixel.hissing = false; + break; + }; + if(!pixel.hissStart) { + //console.log("t-30 ticks or whatever it was"); + pixel.hissStart = pixelTicks; + }; + //Color code { + var ticksHissing = pixelTicks - pixel.hissStart; + var color = pixel.color; //do on each hissing tick + if(color.startsWith("rgb")) { + //console.log("rgb detected"); + color = color.split(","); //split color for addition + var red = parseFloat(color[0].substring(4)); + var green = parseFloat(color[1]); + var blue = parseFloat(color[2].slice(0,-1)); + red = rgbColorBound(red + (ticksHissing * 3)); + green = rgbColorBound(green + (ticksHissing * 3)); + blue = rgbColorBound(blue + (ticksHissing * 3)); + color = `rgb(${red},${green},${blue})`; + pixel.color = color; + //console.log("color set"); + } else if(color.startsWith("hsl")) { + //console.log("hsl detected"); + color = color.split(","); //split color for addition + var hue = parseFloat(color[0].substring(4)); + var saturation = parseFloat(color[1].slice(0,-1)); + var luminance = parseFloat(color[2].slice(0,-2)); + luminance = slBound(luminance + 1.176); + color = `hsl(${hue},${saturation}%,${luminance}%)`; + pixel.color = color; + //console.log("color set"); + }; + //} + + if(pixelTicks - pixel.hissStart > 30) { + //console.log("Kaboom?"); + //console.log(`Exploding with radius ${explosionRadius} (charged: ${pixel.charged})`); + explodeAtPlus(pixel.x,pixel.y,explosionRadius,"fire","fire",null,hellExplosionFire); + //console.log("Yes, Rico, kaboom."); + }; + }; + }; + if(Math.random() < 0.01) { //1% chance each tick to lose interest pixel.following = false; //console.log("Meh.");