| @@ -6,6 +6,14 @@ function attack(attacker, defender, baseDamage) { | |||
| return damage; | |||
| } | |||
| function isNormal(entity) { | |||
| return entity.grappled != true; | |||
| } | |||
| function isGrappled(entity) { | |||
| return entity.grappled == true; | |||
| } | |||
| function punchAttack(attacker) { | |||
| return { | |||
| name: "Punch", | |||
| @@ -15,7 +23,9 @@ function punchAttack(attacker) { | |||
| }, | |||
| attackPlayer: function(defender) { | |||
| return "The " + attacker.description() + " punches you for " + attack(attacker, defender, attacker.str) + " damage"; | |||
| } | |||
| }, requirements: [ | |||
| function(attacker, defender) { return isNormal(attacker) && isNormal(defender); } | |||
| ] | |||
| }; | |||
| } | |||
| @@ -28,6 +38,92 @@ function flankAttack(attacker) { | |||
| }, | |||
| attackPlayer: function(defender) { | |||
| return "The " + attacker.description() + " runs past you, then turns and hits you for " + attack(attacker, defender, attacker.str) + " damage"; | |||
| }, requirements: [ | |||
| function(attacker, defender) { return isNormal(attacker) && isNormal(defender); } | |||
| ] | |||
| }; | |||
| } | |||
| function grapple(attacker) { | |||
| return { | |||
| name: "Grapple", | |||
| desc: "Try to grab your opponent", | |||
| attack: function(defender) { | |||
| let success = Math.random() < 0.5; | |||
| if (success) { | |||
| defender.grappled = true; | |||
| return "You charge at the " + defender.description() + ", tackling them and knocking them to the ground."; | |||
| } else { | |||
| return "You charge at the " + defender.description() + ", but they dodge out of the way!"; | |||
| } | |||
| }, | |||
| attackPlayer: function(defender) { | |||
| let success = Math.random() < 0.5; | |||
| if (success) { | |||
| defender.grappled = true; | |||
| return "The " + attacker.description() + " lunges at you, pinning you to the floor!"; | |||
| } else { | |||
| return "The " + attacker.description() + " tries to tackle you, but you deftly avoid them."; | |||
| } | |||
| }, | |||
| requirements: [ | |||
| function(attacker, defender) { return isNormal(attacker) && isNormal(defender); } | |||
| ] | |||
| }; | |||
| } | |||
| function grappleDevour(attacker) { | |||
| return { | |||
| name: "Devour", | |||
| desc: "Try to consume your grappled opponent", | |||
| attack: function(defender) { | |||
| let success = Math.random() < 0.25 + (1 - defender.health / defender.maxHealth) * 0.75; | |||
| if (success) { | |||
| attacker.stomach.feed(defender); | |||
| defender.grappled = false; | |||
| changeMode("explore"); | |||
| return "You open your jaws wide, stuffing the " + defender.description() + "'s head into your gullet and greedily wolfing them down. Delicious."; | |||
| } else { | |||
| return "Your jaws open wide, but the " + defender.description() + " manages to avoid becoming " + attacker.species + " chow."; | |||
| } | |||
| }, | |||
| attackPlayer: function(defender) { | |||
| let success = Math.random() < 0.5 + (1 - defender.health / defender.maxHealth)*0.5 && Math.random() < 0.5; | |||
| if(success) { | |||
| defender.grappled = false; | |||
| changeMode("eaten"); | |||
| return "The " + attacker.description() + " forces your head into their sloppy jaws, devouring you despite your frantic struggles. Glp."; | |||
| } else { | |||
| return "The " + attacker.description() + " tries to swallow you down, but you manage to resist their hunger."; | |||
| } | |||
| }, requirements: [ | |||
| function(attacker, defender) { return isNormal(attacker) && isGrappled(defender); } | |||
| ] | |||
| }; | |||
| } | |||
| function grappleRelease(attacker) { | |||
| return { | |||
| name: "Release", | |||
| desc: "Release your opponent", | |||
| attack: function(defender) { | |||
| defender.grappled = false; | |||
| return "You throw the " + defender.description() + " back, dealing " + attack(attacker, defender, attacker.str*1.5) + " damage"; | |||
| }, requirements: [ | |||
| function(attacker, defender) { return isNormal(attacker) && isGrappled(defender); } | |||
| ] | |||
| }; | |||
| } | |||
| function pass(attacker) { | |||
| return { | |||
| name: "Pass", | |||
| desc: "You can't do anything!", | |||
| attack: function(defender) { | |||
| return "You do nothing."; | |||
| }, | |||
| attackPlayer: function(defender) { | |||
| return "The " + attacker.description() + " does nothing."; | |||
| } | |||
| }; | |||
| } | |||
| @@ -46,7 +142,7 @@ function devourPlayer(attacker) { | |||
| changeMode("eaten"); | |||
| return "The voracious " + attacker.description() + " pins you down and devours you in seconds."; | |||
| } | |||
| } | |||
| }; | |||
| } | |||
| function leer(attacker) { | |||
| @@ -11,6 +11,7 @@ let time = 9*60*60; | |||
| let newline = " "; | |||
| let player = new Player(); | |||
| let playerAttacks = []; | |||
| let respawnRoom; | |||
| @@ -20,6 +21,11 @@ let prefs = { | |||
| } | |||
| }; | |||
| function filterValid(options, attacker, defender) { | |||
| let filtered = options.filter(option => option.conditions == undefined || option.conditions.reduce((result, test) => result && test(prefs), true)); | |||
| return filtered.filter(option => option.requirements == undefined || option.requirements.reduce((result, test) => result && test(attacker, defender), true)); | |||
| } | |||
| function round(number, digits) { | |||
| return Math.round(number * Math.pow(10,digits)) / Math.pow(10,digits); | |||
| } | |||
| @@ -96,11 +102,16 @@ function updateCombat() { | |||
| list.removeChild(list.firstChild); | |||
| } | |||
| for (let i = 0; i < player.attacks.length; i++) { | |||
| playerAttacks = filterValid(player.attacks, player, currentFoe); | |||
| if (playerAttacks.length == 0) | |||
| playerAttacks = [player.backupAttack]; | |||
| for (let i = 0; i < playerAttacks.length; i++) { | |||
| let li = document.createElement("li"); | |||
| let button = document.createElement("button"); | |||
| button.classList.add("combat-button"); | |||
| button.innerHTML = player.attacks[i].name; | |||
| button.innerHTML = playerAttacks[i].name; | |||
| button.addEventListener("click", function() { attackClicked(i); } ); | |||
| button.addEventListener("mouseover", function() { attackHovered(i); } ); | |||
| button.addEventListener("mouseout", function() { document.getElementById("combat-desc").innerHTML = ""; } ); | |||
| @@ -248,6 +259,7 @@ function generateSettings() { | |||
| function applySettings(settings) { | |||
| player.name = settings.name; | |||
| player.species = settings.species; | |||
| for (let key in settings) { | |||
| if (settings.hasOwnProperty(key)) { | |||
| @@ -309,22 +321,19 @@ function respawn(respawnRoom) { | |||
| } | |||
| function startCombat(opponent) { | |||
| changeMode("combat"); | |||
| currentFoe = opponent; | |||
| changeMode("combat"); | |||
| update(["Oh shit it's a " + opponent.description()]); | |||
| } | |||
| function attackClicked(index) { | |||
| update([player.attacks[index].attack(currentFoe)]); | |||
| update([playerAttacks[index].attack(currentFoe)]); | |||
| if (currentFoe.health <= 0) { | |||
| update(["The " + currentFoe.description() + " falls to the ground!"]); | |||
| startDialog(new FallenFoe(currentFoe)); | |||
| } else { | |||
| let attacks = currentFoe.attacks.filter(attack => attack.conditions == undefined || attack.conditions.reduce((result, test) => result && test(prefs), true)); | |||
| attacks = attacks.filter(attack => attack.requirements == undefined || attack.requirements.reduce((result, test) => result && test(currentFoe, player), true)); | |||
| let attack = pick(attacks); | |||
| } else if (mode == "combat") { | |||
| let attack = pick(filterValid(currentFoe.attacks, currentFoe, player)); | |||
| if (attack == null) { | |||
| attack = currentFoe.backupAttack; | |||
| @@ -344,7 +353,7 @@ function attackClicked(index) { | |||
| } | |||
| function attackHovered(index) { | |||
| document.getElementById("combat-desc").innerHTML = player.attacks[index].desc; | |||
| document.getElementById("combat-desc").innerHTML = playerAttacks[index].desc; | |||
| } | |||
| function struggleClicked(index) { | |||
| @@ -357,10 +366,7 @@ function struggleClicked(index) { | |||
| if (result.escape) { | |||
| changeMode("explore"); | |||
| } else { | |||
| let digests = currentFoe.digests.filter(digest => digest.conditions == undefined || digest.conditions.reduce((result, test) => result && test(prefs), true)); | |||
| digests = digests.filter(digest => digest.requirements == undefined || digest.requirements.reduce((result, test) => result && test(currentFoe, player), true)); | |||
| let digest = pick(digests); | |||
| let digest = pick(filterValid(currentFoe.digests, currentFoe, player)); | |||
| if (digest == null) { | |||
| digest = currentFoe.backupDigest; | |||
| @@ -29,7 +29,12 @@ function Player(name = "Player") { | |||
| this.attacks.push(new punchAttack(this)); | |||
| this.attacks.push(new flankAttack(this)); | |||
| this.attacks.push(new grapple(this)); | |||
| this.attacks.push(new grappleDevour(this)); | |||
| this.attacks.push(new grappleRelease(this)); | |||
| this.backupAttack = new pass(this); | |||
| this.str = 15; | |||
| this.dex = 15; | |||
| this.con = 15; | |||
| @@ -53,6 +58,11 @@ function Anthro() { | |||
| this.attacks.push(new punchAttack(this)); | |||
| this.attacks.push(new flankAttack(this)); | |||
| this.attacks.push(new grapple(this)); | |||
| this.attacks.push(new grappleDevour(this)); | |||
| this.backupAttack = new pass(this); | |||
| this.struggles = []; | |||
| @@ -269,7 +279,7 @@ function plead(predator) { | |||
| name: "Plead", | |||
| desc: "Ask very, very nicely for the predator to let you go. More effective if you haven't hurt your predator.", | |||
| struggle: function(player) { | |||
| let escape = Math.random() < predator.health / predator.maxHealth; | |||
| let escape = Math.random() < predator.health / predator.maxHealth && Math.random() < 0.33; | |||
| if (escape) { | |||
| return { | |||
| @@ -291,7 +301,7 @@ function struggle(predator) { | |||
| name: "Struggle", | |||
| desc: "Try to squirm free. More effective if you've hurt your predator.", | |||
| struggle: function(player) { | |||
| let escape = Math.random() > predator.health / predator.maxHealth; | |||
| let escape = Math.random() > predator.health / predator.maxHealth && Math.random() < 0.33; | |||
| if (escape) { | |||
| return { | |||