| @@ -10,6 +10,7 @@ import { Component, Vue, Prop } from 'vue-property-decorator' | |||||
| import Combat from './components/Combat.vue' | import Combat from './components/Combat.vue' | ||||
| import Header from './components/Header.vue' | import Header from './components/Header.vue' | ||||
| import * as Creatures from '@/game/creatures' | import * as Creatures from '@/game/creatures' | ||||
| import * as Items from '@/game/items' | |||||
| import { Creature, POV } from '@/game/entity' | import { Creature, POV } from '@/game/entity' | ||||
| import { ProperNoun, TheyPronouns, FemalePronouns, MalePronouns, ImproperNoun } from '@/game/language' | import { ProperNoun, TheyPronouns, FemalePronouns, MalePronouns, ImproperNoun } from '@/game/language' | ||||
| @@ -35,6 +36,7 @@ export default class App extends Vue { | |||||
| } | } | ||||
| }) | }) | ||||
| fighter.title = "Lv. 6 Fighter" | fighter.title = "Lv. 6 Fighter" | ||||
| fighter.items.push(Items.Sword) | |||||
| const rogue = new Creatures.Human(new ProperNoun('Lidda'), FemalePronouns, { | const rogue = new Creatures.Human(new ProperNoun('Lidda'), FemalePronouns, { | ||||
| stats: { | stats: { | ||||
| Toughness: 25, | Toughness: 25, | ||||
| @@ -45,6 +47,7 @@ export default class App extends Vue { | |||||
| } | } | ||||
| }) | }) | ||||
| rogue.title = "Lv. 5 Rogue" | rogue.title = "Lv. 5 Rogue" | ||||
| rogue.items.push(Items.Dagger) | |||||
| const wizard = new Creatures.Human(new ProperNoun('Mialee'), FemalePronouns, { | const wizard = new Creatures.Human(new ProperNoun('Mialee'), FemalePronouns, { | ||||
| stats: { | stats: { | ||||
| Toughness: 30, | Toughness: 30, | ||||
| @@ -55,6 +58,7 @@ export default class App extends Vue { | |||||
| } | } | ||||
| }) | }) | ||||
| wizard.title = "Lv. 6 Wizard" | wizard.title = "Lv. 6 Wizard" | ||||
| wizard.items.push(Items.Wand) | |||||
| const cleric = new Creatures.Human(new ProperNoun('Jozan'), MalePronouns, { | const cleric = new Creatures.Human(new ProperNoun('Jozan'), MalePronouns, { | ||||
| stats: { | stats: { | ||||
| Toughness: 35, | Toughness: 35, | ||||
| @@ -64,7 +68,8 @@ export default class App extends Vue { | |||||
| Charm: 50 | Charm: 50 | ||||
| } | } | ||||
| }) | }) | ||||
| cleric.title = "Lv. 5 Wizard" | |||||
| cleric.title = "Lv. 5 Cleric" | |||||
| cleric.items.push(Items.Mace) | |||||
| this.left = fighter | this.left = fighter | ||||
| this.right = new Creatures.Withers() | this.right = new Creatures.Withers() | ||||
| @@ -121,6 +121,11 @@ export class Damage { | |||||
| return new Damage(...results) | return new Damage(...results) | ||||
| } | } | ||||
| // TODO make this combine damage instances when appropriate | |||||
| combine (other: Damage): Damage { | |||||
| return new Damage(...this.damages.concat(other.damages)) | |||||
| } | |||||
| toString (): string { | toString (): string { | ||||
| return this.damages.map(damage => damage.amount + " " + damage.type).join("/") | return this.damages.map(damage => damage.amount + " " + damage.type).join("/") | ||||
| } | } | ||||
| @@ -206,6 +211,44 @@ 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 | |||||
| } | |||||
| )) | |||||
| return new Damage(...instances) | |||||
| } | |||||
| describe (user: Creature, target: Creature): LogEntry { | |||||
| return new LogLine( | |||||
| this.explain(user), | |||||
| `, for a total of `, | |||||
| this.calc(user, target).renderShort() | |||||
| ) | |||||
| } | |||||
| explain (user: Creature): LogEntry { | |||||
| return new LogLine( | |||||
| `Deal `, | |||||
| ...this.factors.map(factor => new LogLine( | |||||
| `${factor.fraction * 100}% of your `, | |||||
| new PropElem(factor.stat), | |||||
| ` as `, | |||||
| new PropElem(factor.target) | |||||
| )).joinGeneral(new LogLine(`, `), new LogLine(` and `)) | |||||
| ) | |||||
| } | |||||
| constructor (private factors: Array<{ stat: Stat; fraction: number; type: DamageType; target: Vigor|Stat }>) { | |||||
| } | |||||
| } | |||||
| export enum Side { | export enum Side { | ||||
| Heroes, | Heroes, | ||||
| Monsters | Monsters | ||||
| @@ -2,6 +2,7 @@ import { DamageType, Damage, Combatant, Stats, Action, Vigor, VoreStats, VoreSta | |||||
| import { Noun, Pronoun, Adjective, ImproperNoun, TextLike } from './language' | import { Noun, Pronoun, Adjective, ImproperNoun, TextLike } from './language' | ||||
| import { LogEntry, LogLine } from './interface' | import { LogEntry, LogLine } from './interface' | ||||
| import { Vore, VoreContainer, VoreType, Container } from './vore' | import { Vore, VoreContainer, VoreType, Container } from './vore' | ||||
| import { Item } from './items' | |||||
| export enum POV {First, Third} | export enum POV {First, Third} | ||||
| @@ -57,6 +58,8 @@ export class Creature extends Vore implements Combatant { | |||||
| groupActions: Array<GroupAction> = [] | groupActions: Array<GroupAction> = [] | ||||
| otherActions: Array<Action> = [] | otherActions: Array<Action> = [] | ||||
| items: Array<Item> = [] | |||||
| get bulk (): number { | 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) | return this.voreStats.Mass + this.containers.reduce((total, conatiner) => { return total + conatiner.contents.reduce((total, prey) => total + prey.voreStats.Bulk, 0) }, 0) | ||||
| } | } | ||||
| @@ -156,7 +159,9 @@ export class Creature extends Vore implements Combatant { | |||||
| let choices = this.actions.concat( | let choices = this.actions.concat( | ||||
| this.containers.flatMap(container => container.actions)).concat( | this.containers.flatMap(container => container.actions)).concat( | ||||
| target.otherActions.concat( | target.otherActions.concat( | ||||
| this.otherContainers.flatMap(container => container.actions) | |||||
| this.otherContainers.flatMap(container => container.actions).concat( | |||||
| this.items.flatMap(item => item.actions) | |||||
| ) | |||||
| ) | ) | ||||
| ) | ) | ||||
| @@ -0,0 +1,54 @@ | |||||
| import { TextLike, LiveText, DynText, Word, ImproperNoun } from './language' | |||||
| import { Actionable, Action, DamageFormula, ConstantDamageFormula, Damage, DamageType, Vigor, StatDamageFormula, Stat } from './combat' | |||||
| import { AttackAction } from './combat/actions' | |||||
| export interface Item extends Actionable { | |||||
| name: Word; | |||||
| desc: TextLike; | |||||
| } | |||||
| export class Weapon implements Actionable { | |||||
| actions: Array<Action> = [] | |||||
| constructor (public name: Word, public desc: TextLike, damageFormula: DamageFormula) { | |||||
| const attack = new AttackAction(damageFormula) | |||||
| attack.desc = new DynText(`Attack with your `, this.name.all) | |||||
| this.actions.push(attack) | |||||
| } | |||||
| } | |||||
| export const Sword = new Weapon( | |||||
| new ImproperNoun('sword', 'swords'), | |||||
| 'An arming sword', | |||||
| 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 } | |||||
| ]) | |||||
| ) | |||||
| export const Dagger = new Weapon( | |||||
| new ImproperNoun('dagger', 'daggers'), | |||||
| 'A pointy dagger', | |||||
| 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 } | |||||
| ]) | |||||
| ) | |||||
| export const Wand = new Weapon( | |||||
| new ImproperNoun('wand', 'wands'), | |||||
| 'A magical wand', | |||||
| 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 } | |||||
| ]) | |||||
| ) | |||||
| export const Mace = new Weapon( | |||||
| new ImproperNoun('mace', 'maces'), | |||||
| 'A heavy mace', | |||||
| 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 } | |||||
| ]) | |||||
| ) | |||||
| @@ -1,6 +1,21 @@ | |||||
| import Vue from 'vue' | import Vue from 'vue' | ||||
| import App from './App.vue' | import App from './App.vue' | ||||
| declare global { | |||||
| interface Array<T> { | |||||
| joinGeneral (item: T, endItem: T|null): Array<T>; | |||||
| } | |||||
| } | |||||
| /* eslint-disable-next-line */ | |||||
| Array.prototype.joinGeneral = function (item, endItem = null) { | |||||
| if (endItem === null) { | |||||
| return this.slice(0, -1).flatMap(x => [x, item]).concat(this.slice(-1)) | |||||
| } else { | |||||
| return this.slice(0, -2).flatMap(x => [x, item]).concat(this.slice(-2, -1).flatMap(x => [x, endItem])).concat(this.slice(-1)) | |||||
| } | |||||
| } | |||||
| Vue.config.productionTip = false | Vue.config.productionTip = false | ||||
| new Vue({ | new Vue({ | ||||