Bladeren bron

Add healing; adjust digestion text; allow VoreStats to be used for damage calculations

vintage
Fen Dweller 5 jaren geleden
bovenliggende
commit
15a12f0c69
9 gewijzigde bestanden met toevoegingen van 239 en 184 verwijderingen
  1. +27
    -10
      src/game/combat.ts
  2. +7
    -7
      src/game/combat/actions.ts
  3. +0
    -5
      src/game/creatures/human.ts
  4. +13
    -3
      src/game/creatures/kenzie.ts
  5. +4
    -1
      src/game/creatures/withers.ts
  6. +147
    -140
      src/game/entity.ts
  7. +3
    -3
      src/game/interface.ts
  8. +11
    -7
      src/game/items.ts
  9. +27
    -8
      src/game/vore.ts

+ 27
- 10
src/game/combat.ts Bestand weergeven

@@ -10,7 +10,8 @@ export enum DamageType {
Crush = "Crush",
Acid = "Acid",
Seduction = "Seduction",
Dominance = "Dominance"
Dominance = "Dominance",
Heal = "Heal"
}

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 statTotals: Stats = Object.keys(Stat).reduce((total: any, key) => { total[key] = 0; return total }, {})
this.damages.forEach(instance => {
const factor = instance.type === DamageType.Heal ? -1 : 1
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) {
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 {
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)
}
@@ -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 }>) {

}
}


+ 7
- 7
src/game/combat/actions.ts Bestand weergeven

@@ -1,5 +1,5 @@
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 { Damage, DamageFormula, Stat, Vigor, Action } from '../combat'
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([
[[POV.First, POV.Third], (user, target, args) => new LogLine(
`You smack ${target.name} for `,
`You ${this.verb} ${target.name} for `,
args.damage.renderShort()
)],
[[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()
)],
[[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()
)]
])

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.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(
'Attack',
verb.root.capital,
'Attack the enemy',
[new CapableCondition(), new TogetherCondition(), new EnemyCondition()]
)


+ 0
- 5
src/game/creatures/human.ts Bestand weergeven

@@ -23,10 +23,5 @@ export class Human extends Creature {

this.title = "Snack"
this.desc = "Definitely going on an adventure"
this.actions.push(new AttackAction(new ConstantDamageFormula(
new Damage(
{ amount: 20, target: Vigor.Health, type: DamageType.Slash }
)
)))
}
}

+ 13
- 3
src/game/creatures/kenzie.ts Bestand weergeven

@@ -1,9 +1,9 @@
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 { Side, Damage, DamageType, Vigor, UniformRandomDamageFormula } from '../combat'
import { Side, Damage, DamageType, Vigor, UniformRandomDamageFormula, ConstantDamageFormula, StatDamageFormula, Stat, VoreStat } from '../combat'
import { LogLine } from '../interface'
import { FeedAction, TransferAction } from '../combat/actions'
import { FeedAction, TransferAction, AttackAction } from '../combat/actions'
import * as Words from '../words'

export class Kenzie extends Creature {
@@ -30,5 +30,15 @@ export class Kenzie extends Creature {
))

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')
)
)
}
}

+ 4
- 1
src/game/creatures/withers.ts Bestand weergeven

@@ -116,7 +116,10 @@ const huge = new RandomWord([

class BiteAction extends AttackAction {
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"
}
}


+ 147
- 140
src/game/entity.ts Bestand weergeven

@@ -30,158 +30,165 @@ export interface Mortal extends Entity {
export class Creature extends Vore implements Combatant {
title = "Lv. 1 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()
}
}

+ 3
- 3
src/game/interface.ts Bestand weergeven

@@ -154,7 +154,7 @@ export class PropElem implements LogEntry {
cls = StatIcons[this.prop as Stat]
} else if (this.prop in 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]
} else {
// this shouldn't be possible, given the typing...
@@ -184,8 +184,8 @@ export class PropElem implements LogEntry {
}

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]


+ 11
- 7
src/game/items.ts Bestand weergeven

@@ -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 { AttackAction } from './combat/actions'

@@ -10,8 +10,8 @@ export interface Item extends Actionable {
export class Weapon implements Actionable {
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)
this.actions.push(attack)
}
@@ -23,7 +23,8 @@ export const Sword = new Weapon(
new StatDamageFormula([
{ fraction: 0.35, stat: Stat.Power, target: Vigor.Health, type: DamageType.Slash },
{ fraction: 0.25, stat: Stat.Power, target: Vigor.Health, type: DamageType.Pierce }
])
]),
new Verb('slash', 'slashes')
)

export const Dagger = new Weapon(
@@ -32,7 +33,8 @@ export const Dagger = new Weapon(
new StatDamageFormula([
{ fraction: 0.50, stat: Stat.Speed, target: Vigor.Health, type: DamageType.Pierce },
{ fraction: 0.05, stat: Stat.Speed, target: Vigor.Health, type: DamageType.Slash }
])
]),
new Verb('stab', 'stabs', 'stabbing', 'stabbed')
)

export const Wand = new Weapon(
@@ -41,7 +43,8 @@ export const Wand = new Weapon(
new StatDamageFormula([
{ fraction: 0.25, stat: Stat.Charm, 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(
@@ -50,5 +53,6 @@ export const Mace = new Weapon(
new StatDamageFormula([
{ fraction: 0.4, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush },
{ fraction: 0.2, stat: Stat.Power, target: Vigor.Health, type: DamageType.Pierce }
])
]),
new Verb('bash', 'bashes', 'bashing', 'bashed')
)

+ 27
- 8
src/game/vore.ts Bestand weergeven

@@ -138,8 +138,8 @@ export abstract class NormalContainer implements Container {
export interface VoreContainer extends Container {
digested: Array<Vore>;
tick: (dt: number) => LogEntry;
digest: (prey: Vore) => LogEntry;
absorb: (prey: Vore) => LogEntry;
digest: (preys: Vore[]) => LogEntry;
absorb: (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 digestedEntries = new LogLines(...justDigested.map(prey => this.digest(prey)))
const digestedEntries = this.digest(justDigested)

this.contents = this.contents.filter(prey => {
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)
}

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 {
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) {
@@ -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.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 {


Laden…
Annuleren
Opslaan