| @@ -11,7 +11,7 @@ import Combat from './components/Combat.vue' | |||
| import Header from './components/Header.vue' | |||
| import * as Creatures from '@/game/creatures' | |||
| import { Creature, POV } from '@/game/entity' | |||
| import { ProperNoun } from '@/game/language' | |||
| import { ProperNoun, TheyPronouns, FemalePronouns, MalePronouns } from '@/game/language' | |||
| @Component({ | |||
| components: { | |||
| @@ -24,15 +24,38 @@ export default class App extends Vue { | |||
| combatants: Array<Creature> | |||
| constructor () { | |||
| super() | |||
| this.left = new Creatures.Wolf() | |||
| this.left.name = new ProperNoun("Wolf") | |||
| this.right = new Creatures.Cafat() | |||
| const wolf1 = new Creatures.Wolf() | |||
| wolf1.name = new ProperNoun("Innermost Wolf") | |||
| const wolf2 = new Creatures.Wolf() | |||
| wolf2.name = new ProperNoun("Inner Wolf") | |||
| this.combatants = [this.left, this.right, wolf1, wolf2] | |||
| this.left.perspective = POV.First | |||
| const fighter = new Creatures.Human(new ProperNoun('Fighter'), TheyPronouns, { | |||
| stats: { | |||
| Toughness: 40, | |||
| Power: 50, | |||
| Speed: 30, | |||
| Willpower: 40, | |||
| Charm: 20 | |||
| } | |||
| }) | |||
| const rogue = new Creatures.Human(new ProperNoun('Wizard'), MalePronouns, { | |||
| stats: { | |||
| Toughness: 25, | |||
| Power: 40, | |||
| Speed: 70, | |||
| Willpower: 50, | |||
| Charm: 80 | |||
| } | |||
| }) | |||
| const wizard = new Creatures.Human(new ProperNoun('Rogue'), FemalePronouns, { | |||
| stats: { | |||
| Toughness: 30, | |||
| Power: 20, | |||
| Speed: 50, | |||
| Willpower: 80, | |||
| Charm: 60 | |||
| } | |||
| }) | |||
| this.left = fighter | |||
| this.right = new Creatures.Withers() | |||
| this.combatants = [this.left, this.right, wizard, rogue] | |||
| console.log(this.left) | |||
| console.log(this.right) | |||
| } | |||
| @@ -1,17 +1,11 @@ | |||
| <template> | |||
| <div class="combat-layout"> | |||
| <div class="left-selector"> | |||
| <button class="combatant-picker" @click="left = combatant" v-for="(combatant, index) in combatants.filter(c => c !== right && c !== left)" :key="'left' + index"> | |||
| {{ combatant.name }} | |||
| </button> | |||
| <div id="left-stats"> | |||
| <Statblock v-on:click.native="left = combatant" class="left-stats" :data-active="combatant === left" v-for="combatant in combatants.filter(c => c.side == Side.Heroes && c.vigors.Health > 0)" v-bind:key="combatant.name" :subject="combatant" /> | |||
| </div> | |||
| <div class="right-selector"> | |||
| <button class="combatant-picker" @click="right = combatant" v-for="(combatant, index) in combatants.filter(c => c !== left && c !== right)" :key="'right' + index"> | |||
| {{ combatant.name }} | |||
| </button> | |||
| <div id="right-stats"> | |||
| <Statblock v-on:click.native="right = combatant" class="right-stats" :data-active="combatant === right" v-for="combatant in combatants.filter(c => c.side == Side.Monsters && c.vigors.Health > 0)" v-bind:key="combatant.name" :subject="combatant" /> | |||
| </div> | |||
| <Statblock class="left-stats" :subject="left" /> | |||
| <Statblock class="right-stats" :subject="right" /> | |||
| <div id="log"> | |||
| </div> | |||
| <div class="left-actions"> | |||
| @@ -42,6 +36,7 @@ import { Creature, POV } from '@/game/entity' | |||
| import { LogEntry } from '@/game/interface' | |||
| import Statblock from './Statblock.vue' | |||
| import ActionButton from './ActionButton.vue' | |||
| import { Side } from '@/game/combat' | |||
| @Component( | |||
| { | |||
| @@ -58,6 +53,8 @@ export default class Combat extends Vue { | |||
| @Prop() | |||
| combatants!: Array<Creature> | |||
| Side = Side | |||
| actionDescription = '' | |||
| constructor () { | |||
| @@ -121,14 +118,6 @@ export default class Combat extends Vue { | |||
| flex: 10; | |||
| } | |||
| .left-stats { | |||
| grid-area: 2 / 1 / 3 / 2 | |||
| } | |||
| .right-stats { | |||
| grid-area: 2 / 3 / 3 / 4; | |||
| } | |||
| #log { | |||
| grid-area: main-row-start / main-col-start / main-row-end / main-col-end; | |||
| overflow-y: scroll; | |||
| @@ -146,12 +135,20 @@ export default class Combat extends Vue { | |||
| grid-area: 1 / 3 / 2 / 4; | |||
| } | |||
| #left-stats { | |||
| grid-area: 2 / 1 / 4 / 2 | |||
| } | |||
| #right-stats { | |||
| grid-area: 2 / 3 / 4 / 4; | |||
| } | |||
| .left-actions { | |||
| grid-area: 4 / 1 / 6 / 2; | |||
| grid-area: 5 / 1 / 6 / 2; | |||
| } | |||
| .right-actions { | |||
| grid-area: 4 / 3 / 6 / 4; | |||
| grid-area: 5 / 3 / 6 / 4; | |||
| } | |||
| #action-desc { | |||
| @@ -2,7 +2,7 @@ | |||
| <div class="statblock"> | |||
| <h2 v-if="subject.perspective === firstperson">You</h2> | |||
| <h2 v-if="subject.perspective !== firstperson">{{subject.name.all.capital}}</h2> | |||
| <div class="stat-line"> | |||
| <div class="stat-line vigors"> | |||
| <div :class="vigorClass(subject.vigors[vigor], subject.maxVigors[vigor])" v-for="vigor in Object.keys(subject.vigors)" v-bind:key="vigor"> | |||
| <i :class="vigorIcons[vigor]" /> | |||
| <div>{{subject.vigors[vigor]}}</div> | |||
| @@ -12,8 +12,7 @@ | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <br> | |||
| <div class="stat-line"> | |||
| <div class="stat-line stats"> | |||
| <div :class="statClass(subject.stats[stat], subject.baseStats[stat])" v-for="stat in Object.keys(subject.stats)" v-bind:key="stat"> | |||
| <i :class="statIcons[stat]" /> | |||
| <div>{{subject.stats[stat]}}</div> | |||
| @@ -23,8 +22,7 @@ | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <br> | |||
| <div class="stat-line"> | |||
| <div class="stat-line vore-stats"> | |||
| <div class="stat-entry" v-for="stat in Object.keys(subject.voreStats)" v-bind:key="stat"> | |||
| <i :class="voreStatIcons[stat]" /> | |||
| <div>{{subject.voreStats[stat]}}</div> | |||
| @@ -34,9 +32,7 @@ | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <br> | |||
| <div>Status: {{subject.status}}</div> | |||
| <ContainerView v-for="container in subject.containers" :key="container.name.toString()" :container="container" /> | |||
| </div> | |||
| </template> | |||
| @@ -45,7 +41,7 @@ import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | |||
| import { Creature, POV } from '@/game/entity' | |||
| import { Stats, Stat, StatIcons, StatDescs, Vigor, VigorIcons, VigorDescs, VoreStatDescs, VoreStatIcons } from '@/game/combat' | |||
| import ContainerView from './ContainerView.vue' | |||
| import tippy from 'tippy.js' | |||
| import tippy, { createSingleton } from 'tippy.js' | |||
| import 'tippy.js/dist/tippy.css' | |||
| @Component({ | |||
| @@ -88,13 +84,14 @@ export default class Statblock extends Vue { | |||
| firstperson: POV = POV.First | |||
| mounted () { | |||
| this.$el.querySelectorAll(".stat-entry").forEach(elem => { | |||
| const tippyInstances = Array.from(this.$el.querySelectorAll(".stat-entry")).map(elem => { | |||
| const tooltip = elem.querySelector(".tooltip-template") as HTMLElement | |||
| tippy(elem, { | |||
| return tippy(elem, { | |||
| content: tooltip | |||
| }) | |||
| }) | |||
| createSingleton(tippyInstances, { delay: 500 }) | |||
| } | |||
| } | |||
| </script> | |||
| @@ -102,7 +99,7 @@ export default class Statblock extends Vue { | |||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | |||
| <style scoped> | |||
| h2 { | |||
| margin-bottom: 16pt; | |||
| margin-bottom: 8pt; | |||
| font-size: 200%; | |||
| } | |||
| ul { | |||
| @@ -118,6 +115,7 @@ a { | |||
| } | |||
| .statblock { | |||
| margin: 16px; | |||
| user-select: none; | |||
| } | |||
| .stat-line { | |||
| @@ -127,6 +125,11 @@ a { | |||
| flex-wrap: wrap; | |||
| } | |||
| .stats, | |||
| .vore-stats { | |||
| display: none; | |||
| } | |||
| .stat-entry { | |||
| position: relative; | |||
| font-size: 16pt; | |||
| @@ -149,6 +152,16 @@ a { | |||
| .stat-entry.buff { | |||
| color: green; | |||
| } | |||
| .statblock[data-active] { | |||
| background: #444; | |||
| border-radius: 4px; | |||
| } | |||
| .statblock[data-active] .stats, | |||
| .statblock[data-active] .vore-stats { | |||
| display: flex; | |||
| } | |||
| </style> | |||
| <style> | |||
| @@ -197,13 +197,17 @@ export class UniformRandomDamageFormula implements DamageFormula { | |||
| } | |||
| } | |||
| export enum Side { | |||
| Heroes, | |||
| Monsters | |||
| } | |||
| /** | |||
| * A Combatant has a list of possible actions to take. | |||
| * | |||
| * This may be merged with [[Actionable]] | |||
| * A Combatant has a list of possible actions to take, as well as a side. | |||
| */ | |||
| export interface Combatant { | |||
| actions: Array<Action>; | |||
| side: Side; | |||
| } | |||
| /** | |||
| @@ -1,5 +1,7 @@ | |||
| import { Wolf } from './creatures/wolf' | |||
| import { Player } from './creatures/player' | |||
| import { Cafat } from './creatures/cafat' | |||
| import { Human } from './creatures/human' | |||
| import { Withers } from './creatures/withers' | |||
| export { Wolf, Player, Cafat } | |||
| export { Wolf, Player, Cafat, Human, Withers } | |||
| @@ -0,0 +1,26 @@ | |||
| import { Creature, POV, Entity } from '../entity' | |||
| import { Stat, Damage, DamageType, ConstantDamageFormula, Vigor, Stats } from '../combat' | |||
| import { MalePronouns, ImproperNoun, POVPair, POVPairArgs, Noun, Pronoun } from '../language' | |||
| import { LogLine, LogLines } from '../interface' | |||
| import { VoreType, Stomach, Bowels } from '../vore' | |||
| import { StatTest } from '../combat/tests' | |||
| import { AttackAction, TransferAction, FeedAction } from '../combat/actions' | |||
| export class Human extends Creature { | |||
| constructor (name: Noun, pronouns: Pronoun, options: { | |||
| stats?: Stats; | |||
| } = {}) { | |||
| let stats | |||
| if (options.stats === undefined) { | |||
| stats = { Toughness: 20, Power: 20, Speed: 20, Willpower: 20, Charm: 20 } | |||
| } else { | |||
| stats = options.stats | |||
| } | |||
| super(name, MalePronouns, stats, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral, VoreType.Anal]), 25) | |||
| this.actions.push(new AttackAction(new ConstantDamageFormula( | |||
| new Damage( | |||
| { amount: 20, target: Vigor.Health, type: DamageType.Slash } | |||
| ) | |||
| ))) | |||
| } | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| import { Creature, POV, Entity } from '../entity' | |||
| import { Stat, Damage, DamageType, ConstantDamageFormula, Vigor, Side, PairAction, CombatTest } from '../combat' | |||
| import { MalePronouns, ImproperNoun, POVPair, POVPairArgs, ProperNoun, TheyPronouns } from '../language' | |||
| import { LogLine, LogLines, LogEntry } from '../interface' | |||
| import { VoreType, Stomach, Bowels } from '../vore' | |||
| import { StatTest } from '../combat/tests' | |||
| import { AttackAction, TransferAction, FeedAction } from '../combat/actions' | |||
| class BiteAction extends AttackAction { | |||
| constructor () { | |||
| super(new ConstantDamageFormula(new Damage({ amount: 50, type: DamageType.Slash, target: Vigor.Health }))) | |||
| this.name = "Bite" | |||
| } | |||
| } | |||
| export class Withers extends Creature { | |||
| constructor () { | |||
| super( | |||
| new ProperNoun('Withers'), | |||
| TheyPronouns, | |||
| { Toughness: 60, Power: 100, Speed: 40, Willpower: 60, Charm: 120 }, | |||
| new Set(), | |||
| new Set([VoreType.Oral]), | |||
| 5000) | |||
| this.actions.push(new BiteAction()) | |||
| this.side = Side.Monsters | |||
| const stomach = new Stomach(this, 50, new Damage( | |||
| { amount: 30, type: DamageType.Acid, target: Vigor.Health }, | |||
| { amount: 20, type: DamageType.Crush, target: Vigor.Stamina }, | |||
| { amount: 20, type: DamageType.Dominance, target: Vigor.Resolve } | |||
| )) | |||
| this.containers.push(stomach) | |||
| this.otherActions.push(new FeedAction(stomach)) | |||
| } | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| import { DamageType, Damage, Combatant, Stats, Action, Vigor, VoreStats, VoreStat, Stat } from './combat' | |||
| import { DamageType, Damage, Combatant, Stats, Action, Vigor, VoreStats, VoreStat, Stat, Side } from './combat' | |||
| import { Noun, Pronoun } from './language' | |||
| import { LogEntry, LogLine } from './interface' | |||
| import { Vore, Container, VoreType } from './vore' | |||
| @@ -38,6 +38,7 @@ export class Creature extends Vore implements Combatant { | |||
| baseStats: Stats | |||
| voreStats: VoreStats | |||
| side: Side | |||
| get disabled (): boolean { | |||
| return Object.values(this.vigors).some(val => val <= 0) | |||
| @@ -59,7 +60,7 @@ export class Creature extends Vore implements Combatant { | |||
| 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) | |||
| @@ -137,7 +138,7 @@ export class Creature extends Vore implements Combatant { | |||
| return "Broken" | |||
| } | |||
| if (this.containedIn !== null) { | |||
| return "Devoured" | |||
| return `Devoured by ${this.containedIn.owner.name}` | |||
| } | |||
| return "Normal" | |||