function ForestExplore() {
GameObject.call(this, "Explore the Forest");
this.actions.push({
"name": "Explore",
"action": function() {
let outcome = Math.random();
advanceTime(60*30 * (Math.random() * 0.2 + 0.9));
if (outcome < 0.35) {
currentRoom.flags.exit = true;
update(["You find a way back!"]);
} else if (outcome < 0.5) {
startCombat(new Wolf());
} else if (outcome < 0.6) {
startCombat(new AlphaWolf());
} else if (outcome < 0.75) {
startCombat(new Anaconda());
} else {
update(["You explore the forest for a while, but don't find anything."]);
}
}
});
this.actions.push({
"name": "Leave",
"action": function() {
moveToByName("East Trail", "You leave the forest");
},
"conditions": [
function(player) {
return currentRoom.flags.exit;
}
]
});
}
function Wolf() {
Creature.call(this, "Wolf", 10, 15, 15);
this.hasName = false;
this.description = function(prefix) { return prefix + " wolf"; };
this.attacks = [];
this.attacks.push(wolfBite(this));
this.attacks.push(wolfHowl(this));
this.attacks.push(wolfTackle(this));
this.attacks.push(wolfTackleBite(this));
this.attacks.push(wolfTackleSwallow(this));
this.attacks.push(grappledStruggle(this));
this.backupAttack = pass(this);
this.struggles = [];
this.struggles.push(new struggle(this));
this.struggles.push(new submit(this));
this.digests = [];
this.digests.push(wolfDigest(this));
this.digests.push(wolfBelch(this));
this.flags.stage = "combat";
this.startCombat = function(player) {
return ["A snapping twig grabs your attention. You turn and find yourself facing a large, mangy wolf. The cur stands at least half your height at the shoulder, and it looks hungry."];
};
this.finishCombat = function() {
if (this.flags.stage == "combat")
return [this.description("The") + " knocks you to the ground. You bash your head on a rock and black out."];
else if (this.flags.stage == "oral")
return ["You fall limp in " + this.description("the") + "'s roiling guts, melting away to feed the mangy predator for a good, long time..."];
};
this.status = function(player) {
return [];
};
}
function AlphaWolf() {
Creature.call(this, "Alpha Wolf", 20, 20, 20);
this.hasName = false;
this.description = function(prefix) { return prefix + " alpha wolf"; };
this.attacks = [];
this.attacks.push(wolfBite(this));
this.attacks.push(wolfHowl(this));
this.attacks.push(wolfTackle(this));
this.attacks.push(wolfTackleBite(this));
this.attacks.push(wolfTackleSwallow(this));
this.attacks.push(wolfSwallow(this));
this.attacks.push(grappledStruggle(this));
this.attacks.push(grappledReverse(this));
this.backupAttack = pass(this);
this.struggles = [];
this.struggles.push(new struggle(this));
this.struggles.push(new submit(this));
this.digests = [];
this.digests.push(wolfDigest(this));
this.digests.push(wolfBelch(this));
this.flags.stage = "combat";
this.startCombat = function(player) {
return ["A low growl sends a chill up your spine. You turn around slowly, coming face-to-face with a massive, snarling wolf. Nearly six feet tall at the shoulder, the beast is eyeing you up as a snack."];
};
this.finishCombat = function() {
if (this.flags.stage == "combat")
return [this.description("The") + " knocks you to the ground. You bash your head on a rock and black out."];
else if (this.flags.stage == "oral")
return ["You fall limp in " + this.description("the") + "'s roiling guts, melting away to feed the mangy predator for a good, long time..."];
};
this.status = function(player) {
return [];
};
}
function wolfBite(attacker) {
return {
attackPlayer: function(defender){
let damage = attack(attacker, defender, attacker.str);
return [attacker.description("The") + " jumps at you, biting for " + damage + " damage"];
},
requirements: [
function(attacker, defender) {
return attacker.flags.stage == "combat";
},
function(attacker, defender) {
return !attacker.flags.grappled && !defender.flags.grappled;
}
],
priority: 1,
weight: function(attacker, defender) { return 1 + defender.health/defender.maxHealth; }
};
}
function wolfHowl(attacker) {
return {
attackPlayer: function(defender){
attacker.statBuffs.push(new StatBuff("str", 1.25));
return [attacker.description("The") + " backs up and lets out a long, wailing howl.",newline,"It seems emboldened."];
},
requirements: [
function(attacker, defender) {
return attacker.flags.stage == "combat";
},
function(attacker, defender) {
return !attacker.flags.grappled && !defender.flags.grappled;
}
],
priority: 1,
weight: function(attacker, defender) { return 0.25; }
};
}
function wolfTackle(attacker) {
return {
attackPlayer: function(defender){
defender.flags.grappled = true;
return [attacker.description("The") + " leaps on top of you, pinning you to the ground!"];
},
requirements: [
function(attacker, defender) {
return attacker.flags.stage == "combat";
},
function(attacker, defender) {
return !attacker.flags.grappled && !defender.flags.grappled;
}
],
priority: 1,
weight: function(attacker, defender) { return 1.25 - defender.health/defender.maxHealth; }
};
}
function wolfTackleBite(attacker) {
return {
attackPlayer: function(defender){
let damage = attack(attacker, defender, attacker.str * 1.5);
return pickRandom([
["Pain shoots through your arm as " + attacker.description("the") + " bites it for " + damage + " damage!"],
["You struggle against " + attacker.description("the") + " as it bites your shoulder for " + damage + " damage."],
[attacker.description("The") + "'s claws dig into your legs for " + damage + " damage."]
]);
},
requirements: [
function(attacker, defender) {
return attacker.flags.stage == "combat";
},
function(attacker, defender) {
return !attacker.flags.grappled && defender.flags.grappled;
}
],
priority: 1,
weight: function(attacker, defender) { return 1 + defender.health/defender.maxHealth; }
};
}
function wolfTackleSwallow(attacker) {
return {
attackPlayer: function(defender){
attacker.flags.stage = "oral";
changeMode("eaten");
return ["You struggle against " + attacker.description("the") + ", but it's not enough - its greedy jaws envelop your head, then your shoulders. The hungry beast swallows you down in seconds, cramming you into its hot, slimy stomach."];
},
conditions: [
function(attacker, defender) {
return defender.prefs.prey && defender.prefs.vore.oral > 0;
}
],
requirements: [
function(attacker, defender) {
return attacker.flags.stage == "combat";
},
function(attacker, defender) {
return !attacker.flags.grappled && defender.flags.grappled;
}
],
priority: 1,
weight: function(attacker, defender) { return 1; }
};
}
function wolfSwallow(attacker) {
return {
attackPlayer: function(defender){
let success = statCheck(attacker, defender, "dex") || defender.stamina == 0;
if (success) {
attacker.flags.stage = "oral";
changeMode("eaten");
return [attacker.description("The") + " charges, closing the gap in the blink of an eye and jamming your upper body into its massive, drool-slathered maw. Glrp, glllpkh, gulp - and you're in its throat, thrashing and struggling as you plunge into the greedy beast's sloppy stomach."];
} else {
return [attacker.description("The") + " lunges at you, racing up with jaws splayed wide open. You leap to the side, barely avoiding the greedy beast's maw as it barrels past, growling and snapping in frustration."];
}
},
conditions: [
function(attacker, defender) {
return defender.prefs.prey && defender.prefs.vore.oral > 0;
}
],
requirements: [
function(attacker, defender) {
return attacker.flags.stage == "combat";
},
function(attacker, defender) {
return !attacker.flags.grappled && !defender.flags.grappled;
}
],
priority: 1,
weight: function(attacker, defender) { return 1; }
};
}
function wolfDigest(attacker) {
return {
digest: function(defender){
let damage = attack(attacker, defender, attacker.str * 3);
return [attacker.description("The") + "'s churning guts wear you down."];
},
requirements: [
function(attacker, defender) {
return attacker.flags.stage == "oral";
}
],
priority: 1,
weight: function(attacker, defender) { return 1; },
gameover: function() { return "Digested by " + attacker.description("a"); }
};
}
function wolfBelch(attacker) {
return {
digest: function(defender){
defender.stamina -= 50;
let damage = attack(attacker, defender, attacker.str * 2);
return [attacker.description("The") + " lets out a crass BELCH, draining air from its snarling gut and squeezing you even tighter than before."];
},
requirements: [
function(attacker, defender) {
return attacker.flags.stage == "oral";
}
],
priority: 1,
weight: function(attacker, defender) { return 1; },
gameover: function() { return "Reduced to a belch by " + attacker.description("a"); }
};
}
function Anaconda() {
Creature.call(this, "Anaconda", 30, 10, 20);
this.hasName = false;
this.description = function(prefix) { return prefix + " anaconda"; };
this.flags.state = "combat";
this.flags.tail = {};
this.flags.oral = {};
this.flags.grapple = {};
this.flags.stomach = {};
this.stomachPull = function(amount) {
this.flags.stomach.depth += amount;
this.flags.stomach.depth = Math.max(0, this.flags.stomach.depth);
this.flags.stomach.depth = Math.min(30, this.flags.stomach.depth);
};
this.status = function() {
if (this.flags.state == "oral") {
return ["Oral depth: " + this.flags.oral.depth];
} else if (this.flags.state == "stomach") {
return ["Stomach depth: " + this.flags.stomach.depth];
} else {
return [];
}
};
let attacker = this;
this.attacks = [];
// hiss at player
this.attacks.push({
attackPlayer: function(defender) {
return pickRandom([
["The anaconda raises its head and hisses"],
["The hulking serpent narrows its eyes and hisses at you"]
]);
},
requirements: [
function(attacker, defender) {
return attacker.flags.state == "combat";
}
],
priority: 1,
weight: function(attacker, defender) { return 1; }
});
// slap player with tail
this.attacks.push({
attackPlayer: function(defender) {
if (statHealthCheck(attacker, defender, "dex")) {
let damage = attack(attacker, defender, attacker.str);
return ["The snake's tail whips around, smacking you for " + damage + " damage!"];
} else {
return ["The serpent's tail lashes at you, but you manage to stay out of its way."];
}
},
requirements: [
function(attacker, defender) {
return attacker.flags.state == "combat";
}
],
priority: 1,
weight: function(attacker, defender) { return 1; }
});
// grab player with tail
this.attacks.push({
attackPlayer: function(defender) {
if (statHealthCheck(attacker, defender, "dex")) {
attacker.flags.state = "grapple";
attacker.flags.tail.turns = 0;
let damage = attack(attacker, defender, attacker.str/2);
return ["The snake's tail whips around and grabs you! A tight embrace of smooth, cold scales grips your entire upper body, a lazy clench of muscle suppressing your meek struggles."];
} else {
return ["The anaconda tries to snatch you up in its tail. You dodge out of the way."];
}
},
requirements: [
function(attacker, defender) {
return attacker.flags.state == "combat";
}
],
priority: 1,
weight: function(attacker, defender) { return 4 - 4 * defender.healthPercentage(); }
});
// squeeze in tail
this.attacks.push({
attackPlayer: function(defender) {
attacker.flags.tail.turns++;
let damage = attack(attacker, defender, attacker.str / 2 * attacker.flags.tail.turns);
defender.changeStamina(attacker.str / 2 * attacker.flags.tail.turns);
return ["Snake squeeze."];
},
requirements: [
function(attacker, defender) {
return attacker.flags.state == "grapple";
}
],
priority: 1,
weight: function(attacker, defender) { return defender.healthPercentage(); }
});
// swallow from tail
this.attacks.push({
attackPlayer: function(defender) {
attacker.flags.state = "oral";
attacker.flags.oral.depth = 1;
return ["Snake vore."];
},
requirements: [
function(attacker, defender) {
return attacker.flags.state == "grapple";
}
],
priority: 1,
weight: function(attacker, defender) { return defender.healthPercentage(); }
});
// swallow player
this.attacks.push({
attackPlayer: function(defender) {
attacker.flags.oral.depth++;
return ["Snake swallow."];
},
requirements: [
function(attacker, defender) {
return attacker.flags.state == "oral";
}
],
priority: 1,
weight: function(attacker, defender) { return defender.healthPercentage(); }
});
// squeeze
this.attacks.push({
attackPlayer: function(defender) {
attack(attacker, defender, attacker.str/2);
defender.changeStamina(-attacker.str/2);
return pickRandom([
["Powerful, rippling walls clench around your imprisoned body, wearing your out."],
["The peristaltic pressure peaks as you're crushed and ground, squeezed and smothered in the anaconda's powerful gullet."],
["The pressure is immense, squeezing and grinding your body like a stick of gum being chewed."]
]);
},
requirements: [
function(attacker, defender) {
return attacker.flags.state == "oral";
}
],
priority: 1,
weight: function(attacker, defender) { return defender.healthPercentage(); }
});
// pull into stomach
this.attacks.push({
attackPlayer: function(defender) {
attacker.flags.state = "stomach";
attacker.flags.stomach.depth = 3;
return ["Snake stomach."];
},
requirements: [
function(attacker, defender) {
return attacker.flags.state == "oral";
},
function(attacker, defender) {
return attacker.flags.oral.depth >= 4;
}
],
priority: 2,
weight: function(attacker, defender) { return defender.healthPercentage(); }
});
// digest
this.attacks.push({
attackPlayer: function(defender) {
attack(attacker, defender, attacker.con / 5 + attacker.con / 20 * attacker.flags.stomach.depth);
defender.changeStamina(-attacker.con / 5 - attacker.con / 20 * attacker.flags.stomach.depth);
attacker.stomachPull(Math.floor(Math.random()*4+4));
return ["Snake digest."];
},
requirements: [
function(attacker, defender) {
return attacker.flags.state == "stomach";
}
],
priority: 1,
weight: function(attacker, defender) { return defender.healthPercentage(); }
});
/** PLAYER ATTACKS **/
this.playerAttacks = [];
// pass in combat
this.playerAttacks.push(
function(attacker) {
return {
name: "Pass",
desc: "Do nothing",
attack: function(defender) {
return ["You do nothing."];
},
requirements: [
function(attacker, defender) {
return defender.flags.state == "combat";
}
]
};
}
);
// struggle in coils
this.playerAttacks.push(
function(attacker) {
return {
name: "Struggle",
desc: "Try to escape the coils!",
attack: function(defender) {
if (statHealthCheck(attacker, defender, "str")) {
defender.flags.state = "combat";
return ["Escaped"];
} else {
return ["No effect"];
}
},
requirements: [
function(attacker, defender) {
return defender.flags.state == "grapple";
}
]
};
}
);
// pass in coils
this.playerAttacks.push(
function(attacker) {
return {
name: "Submit",
desc: "Do nothing",
attack: function(defender) {
return ["You do nothing."];
},
requirements: [
function(attacker, defender) {
return defender.flags.state == "grapple";
}
]
};
}
);
// struggle in throat
this.playerAttacks.push(
function(attacker) {
return {
name: "Struggle",
desc: "Try to escape the snake's gullet",
attack: function(defender) {
if (statHealthCheck(attacker, defender, "str")) {
defender.flags.oral.depth--;
if (defender.flags.oral.depth < 0) {
defender.flags.state = "oral";
return ["Escaped"];
} else {
return ["Up the throat"];
}
} else {
return ["No effect"];
}
},
requirements: [
function(attacker, defender) {
return defender.flags.state == "oral";
}
]
};
}
);
// pass in throat
this.playerAttacks.push(
function(attacker) {
return {
name: "Submit",
desc: "Do nothing",
attack: function(defender) {
return ["You do nothing."];
},
requirements: [
function(attacker, defender) {
return defender.flags.state == "oral";
}
]
};
}
);
// struggle in stomach
this.playerAttacks.push(
function(attacker) {
return {
name: "Struggle",
desc: "Try to struggle free!",
attack: function(defender) {
if (statHealthCheck(attacker, defender, "str") ||
statHealthCheck(attacker, defender, "str")) {
let distance = attacker.str / 10 + Math.floor(Math.random() * -attacker.str / 10);
defender.stomachPull(-distance);
if (defender.flags.stomach.depth <= 0) {
if (statHealthCheck(attacker, defender, "str")) {
defender.flags.state = "oral";
defender.flags.oral.depth = 3;
return ["Pulled into throat"];
} else {
return ["Stuck at entrance"];
}
} else {
return ["Dragged self " + distance + " forward"];
}
} else {
return ["Struggling didn't work"];
}
},
requirements: [
function(attacker, defender) {
return defender.flags.state == "stomach";
}
]
};
}
);
// pass in stomach
this.playerAttacks.push(
function(attacker) {
return {
name: "Rest",
desc: "Rest and recover stamina",
attack: function(defender) {
attacker.changeStamina(attacker.maxStamina / 5);
return ["You rest in the snake's squeezing stomach."];
},
requirements: [
function(attacker, defender) {
return defender.flags.state == "stomach";
}
]
};
}
);
}