| @@ -16,6 +16,7 @@ module.exports = { | |||
| 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', | |||
| 'no-useless-constructor': 'off', | |||
| '@typescript-eslint/no-unused-vars': 'off', | |||
| 'quotes': 'off' | |||
| 'quotes': 'off', | |||
| 'function-paren-newline': ['error', 'multiline-arguments'] | |||
| } | |||
| } | |||
| @@ -73,7 +73,8 @@ export default class App extends Vue { | |||
| this.left = fighter | |||
| this.right = new Creatures.Withers() | |||
| this.combatants = [this.left, this.right, wizard, rogue, cleric] | |||
| const kenzie = new Creatures.Kenzie() | |||
| this.combatants = [this.left, this.right, wizard, rogue, cleric, kenzie] | |||
| console.log(this.left) | |||
| console.log(this.right) | |||
| } | |||
| @@ -1,10 +1,10 @@ | |||
| <template> | |||
| <div class="combat-layout"> | |||
| <div @wheel="horizWheelLeft" class="stat-column" id="left-stats"> | |||
| <Statblock v-on:click.native="left = combatant" class="left-stats" :data-active="combatant === left" v-for="combatant in combatants.filter(c => c.side == Side.Heroes && !c.digested)" v-bind:key="combatant.name" :subject="combatant" /> | |||
| <Statblock v-on:click.native="left = combatant" class="left-stats" :data-active="combatant === left" v-for="(combatant, index) in combatants.filter(c => c.side == Side.Heroes && !c.digested)" v-bind:key="'left-stat-' + index" :subject="combatant" /> | |||
| </div> | |||
| <div @wheel="horizWheelRight" class="stat-column" id="right-stats"> | |||
| <Statblock v-on:click.native="right = combatant" class="right-stats" :data-active="combatant === right" v-for="combatant in combatants.filter(c => c.side == Side.Monsters && !c.digested)" v-bind:key="combatant.name" :subject="combatant" /> | |||
| <Statblock v-on:click.native="right = combatant" class="right-stats" :data-active="combatant === right" v-for="(combatant, index) in combatants.filter(c => c.side == Side.Monsters && !c.digested)" v-bind:key="'right-stat-' + index" :subject="combatant" /> | |||
| </div> | |||
| <div id="log"> | |||
| </div> | |||
| @@ -18,7 +18,7 @@ | |||
| <i class="action-label fas fa-user-friends" v-if="left.validActions(right).length > 0"></i> | |||
| <ActionButton @described="described" @executed="executedLeft" v-for="action in left.validActions(right)" :key="'left' + action.name" :action="action" :user="left" :target="right" :combatants="combatants" /> | |||
| <i class="action-label fas fa-user" v-if="left.validActions(left).length > 0"></i> | |||
| <ActionButton @described="described" @executed="executedLeft" v-for="action in left.validActions(left)" :key="'left' + action.name" :action="action" :user="left" :target="right" :combatants="combatants" /> | |||
| <ActionButton @described="described" @executed="executedLeft" v-for="action in left.validActions(left)" :key="'left' + action.name" :action="action" :user="left" :target="left" :combatants="combatants" /> | |||
| </div> | |||
| <div>{{actionDescription}}</div> | |||
| </div> | |||
| @@ -32,7 +32,7 @@ | |||
| <i class="action-label fas fa-user-friends" v-if="right.validActions(left).length > 0"></i> | |||
| <ActionButton @described="described" @executed="executedRight" v-for="action in right.validActions(left)" :key="'right' + action.name" :action="action" :user="right" :target="left" :combatants="combatants" /> | |||
| <i class="action-label fas fa-user" v-if="right.validActions(right).length > 0"></i> | |||
| <ActionButton @described="described" @executed="executedRight" v-for="action in right.validActions(right)" :key="'right' + action.name" :action="action" :user="right" :target="left" :combatants="combatants" /> | |||
| <ActionButton @described="described" @executed="executedRight" v-for="action in right.validActions(right)" :key="'right' + action.name" :action="action" :user="right" :target="right" :combatants="combatants" /> | |||
| </div> | |||
| </div> | |||
| <div id="action-desc"> | |||
| @@ -295,37 +295,7 @@ export interface Actionable { | |||
| actions: Array<Action>; | |||
| } | |||
| export abstract class SelfAction extends Action { | |||
| allowed (user: Creature, target: Creature) { | |||
| if (user === target) { | |||
| return super.allowed(user, target) | |||
| } else { | |||
| return false | |||
| } | |||
| } | |||
| } | |||
| export abstract class PairAction extends Action { | |||
| allowed (user: Creature, target: Creature) { | |||
| if (user !== target) { | |||
| return super.allowed(user, target) | |||
| } else { | |||
| return false | |||
| } | |||
| } | |||
| } | |||
| export abstract class TogetherAction extends PairAction { | |||
| allowed (user: Creature, target: Creature) { | |||
| if (user.containedIn === target.containedIn) { | |||
| return super.allowed(user, target) | |||
| } else { | |||
| return false | |||
| } | |||
| } | |||
| } | |||
| export abstract class GroupAction extends PairAction { | |||
| export abstract class GroupAction extends Action { | |||
| allowedGroup (user: Creature, targets: Array<Creature>): Array<Creature> { | |||
| return targets.filter(target => this.allowed(user, target)) | |||
| } | |||
| @@ -335,4 +305,8 @@ export abstract class GroupAction extends PairAction { | |||
| } | |||
| abstract describeGroup (user: Creature, targets: Array<Creature>): LogEntry | |||
| constructor (name: TextLike, desc: TextLike, conditions: Array<Condition>) { | |||
| super(name, desc, conditions) | |||
| } | |||
| } | |||
| @@ -1,12 +1,12 @@ | |||
| import { StatTest, StatVigorTest } from './tests' | |||
| import { POVPairArgs, POVPair, DynText, LiveText, TextLike } from '../language' | |||
| import { Entity, POV, Creature } from '../entity' | |||
| import { Damage, DamageFormula, Stat, Vigor, TogetherAction, PairAction, SelfAction } from '../combat' | |||
| import { Damage, DamageFormula, Stat, Vigor, Action } from '../combat' | |||
| import { LogLine, LogLines, LogEntry, CompositeLog } from '../interface' | |||
| import { VoreContainer, Container } from '../vore' | |||
| import { CapableCondition, DrainedVigorCondition } from './conditions' | |||
| import { CapableCondition, DrainedVigorCondition, TogetherCondition, EnemyCondition, SoloCondition, PairCondition } from './conditions' | |||
| export class AttackAction extends TogetherAction { | |||
| export class AttackAction extends Action { | |||
| protected test: StatTest | |||
| protected successLines: POVPairArgs<Entity, Entity, { damage: Damage }> = new POVPairArgs([ | |||
| @@ -31,7 +31,11 @@ export class AttackAction extends TogetherAction { | |||
| ]) | |||
| constructor (protected damage: DamageFormula) { | |||
| super('Attack', 'Attack the enemy', [new CapableCondition()]) | |||
| super( | |||
| 'Attack', | |||
| 'Attack the enemy', | |||
| [new CapableCondition(), new TogetherCondition(), new EnemyCondition()] | |||
| ) | |||
| this.test = new StatTest(Stat.Power) | |||
| } | |||
| @@ -51,7 +55,7 @@ export class AttackAction extends TogetherAction { | |||
| } | |||
| } | |||
| export class DevourAction extends TogetherAction { | |||
| export class DevourAction extends Action { | |||
| private test: StatVigorTest | |||
| protected failLines: POVPairArgs<Entity, Entity, { container: Container }> = new POVPairArgs([ | |||
| @@ -73,7 +77,11 @@ export class DevourAction extends TogetherAction { | |||
| } | |||
| constructor (protected container: Container) { | |||
| super(new DynText(new LiveText(container, x => x.consumeVerb.capital), ' (', new LiveText(container, x => x.name.all), ')'), new LiveText(container, x => `Try to ${x.consumeVerb} your foe`), [new CapableCondition()]) | |||
| super( | |||
| new DynText(new LiveText(container, x => x.consumeVerb.capital), ' (', new LiveText(container, x => x.name.all), ')'), | |||
| new LiveText(container, x => `Try to ${x.consumeVerb} your foe`), | |||
| [new CapableCondition(), new TogetherCondition()] | |||
| ) | |||
| this.test = new StatVigorTest(Stat.Power) | |||
| } | |||
| @@ -90,7 +98,7 @@ export class DevourAction extends TogetherAction { | |||
| } | |||
| } | |||
| export class FeedAction extends TogetherAction { | |||
| export class FeedAction extends Action { | |||
| private test: StatTest | |||
| protected failLines: POVPair<Entity, Entity> = new POVPair([ | |||
| @@ -112,7 +120,11 @@ export class FeedAction extends TogetherAction { | |||
| } | |||
| constructor (protected container: Container) { | |||
| super('Feed', 'Feed yourself to your opponent', [new DrainedVigorCondition(Vigor.Resolve)]) | |||
| super( | |||
| 'Feed', | |||
| 'Feed yourself to your opponent', | |||
| [new DrainedVigorCondition(Vigor.Resolve), new TogetherCondition()] | |||
| ) | |||
| this.name += ` (${container.name})` | |||
| this.test = new StatTest(Stat.Power) | |||
| } | |||
| @@ -126,7 +138,7 @@ export class FeedAction extends TogetherAction { | |||
| } | |||
| } | |||
| export class StruggleAction extends PairAction { | |||
| export class StruggleAction extends Action { | |||
| private test: StatVigorTest | |||
| protected failLines: POVPair<Entity, Entity> = new POVPair([ | |||
| @@ -144,7 +156,11 @@ export class StruggleAction extends PairAction { | |||
| } | |||
| constructor (public container: Container) { | |||
| super(new DynText('Struggle (', new LiveText(container, x => x.name.all), ')'), 'Try to escape from your foe', [new CapableCondition()]) | |||
| super( | |||
| new DynText('Struggle (', new LiveText(container, x => x.name.all), ')'), | |||
| 'Try to escape from your foe', | |||
| [new CapableCondition(), new PairCondition()] | |||
| ) | |||
| this.test = new StatVigorTest(Stat.Power) | |||
| } | |||
| @@ -165,7 +181,7 @@ export class StruggleAction extends PairAction { | |||
| } | |||
| } | |||
| export abstract class EatenAction extends PairAction { | |||
| export abstract class EatenAction extends Action { | |||
| protected lines: POVPair<Entity, Entity> = new POVPair([]) | |||
| allowed (user: Creature, target: Creature) { | |||
| @@ -177,10 +193,15 @@ export abstract class EatenAction extends PairAction { | |||
| } | |||
| constructor (public container: Container, name: TextLike, desc: string) { | |||
| super(new DynText(name, ' (', new LiveText(container, x => x.name.all), ')'), desc, [new CapableCondition()]) | |||
| super( | |||
| new DynText(name, ' (', new LiveText(container, x => x.name.all), ')'), | |||
| desc, | |||
| [new CapableCondition(), new PairCondition()] | |||
| ) | |||
| } | |||
| } | |||
| export class DigestAction extends SelfAction { | |||
| export class DigestAction extends Action { | |||
| protected lines: POVPair<Entity, Entity> = new POVPair([]) | |||
| allowed (user: Creature, target: Creature) { | |||
| @@ -192,7 +213,11 @@ export class DigestAction extends SelfAction { | |||
| } | |||
| constructor (protected container: VoreContainer) { | |||
| super(new DynText('Digest (', new LiveText(container, container => container.name.all), ')'), 'Digest your prey', [new CapableCondition()]) | |||
| super( | |||
| new DynText('Digest (', new LiveText(container, container => container.name.all), ')'), | |||
| 'Digest your prey', | |||
| [new CapableCondition(), new SoloCondition()] | |||
| ) | |||
| } | |||
| execute (user: Creature, target: Creature): LogEntry { | |||
| @@ -205,7 +230,7 @@ export class DigestAction extends SelfAction { | |||
| } | |||
| } | |||
| export class ReleaseAction extends PairAction { | |||
| export class ReleaseAction extends Action { | |||
| allowed (user: Creature, target: Creature) { | |||
| if (target.containedIn === this.container && this.container.contents.indexOf(target) >= 0) { | |||
| return super.allowed(user, target) | |||
| @@ -215,7 +240,11 @@ export class ReleaseAction extends PairAction { | |||
| } | |||
| constructor (protected container: Container) { | |||
| super(new DynText('Release (', new LiveText(container, x => x.name.all), ')'), 'Release one of your prey', [new CapableCondition()]) | |||
| super( | |||
| new DynText('Release (', new LiveText(container, x => x.name.all), ')'), | |||
| 'Release one of your prey', | |||
| [new CapableCondition(), new PairCondition()] | |||
| ) | |||
| } | |||
| execute (user: Creature, target: Creature): LogEntry { | |||
| @@ -227,7 +256,7 @@ export class ReleaseAction extends PairAction { | |||
| } | |||
| } | |||
| export class TransferAction extends PairAction { | |||
| export class TransferAction extends Action { | |||
| lines: POVPairArgs<Entity, Entity, { from: Container; to: Container }> = new POVPairArgs([ | |||
| [[POV.First, POV.Third], (user, target, args) => new LogLine(`You squeeze ${target.name} from your ${args.from.name} to your ${args.to.name}`)], | |||
| [[POV.Third, POV.First], (user, target, args) => new LogLine(`You're squeezed from ${user.name}'s ${args.from.name} to ${user.pronouns.possessive} ${args.to.name}`)], | |||
| @@ -243,7 +272,11 @@ export class TransferAction extends PairAction { | |||
| } | |||
| constructor (protected from: Container, protected to: Container, name = 'Transfer') { | |||
| super(name, `${from.name.all.capital} to ${to.name.all}`, [new CapableCondition()]) | |||
| super( | |||
| name, | |||
| `${from.name.all.capital} to ${to.name.all}`, | |||
| [new CapableCondition(), new PairCondition()] | |||
| ) | |||
| } | |||
| execute (user: Creature, target: Creature): LogEntry { | |||
| @@ -27,9 +27,21 @@ export class DrainedVigorCondition implements Condition { | |||
| } | |||
| } | |||
| export class SoloCondition implements Condition { | |||
| allowed (user: Creature, target: Creature): boolean { | |||
| return user === target | |||
| } | |||
| } | |||
| export class PairCondition implements Condition { | |||
| allowed (user: Creature, target: Creature): boolean { | |||
| return user !== target | |||
| } | |||
| } | |||
| export class TogetherCondition implements Condition { | |||
| allowed (user: Creature, target: Creature): boolean { | |||
| return user.containedIn === target.containedIn | |||
| return user.containedIn === target.containedIn && user !== target | |||
| } | |||
| } | |||
| @@ -42,3 +54,15 @@ export class ContainerCondition implements Condition { | |||
| } | |||
| } | |||
| export class AllyCondition implements Condition { | |||
| allowed (user: Creature, target: Creature): boolean { | |||
| return user.side === target.side | |||
| } | |||
| } | |||
| export class EnemyCondition implements Condition { | |||
| allowed (user: Creature, target: Creature): boolean { | |||
| return user.side !== target.side | |||
| } | |||
| } | |||
| @@ -3,5 +3,6 @@ import { Player } from './creatures/player' | |||
| import { Cafat } from './creatures/cafat' | |||
| import { Human } from './creatures/human' | |||
| import { Withers } from './creatures/withers' | |||
| import { Kenzie } from './creatures/kenzie' | |||
| export { Wolf, Player, Cafat, Human, Withers } | |||
| export { Wolf, Player, Cafat, Human, Withers, Kenzie } | |||
| @@ -0,0 +1,34 @@ | |||
| import { Creature, POV } from '../entity' | |||
| import { ProperNoun, ImproperNoun, FemalePronouns, POVPairArgs, POVPair } from '../language' | |||
| import { VoreType, Stomach, Vore } from '../vore' | |||
| import { Side, Damage, DamageType, Vigor, UniformRandomDamageFormula } from '../combat' | |||
| import { LogLine } from '../interface' | |||
| import { FeedAction, TransferAction } from '../combat/actions' | |||
| import * as Words from '../words' | |||
| export class Kenzie extends Creature { | |||
| title = "Large Lycanroc" | |||
| desc = "Will eat your party" | |||
| constructor () { | |||
| super( | |||
| new ProperNoun('Kenzie'), | |||
| new ImproperNoun('lycanroc', 'lycanrocs'), | |||
| FemalePronouns, | |||
| { Toughness: 60, Power: 70, Speed: 40, Willpower: 60, Charm: 120 }, | |||
| new Set(), | |||
| new Set([VoreType.Oral]), | |||
| 1000 | |||
| ) | |||
| this.side = Side.Monsters | |||
| const stomach = new Stomach(this, 50, new Damage( | |||
| { amount: 100, type: DamageType.Acid, target: Vigor.Health }, | |||
| { amount: 100, type: DamageType.Crush, target: Vigor.Stamina }, | |||
| { amount: 100, type: DamageType.Dominance, target: Vigor.Resolve } | |||
| )) | |||
| this.containers.push(stomach) | |||
| } | |||
| } | |||
| @@ -1,10 +1,10 @@ | |||
| import { Creature, POV } from '../entity' | |||
| import { Damage, DamageType, ConstantDamageFormula, Vigor, Side, GroupAction, CombatTest, Stat, Action, DamageFormula, UniformRandomDamageFormula, PairAction } from '../combat' | |||
| import { Damage, DamageType, ConstantDamageFormula, Vigor, Side, GroupAction, CombatTest, Stat, DamageFormula, UniformRandomDamageFormula, Action } from '../combat' | |||
| import { ImproperNoun, POVPair, ProperNoun, FemalePronouns, RandomWord, Adjective, POVPairArgs, POVSoloArgs, Verb } from '../language' | |||
| import { LogLine, LogLines, LogEntry, Newline } from '../interface' | |||
| import { VoreType, Stomach, VoreContainer, Vore, NormalContainer, Container } from '../vore' | |||
| import { AttackAction, FeedAction, TransferAction, EatenAction } from '../combat/actions' | |||
| import { TogetherCondition, ContainerCondition } from '../combat/conditions' | |||
| import { TogetherCondition, ContainerCondition, EnemyCondition } from '../combat/conditions' | |||
| import { InstantKill } from '../combat/effects' | |||
| import * as Words from '../words' | |||
| import { StatVigorTest } from '../combat/tests' | |||
| @@ -92,12 +92,14 @@ class BootContainer extends NormalContainer { | |||
| constructor (owner: Vore) { | |||
| super(new ImproperNoun('boot'), owner, new Set(), 50) | |||
| const flex = new FlexToesAction(new UniformRandomDamageFormula(new Damage( | |||
| { target: Vigor.Health, type: DamageType.Crush, amount: 50 }, | |||
| { target: Vigor.Stamina, type: DamageType.Crush, amount: 50 }, | |||
| { target: Vigor.Resolve, type: DamageType.Crush, amount: 50 } | |||
| ), 0.5), | |||
| this) | |||
| const flex = new FlexToesAction( | |||
| new UniformRandomDamageFormula(new Damage( | |||
| { target: Vigor.Health, type: DamageType.Crush, amount: 50 }, | |||
| { target: Vigor.Stamina, type: DamageType.Crush, amount: 50 }, | |||
| { target: Vigor.Resolve, type: DamageType.Crush, amount: 50 } | |||
| ), 0.5), | |||
| this | |||
| ) | |||
| this.actions.push(flex) | |||
| } | |||
| @@ -179,7 +181,8 @@ class StompAction extends GroupAction { | |||
| constructor () { | |||
| super('Stomp', 'STOMP!', [ | |||
| new TogetherCondition() | |||
| new TogetherCondition(), | |||
| new EnemyCondition() | |||
| ]) | |||
| } | |||
| } | |||
| @@ -222,7 +225,8 @@ class DevourAllAction extends GroupAction { | |||
| constructor (private container: VoreContainer) { | |||
| super('Devour All', 'GULP!', [ | |||
| new TogetherCondition() | |||
| new TogetherCondition(), | |||
| new EnemyCondition() | |||
| ]) | |||
| this.test = new StatVigorTest(Stat.Power) | |||
| } | |||
| @@ -240,7 +244,8 @@ export class Withers extends Creature { | |||
| { Toughness: 60, Power: 70, Speed: 40, Willpower: 60, Charm: 120 }, | |||
| new Set(), | |||
| new Set([VoreType.Oral]), | |||
| 5000) | |||
| 5000 | |||
| ) | |||
| this.actions.push(new BiteAction()) | |||
| this.groupActions.push(new StompAction()) | |||
| @@ -274,11 +279,13 @@ export class Withers extends Creature { | |||
| this.otherContainers.push(maw) | |||
| this.actions.push(new ChewAction(new UniformRandomDamageFormula(new Damage( | |||
| { target: Vigor.Health, type: DamageType.Crush, amount: 10000 } | |||
| ), 0.5), | |||
| maw, | |||
| new TransferAction(maw, stomach))) | |||
| this.actions.push(new ChewAction( | |||
| new UniformRandomDamageFormula(new Damage( | |||
| { target: Vigor.Health, type: DamageType.Crush, amount: 10000 } | |||
| ), 0.5), | |||
| maw, | |||
| new TransferAction(maw, stomach) | |||
| )) | |||
| const boot = new BootContainer(this) | |||
| this.otherContainers.push(boot) | |||
| @@ -3,6 +3,7 @@ import { Damage, DamageType, Stats, Actionable, Action, Vigor, VoreStats } from | |||
| import { LogLines, LogEntry, CompositeLog, LogLine } from './interface' | |||
| import { Noun, Pronoun, POVPair, POVPairArgs, ImproperNoun, POVSolo, TextLike, Verb, Word } from './language' | |||
| import { DigestAction, DevourAction, ReleaseAction, StruggleAction } from './combat/actions' | |||
| import * as Words from './words' | |||
| export enum VoreType { | |||
| Oral = "Oral Vore", | |||
| @@ -242,15 +243,15 @@ export class Stomach extends NormalVoreContainer { | |||
| ]) | |||
| tickLines = new POVPairArgs<Vore, Vore, { damage: Damage }>([ | |||
| [[POV.First, POV.Third], (user, target, args) => new LogLine(`Your stomach gurgles ${target.name} for `, args.damage.renderShort())], | |||
| [[POV.Third, POV.First], (user, target, args) => new LogLine(`${user.name.capital}'s stomach churns you for `, args.damage.renderShort())], | |||
| [[POV.Third, POV.Third], (user, target, args) => new LogLine(`${user.name.capital} churns ${target.name} for `, args.damage.renderShort())] | |||
| [[POV.First, POV.Third], (user, target, args) => new LogLine(`Your stomach ${Words.Churns.singular} ${target.name} for `, args.damage.renderShort())], | |||
| [[POV.Third, POV.First], (user, target, args) => new LogLine(`${user.name.capital}'s stomach ${Words.Churns.singular} you for `, args.damage.renderShort())], | |||
| [[POV.Third, POV.Third], (user, target, args) => new LogLine(`${user.name.capital} ${Words.Churns.singular} ${target.name} for `, args.damage.renderShort())] | |||
| ]) | |||
| digestLines = new POVPair<Vore, Vore>([ | |||
| [[POV.First, POV.Third], (user, target) => new LogLine(`Your stomach overwhelms ${target.name}`)], | |||
| [[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital}'s stomach finishes you off`)], | |||
| [[POV.Third, POV.Third], (user, target) => new LogLine(`${target.name.capital}'s squirms fade, overwhelmed by the stomach of ${user.name}`)] | |||
| [[POV.First, POV.Third], (user, target) => new LogLine(`Your stomach ${Words.Digests.singular} ${target.name}`)], | |||
| [[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital}'s stomach ${Words.Digests.singular} you`)], | |||
| [[POV.Third, POV.Third], (user, target) => new LogLine(`${target.name.capital}'s ${Words.Struggles.present} fades as the ${target.kind.all} is ${Words.Digests.past} by the ${user.kind.all}'s ${this.name}.`)] | |||
| ]) | |||
| absorbLines = new POVPair<Vore, Vore>([ | |||