From fb7d10b39de2a526c3bf806ade6784a3738c3367 Mon Sep 17 00:00:00 2001 From: Fen Dweller Date: Mon, 13 Jul 2020 22:28:01 -0400 Subject: [PATCH] Add action descrioptions --- src/components/ActionButton.vue | 9 ++- src/components/Combat.vue | 30 +++++++-- src/game/combat.ts | 112 +++++++++++++++++++++++++------- src/game/creatures/cafat.ts | 24 ++++--- src/game/creatures/player.ts | 4 +- src/game/creatures/wolf.ts | 6 +- src/game/interface.ts | 35 +++++++++- 7 files changed, 174 insertions(+), 46 deletions(-) diff --git a/src/components/ActionButton.vue b/src/components/ActionButton.vue index 541e2a2..bd1eeac 100644 --- a/src/components/ActionButton.vue +++ b/src/components/ActionButton.vue @@ -1,5 +1,5 @@ @@ -91,6 +93,20 @@ export default class Combat extends Vue { log.scrollTo({ top: 10000000000, left: 0 }) } } + + @Emit("described") + described (entry: LogEntry) { + const actionDesc = document.querySelector("#action-desc") + + if (actionDesc !== null) { + const holder = document.createElement("div") + entry.render().forEach(element => { + holder.appendChild(element) + }) + actionDesc.innerHTML = '' + actionDesc.appendChild(holder) + } + } } @@ -115,7 +131,7 @@ export default class Combat extends Vue { #log { grid-area: main-row-start / main-col-start / main-row-end / main-col-end; - overflow-y: auto; + overflow-y: scroll; font-size: 16pt; width: 100%; max-height: 100%; @@ -138,6 +154,10 @@ export default class Combat extends Vue { grid-area: 4 / 3 / 6 / 4; } +#action-desc { + grid-area: main-row-end / main-col-start / 6 / main-col-end +} + h3 { margin: 40px 0 0; } diff --git a/src/game/combat.ts b/src/game/combat.ts index 743f7ec..5462881 100644 --- a/src/game/combat.ts +++ b/src/game/combat.ts @@ -25,15 +25,15 @@ export enum Vigor { } export const VigorIcons: {[key in Vigor]: string} = { - [Vigor.Health]: "fas fa-heart", - [Vigor.Stamina]: "fas fa-bolt", - [Vigor.Resolve]: "fas fa-brain" + Health: "fas fa-heart", + Stamina: "fas fa-bolt", + Resolve: "fas fa-brain" } export const VigorDescs: {[key in Vigor]: string} = { - [Vigor.Health]: "How much damage you can take", - [Vigor.Stamina]: "How much energy you have", - [Vigor.Resolve]: "How much dominance you can resist" + Health: "How much damage you can take", + Stamina: "How much energy you have", + Resolve: "How much dominance you can resist" } export type Vigors = {[key in Vigor]: number} @@ -49,19 +49,19 @@ export enum Stat { export type Stats = {[key in Stat]: number} export const StatIcons: {[key in Stat]: string} = { - [Stat.Toughness]: 'fas fa-heartbeat', - [Stat.Power]: 'fas fa-fist-raised', - [Stat.Speed]: 'fas fa-feather', - [Stat.Willpower]: 'fas fa-book', - [Stat.Charm]: 'fas fa-comments' + Toughness: 'fas fa-heartbeat', + Power: 'fas fa-fist-raised', + Speed: 'fas fa-feather', + Willpower: 'fas fa-book', + Charm: 'fas fa-comments' } export const StatDescs: {[key in Stat]: string} = { - [Stat.Toughness]: 'Your physical resistance', - [Stat.Power]: 'Your physical power', - [Stat.Speed]: 'How quickly you can act', - [Stat.Willpower]: 'Your mental resistance', - [Stat.Charm]: 'Your mental power' + Toughness: 'Your physical resistance', + Power: 'Your physical power', + Speed: 'How quickly you can act', + Willpower: 'Your mental resistance', + Charm: 'Your mental power' } export interface CombatTest { @@ -129,7 +129,7 @@ export class StatVigorTest extends RandomTest { result = 'You have ' + delta + ' more ' + this.stat + ' than you foe.' } - result += ' Your odds of success are ' + (100 * this.odds(user, target)) + '%' + result += ' Your odds of success are ' + (100 * this.odds(user, target)).toFixed(1) + '%' return new LogLines(result) } @@ -159,7 +159,7 @@ export class StatTest extends RandomTest { result = 'You have ' + delta + ' more ' + this.stat + ' than you foe.' } - result += ' Your odds of success are ' + (100 * this.odds(user, target)) + '%' + result += ' Your odds of success are ' + (100 * this.odds(user, target)).toFixed(1) + '%' return new LogLines(result) } @@ -221,6 +221,39 @@ export class Damage { } } +export interface DamageFormula { + calc (user: Creature, target: Creature): Damage; + describe (user: Creature, target: Creature): LogEntry; +} + +export class ConstantDamageFormula implements DamageFormula { + calc (user: Creature, target: Creature): Damage { + return this.damage + } + + constructor (private damage: Damage) { + + } + + describe (user: Creature, target: Creature): LogEntry { + return new LogLine('Deal ', this.damage.renderShort(), ' damage') + } +} + +export class UniformRandomDamageFormula implements DamageFormula { + calc (user: Creature, target: Creature): Damage { + return this.damage.scale(Math.random() * this.variance * 2 - this.variance + 1) + } + + constructor (private damage: Damage, private variance: number) { + + } + + describe (user: Creature, target: Creature): LogEntry { + return new LogLine('Deal between ', this.damage.scale(1 - this.variance).renderShort(), ' and ', this.damage.scale(1 + this.variance).renderShort(), ' damage.') + } +} + export interface Combatant { actions: Array; } @@ -232,7 +265,9 @@ export abstract class Action { abstract execute(user: Creature, target: Creature): LogEntry - constructor (public name: TextLike, public desc: string, private conditions: Array = []) { + abstract describe (user: Creature, target: Creature): LogEntry + + constructor (public name: TextLike, public desc: TextLike, private conditions: Array = []) { } @@ -328,20 +363,25 @@ export class AttackAction extends TogetherAction { [[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} misses ${target.name}`)] ]) - constructor (protected damage: Damage) { + constructor (protected damage: DamageFormula) { super('Attack', 'Attack the enemy', [new CapableCondition()]) this.test = new StatTest(Stat.Power) } execute (user: Creature, target: Creature): LogEntry { if (this.test.test(user, target)) { - const targetResult = target.takeDamage(this.damage) - const ownResult = this.successLines.run(user, target, { damage: this.damage }) + const damage = this.damage.calc(user, target) + const targetResult = target.takeDamage(damage) + const ownResult = this.successLines.run(user, target, { damage: damage }) return new CompositeLog(ownResult, targetResult) } else { return this.failLines.run(user, target) } } + + describe (user: Creature, target: Creature): LogEntry { + return new LogLine(`Attack ${target.name}. `, this.damage.describe(user, target), '. ', this.test.explain(user, target)) + } } export class DevourAction extends TogetherAction { @@ -377,6 +417,10 @@ export class DevourAction extends TogetherAction { return this.failLines.run(user, target) } } + + describe (user: Creature, target: Creature): LogEntry { + return new LogLine(`Try to consume your opponent, sending them to your ${this.container.name}. `, this.test.explain(user, target)) + } } export class FeedAction extends TogetherAction { @@ -409,6 +453,10 @@ export class FeedAction extends TogetherAction { execute (user: Creature, target: Creature): LogEntry { return this.container.consume(user) } + + describe (user: Creature, target: Creature): LogEntry { + return new LogLine(`Your willpower is drained, and you really feel like shoving yourself into ${target.name}'s ${this.container.name}...`) + } } export class StruggleAction extends PairAction { @@ -444,6 +492,10 @@ export class StruggleAction extends PairAction { return new LogLines("Vore's bugged!") } } + + describe (user: Creature, target: Creature): LogEntry { + return new LogLine(`Try to escape from ${target.name}'s ${this.container.name}. `, this.test.explain(user, target)) + } } export abstract class EatenAction extends PairAction { @@ -458,7 +510,7 @@ 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), ')'), 'Do something to your prey!', [new CapableCondition()]) + super(new DynText(name, ' (', new LiveText(container, x => x.name.all), ')'), desc, [new CapableCondition()]) } } export class DigestAction extends SelfAction { @@ -480,6 +532,10 @@ export class DigestAction extends SelfAction { const results = this.container.tick(60) return new CompositeLog(results) } + + describe (user: Creature, target: Creature): LogEntry { + return new LogLine(`Digest everyone inside of your ${this.container.name}.`) + } } export class ReleaseAction extends PairAction { @@ -498,6 +554,10 @@ export class ReleaseAction extends PairAction { execute (user: Creature, target: Creature): LogEntry { return this.container.release(target) } + + describe (user: Creature, target: Creature): LogEntry { + return new LogLine(`Release ${target.name} from your ${this.container.name}.`) + } } export class TransferAction extends PairAction { @@ -516,7 +576,7 @@ export class TransferAction extends PairAction { } constructor (protected from: Container, protected to: Container) { - super('Transfer', `Shove your prey from your ${from.name} to your ${to.name}`, [new CapableCondition()]) + super('Transfer', `Move from your ${from.name} to your ${to.name}`, [new CapableCondition()]) } execute (user: Creature, target: Creature): LogEntry { @@ -524,4 +584,8 @@ export class TransferAction extends PairAction { this.to.consume(target) return this.lines.run(user, target, { from: this.from, to: this.to }) } + + describe (user: Creature, target: Creature): LogEntry { + return new LogLine(`Push ${target.name} from your ${this.from.name} to your ${this.to.name}`) + } } diff --git a/src/game/creatures/cafat.ts b/src/game/creatures/cafat.ts index b72a6c5..a083f28 100644 --- a/src/game/creatures/cafat.ts +++ b/src/game/creatures/cafat.ts @@ -1,5 +1,5 @@ import { Creature, POV, Entity } from '../entity' -import { Stat, Damage, DamageType, TransferAction, Vigor, StatTest, FeedAction, DigestAction, EatenAction, AttackAction } from '../combat' +import { Stat, Damage, DamageType, TransferAction, Vigor, StatTest, FeedAction, DigestAction, EatenAction, AttackAction, DamageFormula, ConstantDamageFormula } from '../combat' import { ProperNoun, TheyPronouns, ImproperNoun, POVPair, FemalePronouns, POVPairArgs } from '../language' import { VoreType, Stomach, InnerStomach, Container, Bowels } from '../vore' import { LogLine, LogLines, LogEntry, FAElem, CompositeLog, ImgElem } from '../interface' @@ -21,15 +21,17 @@ class BellyCrushAction extends AttackAction { ), new ImgElem('./media/cafat/images/belly-crush.webp'))] ]) - constructor (private _damage: Damage) { - super(_damage) + constructor (_damage: Damage) { + super({ + calc (user, target) { return _damage.scale(user.bulk / 25) }, + describe (user, target) { return new LogLine('Deal ', _damage.scale(user.bulk / 25).renderShort(), ` with your ${user.bulk} `, new FAElem('fas fa-weight-hanging')) } + }) this.name = 'Belly Crush' - this.desc = 'Deal damage proportional to your bulk' + this.desc = 'Use your weight!' } - execute (user: Creature, target: Creature): LogEntry { - this.damage = this._damage.scale(user.bulk / 25 + 1) - return super.execute(user, target) + describe (user: Creature, target: Creature): LogEntry { + return new LogLine(`Crush ${target.name} under your gut. `, this.damage.describe(user, target)) } } @@ -50,7 +52,7 @@ class BelchAction extends AttackAction { ]) constructor (damage: Damage) { - super(damage) + super(new ConstantDamageFormula(damage)) this.name = 'Belch' this.desc = 'Drain your foe\'s willpower with a solid BELCH' } @@ -75,6 +77,10 @@ class CrushAction extends EatenAction { target.takeDamage(this.damage) return this.lines.run(user, target) } + + describe (user: Creature, target: Creature): LogEntry { + return new LogLine(`Crush ${target.name} in your ${this.container.name} for massive, unavoidable damage.`) + } } export class Cafat extends Creature { @@ -132,7 +138,7 @@ export class Cafat extends Creature { this.actions.push(transfer) this.actions.push(new TransferAction(lowerStomach, stomach)) - this.actions.push(new AttackAction(new Damage({ amount: 40, type: DamageType.Crush, target: Vigor.Health }))) + this.actions.push(new AttackAction(new ConstantDamageFormula(new Damage({ amount: 40, type: DamageType.Crush, target: Vigor.Health })))) this.actions.push(new BellyCrushAction(new Damage({ amount: 10, type: DamageType.Crush, target: Vigor.Health }, { amount: 10, type: DamageType.Dominance, target: Vigor.Resolve }))) this.actions.push(new BelchAction(new Damage( { amount: 100, target: Vigor.Resolve, type: DamageType.Acid } diff --git a/src/game/creatures/player.ts b/src/game/creatures/player.ts index da25d80..ef95e79 100644 --- a/src/game/creatures/player.ts +++ b/src/game/creatures/player.ts @@ -1,13 +1,13 @@ import { Creature, POV } from '../entity' import { ProperNoun, TheyPronouns } from '../language' -import { Stat, Damage, AttackAction, DamageType, Vigor } from '../combat' +import { Stat, Damage, AttackAction, DamageType, Vigor, ConstantDamageFormula } from '../combat' import { Stomach, Bowels, VoreType } from '../vore' export class Player extends Creature { constructor () { super(new ProperNoun('The Dude'), TheyPronouns, { Toughness: 20, Power: 20, Speed: 20, Willpower: 20, Charm: 20 }, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral, VoreType.Anal]), 50) - this.actions.push(new AttackAction(new Damage({ type: DamageType.Pierce, amount: 20, target: Vigor.Health }, { type: DamageType.Pierce, amount: 20, target: Vigor.Stamina }))) + this.actions.push(new AttackAction(new ConstantDamageFormula(new Damage({ type: DamageType.Pierce, amount: 20, target: Vigor.Health }, { type: DamageType.Pierce, amount: 20, target: Vigor.Stamina })))) const stomach = new Stomach(this, 100, new Damage({ amount: 20, type: DamageType.Acid, target: Vigor.Health }, { amount: 10, type: DamageType.Crush, target: Vigor.Health })) diff --git a/src/game/creatures/wolf.ts b/src/game/creatures/wolf.ts index b9fae97..54a10ae 100644 --- a/src/game/creatures/wolf.ts +++ b/src/game/creatures/wolf.ts @@ -1,12 +1,12 @@ import { Creature, POV, Entity } from '../entity' -import { Stat, Damage, DamageType, AttackAction, TransferAction, Vigor, StatTest, FeedAction } from '../combat' +import { Stat, Damage, DamageType, AttackAction, TransferAction, Vigor, StatTest, FeedAction, ConstantDamageFormula } from '../combat' import { MalePronouns, ImproperNoun, POVPair, POVPairArgs } from '../language' import { LogLine, LogLines } from '../interface' import { VoreType, Stomach, Bowels } from '../vore' class BiteAction extends AttackAction { constructor () { - super(new Damage({ amount: 10, type: DamageType.Slash, target: Vigor.Health })) + super(new ConstantDamageFormula(new Damage({ amount: 10, type: DamageType.Slash, target: Vigor.Health }))) this.name = "Bite" } } @@ -34,7 +34,7 @@ class HypnoAction extends AttackAction { ]) constructor () { - super(new Damage({ amount: 30, type: DamageType.Dominance, target: Vigor.Resolve })) + super(new ConstantDamageFormula(new Damage({ amount: 30, type: DamageType.Dominance, target: Vigor.Resolve }))) this.test = new StatTest(Stat.Willpower) this.name = "Hypnotize" } diff --git a/src/game/interface.ts b/src/game/interface.ts index ceee324..86a083c 100644 --- a/src/game/interface.ts +++ b/src/game/interface.ts @@ -1,3 +1,5 @@ +import { Stat, Vigor } from './combat' + export interface LogEntry { render: () => HTMLElement[]; } @@ -62,7 +64,7 @@ export class LogLine implements LogEntry { } render (): HTMLElement[] { - const div = document.createElement("div") + const div = document.createElement("span") this.parts.forEach(part => { if (typeof part === "string") { @@ -92,6 +94,37 @@ export class FAElem implements LogEntry { } } +export class PropElem implements LogEntry { + constructor (private value: number, private prop: Stat | Vigor) { + + } + + render (): HTMLElement[] { + let cls: string + switch (this.prop) { + case Vigor.Health: cls = "fas fa-heart"; break + case Vigor.Stamina: cls = "fas fa-bolt"; break + case Vigor.Resolve: cls = "fas fa-brain"; break + case Stat.Toughness: cls = "fas fa-heartbeat"; break + case Stat.Power: cls = "fas fa-fist-raised"; break + case Stat.Speed: cls = "fas fa-weather"; break + case Stat.Willpower: cls = "fas fa-book"; break + case Stat.Charm: cls = "fas fa-comments"; break + } + + const span = document.createElement("span") + span.classList.add("stat-entry") + span.textContent = this.value.toString() + new FAElem(cls).render().forEach(elem => { + span.appendChild(elem) + }) + span.dataset.tooltip = this.prop + span.dataset["tooltip-full"] = this.prop + + return [span] + } +} + export class ImgElem implements LogEntry { constructor (private url: string) {