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({