| @@ -31,7 +31,7 @@ export default class ActionButton extends Vue { | |||||
| const action = (this.action as GroupAction) | const action = (this.action as GroupAction) | ||||
| this.$emit('executed', (this.action as GroupAction).executeGroup(this.user, action.allowedGroup(this.user, this.combatants))) | this.$emit('executed', (this.action as GroupAction).executeGroup(this.user, action.allowedGroup(this.user, this.combatants))) | ||||
| } else { | } 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 { 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 { | export enum DamageType { | ||||
| Pierce = "Pierce", | 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) { | 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 { Creature } from '../entity' | ||||
| import { ProperNoun, ImproperNoun, FemalePronouns, Verb } from '../language' | import { ProperNoun, ImproperNoun, FemalePronouns, Verb } from '../language' | ||||
| import { VoreType, Stomach } from '../vore' | 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 { 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 { | export class Kenzie extends Creature { | ||||
| title = "Large Lycanroc" | title = "Large Lycanroc" | ||||
| desc = "Will eat your party" | desc = "Will eat your party" | ||||
| @@ -30,7 +54,7 @@ export class Kenzie extends Creature { | |||||
| this.containers.push(stomach) | this.containers.push(stomach) | ||||
| this.actions.push( | this.actions.push( | ||||
| new AttackAction( | |||||
| new StompAttack( | |||||
| new StatDamageFormula([ | new StatDamageFormula([ | ||||
| { fraction: 0.5, stat: Stat.Toughness, target: Vigor.Health, type: DamageType.Crush }, | { fraction: 0.5, stat: Stat.Toughness, target: Vigor.Health, type: DamageType.Crush }, | ||||
| { fraction: 0.05, stat: VoreStat.Bulk, 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 { 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 { Vore, VoreContainer, VoreType, Container } from './vore' | ||||
| import { Item } from './items' | import { Item } from './items' | ||||
| @@ -48,6 +48,28 @@ export class Creature extends Vore implements Combatant { | |||||
| voreStats: VoreStats | voreStats: VoreStats | ||||
| side: Side | 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 { | get disabled (): boolean { | ||||
| return Object.values(this.vigors).some(val => val <= 0) | return Object.values(this.vigors).some(val => val <= 0) | ||||
| } | } | ||||
| @@ -152,18 +174,21 @@ export class Creature extends Vore implements Combatant { | |||||
| const results: Array<VisibleStatus> = [] | const results: Array<VisibleStatus> = [] | ||||
| if (this.vigors[Vigor.Health] <= 0) { | 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) { | 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) { | 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) { | 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 | return results | ||||
| } | } | ||||
| @@ -230,3 +230,12 @@ export class CompositeLog implements LogEntry { | |||||
| return this.entries.flatMap(e => e.render()) | 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 [] | |||||
| } | |||||
| } | |||||