| @@ -2,7 +2,7 @@ import { Creature } from './creature' | |||||
| import { Encounter, Action } from './combat' | import { Encounter, Action } from './combat' | ||||
| import { LogEntry } from './interface' | import { LogEntry } from './interface' | ||||
| import { PassAction } from './combat/actions' | import { PassAction } from './combat/actions' | ||||
| import { NoPassDecider, NoReleaseDecider, ChanceDecider, NoSurrenderDecider, FavorDigestDecider } from './ai/deciders' | |||||
| import { NoPassDecider, NoReleaseDecider, ChanceDecider, NoSurrenderDecider, FavorRubDecider } from './ai/deciders' | |||||
| /** | /** | ||||
| * A Decider determines how favorable an action is to perform. | * A Decider determines how favorable an action is to perform. | ||||
| @@ -68,7 +68,7 @@ export class VoreAI extends AI { | |||||
| new NoSurrenderDecider(), | new NoSurrenderDecider(), | ||||
| new NoPassDecider(), | new NoPassDecider(), | ||||
| new ChanceDecider(), | new ChanceDecider(), | ||||
| new FavorDigestDecider() | |||||
| new FavorRubDecider() | |||||
| ] | ] | ||||
| ) | ) | ||||
| } | } | ||||
| @@ -1,7 +1,7 @@ | |||||
| import { Decider } from '../ai' | import { Decider } from '../ai' | ||||
| import { Encounter, Action, Consequence, CompositionAction } from '../combat' | import { Encounter, Action, Consequence, CompositionAction } from '../combat' | ||||
| import { Creature } from '../creature' | import { Creature } from '../creature' | ||||
| import { PassAction, ReleaseAction, DigestAction } from '../combat/actions' | |||||
| import { PassAction, ReleaseAction, RubAction } from '../combat/actions' | |||||
| import { StatusConsequence } from '../combat/consequences' | import { StatusConsequence } from '../combat/consequences' | ||||
| import { SurrenderEffect } from '../combat/effects' | import { SurrenderEffect } from '../combat/effects' | ||||
| @@ -44,6 +44,7 @@ export class ChanceDecider implements Decider { | |||||
| * Adjusts the weights for [[CompositionAction]]s that contain the specified consequence | * Adjusts the weights for [[CompositionAction]]s that contain the specified consequence | ||||
| */ | */ | ||||
| export class ConsequenceDecider<T extends Consequence> implements Decider { | export class ConsequenceDecider<T extends Consequence> implements Decider { | ||||
| /* eslint-disable-next-line */ | |||||
| constructor (private consequenceType: new (...args: any) => T, private weight: number) { | constructor (private consequenceType: new (...args: any) => T, private weight: number) { | ||||
| } | } | ||||
| @@ -100,11 +101,11 @@ export class NoSurrenderDecider extends ConsequenceFunctionDecider { | |||||
| } | } | ||||
| /** | /** | ||||
| * Favors [[DigestAction]]s | |||||
| * Favors [[RubAction]]s | |||||
| */ | */ | ||||
| export class FavorDigestDecider implements Decider { | |||||
| export class FavorRubDecider implements Decider { | |||||
| decide (encounter: Encounter, user: Creature, target: Creature, action: Action) { | decide (encounter: Encounter, user: Creature, target: Creature, action: Action) { | ||||
| if (action instanceof DigestAction) { | |||||
| if (action instanceof RubAction) { | |||||
| return 5 | return 5 | ||||
| } else { | } else { | ||||
| return 1 | return 1 | ||||
| @@ -152,8 +152,11 @@ export class Damage { | |||||
| })) | })) | ||||
| } | } | ||||
| // TODO is there a way to do this that will satisfy the typechecker? | |||||
| renderShort (): LogEntry { | renderShort (): LogEntry { | ||||
| /* eslint-disable-next-line */ | |||||
| const vigorTotals: Vigors = Object.keys(Vigor).reduce((total: any, key) => { total[key] = 0; return total }, {}) | const vigorTotals: Vigors = Object.keys(Vigor).reduce((total: any, key) => { total[key] = 0; return total }, {}) | ||||
| /* eslint-disable-next-line */ | |||||
| const statTotals: Stats = Object.keys(Stat).reduce((total: any, key) => { total[key] = 0; return total }, {}) | const statTotals: Stats = Object.keys(Stat).reduce((total: any, key) => { total[key] = 0; return total }, {}) | ||||
| this.damages.forEach(instance => { | this.damages.forEach(instance => { | ||||
| const factor = instance.type === DamageType.Heal ? -1 : 1 | const factor = instance.type === DamageType.Heal ? -1 : 1 | ||||
| @@ -388,9 +391,12 @@ export abstract class Action { | |||||
| describe (user: Creature, target: Creature): LogEntry { | describe (user: Creature, target: Creature): LogEntry { | ||||
| return new LogLines( | return new LogLines( | ||||
| ...this.conditions.map(condition => condition.explain(user, target)), | |||||
| new Newline(), | |||||
| new LogLine( | new LogLine( | ||||
| `Success chance: ${(this.odds(user, target) * 100).toFixed(0)}%` | `Success chance: ${(this.odds(user, target) * 100).toFixed(0)}%` | ||||
| ), | ), | ||||
| new Newline(), | |||||
| ...this.tests.map(test => test.explain(user, target)) | ...this.tests.map(test => test.explain(user, target)) | ||||
| ) | ) | ||||
| } | } | ||||
| @@ -439,6 +445,7 @@ export class CompositionAction extends Action { | |||||
| */ | */ | ||||
| export interface Condition { | export interface Condition { | ||||
| allowed: (user: Creature, target: Creature) => boolean; | allowed: (user: Creature, target: Creature) => boolean; | ||||
| explain: (user: Creature, target: Creature) => LogEntry; | |||||
| } | } | ||||
| export interface Actionable { | export interface Actionable { | ||||
| @@ -596,7 +603,7 @@ export type EncounterDesc = { | |||||
| export class Encounter { | export class Encounter { | ||||
| initiatives: Map<Creature, number> | initiatives: Map<Creature, number> | ||||
| currentMove: Creature | currentMove: Creature | ||||
| turnTime = 100 | |||||
| turnTime = 500 | |||||
| constructor (public desc: EncounterDesc, public combatants: Creature[]) { | constructor (public desc: EncounterDesc, public combatants: Creature[]) { | ||||
| this.initiatives = new Map() | this.initiatives = new Map() | ||||
| @@ -607,7 +614,7 @@ export class Encounter { | |||||
| this.nextMove() | this.nextMove() | ||||
| } | } | ||||
| nextMove (): LogEntry { | |||||
| nextMove (totalTime = 0): LogEntry { | |||||
| this.initiatives.set(this.currentMove, 0) | this.initiatives.set(this.currentMove, 0) | ||||
| const times = new Map<Creature, number>() | const times = new Map<Creature, number>() | ||||
| @@ -635,15 +642,26 @@ export class Encounter { | |||||
| // TODO: still let the creature use drained-vigor moves | // TODO: still let the creature use drained-vigor moves | ||||
| if (this.currentMove.disabled) { | if (this.currentMove.disabled) { | ||||
| return this.nextMove() | |||||
| return this.nextMove(closestRemaining + totalTime) | |||||
| } else { | } else { | ||||
| // applies digestion every time combat advances | |||||
| const tickResults = this.combatants.flatMap( | |||||
| combatant => combatant.containers.map( | |||||
| container => container.tick(closestRemaining + totalTime) | |||||
| ) | |||||
| ) | |||||
| const effectResults = this.currentMove.effects.map(effect => effect.preTurn(this.currentMove)).filter(effect => effect.prevented) | const effectResults = this.currentMove.effects.map(effect => effect.preTurn(this.currentMove)).filter(effect => effect.prevented) | ||||
| if (effectResults.some(result => result.prevented)) { | if (effectResults.some(result => result.prevented)) { | ||||
| const parts = effectResults.map(result => result.log).concat([this.nextMove()]) | const parts = effectResults.map(result => result.log).concat([this.nextMove()]) | ||||
| return new LogLines( | return new LogLines( | ||||
| ...parts | |||||
| ...parts, | |||||
| ...tickResults | |||||
| ) | |||||
| } else { | |||||
| return new LogLines( | |||||
| ...tickResults | |||||
| ) | ) | ||||
| } | } | ||||
| } | } | ||||
| @@ -2,10 +2,11 @@ import { StatTest, StatVigorTest, StatVigorSizeTest } from './tests' | |||||
| import { DynText, LiveText, TextLike, Verb, PairLine, PairLineArgs } from '../language' | import { DynText, LiveText, TextLike, Verb, PairLine, PairLineArgs } from '../language' | ||||
| import { Entity } from '../entity' | import { Entity } from '../entity' | ||||
| import { Creature } from "../creature" | import { Creature } from "../creature" | ||||
| import { Damage, DamageFormula, Stat, Vigor, Action, Condition, CombatTest } from '../combat' | |||||
| import { Damage, DamageFormula, Stat, Vigor, Action, Condition, CombatTest, CompositionAction } from '../combat' | |||||
| import { LogLine, LogLines, LogEntry, nilLog } from '../interface' | import { LogLine, LogLines, LogEntry, nilLog } from '../interface' | ||||
| import { VoreContainer, Container } from '../vore' | import { VoreContainer, Container } from '../vore' | ||||
| import { CapableCondition, UserDrainedVigorCondition, TogetherCondition, EnemyCondition, SoloCondition, PairCondition, ContainsCondition, ContainedByCondition, HasRoomCondition } from './conditions' | import { CapableCondition, UserDrainedVigorCondition, TogetherCondition, EnemyCondition, SoloCondition, PairCondition, ContainsCondition, ContainedByCondition, HasRoomCondition } from './conditions' | ||||
| import { ConsumeConsequence } from './consequences' | |||||
| /** | /** | ||||
| * The PassAction has no effect. | * The PassAction has no effect. | ||||
| @@ -90,17 +91,22 @@ export class AttackAction extends DamageAction { | |||||
| /** | /** | ||||
| * Devours the target. | * Devours the target. | ||||
| */ | */ | ||||
| export class DevourAction extends Action { | |||||
| export class DevourAction extends CompositionAction { | |||||
| constructor (protected container: Container) { | constructor (protected container: Container) { | ||||
| super( | super( | ||||
| new DynText(new LiveText(container, x => x.consumeVerb.capital), ' (', new LiveText(container, x => x.name.all), ')'), | 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 LiveText(container, x => `Try to ${x.consumeVerb} your foe`), | ||||
| [new CapableCondition(), new TogetherCondition(), new HasRoomCondition(container)], | |||||
| [new StatVigorSizeTest( | |||||
| Stat.Power, | |||||
| -5, | |||||
| (user, target) => new LogLine(`${user.name.capital} ${user.name.conjugate(new Verb('fail'))} to ${container.consumeVerb} ${target.name.objective}.`) | |||||
| )] | |||||
| { | |||||
| conditions: [new CapableCondition(), new TogetherCondition(), new HasRoomCondition(container)], | |||||
| tests: [new StatVigorSizeTest( | |||||
| Stat.Power, | |||||
| -5, | |||||
| (user, target) => new LogLine(`${user.name.capital} ${user.name.conjugate(new Verb('fail'))} to ${container.consumeVerb} ${target.name.objective}.`) | |||||
| )], | |||||
| consequences: [ | |||||
| new ConsumeConsequence(container) | |||||
| ] | |||||
| } | |||||
| ) | ) | ||||
| } | } | ||||
| @@ -197,11 +203,11 @@ export class StruggleAction extends Action { | |||||
| ) | ) | ||||
| } | } | ||||
| export class DigestAction extends Action { | |||||
| export class RubAction extends Action { | |||||
| constructor (protected container: VoreContainer) { | constructor (protected container: VoreContainer) { | ||||
| super( | super( | ||||
| new DynText('Digest (', new LiveText(container, container => container.name.all), ')'), | |||||
| 'Digest your prey', | |||||
| new DynText('Rub (', new LiveText(container, container => container.name.all), ')'), | |||||
| 'Digest your prey more quickly', | |||||
| [new CapableCondition(), new SoloCondition()] | [new CapableCondition(), new SoloCondition()] | ||||
| ) | ) | ||||
| } | } | ||||
| @@ -1,6 +1,9 @@ | |||||
| import { Condition, Vigor } from "../combat" | import { Condition, Vigor } from "../combat" | ||||
| import { Creature } from "../creature" | import { Creature } from "../creature" | ||||
| import { Container } from '../vore' | import { Container } from '../vore' | ||||
| import { LogEntry, LogLine, PropElem } from '../interface' | |||||
| import { ToBe, Verb } from '../language' | |||||
| import * as Words from '../words' | |||||
| export class InverseCondition implements Condition { | export class InverseCondition implements Condition { | ||||
| constructor (private condition: Condition) { | constructor (private condition: Condition) { | ||||
| @@ -10,12 +13,31 @@ export class InverseCondition implements Condition { | |||||
| allowed (user: Creature, target: Creature): boolean { | allowed (user: Creature, target: Creature): boolean { | ||||
| return !this.condition.allowed(user, target) | return !this.condition.allowed(user, target) | ||||
| } | } | ||||
| explain (user: Creature, target: Creature): LogEntry { | |||||
| return new LogLine( | |||||
| `The following must NOT hold: `, | |||||
| this.condition.explain(user, target) | |||||
| ) | |||||
| } | |||||
| } | } | ||||
| export class CapableCondition implements Condition { | export class CapableCondition implements Condition { | ||||
| allowed (user: Creature, target: Creature): boolean { | allowed (user: Creature, target: Creature): boolean { | ||||
| return !user.disabled | return !user.disabled | ||||
| } | } | ||||
| explain (user: Creature, target: Creature): LogEntry { | |||||
| if (this.allowed(user, target)) { | |||||
| return new LogLine( | |||||
| `${user.name.capital} ${user.name.conjugate(new ToBe())} not incapacitated` | |||||
| ) | |||||
| } else { | |||||
| return new LogLine( | |||||
| `${user.name.capital} ${user.name.conjugate(new ToBe())} incapacitated` | |||||
| ) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| export class UserDrainedVigorCondition implements Condition { | export class UserDrainedVigorCondition implements Condition { | ||||
| @@ -26,6 +48,14 @@ export class UserDrainedVigorCondition implements Condition { | |||||
| allowed (user: Creature, target: Creature): boolean { | allowed (user: Creature, target: Creature): boolean { | ||||
| return user.vigors[this.vigor] <= 0 | return user.vigors[this.vigor] <= 0 | ||||
| } | } | ||||
| explain (user: Creature, target: Creature): LogEntry { | |||||
| return new LogLine( | |||||
| `${user.name.capital} must have ${user.pronouns.possessive} `, | |||||
| new PropElem(this.vigor), | |||||
| ` drained.` | |||||
| ) | |||||
| } | |||||
| } | } | ||||
| export class TargetDrainedVigorCondition implements Condition { | export class TargetDrainedVigorCondition implements Condition { | ||||
| @@ -36,24 +66,68 @@ export class TargetDrainedVigorCondition implements Condition { | |||||
| allowed (user: Creature, target: Creature): boolean { | allowed (user: Creature, target: Creature): boolean { | ||||
| return target.vigors[this.vigor] <= 0 | return target.vigors[this.vigor] <= 0 | ||||
| } | } | ||||
| explain (user: Creature, target: Creature): LogEntry { | |||||
| return new LogLine( | |||||
| `${target.name.capital} must have ${target.pronouns.possessive} `, | |||||
| new PropElem(this.vigor), | |||||
| ` drained.` | |||||
| ) | |||||
| } | |||||
| } | } | ||||
| export class SoloCondition implements Condition { | export class SoloCondition implements Condition { | ||||
| allowed (user: Creature, target: Creature): boolean { | allowed (user: Creature, target: Creature): boolean { | ||||
| return user === target | return user === target | ||||
| } | } | ||||
| explain (user: Creature, target: Creature): LogEntry { | |||||
| if (this.allowed(user, target)) { | |||||
| return new LogLine( | |||||
| `${user.name.capital} can't use this action on others.` | |||||
| ) | |||||
| } else { | |||||
| return new LogLine( | |||||
| `${user.name.capital} can use this action on ${user.pronouns.reflexive}.` | |||||
| ) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| export class PairCondition implements Condition { | export class PairCondition implements Condition { | ||||
| allowed (user: Creature, target: Creature): boolean { | allowed (user: Creature, target: Creature): boolean { | ||||
| return user !== target | return user !== target | ||||
| } | } | ||||
| explain (user: Creature, target: Creature): LogEntry { | |||||
| if (this.allowed(user, target)) { | |||||
| return new LogLine( | |||||
| `${user.name.capital} can use this action on others.` | |||||
| ) | |||||
| } else { | |||||
| return new LogLine( | |||||
| `${user.name.capital} can't use this action on ${user.pronouns.reflexive}.` | |||||
| ) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| export class TogetherCondition implements Condition { | export class TogetherCondition implements Condition { | ||||
| allowed (user: Creature, target: Creature): boolean { | allowed (user: Creature, target: Creature): boolean { | ||||
| return user.containedIn === target.containedIn && user !== target | return user.containedIn === target.containedIn && user !== target | ||||
| } | } | ||||
| explain (user: Creature, target: Creature): LogEntry { | |||||
| if (this.allowed(user, target)) { | |||||
| return new LogLine( | |||||
| `${user.name.capital} ${user.name.conjugate(new ToBe())} together with ${target.name.objective}` | |||||
| ) | |||||
| } else { | |||||
| return new LogLine( | |||||
| `${user.name.capital} ${user.name.conjugate(new ToBe())} not together with ${target.name.objective}` | |||||
| ) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| export class HasRoomCondition implements Condition { | export class HasRoomCondition implements Condition { | ||||
| @@ -64,6 +138,18 @@ export class HasRoomCondition implements Condition { | |||||
| allowed (user: Creature, target: Creature): boolean { | allowed (user: Creature, target: Creature): boolean { | ||||
| return this.container.capacity >= this.container.fullness + target.voreStats.Bulk | return this.container.capacity >= this.container.fullness + target.voreStats.Bulk | ||||
| } | } | ||||
| explain (user: Creature, target: Creature): LogEntry { | |||||
| if (this.allowed(user, target)) { | |||||
| return new LogLine( | |||||
| `${user.name.capital} ${user.name.conjugate(Words.Have)} enough room in ${user.pronouns.possessive} ${this.container.name} to hold ${target.name.objective}` | |||||
| ) | |||||
| } else { | |||||
| return new LogLine( | |||||
| `${user.name.capital} ${user.name.conjugate(Words.Have)} enough room in ${user.pronouns.possessive} ${this.container.name} to hold ${target.name.objective}; ${user.name} only ${user.name.conjugate(Words.Have)} ${this.container.capacity - this.container.fullness}, but ${target.name.objective} ${target.name.conjugate(Words.Have)} a bulk of ${target.voreStats.Bulk}` | |||||
| ) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| export class ContainedByCondition implements Condition { | export class ContainedByCondition implements Condition { | ||||
| @@ -74,6 +160,18 @@ export class ContainedByCondition implements Condition { | |||||
| allowed (user: Creature, target: Creature): boolean { | allowed (user: Creature, target: Creature): boolean { | ||||
| return user.containedIn === this.container && this.container.owner === target | return user.containedIn === this.container && this.container.owner === target | ||||
| } | } | ||||
| explain (user: Creature, target: Creature): LogEntry { | |||||
| if (this.allowed(user, target)) { | |||||
| return new LogLine( | |||||
| `${user.name.capital} ${user.name.conjugate(new ToBe())} contained in ${target.name.possessive} ${this.container.name}` | |||||
| ) | |||||
| } else { | |||||
| return new LogLine( | |||||
| `${user.name.capital} ${user.name.conjugate(new ToBe())} not contained in ${target.name.possessive} ${this.container.name}` | |||||
| ) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| export class ContainsCondition implements Condition { | export class ContainsCondition implements Condition { | ||||
| @@ -84,18 +182,54 @@ export class ContainsCondition implements Condition { | |||||
| allowed (user: Creature, target: Creature): boolean { | allowed (user: Creature, target: Creature): boolean { | ||||
| return target.containedIn === this.container | return target.containedIn === this.container | ||||
| } | } | ||||
| explain (user: Creature, target: Creature): LogEntry { | |||||
| if (this.allowed(user, target)) { | |||||
| return new LogLine( | |||||
| `${target.name} ${target.name.conjugate(new ToBe())} contained within ${user.name.possessive} ${this.container.name}` | |||||
| ) | |||||
| } else { | |||||
| return new LogLine( | |||||
| `${target.name} ${target.name.conjugate(new ToBe())} not contained within ${user.name.possessive} ${this.container.name}` | |||||
| ) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| export class AllyCondition implements Condition { | export class AllyCondition implements Condition { | ||||
| allowed (user: Creature, target: Creature): boolean { | allowed (user: Creature, target: Creature): boolean { | ||||
| return user.side === target.side | return user.side === target.side | ||||
| } | } | ||||
| explain (user: Creature, target: Creature): LogEntry { | |||||
| if (this.allowed(user, target)) { | |||||
| return new LogLine( | |||||
| `${target.name.capital} is an ally of ${user.name.objective}` | |||||
| ) | |||||
| } else { | |||||
| return new LogLine( | |||||
| `${target.name.capital} ${target.name.conjugate(new Verb("need"))} to be an ally of ${user.name.objective}` | |||||
| ) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| export class EnemyCondition implements Condition { | export class EnemyCondition implements Condition { | ||||
| allowed (user: Creature, target: Creature): boolean { | allowed (user: Creature, target: Creature): boolean { | ||||
| return user.side !== target.side | return user.side !== target.side | ||||
| } | } | ||||
| explain (user: Creature, target: Creature): LogEntry { | |||||
| if (this.allowed(user, target)) { | |||||
| return new LogLine( | |||||
| `${target.name.capital} is an enemy of ${user.name.objective}` | |||||
| ) | |||||
| } else { | |||||
| return new LogLine( | |||||
| `${target.name.capital} ${target.name.conjugate(new Verb("need"))} to be an enemy of ${user.name.objective}` | |||||
| ) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| export class ContainerFullCondition implements Condition { | export class ContainerFullCondition implements Condition { | ||||
| @@ -106,6 +240,18 @@ export class ContainerFullCondition implements Condition { | |||||
| allowed (user: Creature, target: Creature): boolean { | allowed (user: Creature, target: Creature): boolean { | ||||
| return this.container.contents.length > 0 | return this.container.contents.length > 0 | ||||
| } | } | ||||
| explain (user: Creature, target: Creature): LogEntry { | |||||
| if (this.allowed(user, target)) { | |||||
| return new LogLine( | |||||
| `${user.name.possessive.capital} ${this.container.name} contains prey` | |||||
| ) | |||||
| } else { | |||||
| return new LogLine( | |||||
| `${user.name.possessive.capital} ${this.container.name} is empty` | |||||
| ) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| export class MassRatioCondition implements Condition { | export class MassRatioCondition implements Condition { | ||||
| @@ -116,4 +262,16 @@ export class MassRatioCondition implements Condition { | |||||
| allowed (user: Creature, target: Creature): boolean { | allowed (user: Creature, target: Creature): boolean { | ||||
| return user.voreStats.Mass / target.voreStats.Mass > this.ratio | return user.voreStats.Mass / target.voreStats.Mass > this.ratio | ||||
| } | } | ||||
| explain (user: Creature, target: Creature): LogEntry { | |||||
| if (this.allowed(user, target)) { | |||||
| return new LogLine( | |||||
| `${user.name.capital} ${user.name.conjugate(new ToBe())} at least ${this.ratio} times as large as ${target.name.objective}` | |||||
| ) | |||||
| } else { | |||||
| return new LogLine( | |||||
| `${user.name.capital} ${user.name.conjugate(new ToBe())} not at least ${this.ratio} times as large as ${target.name.objective}; ${user.name} ${user.name.conjugate(new ToBe())} only ${(user.voreStats.Mass / target.voreStats.Mass).toFixed(2)} times larger` | |||||
| ) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -2,6 +2,7 @@ import { Consequence, DamageFormula, Condition, StatusEffect } from '../combat' | |||||
| import { Creature } from '../creature' | import { Creature } from '../creature' | ||||
| import { LogEntry, LogLines, LogLine, nilLog } from '../interface' | import { LogEntry, LogLines, LogLine, nilLog } from '../interface' | ||||
| import { Verb, PairLine } from '../language' | import { Verb, PairLine } from '../language' | ||||
| import { Container } from '../vore' | |||||
| /** | /** | ||||
| * Takes a function, and thus can do anything. | * Takes a function, and thus can do anything. | ||||
| @@ -99,3 +100,16 @@ export class StatusConsequence extends Consequence { | |||||
| ) | ) | ||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * Consumes the target | |||||
| */ | |||||
| export class ConsumeConsequence extends Consequence { | |||||
| constructor (public container: Container, conditions: Condition[] = []) { | |||||
| super(conditions) | |||||
| } | |||||
| apply (user: Creature, target: Creature): LogEntry { | |||||
| return this.container.consume(target) | |||||
| } | |||||
| } | |||||
| @@ -88,10 +88,7 @@ export class OpposedStatTest extends RandomTest { | |||||
| ` from ${target.name.possessive} stats.` | ` from ${target.name.possessive} stats.` | ||||
| ), | ), | ||||
| new LogLine( | new LogLine( | ||||
| `${user.name.capital.possessive} total score is ${this.getScore(user, this.userStats)}` | |||||
| ), | |||||
| new LogLine( | |||||
| `${target.name.capital.possessive} total score is ${this.getScore(target, this.targetStats)}` | |||||
| `${user.name.capital}: ${this.getScore(user, this.userStats)} // ${this.getScore(target, this.targetStats)} :${target.name.capital}` | |||||
| ), | ), | ||||
| new LogLine( | new LogLine( | ||||
| `${user.name.capital} ${user.name.conjugate(new Verb("have", "has"))} a ${(this.odds(user, target) * 100).toFixed(0)}% chance of winning this test.` | `${user.name.capital} ${user.name.conjugate(new Verb("have", "has"))} a ${(this.odds(user, target) * 100).toFixed(0)}% chance of winning this test.` | ||||
| @@ -29,6 +29,7 @@ export class Creature extends Mortal { | |||||
| statusEffects: Array<StatusEffect> = []; | statusEffects: Array<StatusEffect> = []; | ||||
| groupActions: Array<GroupAction> = []; | groupActions: Array<GroupAction> = []; | ||||
| items: Array<Item> = []; | items: Array<Item> = []; | ||||
| /* eslint-disable-next-line */ | |||||
| wallet: { [key in Currency]: number } = Object.keys(Currency).reduce((total: any, key) => { total[key] = 0; return total }, {}); | wallet: { [key in Currency]: number } = Object.keys(Currency).reduce((total: any, key) => { total[key] = 0; return total }, {}); | ||||
| otherActions: Array<Action> = []; | otherActions: Array<Action> = []; | ||||
| side: Side; | side: Side; | ||||
| @@ -164,7 +165,7 @@ export class Creature extends Mortal { | |||||
| return results | return results | ||||
| } | } | ||||
| validActions (target: Creature): Array<Action> { | |||||
| allActions (target: Creature): Array<Action> { | |||||
| let choices = ([] as Action[]).concat( | let choices = ([] as Action[]).concat( | ||||
| this.actions, | this.actions, | ||||
| this.containers.flatMap(container => container.actions), | this.containers.flatMap(container => container.actions), | ||||
| @@ -178,7 +179,11 @@ export class Creature extends Mortal { | |||||
| choices = choices.concat(this.containedIn.actions) | choices = choices.concat(this.containedIn.actions) | ||||
| } | } | ||||
| return choices.filter(action => { | |||||
| return choices | |||||
| } | |||||
| validActions (target: Creature): Array<Action> { | |||||
| return this.allActions(target).filter(action => { | |||||
| return action.allowed(this, target) | return action.allowed(this, target) | ||||
| }) | }) | ||||
| } | } | ||||
| @@ -50,7 +50,9 @@ export abstract class Mortal extends Entity { | |||||
| constructor (name: Noun, kind: Noun, pronouns: Pronoun, public baseStats: Stats) { | constructor (name: Noun, kind: Noun, pronouns: Pronoun, public baseStats: Stats) { | ||||
| super(name, kind, pronouns) | super(name, kind, pronouns) | ||||
| /* eslint-disable-next-line */ | |||||
| this.stats = Object.keys(Stat).reduce((base: any, key) => { base[key] = baseStats[key as Stat]; return base }, {}) | this.stats = Object.keys(Stat).reduce((base: any, key) => { base[key] = baseStats[key as Stat]; return base }, {}) | ||||
| /* eslint-disable-next-line */ | |||||
| this.baseResistances = Object.keys(DamageType).reduce((resist: any, key) => { resist[key] = 1; return resist }, {}) | this.baseResistances = Object.keys(DamageType).reduce((resist: any, key) => { resist[key] = 1; return resist }, {}) | ||||
| Object.entries(this.maxVigors).forEach(([key, val]) => { | Object.entries(this.maxVigors).forEach(([key, val]) => { | ||||
| this.vigors[key as Vigor] = val | this.vigors[key as Vigor] = val | ||||
| @@ -2,7 +2,7 @@ import { Mortal } from './entity' | |||||
| import { Damage, DamageType, Stats, Actionable, Action, Vigor, VoreStats, VisibleStatus, VoreStat, DamageInstance, DamageFormula } from './combat' | import { Damage, DamageType, Stats, Actionable, Action, Vigor, VoreStats, VisibleStatus, VoreStat, DamageInstance, DamageFormula } from './combat' | ||||
| import { LogLines, LogEntry, LogLine, nilLog } from './interface' | import { LogLines, LogEntry, LogLine, nilLog } from './interface' | ||||
| import { Noun, Pronoun, ImproperNoun, TextLike, Verb, SecondPersonPronouns, PronounAsNoun, FirstPersonPronouns, PairLineArgs, SoloLine, POV, RandomWord } from './language' | import { Noun, Pronoun, ImproperNoun, TextLike, Verb, SecondPersonPronouns, PronounAsNoun, FirstPersonPronouns, PairLineArgs, SoloLine, POV, RandomWord } from './language' | ||||
| import { DigestAction, DevourAction, ReleaseAction, StruggleAction, TransferAction } from './combat/actions' | |||||
| import { RubAction, DevourAction, ReleaseAction, StruggleAction, TransferAction } from './combat/actions' | |||||
| import * as Words from './words' | import * as Words from './words' | ||||
| import { Creature } from './creature' | import { Creature } from './creature' | ||||
| @@ -177,7 +177,7 @@ export abstract class NormalVoreContainer extends NormalContainer implements Vor | |||||
| this.name = name | this.name = name | ||||
| this.actions.push(new DigestAction(this)) | |||||
| this.actions.push(new RubAction(this)) | |||||
| } | } | ||||
| get fullness (): number { | get fullness (): number { | ||||
| @@ -280,7 +280,7 @@ export abstract class InnerVoreContainer extends NormalVoreContainer { | |||||
| this.actions = [] | this.actions = [] | ||||
| this.actions.push(new DigestAction(this)) | |||||
| this.actions.push(new RubAction(this)) | |||||
| this.actions.push(new StruggleAction(this)) | this.actions.push(new StruggleAction(this)) | ||||
| } | } | ||||
| @@ -1,5 +1,10 @@ | |||||
| import { RandomWord, ImproperNoun, Adjective, Verb } from './language' | import { RandomWord, ImproperNoun, Adjective, Verb } from './language' | ||||
| export const Have = new Verb( | |||||
| "have", | |||||
| "has" | |||||
| ) | |||||
| export const SwallowSound = new RandomWord([ | export const SwallowSound = new RandomWord([ | ||||
| new ImproperNoun('gulp'), | new ImproperNoun('gulp'), | ||||
| new ImproperNoun('glurk'), | new ImproperNoun('glurk'), | ||||