diff --git a/src/game/combat.ts b/src/game/combat.ts index 8a8c96a..a30d307 100644 --- a/src/game/combat.ts +++ b/src/game/combat.ts @@ -72,7 +72,10 @@ export enum DamageType { Pierce = "Pierce", Slash = "Slash", Crush = "Crush", - Acid = "Acid"} + Acid = "Acid", + Seduction = "Seduction", + Dominance = "Dominance" +} export interface DamageInstance { type: DamageType; @@ -155,10 +158,13 @@ export interface Combatant { } export abstract class Action { - abstract allowed (user: Creature, target: Creature): boolean + allowed (user: Creature, target: Creature): boolean { + return this.conditions.every(cond => cond.allowed(user, target)) + } + abstract execute(user: Creature, target: Creature): LogEntry - constructor (public name: string, public desc: string) { + constructor (public name: string, public desc: string, private conditions: Array = []) { } @@ -167,19 +173,56 @@ export abstract class Action { } } +export interface Condition { + allowed: (user: Creature, target: Creature) => boolean; +} + +class InverseCondition implements Condition { + allowed (user: Creature, target: Creature): boolean { + return !this.condition.allowed(user, target) + } + + constructor (private condition: Condition) { + + } +} +class CapableCondition implements Condition { + allowed (user: Creature, target: Creature): boolean { + return !user.disabled + } +} + +class DrainedVigorCondition implements Condition { + allowed (user: Creature, target: Creature): boolean { + return user.vigors[this.vigor] <= 0 + } + + constructor (private vigor: Vigor) { + + } +} + export interface Actionable { actions: Array; } abstract class SelfAction extends Action { allowed (user: Creature, target: Creature) { - return user === target + if (user === target) { + return super.allowed(user, target) + } else { + return false + } } } abstract class PairAction extends Action { allowed (user: Creature, target: Creature) { - return user !== target + if (user !== target) { + return super.allowed(user, target) + } else { + return false + } } } @@ -194,7 +237,7 @@ abstract class TogetherAction extends PairAction { } export class AttackAction extends TogetherAction { - private test: StatTest + protected test: StatTest protected successLines: POVPairArgs = new POVPairArgs([ [[POV.First, POV.Third], (user, target, args) => new LogLine( @@ -218,7 +261,7 @@ export class AttackAction extends TogetherAction { ]) constructor (protected damage: Damage) { - super('Attack', 'Attack the enemy') + super('Attack', 'Attack the enemy', [new CapableCondition()]) this.test = new StatTest(Stat.STR) } @@ -254,7 +297,7 @@ export class DevourAction extends TogetherAction { } constructor (protected container: Container) { - super('Devour', 'Try to consume your foe') + super('Devour', 'Try to consume your foe', [new CapableCondition()]) this.name += ` (${container.name})` this.test = new StatTest(Stat.STR) } @@ -268,6 +311,42 @@ export class DevourAction extends TogetherAction { } } +export class FeedAction extends TogetherAction { + private test: StatTest + + protected failLines: POVPair = new POVPair([ + [[POV.First, POV.Third], (user, target) => new LogLines(`You fail to feed yourself to ${target.name}`)], + [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} tries to feed ${user.pronouns.possessive} to you, but fails`)], + [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} unsuccessfully tries to feed ${user.pronouns.possessive} to ${target.name}`)] + ]) + + allowed (user: Creature, target: Creature): boolean { + const owner = this.container.owner === target + const predOk = Array.from(this.container.voreTypes).every(pref => user.predPrefs.has(pref)) + const preyOk = Array.from(this.container.voreTypes).every(pref => target.preyPrefs.has(pref)) + + if (owner && predOk && preyOk) { + return super.allowed(user, target) + } else { + return false + } + } + + constructor (protected container: Container) { + super('Feed', 'Feed yourself to your opponent', [new DrainedVigorCondition(Vigor.Willpower)]) + this.name += ` (${container.name})` + this.test = new StatTest(Stat.STR) + } + + execute (user: Creature, target: Creature): LogEntry { + if (this.test.test(user, target)) { + return this.container.consume(user) + } else { + return this.failLines.run(user, target) + } + } +} + export class StruggleAction extends PairAction { private test: StatTest @@ -286,7 +365,7 @@ export class StruggleAction extends PairAction { } constructor (public container: Container) { - super('Struggle', 'Try to escape your predator') + super('Struggle', 'Try to escape your predator', [new CapableCondition()]) this.test = new StatTest(Stat.STR) } @@ -315,7 +394,7 @@ export class DigestAction extends SelfAction { } constructor (protected container: Container) { - super('Digest', 'Digest all of your current prey') + super('Digest', 'Digest all of your current prey', [new CapableCondition()]) this.name += ` (${container.name})` } @@ -356,7 +435,7 @@ export class TransferAction extends PairAction { } constructor (protected from: Container, protected to: Container) { - super('Transfer', `Shove your prey from your ${from.name} to your ${to.name}`) + super('Transfer', `Shove your prey from your ${from.name} to your ${to.name}`, [new CapableCondition()]) } execute (user: Creature, target: Creature): LogEntry { diff --git a/src/game/creatures/wolf.ts b/src/game/creatures/wolf.ts index 4c6361b..b8ecd11 100644 --- a/src/game/creatures/wolf.ts +++ b/src/game/creatures/wolf.ts @@ -1,18 +1,50 @@ -import { Creature, POV } from '../entity' -import { Stat, Damage, DamageType, AttackAction, TransferAction, Vigor } from '../combat' -import { MalePronouns, ImproperNoun } from '../language' +import { Creature, POV, Entity } from '../entity' +import { Stat, Damage, DamageType, AttackAction, TransferAction, Vigor, StatTest, FeedAction } from '../combat' +import { MalePronouns, ImproperNoun, POVPair, POVPairArgs } from '../language' +import { LogLine, LogLines } from '../interface' import { VoreType, Stomach, Bowels } from '../vore' class BiteAction extends AttackAction { constructor () { super(new Damage({ amount: 10, type: DamageType.Slash, target: Vigor.Health })) + this.name = "Bite" + } +} + +class HypnoAction extends AttackAction { + protected successLines: POVPairArgs = new POVPairArgs([ + [[POV.First, POV.Third], (user, target, args) => new LogLine( + `You hypnotize ${target.name} for `, + args.damage.renderShort() + )], + [[POV.Third, POV.First], (user, target, args) => new LogLine( + `${user.name.capital} hypnotizes you for `, + args.damage.renderShort() + )], + [[POV.Third, POV.Third], (user, target, args) => new LogLine( + `${user.name.capital} hypnotizes ${target.name} for `, + args.damage.renderShort() + )] + ]) + + protected failLines: POVPair = new POVPair([ + [[POV.First, POV.Third], (user, target) => new LogLines(`You try to hypnotize ${target.name}, but you miss`)], + [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} misses you`)], + [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} misses ${target.name}`)] + ]) + + constructor () { + super(new Damage({ amount: 30, type: DamageType.Dominance, target: Vigor.Willpower })) + this.test = new StatTest(Stat.CON) + this.name = "Hypnotize" } } export class Wolf extends Creature { constructor () { - super(new ImproperNoun('wolf', 'wolves'), MalePronouns, { [Stat.STR]: 10, [Stat.DEX]: 10, [Stat.CON]: 10 }, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral]), 25) + super(new ImproperNoun('wolf', 'wolves'), MalePronouns, { [Stat.STR]: 15, [Stat.DEX]: 15, [Stat.CON]: 25 }, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral]), 25) this.actions.push(new BiteAction()) + this.actions.push(new HypnoAction()) const stomach = new Stomach(this, 50, new Damage({ amount: 50, type: DamageType.Acid, target: Vigor.Health }, { amount: 500, type: DamageType.Crush, target: Vigor.Health })) @@ -22,5 +54,7 @@ export class Wolf extends Creature { this.containers.push(bowels) this.actions.push(new TransferAction(bowels, stomach)) + + this.otherActions.push(new FeedAction(stomach)) } } diff --git a/src/game/entity.ts b/src/game/entity.ts index 14a2a11..96d10cc 100644 --- a/src/game/entity.ts +++ b/src/game/entity.ts @@ -14,7 +14,7 @@ export interface Entity { export interface Mortal extends Entity { vigors: {[key in Vigor]: number}; maxVigors: {[key in Vigor]: number}; - + disabled: boolean; resistances: Map; takeDamage: (damage: Damage) => void; stats: Stats; @@ -34,10 +34,15 @@ export class Creature implements Mortal, Pred, Prey, Combatant { [Vigor.Willpower]: 100 } + get disabled (): boolean { + return Object.values(this.vigors).some(val => val <= 0) + } + resistances: Map = new Map() perspective: POV = POV.Third containers: Array = [] actions: Array = []; + otherActions: Array = []; private baseBulk: number; get bulk (): number { @@ -83,7 +88,7 @@ export class Creature implements Mortal, Pred, Prey, Combatant { } validActions (target: Creature): Array { - let choices = this.actions.concat(this.containers.flatMap(container => container.actions)) + let choices = this.actions.concat(this.containers.flatMap(container => container.actions)).concat(target.otherActions) if (this.containedIn !== null) { choices = choices.concat(this.containedIn.actions) diff --git a/src/game/vore.ts b/src/game/vore.ts index d636a8c..70b326d 100644 --- a/src/game/vore.ts +++ b/src/game/vore.ts @@ -1,5 +1,5 @@ import { Entity, Mortal, POV } from './entity' -import { Damage, Actionable, Action, DevourAction, DigestAction, ReleaseAction, StruggleAction, Vigor } from './combat' +import { Damage, Actionable, Action, DevourAction, FeedAction, DigestAction, ReleaseAction, StruggleAction, Vigor } from './combat' import { LogLines, LogEntry, CompositeLog } from './interface' import { POVSolo, POVPair, POVPairArgs } from './language'