| @@ -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)) | |||
| } | |||
| } | |||
| @@ -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!` | |||
| ) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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 }, | |||
| @@ -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<StatusEffect> = [] | |||
| 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<VisibleStatus> = [] | |||
| 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 | |||
| } | |||
| @@ -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 [] | |||
| } | |||
| } | |||