|
- import { Creature, POV, Entity } from './entity'
- import { POVPair, POVPairArgs } from './language'
- import { Container } from './vore'
- import { LogEntry, LogLines, CompositeLog, FAElem, LogLine } from './interface'
-
- export interface CombatTest {
- test: (user: Creature, target: Creature) => boolean;
- odds: (user: Creature, target: Creature) => number;
- explain: (user: Creature, target: Creature) => LogEntry;
- }
-
- function logistic (x0: number, L: number, k: number): (x: number) => number {
- return (x: number) => {
- return L / (1 + Math.exp(-k * (x - x0)))
- }
- }
-
- abstract class RandomTest implements CombatTest {
- test (user: Creature, target: Creature): boolean {
- return Math.random() < this.odds(user, target)
- }
-
- abstract odds(user: Creature, target: Creature): number
- abstract explain(user: Creature, target: Creature): LogEntry
- }
-
- export class StatTest extends RandomTest {
- private f: (x: number) => number
-
- constructor (public readonly stat: Stat, k = 0.1) {
- super()
- this.f = logistic(0, 1, k)
- }
-
- odds (user: Creature, target: Creature): number {
- return this.f(user.stats[this.stat] - target.stats[this.stat])
- }
-
- explain (user: Creature, target: Creature): LogEntry {
- const delta: number = user.stats[this.stat] - target.stats[this.stat]
- let result: string
-
- if (delta === 0) {
- result = 'You and the target have the same ' + this.stat + '.'
- } else if (delta < 0) {
- result = 'You have ' + delta + ' less ' + this.stat + ' than your foe.'
- } else {
- result = 'You have ' + delta + ' more ' + this.stat + ' than you foe.'
- }
-
- result += ' Your odds of success are ' + (100 * this.odds(user, target)) + '%'
-
- return new LogLines(result)
- }
- }
-
- export class ChanceTest extends RandomTest {
- constructor (public readonly chance: number) {
- super()
- }
-
- odds (user: Creature, target: Creature): number {
- return this.chance
- }
-
- explain (user: Creature, target: Creature): LogEntry {
- return new LogLines('You have a flat ' + (100 * this.chance) + '% chance.')
- }
- }
-
- export enum DamageType {
- Pierce = "Pierce",
- Slash = "Slash",
- Crush = "Crush",
- Acid = "Acid",
- Seduction = "Seduction",
- Dominance = "Dominance"
- }
-
- export interface DamageInstance {
- type: DamageType;
- amount: number;
- target: Vigor;
- }
-
- export enum Vigor {
- Health = "Health",
- Stamina = "Stamina",
- Willpower = "Willpower"
- }
-
- export const VigorIcons: {[key in Vigor]: string} = {
- [Vigor.Health]: "fas fa-heart",
- [Vigor.Stamina]: "fas fa-bolt",
- [Vigor.Willpower]: "fas fa-brain"
- }
-
- export type Vigors = {[key in Vigor]: number}
-
- export enum Stat {
- STR = 'Strength',
- DEX = 'Dexterity',
- CON = 'Constitution'
- }
-
- export type Stats = {[key in Stat]: number}
-
- export const StatIcons: {[key in Stat]: string} = {
- [Stat.STR]: 'fas fa-fist-raised',
- [Stat.DEX]: 'fas fa-feather',
- [Stat.CON]: 'fas fa-heartbeat'
- }
-
- export class Damage {
- readonly damages: DamageInstance[]
-
- constructor (...damages: DamageInstance[]) {
- this.damages = damages
- }
-
- scale (factor: number): Damage {
- const results: Array<DamageInstance> = []
-
- this.damages.forEach(damage => {
- results.push({
- type: damage.type,
- amount: damage.amount * factor,
- target: damage.target
- })
- })
-
- return new Damage(...results)
- }
-
- toString (): string {
- return this.damages.map(damage => damage.amount + " " + damage.type).join("/")
- }
-
- render (): LogEntry {
- return new LogLine(...this.damages.flatMap(instance => {
- return [instance.amount.toString(), new FAElem(VigorIcons[instance.target]), " " + instance.type]
- }))
- }
-
- renderShort (): LogEntry {
- const totals: Vigors = Object.keys(Vigor).reduce((total: any, key) => { total[key] = 0; return total }, {})
-
- this.damages.forEach(instance => {
- totals[instance.target] += instance.amount
- })
-
- return new LogLine(...Object.keys(Vigor).flatMap(key => totals[key as Vigor] === 0 ? [] : [totals[key as Vigor].toString(), new FAElem(VigorIcons[key as Vigor])]))
- }
- }
-
- export interface Combatant {
- actions: Array<Action>;
- }
-
- export abstract class Action {
- allowed (user: Creature, target: Creature): boolean {
- return this.conditions.every(cond => cond.allowed(user, target))
- }
-
- abstract execute(user: Creature, target: Creature): LogEntry
-
- constructor (public name: string, public desc: string, private conditions: Array<Condition> = []) {
-
- }
-
- toString (): string {
- return this.name
- }
- }
-
- export interface Condition {
- allowed: (user: Creature, target: Creature) => boolean;
- }
-
- class InverseCondition implements Condition {
- allowed (user: Creature, target: Creature): boolean {
- return !this.condition.allowed(user, target)
- }
-
- constructor (private condition: Condition) {
-
- }
- }
- class CapableCondition implements Condition {
- allowed (user: Creature, target: Creature): boolean {
- return !user.disabled
- }
- }
-
- class DrainedVigorCondition implements Condition {
- allowed (user: Creature, target: Creature): boolean {
- return user.vigors[this.vigor] <= 0
- }
-
- constructor (private vigor: Vigor) {
-
- }
- }
-
- export interface Actionable {
- actions: Array<Action>;
- }
-
- abstract class SelfAction extends Action {
- allowed (user: Creature, target: Creature) {
- if (user === target) {
- return super.allowed(user, target)
- } else {
- return false
- }
- }
- }
-
- abstract class PairAction extends Action {
- allowed (user: Creature, target: Creature) {
- if (user !== target) {
- return super.allowed(user, target)
- } else {
- return false
- }
- }
- }
-
- abstract class TogetherAction extends PairAction {
- allowed (user: Creature, target: Creature) {
- if (user.containedIn === target.containedIn) {
- return super.allowed(user, target)
- } else {
- return false
- }
- }
- }
-
- export class AttackAction extends TogetherAction {
- protected test: StatTest
-
- protected successLines: POVPairArgs<Entity, Entity, { damage: Damage }> = new POVPairArgs([
- [[POV.First, POV.Third], (user, target, args) => new LogLine(
- `You smack ${target.name} for `,
- args.damage.renderShort()
- )],
- [[POV.Third, POV.First], (user, target, args) => new LogLine(
- `${user.name.capital} smacks you for `,
- args.damage.renderShort()
- )],
- [[POV.Third, POV.Third], (user, target, args) => new LogLine(
- `${user.name.capital} smacks ${target.name} for `,
- args.damage.renderShort()
- )]
- ])
-
- protected failLines: POVPair<Entity, Entity> = new POVPair([
- [[POV.First, POV.Third], (user, target) => new LogLines(`You try to smack ${target.name}, but you miss`)],
- [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} misses you`)],
- [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} misses ${target.name}`)]
- ])
-
- constructor (protected damage: Damage) {
- super('Attack', 'Attack the enemy', [new CapableCondition()])
- this.test = new StatTest(Stat.STR)
- }
-
- execute (user: Creature, target: Creature): LogEntry {
- if (this.test.test(user, target)) {
- target.takeDamage(this.damage)
- return this.successLines.run(user, target, { damage: this.damage })
- } else {
- return this.failLines.run(user, target)
- }
- }
- }
-
- export class DevourAction extends TogetherAction {
- private test: StatTest
-
- protected failLines: POVPair<Entity, Entity> = new POVPair([
- [[POV.First, POV.Third], (user, target) => new LogLines(`You fail to make a meal out of ${target.name}`)],
- [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} tries to devour you, but fails`)],
- [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} unsuccessfully tries to swallow ${target.name}`)]
- ])
-
- allowed (user: Creature, target: Creature): boolean {
- const owner = this.container.owner === user
- const predOk = Array.from(this.container.voreTypes).every(pref => user.predPrefs.has(pref))
- const preyOk = Array.from(this.container.voreTypes).every(pref => target.preyPrefs.has(pref))
-
- if (owner && predOk && preyOk) {
- return super.allowed(user, target)
- } else {
- return false
- }
- }
-
- constructor (protected container: Container) {
- super('Devour', 'Try to consume your foe', [new CapableCondition()])
- this.name += ` (${container.name})`
- this.test = new StatTest(Stat.STR)
- }
-
- execute (user: Creature, target: Creature): LogEntry {
- if (this.test.test(user, target)) {
- return this.container.consume(target)
- } else {
- return this.failLines.run(user, target)
- }
- }
- }
-
- export class FeedAction extends TogetherAction {
- private test: StatTest
-
- protected failLines: POVPair<Entity, Entity> = new POVPair([
- [[POV.First, POV.Third], (user, target) => new LogLines(`You fail to feed yourself to ${target.name}`)],
- [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} tries to feed ${user.pronouns.possessive} to you, but fails`)],
- [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} unsuccessfully tries to feed ${user.pronouns.possessive} to ${target.name}`)]
- ])
-
- allowed (user: Creature, target: Creature): boolean {
- const owner = this.container.owner === target
- const predOk = Array.from(this.container.voreTypes).every(pref => user.predPrefs.has(pref))
- const preyOk = Array.from(this.container.voreTypes).every(pref => target.preyPrefs.has(pref))
-
- if (owner && predOk && preyOk) {
- return super.allowed(user, target)
- } else {
- return false
- }
- }
-
- constructor (protected container: Container) {
- super('Feed', 'Feed yourself to your opponent', [new DrainedVigorCondition(Vigor.Willpower)])
- this.name += ` (${container.name})`
- this.test = new StatTest(Stat.STR)
- }
-
- execute (user: Creature, target: Creature): LogEntry {
- if (this.test.test(user, target)) {
- return this.container.consume(user)
- } else {
- return this.failLines.run(user, target)
- }
- }
- }
-
- export class StruggleAction extends PairAction {
- private test: StatTest
-
- protected failLines: POVPair<Entity, Entity> = new POVPair([
- [[POV.First, POV.Third], (user, target) => new LogLines(`You fail to escape from ${target.name}`)],
- [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} tries to escape from you, but fails`)],
- [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} unsuccessfully struggles within ${target.name}`)]
- ])
-
- allowed (user: Creature, target: Creature) {
- if (user.containedIn === this.container) {
- return super.allowed(user, target)
- } else {
- return false
- }
- }
-
- constructor (public container: Container) {
- super('Struggle', 'Try to escape your predator', [new CapableCondition()])
- this.test = new StatTest(Stat.STR)
- }
-
- execute (user: Creature, target: Creature): LogEntry {
- if (user.containedIn !== null) {
- if (this.test.test(user, target)) {
- return user.containedIn.release(user)
- } else {
- return this.failLines.run(user, target)
- }
- } else {
- return new LogLines("Vore's bugged!")
- }
- }
- }
-
- export class DigestAction extends SelfAction {
- protected lines: POVPair<Entity, Entity> = new POVPair([])
-
- allowed (user: Creature, target: Creature) {
- if (this.container.owner === user && this.container.contents.length > 0) {
- return super.allowed(user, target)
- } else {
- return false
- }
- }
-
- constructor (protected container: Container) {
- super('Digest', 'Digest all of your current prey', [new CapableCondition()])
- this.name += ` (${container.name})`
- }
-
- execute (user: Creature, target: Creature): LogEntry {
- const results = this.container.tick(60)
- return new CompositeLog(results)
- }
- }
-
- export class ReleaseAction extends PairAction {
- allowed (user: Creature, target: Creature) {
- if (target.containedIn === this.container) {
- return super.allowed(user, target)
- } else {
- return false
- }
- }
-
- constructor (protected container: Container) {
- super('Release', 'Release one of your prey')
- this.name += ` (${container.name})`
- }
-
- execute (user: Creature, target: Creature): LogEntry {
- return this.container.release(target)
- }
- }
-
- export class TransferAction extends PairAction {
- protected lines: POVPair<Entity, Entity> = new POVPair([])
-
- allowed (user: Creature, target: Creature) {
- if (target.containedIn === this.from) {
- return super.allowed(user, target)
- } else {
- return false
- }
- }
-
- constructor (protected from: Container, protected to: Container) {
- super('Transfer', `Shove your prey from your ${from.name} to your ${to.name}`, [new CapableCondition()])
- }
-
- execute (user: Creature, target: Creature): LogEntry {
- this.from.release(target)
- this.to.consume(target)
- return this.lines.run(user, target)
- }
- }
|