| @@ -1,6 +1,7 @@ | |||
| import { Creature } from "./creature" | |||
| import { TextLike, DynText, ToBe, LiveText, PairLineArgs, PairLine } from './language' | |||
| import { LogEntry, LogLines, FAElem, LogLine, FormatEntry, FormatOpt, PropElem, nilLog } from './interface' | |||
| import { Resistances } from './entity' | |||
| export enum DamageType { | |||
| Pierce = "Pierce", | |||
| @@ -458,6 +459,13 @@ export class Effective { | |||
| log: nilLog | |||
| } | |||
| } | |||
| /** | |||
| * Modifies the effective resistance to a certain damage type | |||
| */ | |||
| modResistance (type: DamageType, factor: number): number { | |||
| return factor | |||
| } | |||
| } | |||
| /** | |||
| * A displayable status effect | |||
| @@ -20,6 +20,7 @@ export class InstantKillEffect extends StatusEffect { | |||
| ) | |||
| } | |||
| } | |||
| export class StunEffect extends StatusEffect { | |||
| constructor (private duration: number) { | |||
| super('Stun', 'Cannot act!', 'fas fa-sun') | |||
| @@ -58,9 +59,9 @@ export class StunEffect extends StatusEffect { | |||
| } | |||
| } | |||
| export class ShieldEffect extends StatusEffect { | |||
| export class DamageTypeResistanceEffect extends StatusEffect { | |||
| constructor (private damageTypes: DamageType[], private amount: number) { | |||
| super('Shield', 'Block a fraction of incoming damage', 'fas fa-shield-alt') | |||
| super('Resistance', 'Block ' + ((1 - amount) * 100).toFixed() + '% of these damage types: ' + damageTypes.join(", "), 'fas fa-shield-alt') | |||
| } | |||
| onApply (creature: Creature) { | |||
| @@ -71,8 +72,12 @@ export class ShieldEffect extends StatusEffect { | |||
| return new LogLine(`${creature.name.capital} ${creature.name.conjugate(new Verb('lose'))} ${creature.pronouns.possessive} shield!`) | |||
| } | |||
| preDamage (creature: Creature, damage: Damage) { | |||
| return damage.scale(this.amount) | |||
| modResistance (type: DamageType, factor: number) { | |||
| if (this.damageTypes.includes(type)) { | |||
| return factor * (1 - this.amount) | |||
| } else { | |||
| return factor | |||
| } | |||
| } | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| import { Damage, Combatant, Stats, Action, Vigor, Side, GroupAction, VisibleStatus, ImplicitStatus, StatusEffect } from './combat' | |||
| import { Damage, Combatant, Stats, Action, Vigor, Side, GroupAction, VisibleStatus, ImplicitStatus, StatusEffect, DamageType } from './combat' | |||
| import { Noun, Pronoun } from './language' | |||
| import { LogEntry, LogLines } from './interface' | |||
| import { Vore, VoreContainer, VoreType } from './vore' | |||
| @@ -32,9 +32,17 @@ export class Creature extends Vore implements Combatant { | |||
| * Determines how much damage an attack would do | |||
| */ | |||
| effectiveDamage (damage: Damage): Damage { | |||
| return this.effects.reduce((modifiedDamage: Damage, effect: StatusEffect) => { | |||
| const preDamage = this.effects.reduce((modifiedDamage: Damage, effect: StatusEffect) => { | |||
| return effect.preDamage(this, modifiedDamage) | |||
| }, damage) | |||
| return super.effectiveDamage(preDamage) | |||
| } | |||
| resistanceTo (damageType: DamageType) { | |||
| const base = super.resistanceTo(damageType) | |||
| const modified = this.effects.reduce((resist, effect) => effect.modResistance(damageType, resist), base) | |||
| return modified | |||
| } | |||
| executeAction (action: Action, target: Creature): LogEntry { | |||
| @@ -151,6 +151,7 @@ export class Goldeneye extends Creature { | |||
| ) | |||
| ) | |||
| this.groupActions.push(new Flaunt(crop)) | |||
| this.groupActions.push(new Flaunt(stomach)) | |||
| this.groupActions.push(new Taunt()) | |||
| @@ -5,7 +5,7 @@ import { LogLine, LogLines, LogEntry, Newline } from '../interface' | |||
| import { VoreType, Stomach, VoreContainer, Vore, NormalContainer, Container } from '../vore' | |||
| import { AttackAction, FeedAction, TransferAction } from '../combat/actions' | |||
| import { TogetherCondition, ContainsCondition, EnemyCondition, AllyCondition, PairCondition, CapableCondition } from '../combat/conditions' | |||
| import { InstantKillEffect, ShieldEffect } from '../combat/effects' | |||
| import { InstantKillEffect, ResistanceEffect } from '../combat/effects' | |||
| import * as Words from '../words' | |||
| import { StatVigorTest } from '../combat/tests' | |||
| @@ -248,7 +248,7 @@ class StompAllyAction extends Action { | |||
| return new LogLines( | |||
| this.line(user, target), | |||
| target.applyEffect(new InstantKillEffect()), | |||
| user.applyEffect(new ShieldEffect( | |||
| user.applyEffect(new ResistanceEffect( | |||
| [DamageType.Crush, DamageType.Slash, DamageType.Pierce], | |||
| 0.5 | |||
| )), | |||
| @@ -309,7 +309,7 @@ export class Withers extends Creature { | |||
| new ProperNoun('Withers'), | |||
| new ImproperNoun('hellhound', 'hellhounds'), | |||
| FemalePronouns, | |||
| { Toughness: 40, Power: 50, Speed: 30, Willpower: 40, Charm: 70 }, | |||
| { Toughness: 40, Power: 10, Speed: 30, Willpower: 40, Charm: 70 }, | |||
| new Set(), | |||
| new Set([VoreType.Oral]), | |||
| 5000 | |||
| @@ -1,4 +1,4 @@ | |||
| import { DamageType, Damage, Stats, Vigor, VoreStats, VoreStat, Stat, Vigors } from './combat' | |||
| import { DamageType, Damage, Stats, Vigor, VoreStats, VoreStat, Stat, Vigors, DamageInstance } from './combat' | |||
| import { Noun, Pronoun, TextLike, POV, PronounAsNoun, FirstPersonPronouns, SecondPersonPronouns } from './language' | |||
| import { LogEntry, LogLine } from './interface' | |||
| import { Place, Nowhere } from './world' | |||
| @@ -34,8 +34,11 @@ export abstract class Entity { | |||
| } | |||
| } | |||
| export type Resistances = {[key in DamageType]: number} | |||
| export abstract class Mortal extends Entity { | |||
| resistances: Map<DamageType, number> = new Map() | |||
| baseResistances: Resistances | |||
| stats: Stats; | |||
| vigors: {[key in Vigor]: number} = { | |||
| [Vigor.Health]: 100, | |||
| @@ -46,11 +49,16 @@ export abstract class Mortal extends Entity { | |||
| constructor (name: Noun, kind: Noun, pronouns: Pronoun, public baseStats: Stats) { | |||
| super(name, kind, pronouns) | |||
| this.stats = Object.keys(Stat).reduce((base: any, key) => { base[key] = baseStats[key as Stat]; return base }, {}) | |||
| this.baseResistances = Object.keys(DamageType).reduce((resist: any, key) => { resist[key] = 1; return resist }, {}) | |||
| Object.entries(this.maxVigors).forEach(([key, val]) => { | |||
| this.vigors[key as Vigor] = val | |||
| }) | |||
| } | |||
| resistanceTo (damageType: DamageType): number { | |||
| return this.baseResistances[damageType] | |||
| } | |||
| get maxVigors (): Readonly<Vigors> { | |||
| return { | |||
| Health: this.stats.Toughness * 10 + this.stats.Power * 5, | |||
| @@ -63,6 +71,22 @@ export abstract class Mortal extends Entity { | |||
| return Object.values(this.vigors).some(val => val <= 0) | |||
| } | |||
| effectiveDamage (damage: Damage): Damage { | |||
| const newDamages: DamageInstance[] = [] | |||
| damage.damages.forEach(instance => { | |||
| const factor = instance.type === DamageType.Heal ? -1 : 1 | |||
| const baseResistance: number = this.resistanceTo(instance.type) | |||
| const resistance = baseResistance * factor | |||
| newDamages.push({ | |||
| amount: instance.amount * resistance, | |||
| target: instance.target, | |||
| type: instance.type | |||
| }) | |||
| }) | |||
| return new Damage(...newDamages) | |||
| } | |||
| takeDamage (damage: Damage): LogEntry { | |||
| // first, we record health to decide if the entity just died | |||
| const startHealth = this.vigors.Health | |||
| @@ -70,17 +94,13 @@ export abstract class Mortal extends Entity { | |||
| damage = this.effectiveDamage(damage) | |||
| damage.damages.forEach(instance => { | |||
| const factor = instance.type === DamageType.Heal ? -1 : 1 | |||
| const effectiveResistance: number|undefined = this.resistances.get(instance.type) | |||
| const resistance = effectiveResistance === undefined ? factor : effectiveResistance * factor | |||
| if (instance.target in Vigor) { | |||
| // just deal damage | |||
| this.vigors[instance.target as Vigor] -= instance.amount * resistance | |||
| this.vigors[instance.target as Vigor] -= instance.amount | |||
| } else if (instance.target in Stat) { | |||
| // drain the stats, then deal damage to match | |||
| const startVigors = this.maxVigors | |||
| this.stats[instance.target as Stat] -= instance.amount * resistance | |||
| this.stats[instance.target as Stat] -= instance.amount | |||
| const endVigors = this.maxVigors | |||
| Object.keys(Vigor).map(vigor => { | |||
| @@ -101,5 +121,4 @@ export abstract class Mortal extends Entity { | |||
| } | |||
| abstract destroy (): LogEntry; | |||
| abstract effectiveDamage (damage: Damage): Damage | |||
| } | |||