From fb82130cfc77294fd8793f17321357ee48345349 Mon Sep 17 00:00:00 2001 From: "Laetitia (O-01-67)" <68935009+O-01-67@users.noreply.github.com> Date: Fri, 11 Nov 2022 14:22:36 -0500 Subject: [PATCH] Add skeletons --- mods/mobs.js | 654 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 631 insertions(+), 23 deletions(-) diff --git a/mods/mobs.js b/mods/mobs.js index 3d1c7154..19c03539 100644 --- a/mods/mobs.js +++ b/mods/mobs.js @@ -5,10 +5,14 @@ var explodeAtPlusMod = "mods/explodeAtPlus.js"; if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlusMod)) { //Prerequisite Functions and Variables + minimumCreeperTries = 3; maximumCreeperTries = 3; - minimumCreeperTries = 1; - maximumZombieTries = 3; + minimumZombieTries = 3; + maximumZombieTries = 3; + + minimumSkeletonTries = 3; + maximumSkeletonTries = 3; headBodyObject = { "head": "body", @@ -17,6 +21,8 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu "hell_creeper_head": "hell_creeper_body", "bombing_creeper_head": "bombing_creeper_body", "zombie_head": "zombie_body", + "skeleton_head": "skeleton_body", + "nothing_there_phase_3_head": "nothing_there_phase_3_body", }; var style = document.createElement('style'); //Initialize CSS for creeper spawning's status indicator @@ -34,13 +40,17 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu }; document.getElementsByTagName('head')[0].appendChild(style); - function pyth(xA,yA,xB,yB) { //Distance function, used for explosion trigger + function coordPyth(xA,yA,xB,yB) { //Distance function, used for explosion trigger var a = Math.abs(xB - xA); var b = Math.abs(yB - yA); var c = Math.sqrt(a**2 + b**2); return c; }; + function pythSpeed(number1,number2) { + return Math.sqrt(number1**2 + number2**2); + }; + function rgbColorBound(number) { //RGB bounding function, used for safety checking color changes return Math.min(255,Math.max(0,number)); }; @@ -204,6 +214,21 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu }; }; + function arrowAltTb(pixel,breakChanceMultiplier,changetemp=false,defaultBreakIntoDust=false) { + var info = elements[pixel.element]; + var hardness = defaultHardness; + if(typeof(info.hardness) === "number") { + hardness = info.hardness; + }; + hardness = 1 - hardness; //invert hardness, so a hardness of 0 becomes a 100% chance and a hardness of 1 becomes a 0% chance + hardness *= breakChanceMultiplier; + if(Math.random() < hardness) { + return breakPixel(pixel,changetemp=false,defaultBreakIntoDust=false); + } else { + return false; + }; + }; + function nothingThereBulletMovement(pixel,x,y) { if(!tryMove(pixel,x,y)) { if(!isEmpty(x,y,true)) { @@ -616,6 +641,124 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu }; }; + var style = document.createElement('style'); //Initialize CSS for skeleton spawning's status indicator + style.type = 'text/css'; + style.id = 'skeletonStatusStylesheet'; + //initial style conditional branch + if(typeof(settings.skeletonSpawning) === "undefined") { //undefined (falsy but it needs special handling) + style.innerHTML = '.skeletonStatus { color: #E11; text-decoration: none; }'; + } else { + if(!settings.skeletonSpawning) { //falsy: red + style.innerHTML = '.skeletonStatus { color: #E11; text-decoration: none; }'; + } else if(settings.skeletonSpawning) { //truthy: green + style.innerHTML = '.skeletonStatus { color: #1E1; text-decoration: none; }'; + }; + }; + document.getElementsByTagName('head')[0].appendChild(style); + + if(typeof(settings.skeletonSpawning) === "undefined") { //Default skeleton setting + setSetting("skeletonSpawning",false); + }; + + function updateSkeletonPreferences() { //Skeleton setting handler + if(settings.skeletonSpawning) { //If the setting is on + if(typeof(randomEvents.skeleton) !== "function") { //add the event if it's missing + randomEvents.skeleton = function() { + var amount = Math.floor((Math.random() * maximumSkeletonTries)+minimumSkeletonTries); //1-3 + //In worldgen worlds, you can expect about half of this because about half of the world is pixels in it. + for(i = 0; i < amount; i++) { //dummy for to break + if(settings.skeletonSpawning) { //setting validation + // random x between 1 and width-1 + var x = Math.floor(Math.random()*(width-1))+1; + // random y between 1 and height + var y = Math.floor(Math.random()*height-1)+1; + if (isEmpty(x,y)) { + // random element from the list of spawnable skeletons + var element = spawnSkeletons[Math.floor(Math.random()*spawnSkeletons.length)]; + // if element is an array, choose a random element from the array + if (Array.isArray(element)) { + element = element[Math.floor(Math.random()*element.length)]; + } + createPixel(element,x,y); + }; + } else { //if false (this function is never supposed to fire with the setting false) + delete randomEvents.skeleton; //self-disable + //substitute event + var event = randomEvents[Object.keys(randomEvents)[Math.floor(Math.random()*Object.keys(randomEvents).length)]]; + event(); + break; + }; + }; + }; + }; + } else if(!settings.skeletonSpawning) { //and if it's off + if(randomEvents.skeleton) { delete randomEvents.skeleton }; //delete it if it exists. + }; + }; + + function toggleSkeletonSpawning() { //Skeleton toggle handler + if(settings.skeletonSpawning != true) { //If it's false + setSetting("skeletonSpawning",true); //make it true and update the status display CSS + updateSkeletonPreferences(); //apply + document.getElementById("skeletonStatusStylesheet").innerHTML = '.skeletonStatus { color: #1E1; text-decoration: underline; }'; //Displayed info doen't update until it's pulled up again, so I'm using CSS to dynamically change the color of an element, like with find.js (RIP). + } else { //and the inverse if it's true + setSetting("skeletonSpawning",false); + updateSkeletonPreferences(); + document.getElementById("skeletonStatusStylesheet").innerHTML = '.skeletonStatus { color: #E11; text-decoration: none; }'; + }; + }; + + spawnSkeletons = ["skeleton"]; + + if(settings.skeletonSpawning) { //skeleton spawning option + randomEvents.skeleton = function() { + var amount = Math.floor((Math.random() * maximumSkeletonTries)+minimumSkeletonTries); //1-3 + for(i = 0; i < amount; i++) { //dummy for to break + if(settings.skeletonSpawning) { //setting validation + // random x between 1 and width-1 + var x = Math.floor(Math.random()*(width-1))+1; + // random y between 1 and height + var y = Math.floor(Math.random()*height-1)+1; + if (isEmpty(x,y)) { + // random element from the list of spawnable skeletons + var element = spawnSkeletons[Math.floor(Math.random()*spawnSkeletons.length)]; + // if element is an array, choose a random element from the array + if (Array.isArray(element)) { + element = element[Math.floor(Math.random()*element.length)]; + } + createPixel(element,x,y); + }; + } else { //if false (this function is never supposed to fire with the setting false) + delete randomEvents.skeleton; //self-disable + //substitute event + var event = randomEvents[Object.keys(randomEvents)[Math.floor(Math.random()*Object.keys(randomEvents).length)]]; + event(); + break; + }; + }; + }; + }; + + standaloneSpawnSkeleton = function(amount=1) { + /* The amount is the maximum amount of *attempts*. Often, less skeletons will spawn due to things in the way. + In a generated world, which uses half of the space, you can expect about half of this number to spawn. */ + for(i = 0; i < amount; i++) { //dummy for to break + // random x between 1 and width-1 + var x = Math.floor(Math.random()*(width-1))+1; + // random y between 1 and height + var y = Math.floor(Math.random()*height-1)+1; + if (isEmpty(x,y)) { + // random element from the list of spawnable skeletons + var element = spawnSkeletons[Math.floor(Math.random()*spawnSkeletons.length)]; + // if element is an array, choose a random element from the array + if (Array.isArray(element)) { + element = element[Math.floor(Math.random()*element.length)]; + } + createPixel(element,x,y); + }; + }; + }; + /*Start Main Zombie .................. .........###...... @@ -874,7 +1017,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu pixel.following = true; //console.log(`Human detected at (${nX},${nY})`) //Infect/kill if a human is close enough - if(pyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2) + if(coordPyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2) if(Math.random() < 1/3) { //One-third chance to mutilate (changePixel) if(Math.random() < 1/3) { //One-third chance to change to blood changePixel(newPixel,"zombie_blood",false); //blood is turned in place @@ -924,7 +1067,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu pixel.following = true; //console.log(`Human detected at (${nX},${nY})`) //Infect/kill if a human is close enough - if(pyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2) + if(coordPyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2) if(Math.random() < 1/3) { //One-third chance to mutilate (changePixel) if(Math.random() < 1/3) { //One-third chance to change to blood changePixel(newPixel,"zombie_blood",false); //blood is turned in place @@ -1060,7 +1203,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu pixel.following = true; //console.log(`Human detected at (${nX},${nY})`) //Infect/kill if a human is close enough - if(pyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2) + if(coordPyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2) if(Math.random() < 1/3) { //One-third chance to mutilate (changePixel) if(Math.random() < 1/4) { //One-fourth chance to change to blood changePixel(newPixel,"zombie_blood",false); //blood is turned in place @@ -1110,7 +1253,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu pixel.following = true; //console.log(`Human detected at (${nX},${nY})`) //Infect/kill if a human is close enough - if(pyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2) + if(coordPyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2) if(Math.random() < 1/3) { //One-third chance to mutilate (changePixel) if(Math.random() < 1/4) { //One-fourth chance to change to blood changePixel(newPixel,"zombie_blood",false); //blood is turned in place @@ -1587,7 +1730,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu 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" + if(coordPyth(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; @@ -1626,7 +1769,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu 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) { + if(coordPyth(pX,pY,nX,nY) <= 3.15) { pixel.hissing = true; if(!pixel.hissStart) { pixel.hissStart = pixelTicks; @@ -1939,7 +2082,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu 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" + if(coordPyth(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; @@ -1978,7 +2121,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu 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) { + if(coordPyth(pX,pY,nX,nY) <= 3.15) { pixel.hissing = true; if(!pixel.hissStart) { pixel.hissStart = pixelTicks; @@ -2441,7 +2584,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu 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" + if(coordPyth(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; @@ -2480,7 +2623,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu 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) { + if(coordPyth(pX,pY,nX,nY) <= 3.15) { pixel.hissing = true; if(!pixel.hissStart) { pixel.hissStart = pixelTicks; @@ -2980,7 +3123,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu 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" + if(coordPyth(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; @@ -3019,7 +3162,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu 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) { + if(coordPyth(pX,pY,nX,nY) <= 3.15) { pixel.hissing = true; if(!pixel.hissStart) { pixel.hissStart = pixelTicks; @@ -3510,7 +3653,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu 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" + if(coordPyth(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; @@ -3549,7 +3692,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu 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) { + if(coordPyth(pX,pY,nX,nY) <= 3.15) { pixel.hissing = true; if(!pixel.hissStart) { pixel.hissStart = pixelTicks; @@ -3849,7 +3992,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu pixel.following = true; //console.log(`Human detected at (${nX},${nY})`) //Infect/kill if a human is close enough - if(pyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2) + if(coordPyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2) if(Math.random() < 1/4) { //One-fourth chance to change to blood changePixel(newPixel,"blood",false); } else { //Remaining 3/4 chance to change to meat @@ -3895,7 +4038,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu pixel.following = true; //console.log(`Human detected at (${nX},${nY})`) //Infect/kill if a human is close enough - if(pyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2) + if(coordPyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2) if(Math.random() < 1/4) { //One-fourth chance to change to blood changePixel(newPixel,"blood",false); } else { //Remaining 3/4 chance to change to meat @@ -4287,7 +4430,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu pixel.following = true; //console.log(`Human detected at (${nX},${nY})`) //Infect/kill if a human is close enough - if(pyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2) + if(coordPyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2) if(Math.random() < 1/4) { //One-fourth chance to change to blood changePixel(newPixel,"blood",false); } else { //Remaining 3/4 chance to change to meat @@ -4393,7 +4536,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu pixel.following = true; //console.log(`Human detected at (${nX},${nY})`) //Infect/kill if a human is close enough - if(pyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2) + if(coordPyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2) if(Math.random() < 1/4) { //One-fourth chance to change to blood changePixel(newPixel,"blood",false); } else { //Remaining 3/4 chance to change to meat @@ -4442,6 +4585,471 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu | | +-----------------------------------+ */ + /* +++++++++++++++++++++++++++ + + Start skeleton elements + + +++++++++++++++++++++++++++ */ + + arrowExcludedElements = ["wall"]; + + arrowPlacementExcludedElements = ["skeleton_head", "skeleton_body", "arrow", "wall"]; + + elements.rock.hardness = 0.55; + + elements.arrow = { + cooldown: 2, + flippableX: true, + movable: true, + properties: { + flipY: false, + speed: 5, + fall: 0, + attached: false, + attachOffsets: [null, null], + penetrateCounter: 7, + }, + density: 2471, + color: "#cacdcf", + related: ["skeleton_body","skeleton_head"], + movable: true, + burn: 20, + //burnInto: "flint", + burnInto: "rock", + burnTime: 250, + breakInto: ["gravel","gravel","sawdust","feather"], + tick: function(pixel) { + if(pixel.attachOffsets.includes(null)) { + pixel.attached = false; + }; + if(pixel.attached) { + var attachCoords = [pixel.x+pixel.attachOffsets[0], pixel.y+pixel.attachOffsets[1]]; + var attachX = pixel.x + pixel.attachOffsets[0]; + var attachY = pixel.y + pixel.attachOffsets[1]; + if(isEmpty(attachX,attachY,true)) { + pixel.attached = false; + } else { + var attachPixel = pixelMap[attachX][attachY]; + var attachInfo = elements[attachPixel.element]; + var attachState = "solid"; + if(typeof(attachInfo.state) === "string") { + attachState = attachInfo.state; + }; + var attachBlacklistStates = ["liquid","gas"]; + if(attachBlacklistStates.includes(attachState)) { + pixel.attached = false; + }; + }; + } else { //Move if not attached + var speedForBreakMult = pythSpeed(pixel.speed,pixel.y); + var breakMult = speedForBreakMult/5; + + if(typeof(pixel.flipX) == undefined) { + pixel.flipX = !!Math.floor(Math.random() * 2); + }; + var dir = pixel.flipX ? -1 : 1; + if(Math.random() < (1/(pixel.speed**1.585))) { //1/0 is Infinity in JavaScript, so this should always be true at 0 speed) + pixel.fall++; + }; + //Horizontal movement + for(i = 0; i < pixel.speed; i++) { + if(outOfBounds(pixel.x+dir,pixel.y)) { + deletePixel(pixel.x,pixel.y); + break; + }; + if(!isEmpty(pixel.x+dir,pixel.y,true)) { + var otherPixel = pixelMap[pixel.x+dir][pixel.y]; + var otherElement = otherPixel.element; + var otherInfo = elements[otherElement]; + if(arrowExcludedElements.includes(otherElement)) { + pixel.attached = true; //attach + pixel.speed = 0; + pixel.fall = 0; + pixel.attachOffsets = [dir, 0]; + break; + }; + var otherDensity = (typeof(otherInfo.density) === "undefined" ? 1000 : otherInfo.density); + var swapChance = 1 - Math.max(0,(otherDensity / 2471)); + if(Math.random() < swapChance && pixel.penetrateCounter > 0) { + swapPixels(pixel,otherPixel); + arrowAltTb(otherPixel,breakMult); + pixel.speed = Math.max(0,--pixel.speed); + pixel.penetrateCounter--; + } else { + if(!arrowAltTb(otherPixel,breakMult)) { //if this didn't break it + pixel.attached = true; //attach + pixel.speed = 0; + pixel.fall = 0; + pixel.attachOffsets = [dir, 0]; + }; + }; + break; + } else { + tryMove(pixel,pixel.x+dir,pixel.y); + }; + }; + if(Math.random() < 0.1) { + pixel.speed = Math.max(0,--pixel.speed); + }; + + var dirY = 1; + //Vertical movement + if(typeof(pixel.flipY) !== "undefined") { + if(pixel.flipY) { + pixel.dirY = -1; + }; + }; + + for(j = 0; j < pixel.fall; j++) { + if(outOfBounds(pixel.x,pixel.y+dirY)) { + deletePixel(pixel.x,pixel.y); + break; + }; + if(!isEmpty(pixel.x,pixel.y+dirY,true)) { + var otherPixel = pixelMap[pixel.x][pixel.y+dirY]; + var otherElement = otherPixel.element; + var otherInfo = elements[otherElement]; + if(arrowExcludedElements.includes(otherElement)) { + pixel.attached = true; //attach + pixel.speed = 0; + pixel.fall = 0; + pixel.attachOffsets = [0, dirY]; + break; + }; + var otherDensity = (typeof(otherInfo.density) === "undefined" ? 1000 : otherInfo.density); + var swapChance = 1 - Math.max(0,(otherDensity / 2471)); + if(Math.random() < swapChance && pixel.penetrateCounter > 0) { + swapPixels(pixel,otherPixel); + arrowAltTb(otherPixel,breakMult); + pixel.speed = Math.max(0,--pixel.speed); + pixel.penetrateCounter--; + } else { + if(!arrowAltTb(otherPixel,breakMult)) { //if this didn't break it + pixel.attached = true; //attach + pixel.speed = 0; + pixel.fall = 0; + pixel.attachOffsets = [0, dirY]; + }; + }; + break; + } else { + tryMove(pixel,pixel.x,pixel.y+dirY); + }; + }; + + //End + }; + }, + }; + + elements.skeleton = { + color: ["#ebebe6", "#cfcfc8"], + category: "life", + properties: { + dead: false, + dir: 1, + panic: 0, + following: false, + }, + movable: true, + tick: function(pixel) { + if (isEmpty(pixel.x, pixel.y+1)) { + createPixel("skeleton_body", pixel.x, pixel.y+1); + pixel.element = "skeleton_head"; + pixel.color = pixelColorPick(pixel) + } + else if (isEmpty(pixel.x, pixel.y-1)) { + createPixel("skeleton_head", pixel.x, pixel.y-1); + pixelMap[pixel.x][pixel.y-1].color = pixel.color; + pixel.element = "skeleton_body"; + pixel.color = pixelColorPick(pixel) + } + else { + deletePixel(pixel.x, pixel.y); + } + }, + related: ["skeleton_body","skeleton_head"], + desc: "If this text is green or underlined, skeletons can spawn. Click here to toggle skeleton spawning. If it's on, skeletons (all types) can spawn through random events." + }; + + elements.skeleton_body = { + color: "#ebebe6", + category: "life", + hidden: true, + density: 1500, + state: "solid", + conduct: 25, + tempHigh: 250, + stateHigh: "bone", + burn: 10, + burnTime: 250, + burnInto: ["bone","ash","arrow"], + hardness: 0.55, + breakInto: ["bone","bone","bone","bone_marrow"], + 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, + chargeCounter: 20, + shooting: false, + }, + movable: true, + 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 == "skeleton_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 bone if pixelTicks-dead > 500 + if (pixelTicks-pixel.dead > 200) { + changePixel(pixel,"bone"); + } + return + } + + // Find the head + if (!isEmpty(pixel.x, pixel.y-1, true) && pixelMap[pixel.x][pixel.y-1].element == "skeleton_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 (bone marrow) + 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.shooting) { + if(pixel.chargeCounter <= 0) { + var bX = pixel.x + pixel.dir; + var bY = pixel.y - 1; + var arrowFlipX = null; + if(pixel.dir < 0) { + arrowFlipX = true; + } else if(pixel.dir > 0) { + arrowFlipX = false; + }; + if(!outOfBounds(bX,bY)) { + if(isEmpty(bX,bY)) { + createPixel("arrow",bX,bY); + pixelMap[bX][bY].flipX = arrowFlipX; + } else { + if(!arrowExcludedElements.includes(pixelMap[bX][bY].element) && !arrowPlacementExcludedElements.includes(pixelMap[bX][bY].element)) { + deletePixel(bX,bY); + createPixel("arrow",bX,bY); + pixelMap[bX][bY].flipX = arrowFlipX; + }; + }; + }; + pixel.chargeCounter = 20; + }; + if(pixel.chargeCounter > 0) { + pixel.chargeCounter--; + }; + }; + }, + }; + + elements.skeleton_head = { + color: ["#ebebe6", "#cfcfc8"], + category: "life", + hidden: true, + density: 1500, + state: "solid", + conduct: 25, + tempHigh: 250, + stateHigh: "bone", + burn: 10, + burnTime: 250, + burnInto: ["bone","ash","arrow"], + hardness: 0.55, + breakInto: ["bone","bone","bone","bone_marrow"], + 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, + }, + movable: true, + tick: function(pixel) { + doHeat(pixel); + doBurning(pixel); + doElectricity(pixel); + if (pixel.dead) { + // Turn into bone if pixelTicks-dead > 500 + if (pixelTicks-pixel.dead > 200) { + changePixel(pixel,"bone"); + } + return + } + + // Find the body + if (!isEmpty(pixel.x, pixel.y+1, true) && pixelMap[pixel.x][pixel.y+1].element == "skeleton_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; + + //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; + body.shooting = true; + }; + } 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; + body.shooting = true; + }; + } else { + //console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`) + break; + }; + }; + }; + }; + }; + + if(Math.random() < 0.01) { //1% chance each tick to lose interest + pixel.following = false; + //console.log("Meh."); + }; + }, + }; + + /* ------------------------- + - End skeleton elements - + ------------------------- */ + /* Creeper generation ___#___#___#___#___#___#___#___#___#___ __#___#___#___#___#___#___#___#___#___# @@ -4880,7 +5488,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu 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" + if(coordPyth(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; @@ -4919,7 +5527,7 @@ if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(explodeAtPlu 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) { + if(coordPyth(pX,pY,nX,nY) <= 3.15) { pixel.hissing = true; if(!pixel.hissStart) { pixel.hissStart = pixelTicks;