munch
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

669 lines
19 KiB

  1. function ForestExplore() {
  2. GameObject.call(this, "Explore the Forest");
  3. this.actions.push({
  4. "name": "Explore",
  5. "action": function() {
  6. let outcome = Math.random();
  7. advanceTime(60*30 * (Math.random() * 0.2 + 0.9));
  8. if (outcome < 0.35) {
  9. currentRoom.flags.exit = true;
  10. update(["You find a way back!"]);
  11. } else if (outcome < 0.5) {
  12. startCombat(new Wolf());
  13. } else if (outcome < 0.6) {
  14. startCombat(new AlphaWolf());
  15. } else if (outcome < 0.75) {
  16. startCombat(new Anaconda());
  17. } else {
  18. update(["You explore the forest for a while, but don't find anything."]);
  19. }
  20. }
  21. });
  22. this.actions.push({
  23. "name": "Leave",
  24. "action": function() {
  25. moveToByName("East Trail", "You leave the forest");
  26. },
  27. "conditions": [
  28. function(player) {
  29. return currentRoom.flags.exit;
  30. }
  31. ]
  32. });
  33. }
  34. function Wolf() {
  35. Creature.call(this, "Wolf", 10, 15, 15);
  36. this.hasName = false;
  37. this.description = function(prefix) { return prefix + " wolf"; };
  38. this.attacks = [];
  39. this.attacks.push(wolfBite(this));
  40. this.attacks.push(wolfHowl(this));
  41. this.attacks.push(wolfTackle(this));
  42. this.attacks.push(wolfTackleBite(this));
  43. this.attacks.push(wolfTackleSwallow(this));
  44. this.attacks.push(grappledStruggle(this));
  45. this.backupAttack = pass(this);
  46. this.struggles = [];
  47. this.struggles.push(new struggle(this));
  48. this.struggles.push(new submit(this));
  49. this.digests = [];
  50. this.digests.push(wolfDigest(this));
  51. this.digests.push(wolfBelch(this));
  52. this.flags.stage = "combat";
  53. this.startCombat = function(player) {
  54. 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 <i>hungry.</i>"];
  55. };
  56. this.finishCombat = function() {
  57. if (this.flags.stage == "combat")
  58. return [this.description("The") + " knocks you to the ground. You bash your head on a rock and black out."];
  59. else if (this.flags.stage == "oral")
  60. return ["You fall limp in " + this.description("the") + "'s roiling guts, melting away to feed the mangy predator for a good, long time..."];
  61. };
  62. this.status = function(player) {
  63. return [];
  64. };
  65. }
  66. function AlphaWolf() {
  67. Creature.call(this, "Alpha Wolf", 20, 20, 20);
  68. this.hasName = false;
  69. this.description = function(prefix) { return prefix + " alpha wolf"; };
  70. this.attacks = [];
  71. this.attacks.push(wolfBite(this));
  72. this.attacks.push(wolfHowl(this));
  73. this.attacks.push(wolfTackle(this));
  74. this.attacks.push(wolfTackleBite(this));
  75. this.attacks.push(wolfTackleSwallow(this));
  76. this.attacks.push(wolfSwallow(this));
  77. this.attacks.push(grappledStruggle(this));
  78. this.attacks.push(grappledReverse(this));
  79. this.backupAttack = pass(this);
  80. this.struggles = [];
  81. this.struggles.push(new struggle(this));
  82. this.struggles.push(new submit(this));
  83. this.digests = [];
  84. this.digests.push(wolfDigest(this));
  85. this.digests.push(wolfBelch(this));
  86. this.flags.stage = "combat";
  87. this.startCombat = function(player) {
  88. 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."];
  89. };
  90. this.finishCombat = function() {
  91. if (this.flags.stage == "combat")
  92. return [this.description("The") + " knocks you to the ground. You bash your head on a rock and black out."];
  93. else if (this.flags.stage == "oral")
  94. return ["You fall limp in " + this.description("the") + "'s roiling guts, melting away to feed the mangy predator for a good, long time..."];
  95. };
  96. this.status = function(player) {
  97. return [];
  98. };
  99. }
  100. function wolfBite(attacker) {
  101. return {
  102. attackPlayer: function(defender){
  103. let damage = attack(attacker, defender, attacker.str);
  104. return [attacker.description("The") + " jumps at you, biting for " + damage + " damage"];
  105. },
  106. requirements: [
  107. function(attacker, defender) {
  108. return attacker.flags.stage == "combat";
  109. },
  110. function(attacker, defender) {
  111. return !attacker.flags.grappled && !defender.flags.grappled;
  112. }
  113. ],
  114. priority: 1,
  115. weight: function(attacker, defender) { return 1 + defender.health/defender.maxHealth; }
  116. };
  117. }
  118. function wolfHowl(attacker) {
  119. return {
  120. attackPlayer: function(defender){
  121. attacker.statBuffs.push(new StatBuff("str", 1.25));
  122. return [attacker.description("The") + " backs up and lets out a long, wailing howl.",newline,"It seems emboldened."];
  123. },
  124. requirements: [
  125. function(attacker, defender) {
  126. return attacker.flags.stage == "combat";
  127. },
  128. function(attacker, defender) {
  129. return !attacker.flags.grappled && !defender.flags.grappled;
  130. }
  131. ],
  132. priority: 1,
  133. weight: function(attacker, defender) { return 0.25; }
  134. };
  135. }
  136. function wolfTackle(attacker) {
  137. return {
  138. attackPlayer: function(defender){
  139. defender.flags.grappled = true;
  140. return [attacker.description("The") + " leaps on top of you, pinning you to the ground!"];
  141. },
  142. requirements: [
  143. function(attacker, defender) {
  144. return attacker.flags.stage == "combat";
  145. },
  146. function(attacker, defender) {
  147. return !attacker.flags.grappled && !defender.flags.grappled;
  148. }
  149. ],
  150. priority: 1,
  151. weight: function(attacker, defender) { return 1.25 - defender.health/defender.maxHealth; }
  152. };
  153. }
  154. function wolfTackleBite(attacker) {
  155. return {
  156. attackPlayer: function(defender){
  157. let damage = attack(attacker, defender, attacker.str * 1.5);
  158. return pickRandom([
  159. ["Pain shoots through your arm as " + attacker.description("the") + " bites it for " + damage + " damage!"],
  160. ["You struggle against " + attacker.description("the") + " as it bites your shoulder for " + damage + " damage."],
  161. [attacker.description("The") + "'s claws dig into your legs for " + damage + " damage."]
  162. ]);
  163. },
  164. requirements: [
  165. function(attacker, defender) {
  166. return attacker.flags.stage == "combat";
  167. },
  168. function(attacker, defender) {
  169. return !attacker.flags.grappled && defender.flags.grappled;
  170. }
  171. ],
  172. priority: 1,
  173. weight: function(attacker, defender) { return 1 + defender.health/defender.maxHealth; }
  174. };
  175. }
  176. function wolfTackleSwallow(attacker) {
  177. return {
  178. attackPlayer: function(defender){
  179. attacker.flags.stage = "oral";
  180. changeMode("eaten");
  181. 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."];
  182. },
  183. conditions: [
  184. function(attacker, defender) {
  185. return defender.prefs.prey && defender.prefs.vore.oral > 0;
  186. }
  187. ],
  188. requirements: [
  189. function(attacker, defender) {
  190. return attacker.flags.stage == "combat";
  191. },
  192. function(attacker, defender) {
  193. return !attacker.flags.grappled && defender.flags.grappled;
  194. }
  195. ],
  196. priority: 1,
  197. weight: function(attacker, defender) { return 1; }
  198. };
  199. }
  200. function wolfSwallow(attacker) {
  201. return {
  202. attackPlayer: function(defender){
  203. let success = statCheck(attacker, defender, "dex") || defender.stamina == 0;
  204. if (success) {
  205. attacker.flags.stage = "oral";
  206. changeMode("eaten");
  207. 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. <i>Glrp, glllpkh, gulp</i> - and you're in its throat, thrashing and struggling as you plunge into the greedy beast's sloppy stomach."];
  208. } else {
  209. 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."];
  210. }
  211. },
  212. conditions: [
  213. function(attacker, defender) {
  214. return defender.prefs.prey && defender.prefs.vore.oral > 0;
  215. }
  216. ],
  217. requirements: [
  218. function(attacker, defender) {
  219. return attacker.flags.stage == "combat";
  220. },
  221. function(attacker, defender) {
  222. return !attacker.flags.grappled && !defender.flags.grappled;
  223. }
  224. ],
  225. priority: 1,
  226. weight: function(attacker, defender) { return 1; }
  227. };
  228. }
  229. function wolfDigest(attacker) {
  230. return {
  231. digest: function(defender){
  232. let damage = attack(attacker, defender, attacker.str * 3);
  233. return [attacker.description("The") + "'s churning guts wear you down."];
  234. },
  235. requirements: [
  236. function(attacker, defender) {
  237. return attacker.flags.stage == "oral";
  238. }
  239. ],
  240. priority: 1,
  241. weight: function(attacker, defender) { return 1; },
  242. gameover: function() { return "Digested by " + attacker.description("a"); }
  243. };
  244. }
  245. function wolfBelch(attacker) {
  246. return {
  247. digest: function(defender){
  248. defender.stamina -= 50;
  249. let damage = attack(attacker, defender, attacker.str * 2);
  250. return [attacker.description("The") + " lets out a crass <i>BELCH</i>, draining air from its snarling gut and squeezing you even tighter than before."];
  251. },
  252. requirements: [
  253. function(attacker, defender) {
  254. return attacker.flags.stage == "oral";
  255. }
  256. ],
  257. priority: 1,
  258. weight: function(attacker, defender) { return 1; },
  259. gameover: function() { return "Reduced to a belch by " + attacker.description("a"); }
  260. };
  261. }
  262. function Anaconda() {
  263. Creature.call(this, "Anaconda", 30, 10, 20);
  264. this.hasName = false;
  265. this.description = function(prefix) { return prefix + " anaconda"; };
  266. this.flags.state = "combat";
  267. this.flags.tail = {};
  268. this.flags.oral = {};
  269. this.flags.grapple = {};
  270. this.flags.stomach = {};
  271. this.stomachPull = function(amount) {
  272. this.flags.stomach.depth += amount;
  273. this.flags.stomach.depth = Math.max(0, this.flags.stomach.depth);
  274. this.flags.stomach.depth = Math.min(30, this.flags.stomach.depth);
  275. };
  276. this.status = function() {
  277. if (this.flags.state == "oral") {
  278. return ["Oral depth: " + this.flags.oral.depth];
  279. } else if (this.flags.state == "stomach") {
  280. return ["Stomach depth: " + this.flags.stomach.depth];
  281. } else {
  282. return [];
  283. }
  284. };
  285. let attacker = this;
  286. this.attacks = [];
  287. // hiss at player
  288. this.attacks.push({
  289. attackPlayer: function(defender) {
  290. return pickRandom([
  291. ["The anaconda raises its head and hisses"],
  292. ["The hulking serpent narrows its eyes and hisses at you"]
  293. ]);
  294. },
  295. requirements: [
  296. function(attacker, defender) {
  297. return attacker.flags.state == "combat";
  298. }
  299. ],
  300. priority: 1,
  301. weight: function(attacker, defender) { return 1; }
  302. });
  303. // slap player with tail
  304. this.attacks.push({
  305. attackPlayer: function(defender) {
  306. if (statHealthCheck(attacker, defender, "dex")) {
  307. let damage = attack(attacker, defender, attacker.str);
  308. return ["The snake's tail whips around, smacking you for " + damage + " damage!"];
  309. } else {
  310. return ["The serpent's tail lashes at you, but you manage to stay out of its way."];
  311. }
  312. },
  313. requirements: [
  314. function(attacker, defender) {
  315. return attacker.flags.state == "combat";
  316. }
  317. ],
  318. priority: 1,
  319. weight: function(attacker, defender) { return 1; }
  320. });
  321. // grab player with tail
  322. this.attacks.push({
  323. attackPlayer: function(defender) {
  324. if (statHealthCheck(attacker, defender, "dex")) {
  325. attacker.flags.state = "grapple";
  326. attacker.flags.tail.turns = 0;
  327. let damage = attack(attacker, defender, attacker.str/2);
  328. return ["The snake's tail whips around and grabs you! A tight embrace of smooth, cold scales grips your entire upper body, a lazy <i>clench</i> of muscle suppressing your meek struggles."];
  329. } else {
  330. return ["The anaconda tries to snatch you up in its tail. You dodge out of the way."];
  331. }
  332. },
  333. requirements: [
  334. function(attacker, defender) {
  335. return attacker.flags.state == "combat";
  336. }
  337. ],
  338. priority: 1,
  339. weight: function(attacker, defender) { return 4 - 4 * defender.healthPercentage(); }
  340. });
  341. // squeeze in tail
  342. this.attacks.push({
  343. attackPlayer: function(defender) {
  344. attacker.flags.tail.turns++;
  345. let damage = attack(attacker, defender, attacker.str / 2 * attacker.flags.tail.turns);
  346. defender.changeStamina(attacker.str / 2 * attacker.flags.tail.turns);
  347. return ["Snake squeeze."];
  348. },
  349. requirements: [
  350. function(attacker, defender) {
  351. return attacker.flags.state == "grapple";
  352. }
  353. ],
  354. priority: 1,
  355. weight: function(attacker, defender) { return defender.healthPercentage(); }
  356. });
  357. // swallow from tail
  358. this.attacks.push({
  359. attackPlayer: function(defender) {
  360. attacker.flags.state = "oral";
  361. attacker.flags.oral.depth = 1;
  362. return ["Snake vore."];
  363. },
  364. requirements: [
  365. function(attacker, defender) {
  366. return attacker.flags.state == "grapple";
  367. }
  368. ],
  369. priority: 1,
  370. weight: function(attacker, defender) { return defender.healthPercentage(); }
  371. });
  372. // swallow player
  373. this.attacks.push({
  374. attackPlayer: function(defender) {
  375. attacker.flags.oral.depth++;
  376. return ["Snake swallow."];
  377. },
  378. requirements: [
  379. function(attacker, defender) {
  380. return attacker.flags.state == "oral";
  381. }
  382. ],
  383. priority: 1,
  384. weight: function(attacker, defender) { return defender.healthPercentage(); }
  385. });
  386. // squeeze
  387. this.attacks.push({
  388. attackPlayer: function(defender) {
  389. attack(attacker, defender, attacker.str/2);
  390. defender.changeStamina(-attacker.str/2);
  391. return pickRandom([
  392. ["Powerful, rippling walls clench around your imprisoned body, wearing your out."],
  393. ["The peristaltic pressure peaks as you're crushed and ground, squeezed and smothered in the anaconda's powerful gullet."],
  394. ["The pressure is immense, squeezing and grinding your body like a stick of gum being chewed."]
  395. ]);
  396. },
  397. requirements: [
  398. function(attacker, defender) {
  399. return attacker.flags.state == "oral";
  400. }
  401. ],
  402. priority: 1,
  403. weight: function(attacker, defender) { return defender.healthPercentage(); }
  404. });
  405. // pull into stomach
  406. this.attacks.push({
  407. attackPlayer: function(defender) {
  408. attacker.flags.state = "stomach";
  409. attacker.flags.stomach.depth = 3;
  410. return ["Snake stomach."];
  411. },
  412. requirements: [
  413. function(attacker, defender) {
  414. return attacker.flags.state == "oral";
  415. },
  416. function(attacker, defender) {
  417. return attacker.flags.oral.depth >= 4;
  418. }
  419. ],
  420. priority: 2,
  421. weight: function(attacker, defender) { return defender.healthPercentage(); }
  422. });
  423. // digest
  424. this.attacks.push({
  425. attackPlayer: function(defender) {
  426. attack(attacker, defender, attacker.con / 5 + attacker.con / 20 * attacker.flags.stomach.depth);
  427. defender.changeStamina(-attacker.con / 5 - attacker.con / 20 * attacker.flags.stomach.depth);
  428. attacker.stomachPull(Math.floor(Math.random()*4+4));
  429. return ["Snake digest."];
  430. },
  431. requirements: [
  432. function(attacker, defender) {
  433. return attacker.flags.state == "stomach";
  434. }
  435. ],
  436. priority: 1,
  437. weight: function(attacker, defender) { return defender.healthPercentage(); }
  438. });
  439. /** PLAYER ATTACKS **/
  440. this.playerAttacks = [];
  441. // pass in combat
  442. this.playerAttacks.push(
  443. function(attacker) {
  444. return {
  445. name: "Pass",
  446. desc: "Do nothing",
  447. attack: function(defender) {
  448. return ["You do nothing."];
  449. },
  450. requirements: [
  451. function(attacker, defender) {
  452. return defender.flags.state == "combat";
  453. }
  454. ]
  455. };
  456. }
  457. );
  458. // struggle in coils
  459. this.playerAttacks.push(
  460. function(attacker) {
  461. return {
  462. name: "Struggle",
  463. desc: "Try to escape the coils!",
  464. attack: function(defender) {
  465. if (statHealthCheck(attacker, defender, "str")) {
  466. defender.flags.state = "combat";
  467. return ["Escaped"];
  468. } else {
  469. return ["No effect"];
  470. }
  471. },
  472. requirements: [
  473. function(attacker, defender) {
  474. return defender.flags.state == "grapple";
  475. }
  476. ]
  477. };
  478. }
  479. );
  480. // pass in coils
  481. this.playerAttacks.push(
  482. function(attacker) {
  483. return {
  484. name: "Submit",
  485. desc: "Do nothing",
  486. attack: function(defender) {
  487. return ["You do nothing."];
  488. },
  489. requirements: [
  490. function(attacker, defender) {
  491. return defender.flags.state == "grapple";
  492. }
  493. ]
  494. };
  495. }
  496. );
  497. // struggle in throat
  498. this.playerAttacks.push(
  499. function(attacker) {
  500. return {
  501. name: "Struggle",
  502. desc: "Try to escape the snake's gullet",
  503. attack: function(defender) {
  504. if (statHealthCheck(attacker, defender, "str")) {
  505. defender.flags.oral.depth--;
  506. if (defender.flags.oral.depth < 0) {
  507. defender.flags.state = "oral";
  508. return ["Escaped"];
  509. } else {
  510. return ["Up the throat"];
  511. }
  512. } else {
  513. return ["No effect"];
  514. }
  515. },
  516. requirements: [
  517. function(attacker, defender) {
  518. return defender.flags.state == "oral";
  519. }
  520. ]
  521. };
  522. }
  523. );
  524. // pass in throat
  525. this.playerAttacks.push(
  526. function(attacker) {
  527. return {
  528. name: "Submit",
  529. desc: "Do nothing",
  530. attack: function(defender) {
  531. return ["You do nothing."];
  532. },
  533. requirements: [
  534. function(attacker, defender) {
  535. return defender.flags.state == "oral";
  536. }
  537. ]
  538. };
  539. }
  540. );
  541. // struggle in stomach
  542. this.playerAttacks.push(
  543. function(attacker) {
  544. return {
  545. name: "Struggle",
  546. desc: "Try to struggle free!",
  547. attack: function(defender) {
  548. if (statHealthCheck(attacker, defender, "str") ||
  549. statHealthCheck(attacker, defender, "str")) {
  550. let distance = attacker.str / 10 + Math.floor(Math.random() * -attacker.str / 10);
  551. defender.stomachPull(-distance);
  552. if (defender.flags.stomach.depth <= 0) {
  553. if (statHealthCheck(attacker, defender, "str")) {
  554. defender.flags.state = "oral";
  555. defender.flags.oral.depth = 3;
  556. return ["Pulled into throat"];
  557. } else {
  558. return ["Stuck at entrance"];
  559. }
  560. } else {
  561. return ["Dragged self " + distance + " forward"];
  562. }
  563. } else {
  564. return ["Struggling didn't work"];
  565. }
  566. },
  567. requirements: [
  568. function(attacker, defender) {
  569. return defender.flags.state == "stomach";
  570. }
  571. ]
  572. };
  573. }
  574. );
  575. // pass in stomach
  576. this.playerAttacks.push(
  577. function(attacker) {
  578. return {
  579. name: "Rest",
  580. desc: "Rest and recover stamina",
  581. attack: function(defender) {
  582. attacker.changeStamina(attacker.maxStamina / 5);
  583. return ["You rest in the snake's squeezing stomach."];
  584. },
  585. requirements: [
  586. function(attacker, defender) {
  587. return defender.flags.state == "stomach";
  588. }
  589. ]
  590. };
  591. }
  592. );
  593. }