diff --git a/src/components/ActionButton.vue b/src/components/ActionButton.vue index 721343e..4f3d2cd 100644 --- a/src/components/ActionButton.vue +++ b/src/components/ActionButton.vue @@ -31,7 +31,7 @@ export default class ActionButton extends Vue { const action = (this.action as GroupAction) this.$emit('executed', (this.action as GroupAction).executeGroup(this.user, action.allowedGroup(this.user, this.combatants))) } else { - this.$emit('executed', this.action.execute(this.user, this.target)) + this.$emit('executed', this.user.executeAction(this.action, this.target)) } } diff --git a/src/game/combat.ts b/src/game/combat.ts index 8a155de..9f5b52b 100644 --- a/src/game/combat.ts +++ b/src/game/combat.ts @@ -1,6 +1,6 @@ import { Creature } from './entity' -import { TextLike } from './language' -import { LogEntry, LogLines, FAElem, LogLine, FormatEntry, FormatOpt, PropElem } from './interface' +import { TextLike, DynText, ToBe } from './language' +import { LogEntry, LogLines, FAElem, LogLine, FormatEntry, FormatOpt, PropElem, nilLog } from './interface' export enum DamageType { Pierce = "Pierce", @@ -327,10 +327,75 @@ export abstract class GroupAction extends Action { } /** - * A displayable status effect (whether implicit, like being dead, or explicit, like having a stun effect on you) + * A displayable status effect */ -export class VisibleStatus { +export interface VisibleStatus { + name: TextLike; + desc: TextLike; + icon: TextLike; +} + +/** + * This kind of status is never explicitly applied to an entity -- e.g., a dead entity will show + * a status indicating that it is dead, but entities cannot be "given" the dead effect + */ +export class ImplicitStatus implements VisibleStatus { + constructor (public name: TextLike, public desc: TextLike, public icon: string) { + + } +} + +/** + * This kind of status is explicitly given to a creature. + * + * Individual status effects should override some of its hooks. + * Some hooks just produce a log entry. + * Some hooks return results along with a log entry. + */ +export abstract class StatusEffect implements VisibleStatus { constructor (public name: TextLike, public desc: TextLike, public icon: string) { } + + onApply (creature: Creature): LogEntry { return nilLog } + onRemove (creature: Creature): LogEntry { return nilLog } + preAction (creature: Creature): { prevented: boolean; log: LogEntry } { + return { + prevented: false, + log: nilLog + } + } +} + +export class StunEffect extends StatusEffect { + constructor () { + super('Stun', 'Stunned!', 'fas fa-sun') + } + + onApply (creature: Creature) { + return new LogLine(`${creature.name.capital} ${creature.name.conjugate(new ToBe())} is stunned!`) + } + + onRemove (creature: Creature) { + return new LogLine(`${creature.name.capital} ${creature.name.conjugate(new ToBe())} no longer stunned.`) + } + + preAction (creature: Creature): { prevented: boolean; log: LogEntry } { + if (Math.random() < 0.3) { + return { + prevented: true, + log: new LogLines( + `${creature.name.capital} ${creature.name.conjugate(new ToBe())} stunned! ${creature.pronouns.capital.subjective} can't move.`, + creature.removeEffect(this) + ) + } + } else { + return { + prevented: true, + log: new LogLines( + `${creature.name.capital} ${creature.name.conjugate(new ToBe())} stunned! ${creature.pronouns.capital.subjective} can't move!` + ) + } + } + } } diff --git a/src/game/creatures/kenzie.ts b/src/game/creatures/kenzie.ts index ae6dc32..f818964 100644 --- a/src/game/creatures/kenzie.ts +++ b/src/game/creatures/kenzie.ts @@ -1,9 +1,33 @@ import { Creature } from '../entity' import { ProperNoun, ImproperNoun, FemalePronouns, Verb } from '../language' import { VoreType, Stomach } from '../vore' -import { Side, Damage, DamageType, Vigor, StatDamageFormula, Stat, VoreStat } from '../combat' +import { Side, Damage, DamageType, Vigor, StatDamageFormula, Stat, VoreStat, DamageFormula, StunEffect } from '../combat' import { AttackAction } from '../combat/actions' +import { LogEntry, LogLines } from '../interface' +import { StatTest } from '../combat/tests' +class StompAttack extends AttackAction { + execute (user: Creature, target: Creature): LogEntry { + if (this.test.test(user, target)) { + const damage = this.damage.calc(user, target) + const targetResult = target.takeDamage(damage) + const ownResult = this.successLine(user, target, { damage: damage }) + const effResult = target.applyEffect(new StunEffect()) + console.log(target.effects) + return new LogLines(ownResult, targetResult, effResult) + } else { + return this.failLine(user, target) + } + } + + constructor (protected damage: DamageFormula, protected verb: Verb = new Verb('smack')) { + super( + damage, + verb + ) + this.test = new StatTest(Stat.Power) + } +} export class Kenzie extends Creature { title = "Large Lycanroc" desc = "Will eat your party" @@ -30,7 +54,7 @@ export class Kenzie extends Creature { this.containers.push(stomach) this.actions.push( - new AttackAction( + new StompAttack( new StatDamageFormula([ { fraction: 0.5, stat: Stat.Toughness, target: Vigor.Health, type: DamageType.Crush }, { fraction: 0.05, stat: VoreStat.Bulk, target: Vigor.Health, type: DamageType.Crush }, diff --git a/src/game/entity.ts b/src/game/entity.ts index 9d719ef..a215508 100644 --- a/src/game/entity.ts +++ b/src/game/entity.ts @@ -1,6 +1,6 @@ -import { DamageType, Damage, Combatant, Stats, Action, Vigor, VoreStats, VoreStat, Stat, Side, GroupAction, Vigors, VisibleStatus } from './combat' +import { DamageType, Damage, Combatant, Stats, Action, Vigor, VoreStats, VoreStat, Stat, Side, GroupAction, Vigors, VisibleStatus, ImplicitStatus, StatusEffect } from './combat' import { Noun, Pronoun, TextLike, POV } from './language' -import { LogEntry, LogLine } from './interface' +import { LogEntry, LogLine, LogLines } from './interface' import { Vore, VoreContainer, VoreType, Container } from './vore' import { Item } from './items' @@ -48,6 +48,28 @@ export class Creature extends Vore implements Combatant { voreStats: VoreStats side: Side + effects: Array = [] + + applyEffect (effect: StatusEffect): LogEntry { + this.effects.push(effect) + return effect.onApply(this) + } + + removeEffect (effect: StatusEffect): LogEntry { + this.effects = this.effects.filter(eff => eff !== effect) + return effect.onRemove(this) + } + + executeAction (action: Action, target: Creature): LogEntry { + const effectResults = this.effects.map(effect => effect.preAction(this)) + const blocking = effectResults.filter(result => result.prevented) + if (blocking.length > 0) { + return new LogLines(...blocking.map(result => result.log)) + } else { + return action.execute(this, target) + } + } + get disabled (): boolean { return Object.values(this.vigors).some(val => val <= 0) } @@ -152,18 +174,21 @@ export class Creature extends Vore implements Combatant { const results: Array = [] if (this.vigors[Vigor.Health] <= 0) { - results.push(new VisibleStatus('Dead', 'Out of health', 'fas fa-heart')) + results.push(new ImplicitStatus('Dead', 'Out of health', 'fas fa-heart')) } if (this.vigors[Vigor.Stamina] <= 0) { - results.push(new VisibleStatus('Unconscious', 'Out of stamina', 'fas fa-bolt')) + results.push(new ImplicitStatus('Unconscious', 'Out of stamina', 'fas fa-bolt')) } if (this.vigors[Vigor.Resolve] <= 0) { - results.push(new VisibleStatus('Broken', 'Out of resolve', 'fas fa-brain')) + results.push(new ImplicitStatus('Broken', 'Out of resolve', 'fas fa-brain')) } if (this.containedIn !== null) { - results.push(new VisibleStatus('Eaten', 'Devoured by ' + this.containedIn.owner.name, 'fas fa-drumstick-bite')) + results.push(new ImplicitStatus('Eaten', 'Devoured by ' + this.containedIn.owner.name, 'fas fa-drumstick-bite')) } + this.effects.forEach(effect => { + results.push(effect) + }) return results } diff --git a/src/game/interface.ts b/src/game/interface.ts index b073df6..1a61880 100644 --- a/src/game/interface.ts +++ b/src/game/interface.ts @@ -230,3 +230,12 @@ export class CompositeLog implements LogEntry { return this.entries.flatMap(e => e.render()) } } + +/** + * Represents nothing. Other elements should filter this out if they don't want blank spaces + */ +export const nilLog = { + render (): HTMLElement[] { + return [] + } +}