| @@ -10,7 +10,8 @@ export enum DamageType { | |||||
| Crush = "Crush", | Crush = "Crush", | ||||
| Acid = "Acid", | Acid = "Acid", | ||||
| Seduction = "Seduction", | Seduction = "Seduction", | ||||
| Dominance = "Dominance" | |||||
| Dominance = "Dominance", | |||||
| Heal = "Heal" | |||||
| } | } | ||||
| export interface DamageInstance { | export interface DamageInstance { | ||||
| @@ -147,10 +148,11 @@ export class Damage { | |||||
| const vigorTotals: Vigors = Object.keys(Vigor).reduce((total: any, key) => { total[key] = 0; return total }, {}) | const vigorTotals: Vigors = Object.keys(Vigor).reduce((total: any, key) => { total[key] = 0; return total }, {}) | ||||
| const statTotals: Stats = Object.keys(Stat).reduce((total: any, key) => { total[key] = 0; return total }, {}) | const statTotals: Stats = Object.keys(Stat).reduce((total: any, key) => { total[key] = 0; return total }, {}) | ||||
| this.damages.forEach(instance => { | this.damages.forEach(instance => { | ||||
| const factor = instance.type === DamageType.Heal ? -1 : 1 | |||||
| if (instance.target in Vigor) { | if (instance.target in Vigor) { | ||||
| vigorTotals[instance.target as Vigor] += instance.amount | |||||
| vigorTotals[instance.target as Vigor] += factor * instance.amount | |||||
| } else if (instance.target in Stat) { | } else if (instance.target in Stat) { | ||||
| statTotals[instance.target as Stat] += instance.amount | |||||
| statTotals[instance.target as Stat] += factor * instance.amount | |||||
| } | } | ||||
| }) | }) | ||||
| @@ -213,13 +215,28 @@ export class UniformRandomDamageFormula implements DamageFormula { | |||||
| export class StatDamageFormula implements DamageFormula { | export class StatDamageFormula implements DamageFormula { | ||||
| calc (user: Creature, target: Creature): Damage { | calc (user: Creature, target: Creature): Damage { | ||||
| const instances: Array<DamageInstance> = this.factors.map(factor => ( | |||||
| { | |||||
| amount: factor.fraction * user.stats[factor.stat], | |||||
| target: factor.target, | |||||
| type: factor.type | |||||
| const instances: Array<DamageInstance> = this.factors.map(factor => { | |||||
| if (factor.stat in Stat) { | |||||
| return { | |||||
| amount: factor.fraction * user.stats[factor.stat as Stat], | |||||
| target: factor.target, | |||||
| type: factor.type | |||||
| } | |||||
| } else if (factor.stat in VoreStat) { | |||||
| return { | |||||
| amount: factor.fraction * user.voreStats[factor.stat as VoreStat], | |||||
| target: factor.target, | |||||
| type: factor.type | |||||
| } | |||||
| } else { | |||||
| // should be impossible; .stat is Stat|VoreStat | |||||
| return { | |||||
| amount: 0, | |||||
| target: Vigor.Health, | |||||
| type: DamageType.Heal | |||||
| } | |||||
| } | } | ||||
| )) | |||||
| }) | |||||
| return new Damage(...instances) | return new Damage(...instances) | ||||
| } | } | ||||
| @@ -244,7 +261,7 @@ export class StatDamageFormula implements DamageFormula { | |||||
| ) | ) | ||||
| } | } | ||||
| constructor (private factors: Array<{ stat: Stat; fraction: number; type: DamageType; target: Vigor|Stat }>) { | |||||
| constructor (private factors: Array<{ stat: Stat|VoreStat; fraction: number; type: DamageType; target: Vigor|Stat }>) { | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,5 +1,5 @@ | |||||
| import { StatTest, StatVigorTest } from './tests' | import { StatTest, StatVigorTest } from './tests' | ||||
| import { POVPairArgs, POVPair, DynText, LiveText, TextLike } from '../language' | |||||
| import { POVPairArgs, POVPair, DynText, LiveText, TextLike, Verb } from '../language' | |||||
| import { Entity, POV, Creature } from '../entity' | import { Entity, POV, Creature } from '../entity' | ||||
| import { Damage, DamageFormula, Stat, Vigor, Action } from '../combat' | import { Damage, DamageFormula, Stat, Vigor, Action } from '../combat' | ||||
| import { LogLine, LogLines, LogEntry, CompositeLog } from '../interface' | import { LogLine, LogLines, LogEntry, CompositeLog } from '../interface' | ||||
| @@ -11,28 +11,28 @@ export class AttackAction extends Action { | |||||
| protected successLines: POVPairArgs<Entity, Entity, { damage: Damage }> = new POVPairArgs([ | protected successLines: POVPairArgs<Entity, Entity, { damage: Damage }> = new POVPairArgs([ | ||||
| [[POV.First, POV.Third], (user, target, args) => new LogLine( | [[POV.First, POV.Third], (user, target, args) => new LogLine( | ||||
| `You smack ${target.name} for `, | |||||
| `You ${this.verb} ${target.name} for `, | |||||
| args.damage.renderShort() | args.damage.renderShort() | ||||
| )], | )], | ||||
| [[POV.Third, POV.First], (user, target, args) => new LogLine( | [[POV.Third, POV.First], (user, target, args) => new LogLine( | ||||
| `${user.name.capital} smacks you for `, | |||||
| `${user.name.capital} ${this.verb.singular} you for `, | |||||
| args.damage.renderShort() | args.damage.renderShort() | ||||
| )], | )], | ||||
| [[POV.Third, POV.Third], (user, target, args) => new LogLine( | [[POV.Third, POV.Third], (user, target, args) => new LogLine( | ||||
| `${user.name.capital} smacks ${target.name} for `, | |||||
| `${user.name.capital} ${this.verb.singular} ${target.name} for `, | |||||
| args.damage.renderShort() | args.damage.renderShort() | ||||
| )] | )] | ||||
| ]) | ]) | ||||
| protected failLines: POVPair<Entity, Entity> = new POVPair([ | protected failLines: POVPair<Entity, Entity> = new POVPair([ | ||||
| [[POV.First, POV.Third], (user, target) => new LogLine(`You try to smack ${target.name}, but you miss`)], | |||||
| [[POV.First, POV.Third], (user, target) => new LogLine(`You try to ${this.verb.present} ${target.name}, but you miss`)], | |||||
| [[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital} misses you`)], | [[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital} misses you`)], | ||||
| [[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} misses ${target.name}`)] | [[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} misses ${target.name}`)] | ||||
| ]) | ]) | ||||
| constructor (protected damage: DamageFormula) { | |||||
| constructor (protected damage: DamageFormula, protected verb: Verb = new Verb('smack')) { | |||||
| super( | super( | ||||
| 'Attack', | |||||
| verb.root.capital, | |||||
| 'Attack the enemy', | 'Attack the enemy', | ||||
| [new CapableCondition(), new TogetherCondition(), new EnemyCondition()] | [new CapableCondition(), new TogetherCondition(), new EnemyCondition()] | ||||
| ) | ) | ||||
| @@ -23,10 +23,5 @@ export class Human extends Creature { | |||||
| this.title = "Snack" | this.title = "Snack" | ||||
| this.desc = "Definitely going on an adventure" | this.desc = "Definitely going on an adventure" | ||||
| this.actions.push(new AttackAction(new ConstantDamageFormula( | |||||
| new Damage( | |||||
| { amount: 20, target: Vigor.Health, type: DamageType.Slash } | |||||
| ) | |||||
| ))) | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,9 +1,9 @@ | |||||
| import { Creature, POV } from '../entity' | import { Creature, POV } from '../entity' | ||||
| import { ProperNoun, ImproperNoun, FemalePronouns, POVPairArgs, POVPair } from '../language' | |||||
| import { ProperNoun, ImproperNoun, FemalePronouns, POVPairArgs, POVPair, Verb } from '../language' | |||||
| import { VoreType, Stomach, Vore } from '../vore' | import { VoreType, Stomach, Vore } from '../vore' | ||||
| import { Side, Damage, DamageType, Vigor, UniformRandomDamageFormula } from '../combat' | |||||
| import { Side, Damage, DamageType, Vigor, UniformRandomDamageFormula, ConstantDamageFormula, StatDamageFormula, Stat, VoreStat } from '../combat' | |||||
| import { LogLine } from '../interface' | import { LogLine } from '../interface' | ||||
| import { FeedAction, TransferAction } from '../combat/actions' | |||||
| import { FeedAction, TransferAction, AttackAction } from '../combat/actions' | |||||
| import * as Words from '../words' | import * as Words from '../words' | ||||
| export class Kenzie extends Creature { | export class Kenzie extends Creature { | ||||
| @@ -30,5 +30,15 @@ export class Kenzie extends Creature { | |||||
| )) | )) | ||||
| this.containers.push(stomach) | this.containers.push(stomach) | ||||
| this.actions.push( | |||||
| new AttackAction( | |||||
| 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 } | |||||
| ]), | |||||
| new Verb('crush', 'crushes', 'crushing', 'crushed') | |||||
| ) | |||||
| ) | |||||
| } | } | ||||
| } | } | ||||
| @@ -116,7 +116,10 @@ const huge = new RandomWord([ | |||||
| class BiteAction extends AttackAction { | class BiteAction extends AttackAction { | ||||
| constructor () { | constructor () { | ||||
| super(new ConstantDamageFormula(new Damage({ amount: 50, type: DamageType.Slash, target: Vigor.Health }))) | |||||
| super( | |||||
| new ConstantDamageFormula(new Damage({ amount: 50, type: DamageType.Slash, target: Vigor.Health })), | |||||
| new Verb('bite', 'bites', 'biting', 'bit') | |||||
| ) | |||||
| this.name = "Bite" | this.name = "Bite" | ||||
| } | } | ||||
| } | } | ||||
| @@ -30,158 +30,165 @@ export interface Mortal extends Entity { | |||||
| export class Creature extends Vore implements Combatant { | export class Creature extends Vore implements Combatant { | ||||
| title = "Lv. 1 Creature" | title = "Lv. 1 Creature" | ||||
| desc = "Some creature" | desc = "Some creature" | ||||
| vigors = { | |||||
| [Vigor.Health]: 100, | |||||
| [Vigor.Stamina]: 100, | |||||
| [Vigor.Resolve]: 100 | |||||
| vigors = { | |||||
| [Vigor.Health]: 100, | |||||
| [Vigor.Stamina]: 100, | |||||
| [Vigor.Resolve]: 100 | |||||
| } | |||||
| maxVigors = { | |||||
| [Vigor.Health]: 100, | |||||
| [Vigor.Stamina]: 100, | |||||
| [Vigor.Resolve]: 100 | |||||
| } | |||||
| baseStats: Stats | |||||
| voreStats: VoreStats | |||||
| side: Side | |||||
| get disabled (): boolean { | |||||
| return Object.values(this.vigors).some(val => val <= 0) | |||||
| } | |||||
| resistances: Map<DamageType, number> = new Map() | |||||
| perspective: POV = POV.Third | |||||
| containers: Array<VoreContainer> = [] | |||||
| otherContainers: Array<Container> = [] | |||||
| actions: Array<Action> = [] | |||||
| groupActions: Array<GroupAction> = [] | |||||
| otherActions: Array<Action> = [] | |||||
| items: Array<Item> = [] | |||||
| get bulk (): number { | |||||
| return this.voreStats.Mass + this.containers.reduce((total, conatiner) => { return total + conatiner.contents.reduce((total, prey) => total + prey.voreStats.Bulk, 0) }, 0) | |||||
| } | |||||
| containedIn: VoreContainer|null = null; | |||||
| constructor (public name: Noun, public kind: Noun, public pronouns: Pronoun, public stats: Stats, public preyPrefs: Set<VoreType>, public predPrefs: Set<VoreType>, mass: number) { | |||||
| super() | |||||
| const containers = this.containers | |||||
| this.vigors.Health = this.maxVigors.Health = stats.Toughness * 10 + stats.Power * 5 | |||||
| this.vigors.Stamina = this.maxVigors.Stamina = stats.Toughness * 3 + stats.Speed * 10 + stats.Willpower * 3 | |||||
| this.vigors.Resolve = this.maxVigors.Resolve = stats.Willpower * 10 + stats.Charm * 5 | |||||
| this.baseStats = Object.keys(Stat).reduce((base: any, key) => { base[key] = stats[key as Stat]; return base }, {}) | |||||
| this.side = Side.Heroes | |||||
| this.voreStats = { | |||||
| get [VoreStat.Bulk] () { | |||||
| console.log(containers) | |||||
| return containers.reduce( | |||||
| (total: number, container: VoreContainer) => { | |||||
| return total + container.contents.reduce( | |||||
| (total: number, prey: Vore) => { | |||||
| return total + prey.voreStats.Bulk | |||||
| }, | |||||
| 0 | |||||
| ) + container.digested.reduce( | |||||
| (total: number, prey: Vore) => { | |||||
| return total + prey.voreStats.Bulk | |||||
| }, | |||||
| 0 | |||||
| ) | |||||
| }, | |||||
| this.Mass | |||||
| ) | |||||
| }, | |||||
| [VoreStat.Mass]: mass, | |||||
| get [VoreStat.PreyCount] () { | |||||
| return containers.reduce( | |||||
| (total: number, container: VoreContainer) => { | |||||
| return total + container.contents.reduce( | |||||
| (total: number, prey: Vore) => { | |||||
| return total + 1 + prey.voreStats[VoreStat.PreyCount] | |||||
| }, | |||||
| 0 | |||||
| ) | |||||
| }, | |||||
| 0 | |||||
| ) | |||||
| } | |||||
| } | } | ||||
| } | |||||
| toString (): string { | |||||
| return this.name.toString() | |||||
| } | |||||
| takeDamage (damage: Damage): LogEntry { | |||||
| const startHealth = this.vigors.Health | |||||
| damage.damages.forEach(instance => { | |||||
| const factor = instance.type === DamageType.Heal ? -1 : 1 | |||||
| const resistance: number|undefined = this.resistances.get(instance.type) | |||||
| if (resistance !== undefined) { | |||||
| if (instance.target in Vigor) { | |||||
| this.vigors[instance.target as Vigor] -= instance.amount * factor * resistance | |||||
| } else if (instance.target in Stat) { | |||||
| this.stats[instance.target as Stat] -= instance.amount * factor * resistance | |||||
| } | |||||
| } else { | |||||
| if (instance.target in Vigor) { | |||||
| this.vigors[instance.target as Vigor] -= instance.amount * factor | |||||
| } else if (instance.target in Stat) { | |||||
| this.stats[instance.target as Stat] -= instance.amount * factor | |||||
| } | |||||
| } | |||||
| }) | |||||
| maxVigors = { | |||||
| [Vigor.Health]: 100, | |||||
| [Vigor.Stamina]: 100, | |||||
| [Vigor.Resolve]: 100 | |||||
| if (this.vigors.Health <= 0 && startHealth > 0) { | |||||
| return this.destroy() | |||||
| } else { | |||||
| return new LogLine() | |||||
| } | } | ||||
| } | |||||
| baseStats: Stats | |||||
| voreStats: VoreStats | |||||
| side: Side | |||||
| get disabled (): boolean { | |||||
| return Object.values(this.vigors).some(val => val <= 0) | |||||
| get status (): string { | |||||
| if (this.vigors[Vigor.Health] <= 0) { | |||||
| return "Dead" | |||||
| } | } | ||||
| resistances: Map<DamageType, number> = new Map() | |||||
| perspective: POV = POV.Third | |||||
| containers: Array<VoreContainer> = [] | |||||
| otherContainers: Array<Container> = [] | |||||
| actions: Array<Action> = [] | |||||
| groupActions: Array<GroupAction> = [] | |||||
| otherActions: Array<Action> = [] | |||||
| items: Array<Item> = [] | |||||
| get bulk (): number { | |||||
| return this.voreStats.Mass + this.containers.reduce((total, conatiner) => { return total + conatiner.contents.reduce((total, prey) => total + prey.voreStats.Bulk, 0) }, 0) | |||||
| if (this.vigors[Vigor.Stamina] <= 0) { | |||||
| return "Unconscious" | |||||
| } | } | ||||
| containedIn: VoreContainer|null = null; | |||||
| constructor (public name: Noun, public kind: Noun, public pronouns: Pronoun, public stats: Stats, public preyPrefs: Set<VoreType>, public predPrefs: Set<VoreType>, mass: number) { | |||||
| super() | |||||
| const containers = this.containers | |||||
| this.baseStats = Object.keys(Stat).reduce((base: any, key) => { base[key] = stats[key as Stat]; return base }, {}) | |||||
| this.side = Side.Heroes | |||||
| this.voreStats = { | |||||
| get [VoreStat.Bulk] () { | |||||
| console.log(containers) | |||||
| return containers.reduce( | |||||
| (total: number, container: VoreContainer) => { | |||||
| return total + container.contents.reduce( | |||||
| (total: number, prey: Vore) => { | |||||
| return total + prey.voreStats.Bulk | |||||
| }, | |||||
| 0 | |||||
| ) + container.digested.reduce( | |||||
| (total: number, prey: Vore) => { | |||||
| return total + prey.voreStats.Bulk | |||||
| }, | |||||
| 0 | |||||
| ) | |||||
| }, | |||||
| this.Mass | |||||
| ) | |||||
| }, | |||||
| [VoreStat.Mass]: mass, | |||||
| get [VoreStat.PreyCount] () { | |||||
| return containers.reduce( | |||||
| (total: number, container: VoreContainer) => { | |||||
| return total + container.contents.reduce( | |||||
| (total: number, prey: Vore) => { | |||||
| return total + 1 + prey.voreStats[VoreStat.PreyCount] | |||||
| }, | |||||
| 0 | |||||
| ) | |||||
| }, | |||||
| 0 | |||||
| ) | |||||
| } | |||||
| } | |||||
| if (this.vigors[Vigor.Resolve] <= 0) { | |||||
| return "Broken" | |||||
| } | } | ||||
| toString (): string { | |||||
| return this.name.toString() | |||||
| if (this.containedIn !== null) { | |||||
| return `${this.containedIn.consumeVerb.past.capital} by ${this.containedIn.owner.name}` | |||||
| } | } | ||||
| takeDamage (damage: Damage): LogEntry { | |||||
| const startHealth = this.vigors.Health | |||||
| damage.damages.forEach(instance => { | |||||
| const resistance: number|undefined = this.resistances.get(instance.type) | |||||
| if (resistance !== undefined) { | |||||
| if (instance.target in Vigor) { | |||||
| this.vigors[instance.target as Vigor] -= instance.amount * resistance | |||||
| } else if (instance.target in Stat) { | |||||
| this.stats[instance.target as Stat] -= instance.amount * resistance | |||||
| } | |||||
| } else { | |||||
| if (instance.target in Vigor) { | |||||
| this.vigors[instance.target as Vigor] -= instance.amount | |||||
| } else if (instance.target in Stat) { | |||||
| this.stats[instance.target as Stat] -= instance.amount | |||||
| } | |||||
| } | |||||
| }) | |||||
| return "Normal" | |||||
| } | |||||
| if (this.vigors.Health <= 0 && startHealth > 0) { | |||||
| return this.destroy() | |||||
| } else { | |||||
| return new LogLine() | |||||
| } | |||||
| } | |||||
| get status (): string { | |||||
| if (this.vigors[Vigor.Health] <= 0) { | |||||
| return "Dead" | |||||
| } | |||||
| if (this.vigors[Vigor.Stamina] <= 0) { | |||||
| return "Unconscious" | |||||
| } | |||||
| if (this.vigors[Vigor.Resolve] <= 0) { | |||||
| return "Broken" | |||||
| } | |||||
| if (this.containedIn !== null) { | |||||
| return `${this.containedIn.consumeVerb.past.capital} by ${this.containedIn.owner.name}` | |||||
| } | |||||
| return "Normal" | |||||
| } | |||||
| validActions (target: Creature): Array<Action> { | |||||
| let choices = this.actions.concat( | |||||
| this.containers.flatMap(container => container.actions)).concat( | |||||
| target.otherActions.concat( | |||||
| this.otherContainers.flatMap(container => container.actions).concat( | |||||
| this.items.flatMap(item => item.actions) | |||||
| ) | |||||
| validActions (target: Creature): Array<Action> { | |||||
| let choices = this.actions.concat( | |||||
| this.containers.flatMap(container => container.actions) | |||||
| ).concat( | |||||
| target.otherActions.concat( | |||||
| this.otherContainers.flatMap(container => container.actions).concat( | |||||
| this.items.flatMap(item => item.actions) | |||||
| ) | ) | ||||
| ) | ) | ||||
| ) | |||||
| if (this.containedIn !== null) { | |||||
| choices = choices.concat(this.containedIn.actions) | |||||
| } | |||||
| return choices.filter(action => { | |||||
| return action.allowed(this, target) | |||||
| }) | |||||
| } | |||||
| validGroupActions (targets: Array<Creature>): Array<GroupAction> { | |||||
| const choices = this.groupActions | |||||
| return choices.filter(action => { | |||||
| return targets.some(target => action.allowed(this, target)) | |||||
| }) | |||||
| } | |||||
| destroy (): LogEntry { | |||||
| return super.destroy() | |||||
| if (this.containedIn !== null) { | |||||
| choices = choices.concat(this.containedIn.actions) | |||||
| } | } | ||||
| return choices.filter(action => { | |||||
| return action.allowed(this, target) | |||||
| }) | |||||
| } | |||||
| validGroupActions (targets: Array<Creature>): Array<GroupAction> { | |||||
| const choices = this.groupActions | |||||
| return choices.filter(action => { | |||||
| return targets.some(target => action.allowed(this, target)) | |||||
| }) | |||||
| } | |||||
| destroy (): LogEntry { | |||||
| return super.destroy() | |||||
| } | |||||
| } | } | ||||
| @@ -154,7 +154,7 @@ export class PropElem implements LogEntry { | |||||
| cls = StatIcons[this.prop as Stat] | cls = StatIcons[this.prop as Stat] | ||||
| } else if (this.prop in Vigor) { | } else if (this.prop in Vigor) { | ||||
| cls = VigorIcons[this.prop as Vigor] | cls = VigorIcons[this.prop as Vigor] | ||||
| } else if (this.prop in Vigor) { | |||||
| } else if (this.prop in VoreStat) { | |||||
| cls = VoreStatIcons[this.prop as VoreStat] | cls = VoreStatIcons[this.prop as VoreStat] | ||||
| } else { | } else { | ||||
| // this shouldn't be possible, given the typing... | // this shouldn't be possible, given the typing... | ||||
| @@ -184,8 +184,8 @@ export class PropElem implements LogEntry { | |||||
| } | } | ||||
| if (this.value !== null) { | if (this.value !== null) { | ||||
| const numText = Math.round(this.value).toFixed(0) === this.value.toFixed(0) ? this.value.toFixed(0) : this.value.toFixed(1) | |||||
| span.textContent = numText + ' ' | |||||
| const numText = Math.round(this.value).toFixed(0) === this.value.toFixed(0) ? Math.abs(this.value).toFixed(0) : Math.abs(this.value).toFixed(1) | |||||
| span.textContent = (this.value < 0 ? '+' : '') + numText + ' ' | |||||
| } | } | ||||
| const icon = new FAElem(cls).render()[0] | const icon = new FAElem(cls).render()[0] | ||||
| @@ -1,4 +1,4 @@ | |||||
| import { TextLike, LiveText, DynText, Word, ImproperNoun } from './language' | |||||
| import { TextLike, LiveText, DynText, Word, ImproperNoun, Verb } from './language' | |||||
| import { Actionable, Action, DamageFormula, ConstantDamageFormula, Damage, DamageType, Vigor, StatDamageFormula, Stat } from './combat' | import { Actionable, Action, DamageFormula, ConstantDamageFormula, Damage, DamageType, Vigor, StatDamageFormula, Stat } from './combat' | ||||
| import { AttackAction } from './combat/actions' | import { AttackAction } from './combat/actions' | ||||
| @@ -10,8 +10,8 @@ export interface Item extends Actionable { | |||||
| export class Weapon implements Actionable { | export class Weapon implements Actionable { | ||||
| actions: Array<Action> = [] | actions: Array<Action> = [] | ||||
| constructor (public name: Word, public desc: TextLike, damageFormula: DamageFormula) { | |||||
| const attack = new AttackAction(damageFormula) | |||||
| constructor (public name: Word, public desc: TextLike, damageFormula: DamageFormula, verb: Verb) { | |||||
| const attack = new AttackAction(damageFormula, verb) | |||||
| attack.desc = new DynText(`Attack with your `, this.name.all) | attack.desc = new DynText(`Attack with your `, this.name.all) | ||||
| this.actions.push(attack) | this.actions.push(attack) | ||||
| } | } | ||||
| @@ -23,7 +23,8 @@ export const Sword = new Weapon( | |||||
| new StatDamageFormula([ | new StatDamageFormula([ | ||||
| { fraction: 0.35, stat: Stat.Power, target: Vigor.Health, type: DamageType.Slash }, | { fraction: 0.35, stat: Stat.Power, target: Vigor.Health, type: DamageType.Slash }, | ||||
| { fraction: 0.25, stat: Stat.Power, target: Vigor.Health, type: DamageType.Pierce } | { fraction: 0.25, stat: Stat.Power, target: Vigor.Health, type: DamageType.Pierce } | ||||
| ]) | |||||
| ]), | |||||
| new Verb('slash', 'slashes') | |||||
| ) | ) | ||||
| export const Dagger = new Weapon( | export const Dagger = new Weapon( | ||||
| @@ -32,7 +33,8 @@ export const Dagger = new Weapon( | |||||
| new StatDamageFormula([ | new StatDamageFormula([ | ||||
| { fraction: 0.50, stat: Stat.Speed, target: Vigor.Health, type: DamageType.Pierce }, | { fraction: 0.50, stat: Stat.Speed, target: Vigor.Health, type: DamageType.Pierce }, | ||||
| { fraction: 0.05, stat: Stat.Speed, target: Vigor.Health, type: DamageType.Slash } | { fraction: 0.05, stat: Stat.Speed, target: Vigor.Health, type: DamageType.Slash } | ||||
| ]) | |||||
| ]), | |||||
| new Verb('stab', 'stabs', 'stabbing', 'stabbed') | |||||
| ) | ) | ||||
| export const Wand = new Weapon( | export const Wand = new Weapon( | ||||
| @@ -41,7 +43,8 @@ export const Wand = new Weapon( | |||||
| new StatDamageFormula([ | new StatDamageFormula([ | ||||
| { fraction: 0.25, stat: Stat.Charm, target: Vigor.Health, type: DamageType.Crush }, | { fraction: 0.25, stat: Stat.Charm, target: Vigor.Health, type: DamageType.Crush }, | ||||
| { fraction: 0.25, stat: Stat.Willpower, target: Vigor.Health, type: DamageType.Crush } | { fraction: 0.25, stat: Stat.Willpower, target: Vigor.Health, type: DamageType.Crush } | ||||
| ]) | |||||
| ]), | |||||
| new Verb('zap', 'zaps', 'zapping', 'zapped') | |||||
| ) | ) | ||||
| export const Mace = new Weapon( | export const Mace = new Weapon( | ||||
| @@ -50,5 +53,6 @@ export const Mace = new Weapon( | |||||
| new StatDamageFormula([ | new StatDamageFormula([ | ||||
| { fraction: 0.4, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush }, | { fraction: 0.4, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush }, | ||||
| { fraction: 0.2, stat: Stat.Power, target: Vigor.Health, type: DamageType.Pierce } | { fraction: 0.2, stat: Stat.Power, target: Vigor.Health, type: DamageType.Pierce } | ||||
| ]) | |||||
| ]), | |||||
| new Verb('bash', 'bashes', 'bashing', 'bashed') | |||||
| ) | ) | ||||
| @@ -138,8 +138,8 @@ export abstract class NormalContainer implements Container { | |||||
| export interface VoreContainer extends Container { | export interface VoreContainer extends Container { | ||||
| digested: Array<Vore>; | digested: Array<Vore>; | ||||
| tick: (dt: number) => LogEntry; | tick: (dt: number) => LogEntry; | ||||
| digest: (prey: Vore) => LogEntry; | |||||
| absorb: (prey: Vore) => LogEntry; | |||||
| digest: (preys: Vore[]) => LogEntry; | |||||
| absorb: (preys: Vore[]) => LogEntry; | |||||
| dispose: (preys: Vore[]) => LogEntry; | dispose: (preys: Vore[]) => LogEntry; | ||||
| } | } | ||||
| @@ -172,7 +172,7 @@ export abstract class NormalVoreContainer extends NormalContainer implements Vor | |||||
| }) | }) | ||||
| const tickedEntries = new LogLines(...this.contents.map(prey => this.tickLines.run(this.owner, prey, { damage: scaled }))) | const tickedEntries = new LogLines(...this.contents.map(prey => this.tickLines.run(this.owner, prey, { damage: scaled }))) | ||||
| const digestedEntries = new LogLines(...justDigested.map(prey => this.digest(prey))) | |||||
| const digestedEntries = this.digest(justDigested) | |||||
| this.contents = this.contents.filter(prey => { | this.contents = this.contents.filter(prey => { | ||||
| return prey.vigors[Vigor.Health] > 0 | return prey.vigors[Vigor.Health] > 0 | ||||
| @@ -181,16 +181,16 @@ export abstract class NormalVoreContainer extends NormalContainer implements Vor | |||||
| return new LogLines(tickedEntries, new LogLines(...damageResults), digestedEntries) | return new LogLines(tickedEntries, new LogLines(...damageResults), digestedEntries) | ||||
| } | } | ||||
| digest (prey: Vore): LogEntry { | |||||
| return this.digestLines.run(this.owner, prey) | |||||
| digest (preys: Vore[]): LogEntry { | |||||
| return new LogLines(...preys.map(prey => this.digestLines.run(this.owner, prey))) | |||||
| } | } | ||||
| absorb (prey: Vore): LogEntry { | |||||
| return this.absorbLines.run(this.owner, prey) | |||||
| absorb (preys: Vore[]): LogEntry { | |||||
| return new LogLines(...preys.map(prey => this.absorbLines.run(this.owner, prey))) | |||||
| } | } | ||||
| dispose (preys: Vore[]): LogEntry { | dispose (preys: Vore[]): LogEntry { | ||||
| return new CompositeLog(...preys.map(prey => this.disposeLines.run(this.owner, prey))) | |||||
| return new LogLines(...preys.map(prey => this.disposeLines.run(this.owner, prey))) | |||||
| } | } | ||||
| constructor (name: Noun, owner: Vore, voreTypes: Set<VoreType>, capacity: number, private damage: Damage) { | constructor (name: Noun, owner: Vore, voreTypes: Set<VoreType>, capacity: number, private damage: Damage) { | ||||
| @@ -265,6 +265,25 @@ export class Stomach extends NormalVoreContainer { | |||||
| [[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital}'s guts soak you up like water in a sponge`)], | [[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital}'s guts soak you up like water in a sponge`)], | ||||
| [[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} finishes absorbing the remains of ${target.name}`)] | [[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} finishes absorbing the remains of ${target.name}`)] | ||||
| ]) | ]) | ||||
| digest (preys: Vore[]): LogEntry { | |||||
| if (preys.length === 0) { | |||||
| return super.digest(preys) | |||||
| } | |||||
| const heal = new Damage( | |||||
| { | |||||
| amount: preys.reduce((total: number, next: Vore) => total + next.maxVigors.Health / 5, 0), | |||||
| type: DamageType.Heal, | |||||
| target: Vigor.Health | |||||
| } | |||||
| ) | |||||
| this.owner.takeDamage(heal) | |||||
| return new LogLines( | |||||
| super.digest(preys), | |||||
| new LogLine(`${this.owner.name.capital} heals for `, heal.renderShort()) | |||||
| ) | |||||
| } | |||||
| } | } | ||||
| export class InnerStomach extends InnerContainer { | export class InnerStomach extends InnerContainer { | ||||