From c40614e3abd027eb678fc9a23835a6d14a85feaf Mon Sep 17 00:00:00 2001 From: Fen Dweller Date: Sat, 18 Jun 2022 10:50:11 -0400 Subject: [PATCH] Add status effects to stomachs This also restores preAction and preReceiveAction effects, which were previouosly ignored. To support this, findResult and mapUntil were added to the prototype for arrays. These allow for functions with side effects to be searched or mapped over. --- src/game/combat.ts | 30 ++++++++++++++++++++ src/game/combat/effects.ts | 15 +++++++--- src/game/creature.ts | 5 +++- src/game/creatures/monsters/werewolf.ts | 34 +++++++++++++++++++++-- src/game/vore.ts | 16 ++++++++--- src/main.ts | 37 +++++++++++++++++++++++++ 6 files changed, 126 insertions(+), 11 deletions(-) diff --git a/src/game/combat.ts b/src/game/combat.ts index 47dae17..51e6fb7 100644 --- a/src/game/combat.ts +++ b/src/game/combat.ts @@ -481,6 +481,34 @@ export abstract class Action { } try (user: Creature, targets: Array): LogEntry { + // Check if any pre-action effect will cancel this action. + const preActionResults = user.effects.mapUntil(effect => effect.preAction(user), result => result.prevented) + const preActionLogs = new LogLines(...preActionResults.map(result => result.log)) + + if (preActionResults.some(result => result.prevented)) { + return preActionLogs + } + + // Check if any pre-receive-action effect will cancel this action. + const preReceiveActionResults = targets.mapUntil(target => { + const outcome = target.effects.mapUntil(effect => effect.preReceiveAction(user, target), result => result.prevented) + + return outcome + }, results => results.some(result => result.prevented)) + + console.log(preReceiveActionResults) + + const preReceiveActionLogs = new LogLines(...preReceiveActionResults.flatMap( + target => target.map(result => result.log) + )) + + if (preReceiveActionResults.some(results => results.some(result => result.prevented))) { + return new LogLines( + preActionLogs, + preReceiveActionLogs + ) + } + const results = targets.map(target => { const failReason = this.tests.find(test => !test.test(user, target)) if (failReason !== undefined) { @@ -499,6 +527,8 @@ export abstract class Action { }) return new LogLines( + preActionLogs, + preReceiveActionLogs, ...results.map(result => result.log), this.executeAll( user, diff --git a/src/game/combat/effects.ts b/src/game/combat/effects.ts index b736408..9f74dde 100644 --- a/src/game/combat/effects.ts +++ b/src/game/combat/effects.ts @@ -161,10 +161,17 @@ export class UntouchableEffect extends StatusEffect { super("Untouchable", "Cannot be attacked", "fas fa-times") } - preAttack (creature: Creature, attacker: Creature) { - return { - prevented: true, - log: new LogLine(`${creature.name.capital} cannot be attacked.`) + preReceiveAction (user: Creature, target: Creature) { + if (user !== target) { + return { + prevented: true, + log: new LogLine(`${target.name.capital} cannot be attacked.`) + } + } else { + return { + prevented: false, + log: nilLog + } } } } diff --git a/src/game/creature.ts b/src/game/creature.ts index 4e2dbb5..d2079d2 100644 --- a/src/game/creature.ts +++ b/src/game/creature.ts @@ -41,12 +41,14 @@ export class Creature extends Entity { desc = "Some creature"; get effects (): Array { - return (this.statusEffects as Effective[]).concat( + const effects: Array = (this.statusEffects as Effective[]).concat( Object.values(this.equipment).filter(item => item !== undefined).flatMap( item => (item as Equipment).effects ), this.perks ) + + return effects } statusEffects: Array = []; @@ -260,6 +262,7 @@ export class Creature extends Entity { this.statusEffects.forEach(effect => { results.push(effect) }) + return results } diff --git a/src/game/creatures/monsters/werewolf.ts b/src/game/creatures/monsters/werewolf.ts index b5b4a7e..c93493a 100644 --- a/src/game/creatures/monsters/werewolf.ts +++ b/src/game/creatures/monsters/werewolf.ts @@ -1,8 +1,8 @@ import { VoreAI } from '@/game/ai' -import { DamageType, Side, Stat, StatDamageFormula, Vigor } from '@/game/combat' +import { DamageType, Side, Stat, StatDamageFormula, StatusEffect, Vigor } from '@/game/combat' import { DigestionPowerEffect } from '@/game/combat/effects' import { Creature } from '@/game/creature' -import { LogEntry, LogLine } from '@/game/interface' +import { LogEntry, LogLine, nilLog } from '@/game/interface' import { ImproperNoun, MalePronouns, ObjectPronouns, Preposition, Verb } from '@/game/language' import { anyVore, ConnectionDirection, Container, Stomach, Throat, transferDescription } from '@/game/vore' import * as Words from '@/game/words' @@ -40,6 +40,36 @@ export default class Werewolf extends Creature { ]) ) + stomach.effects.push(new class extends StatusEffect { + constructor () { + super( + "Pinned", + "Prey sometimes can't move.", + "fas fa-sun" + ) + } + + onApply (creature: Creature): LogEntry { + return new LogLine( + `${stomach.owner.name.capital.possessive} ${stomach.name} is incredibly tight, gripping ${creature.name.objective} like a vice!` + ) + } + + preAction (creature: Creature): { prevented: boolean; log: LogEntry } { + if (Math.random() < 0.5) { + return { + prevented: true, + log: new LogLine(`${creature.name.capital} can't move!`) + } + } else { + return { + prevented: false, + log: nilLog + } + } + } + }()) + this.addContainer(throat) this.addContainer(stomach) diff --git a/src/game/vore.ts b/src/game/vore.ts index cea9404..e91f918 100644 --- a/src/game/vore.ts +++ b/src/game/vore.ts @@ -1,4 +1,4 @@ -import { Damage, DamageType, Actionable, Action, Vigor, DamageInstance, DamageFormula, ConstantDamageFormula } from '@/game/combat' +import { Damage, DamageType, Actionable, Action, Vigor, DamageInstance, DamageFormula, ConstantDamageFormula, StatusEffect } from '@/game/combat' import { LogLines, LogEntry, LogLine, nilLog, RandomEntry, FormatEntry, FormatOpt } from '@/game/interface' import { Noun, ImproperNoun, Verb, RandomWord, Word, Preposition, ToBe, Adjective } from '@/game/language' import { RubAction, DevourAction, ReleaseAction, StruggleAction, TransferAction, StruggleMoveAction } from '@/game/combat/actions' @@ -65,6 +65,8 @@ export interface Container extends Actionable { connections: Array; + effects: Array; + wall: Wall | null; fluid: Fluid | null; gas: Gas | null; @@ -127,6 +129,8 @@ export abstract class DefaultContainer implements Container { voreRelay = new VoreRelay() + effects: Array = [] + consumeVerb = new Verb('devour') consumePreposition = new Preposition("into") releaseVerb = new Verb('release', 'releases', 'releasing', 'released') @@ -226,6 +230,8 @@ export abstract class DefaultContainer implements Container { this.contents.push(prey) prey.containedIn = this + const effectResults = this.effects.map(effect => prey.applyEffect(effect)) + const results = [ this.voreRelay.dispatch("onEntered", this, { prey: prey }), prey.voreRelay.dispatch("onEntered", this, { prey: prey }) @@ -233,7 +239,7 @@ export abstract class DefaultContainer implements Container { this.owner.effects.forEach(effect => results.push(effect.postEnter(this.owner, prey, this))) - return new LogLines(...results) + return new LogLines(...results, ...effectResults) } exit (prey: Creature): LogEntry { @@ -244,12 +250,14 @@ export abstract class DefaultContainer implements Container { this.owner.containedIn.contents.push(prey) } + const effectResults = this.effects.map(effect => prey.removeEffect(effect)) + const results = [ this.voreRelay.dispatch("onExited", this, { prey: prey }), prey.voreRelay.dispatch("onExited", this, { prey: prey }) ] - return new LogLines(...results) + return new LogLines(...results, ...effectResults) } struggle (prey: Creature): LogEntry { @@ -297,7 +305,7 @@ export abstract class DefaultContainer implements Container { tickLine (user: Creature, target: Creature, args: { damage: Damage }): LogEntry { const options = [ - new LogLine(`${user.name.capital} ${Words.Churns.present}} ${target.name.objective} ${this.strugglePreposition} ${user.pronouns.possessive} ${this.name} for `, args.damage.renderShort(), `.`), + new LogLine(`${user.name.capital} ${Words.Churns.singular} ${target.name.objective} ${this.strugglePreposition} ${user.pronouns.possessive} ${this.name} for `, args.damage.renderShort(), `.`), new LogLine(`${user.name.capital.possessive} ${this.name} ${this.name.conjugate(Words.Churns)}, ${Words.Churns.present} ${target.name.objective} for `, args.damage.renderShort(), `.`), new LogLine(`${target.name.capital} ${target.name.conjugate(Words.Struggle)} ${this.strugglePreposition} ${user.name.possessive} ${Words.Slick} ${this.name} as it ${Words.Churns.singular} ${target.pronouns.objective} for `, args.damage.renderShort(), `.`) ] diff --git a/src/main.ts b/src/main.ts index ce48193..f143901 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,6 +6,8 @@ declare global { joinGeneral (item: T, endItem: T|null): Array; /* eslint-disable-next-line */ unique (predicate?: (elem: T) => any): Array; + findResult (func: (elem: T) => U, predicate: (result: U) => boolean): U | undefined; + mapUntil (func: (elem: T) => U, predicate: (result: U) => boolean): Array; } } @@ -33,6 +35,41 @@ Array.prototype.unique = function (predicate?: (elem: T) => any): Array { return result } +/* eslint-disable-next-line */ +Array.prototype.findResult = function (func: (elem: T) => U, predicate: (result: U) => boolean): U | undefined { + for (let i = 0; i < this.length; i++) { + const elem: T = this[i] + const result: U = func(elem) + const decision = predicate(result) + if (decision) { + return result + } + } + return undefined +} + +/** + * Maps over the array until the predicate is satisfied. + */ +/* eslint-disable-next-line */ +Array.prototype.mapUntil = function (func: (elem: T) => U, predicate: (result: U) => boolean): Array { + const results: Array = [] + + for (let i = 0; i < this.length; i++) { + const elem: T = this[i] + const result: U = func(elem) + const decision = predicate(result) + + results.push(result) + + if (decision) { + break + } + } + + return results +} + Vue.config.productionTip = false new Vue({