| @@ -1,6 +1,7 @@ | |||||
| import { Creature } from "./creature" | import { Creature } from "./creature" | ||||
| import { TextLike, DynText, ToBe, LiveText, PairLineArgs, PairLine } from './language' | import { TextLike, DynText, ToBe, LiveText, PairLineArgs, PairLine } from './language' | ||||
| import { LogEntry, LogLines, FAElem, LogLine, FormatEntry, FormatOpt, PropElem, nilLog } from './interface' | import { LogEntry, LogLines, FAElem, LogLine, FormatEntry, FormatOpt, PropElem, nilLog } from './interface' | ||||
| import { Resistances } from './entity' | |||||
| export enum DamageType { | export enum DamageType { | ||||
| Pierce = "Pierce", | Pierce = "Pierce", | ||||
| @@ -458,6 +459,13 @@ export class Effective { | |||||
| log: nilLog | log: nilLog | ||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * Modifies the effective resistance to a certain damage type | |||||
| */ | |||||
| modResistance (type: DamageType, factor: number): number { | |||||
| return factor | |||||
| } | |||||
| } | } | ||||
| /** | /** | ||||
| * A displayable status effect | * A displayable status effect | ||||
| @@ -20,6 +20,7 @@ export class InstantKillEffect extends StatusEffect { | |||||
| ) | ) | ||||
| } | } | ||||
| } | } | ||||
| export class StunEffect extends StatusEffect { | export class StunEffect extends StatusEffect { | ||||
| constructor (private duration: number) { | constructor (private duration: number) { | ||||
| super('Stun', 'Cannot act!', 'fas fa-sun') | 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) { | 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) { | 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!`) | 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 { Noun, Pronoun } from './language' | ||||
| import { LogEntry, LogLines } from './interface' | import { LogEntry, LogLines } from './interface' | ||||
| import { Vore, VoreContainer, VoreType } from './vore' | 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 | * Determines how much damage an attack would do | ||||
| */ | */ | ||||
| effectiveDamage (damage: Damage): Damage { | 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) | return effect.preDamage(this, modifiedDamage) | ||||
| }, damage) | }, 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 { | 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 Flaunt(stomach)) | ||||
| this.groupActions.push(new Taunt()) | 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 { VoreType, Stomach, VoreContainer, Vore, NormalContainer, Container } from '../vore' | ||||
| import { AttackAction, FeedAction, TransferAction } from '../combat/actions' | import { AttackAction, FeedAction, TransferAction } from '../combat/actions' | ||||
| import { TogetherCondition, ContainsCondition, EnemyCondition, AllyCondition, PairCondition, CapableCondition } from '../combat/conditions' | 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 * as Words from '../words' | ||||
| import { StatVigorTest } from '../combat/tests' | import { StatVigorTest } from '../combat/tests' | ||||
| @@ -248,7 +248,7 @@ class StompAllyAction extends Action { | |||||
| return new LogLines( | return new LogLines( | ||||
| this.line(user, target), | this.line(user, target), | ||||
| target.applyEffect(new InstantKillEffect()), | target.applyEffect(new InstantKillEffect()), | ||||
| user.applyEffect(new ShieldEffect( | |||||
| user.applyEffect(new ResistanceEffect( | |||||
| [DamageType.Crush, DamageType.Slash, DamageType.Pierce], | [DamageType.Crush, DamageType.Slash, DamageType.Pierce], | ||||
| 0.5 | 0.5 | ||||
| )), | )), | ||||
| @@ -309,7 +309,7 @@ export class Withers extends Creature { | |||||
| new ProperNoun('Withers'), | new ProperNoun('Withers'), | ||||
| new ImproperNoun('hellhound', 'hellhounds'), | new ImproperNoun('hellhound', 'hellhounds'), | ||||
| FemalePronouns, | 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(), | ||||
| new Set([VoreType.Oral]), | new Set([VoreType.Oral]), | ||||
| 5000 | 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 { Noun, Pronoun, TextLike, POV, PronounAsNoun, FirstPersonPronouns, SecondPersonPronouns } from './language' | ||||
| import { LogEntry, LogLine } from './interface' | import { LogEntry, LogLine } from './interface' | ||||
| import { Place, Nowhere } from './world' | 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 { | export abstract class Mortal extends Entity { | ||||
| resistances: Map<DamageType, number> = new Map() | |||||
| baseResistances: Resistances | |||||
| stats: Stats; | stats: Stats; | ||||
| vigors: {[key in Vigor]: number} = { | vigors: {[key in Vigor]: number} = { | ||||
| [Vigor.Health]: 100, | [Vigor.Health]: 100, | ||||
| @@ -46,11 +49,16 @@ export abstract class Mortal extends Entity { | |||||
| constructor (name: Noun, kind: Noun, pronouns: Pronoun, public baseStats: Stats) { | constructor (name: Noun, kind: Noun, pronouns: Pronoun, public baseStats: Stats) { | ||||
| super(name, kind, pronouns) | super(name, kind, pronouns) | ||||
| this.stats = Object.keys(Stat).reduce((base: any, key) => { base[key] = baseStats[key as Stat]; return base }, {}) | 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]) => { | Object.entries(this.maxVigors).forEach(([key, val]) => { | ||||
| this.vigors[key as Vigor] = val | this.vigors[key as Vigor] = val | ||||
| }) | }) | ||||
| } | } | ||||
| resistanceTo (damageType: DamageType): number { | |||||
| return this.baseResistances[damageType] | |||||
| } | |||||
| get maxVigors (): Readonly<Vigors> { | get maxVigors (): Readonly<Vigors> { | ||||
| return { | return { | ||||
| Health: this.stats.Toughness * 10 + this.stats.Power * 5, | 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) | 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 { | takeDamage (damage: Damage): LogEntry { | ||||
| // first, we record health to decide if the entity just died | // first, we record health to decide if the entity just died | ||||
| const startHealth = this.vigors.Health | const startHealth = this.vigors.Health | ||||
| @@ -70,17 +94,13 @@ export abstract class Mortal extends Entity { | |||||
| damage = this.effectiveDamage(damage) | damage = this.effectiveDamage(damage) | ||||
| damage.damages.forEach(instance => { | 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) { | if (instance.target in Vigor) { | ||||
| // just deal damage | // 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) { | } else if (instance.target in Stat) { | ||||
| // drain the stats, then deal damage to match | // drain the stats, then deal damage to match | ||||
| const startVigors = this.maxVigors | 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 | const endVigors = this.maxVigors | ||||
| Object.keys(Vigor).map(vigor => { | Object.keys(Vigor).map(vigor => { | ||||
| @@ -101,5 +121,4 @@ export abstract class Mortal extends Entity { | |||||
| } | } | ||||
| abstract destroy (): LogEntry; | abstract destroy (): LogEntry; | ||||
| abstract effectiveDamage (damage: Damage): Damage | |||||
| } | } | ||||