| @@ -44,6 +44,7 @@ body, html { | |||
| background: #181818; | |||
| width: 100%; | |||
| height: 100%; | |||
| overflow-x: hidden; | |||
| } | |||
| #app { | |||
| @@ -2,14 +2,18 @@ | |||
| <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"><i class="fas fa-heart" /> {{ subject.vigors[vigor.Health].toFixed(0) }}</div> | |||
| <div class="stat-line"><i class="fas fa-bolt" /> {{ subject.vigors[vigor.Stamina].toFixed(0) }}</div> | |||
| <div class="stat-line"><i class="fas fa-brain" /> {{ subject.vigors[vigor.Willpower].toFixed(0) }}</div> | |||
| <div class="stat-line"> | |||
| <div class="stat-entry" :data-tooltip="vigor" v-for="vigor in Object.keys(subject.vigors)" v-bind:key="vigor"><i :class="vigorIcons[vigor]" /><div>{{subject.vigors[vigor]}}</div></div> | |||
| </div> | |||
| <br> | |||
| <div class="stat-line" v-for="stat in Object.keys(subject.stats)" v-bind:key="stat"><i :class="statIcons[stat]" /> {{subject.stats[stat]}}</div> | |||
| <div class="stat-line"> | |||
| <div class="stat-entry" :data-tooltip="stat" v-for="stat in Object.keys(subject.stats)" v-bind:key="stat"><i :class="statIcons[stat]" /> {{subject.stats[stat]}}</div> | |||
| </div> | |||
| <br> | |||
| <div class="stat-line"><i class="fas fa-weight-hanging" /> {{subject.bulk}}</div> | |||
| <div class="stat-line"><i class="fas fa-utensils" /> {{ subject.containers.reduce((total, container) => total + container.contents.length, 0) }} </div> | |||
| <div class="stat-line"> | |||
| <div class="stat-entry" data-tooltip="Bulk"><i class="fas fa-weight-hanging" /> {{subject.bulk}}</div> | |||
| <div class="stat-entry" data-tooltip="Prey Count"><i class="fas fa-utensils" /> {{ subject.containers.reduce((total, container) => total + container.contents.length, 0) }} </div> | |||
| </div> | |||
| <br> | |||
| <div>Status: {{subject.status}}</div> | |||
| <ContainerView v-for="container in subject.containers" :key="container.name.toString()" :container="container" /> | |||
| @@ -19,9 +23,8 @@ | |||
| <script lang="ts"> | |||
| import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | |||
| import { Creature, POV } from '@/game/entity' | |||
| import { Stats, Stat, StatIcons, Vigor } from '@/game/combat' | |||
| import { Stats, Stat, StatIcons, Vigor, VigorIcons } from '@/game/combat' | |||
| import ContainerView from './ContainerView.vue' | |||
| @Component({ | |||
| components: { | |||
| ContainerView | |||
| @@ -31,6 +34,7 @@ export default class Statblock extends Vue { | |||
| @Prop({ type: Creature, required: true }) | |||
| subject!: Creature | |||
| private vigorIcons = VigorIcons | |||
| private statIcons = StatIcons | |||
| private vigor = Vigor | |||
| @@ -61,11 +65,43 @@ a { | |||
| .statblock { | |||
| margin: 16px; | |||
| } | |||
| .stat-line { | |||
| width: 100%; | |||
| display: flex; | |||
| justify-content: space-around; | |||
| flex-wrap: wrap; | |||
| } | |||
| .stat-entry { | |||
| position: relative; | |||
| font-size: 2vh; | |||
| padding-top: 4pt; | |||
| padding-bottom: 4pt; | |||
| display: flex; | |||
| flex-direction: column; | |||
| justify-content: space-evenly; | |||
| user-select: none; | |||
| } | |||
| .stat-entry::after { | |||
| opacity: 0; | |||
| position: absolute; | |||
| color: #eee; | |||
| font-size: 0pt; | |||
| content: attr(data-tooltip); | |||
| transition: 0.1s; | |||
| pointer-events: none; | |||
| left: 0pt; | |||
| top: 0pt; | |||
| transform: translate(calc(-50% + 16pt), -100%); | |||
| background: #555; | |||
| padding: 8pt; | |||
| border-radius: 8pt; | |||
| } | |||
| .stat-entry:hover::after { | |||
| font-size: 18pt; | |||
| opacity: 1; | |||
| } | |||
| </style> | |||
| @@ -21,29 +21,33 @@ export interface DamageInstance { | |||
| export enum Vigor { | |||
| Health = "Health", | |||
| Stamina = "Stamina", | |||
| Willpower = "Willpower" | |||
| Resolve = "Resolve" | |||
| } | |||
| export const VigorIcons: {[key in Vigor]: string} = { | |||
| [Vigor.Health]: "fas fa-heart", | |||
| [Vigor.Stamina]: "fas fa-bolt", | |||
| [Vigor.Willpower]: "fas fa-brain" | |||
| [Vigor.Resolve]: "fas fa-brain" | |||
| } | |||
| export type Vigors = {[key in Vigor]: number} | |||
| export enum Stat { | |||
| STR = 'Strength', | |||
| DEX = 'Dexterity', | |||
| CON = 'Constitution' | |||
| Toughness = "Toughness", | |||
| Power = "Power", | |||
| Speed = "Speed", | |||
| Willpower = "Willpower", | |||
| Charm = "Charm" | |||
| } | |||
| export type Stats = {[key in Stat]: number} | |||
| export const StatIcons: {[key in Stat]: string} = { | |||
| [Stat.STR]: 'fas fa-fist-raised', | |||
| [Stat.DEX]: 'fas fa-feather', | |||
| [Stat.CON]: 'fas fa-heartbeat' | |||
| [Stat.Toughness]: 'fas fa-heartbeat', | |||
| [Stat.Power]: 'fas fa-fist-raised', | |||
| [Stat.Speed]: 'fas fa-feather', | |||
| [Stat.Willpower]: 'fas fa-book', | |||
| [Stat.Charm]: 'fas fa-comments' | |||
| } | |||
| export interface CombatTest { | |||
| test: (user: Creature, target: Creature) => boolean; | |||
| @@ -311,7 +315,7 @@ export class AttackAction extends TogetherAction { | |||
| constructor (protected damage: Damage) { | |||
| super('Attack', 'Attack the enemy', [new CapableCondition()]) | |||
| this.test = new StatTest(Stat.STR) | |||
| this.test = new StatTest(Stat.Power) | |||
| } | |||
| execute (user: Creature, target: Creature): LogEntry { | |||
| @@ -348,7 +352,7 @@ export class DevourAction extends TogetherAction { | |||
| constructor (protected container: Container) { | |||
| super(new DynText('Devour (', new LiveText(container, x => x.name.all), ')'), 'Try to consume your foe', [new CapableCondition()]) | |||
| this.test = new StatVigorTest(Stat.STR) | |||
| this.test = new StatVigorTest(Stat.Power) | |||
| } | |||
| execute (user: Creature, target: Creature): LogEntry { | |||
| @@ -382,9 +386,9 @@ export class FeedAction extends TogetherAction { | |||
| } | |||
| constructor (protected container: Container) { | |||
| super('Feed', 'Feed yourself to your opponent', [new DrainedVigorCondition(Vigor.Willpower)]) | |||
| super('Feed', 'Feed yourself to your opponent', [new DrainedVigorCondition(Vigor.Resolve)]) | |||
| this.name += ` (${container.name})` | |||
| this.test = new StatTest(Stat.STR) | |||
| this.test = new StatTest(Stat.Power) | |||
| } | |||
| execute (user: Creature, target: Creature): LogEntry { | |||
| @@ -411,7 +415,7 @@ export class StruggleAction extends PairAction { | |||
| constructor (public container: Container) { | |||
| super(new DynText('Struggle (', new LiveText(container, x => x.name.all), ')'), 'Try to escape from your foe', [new CapableCondition()]) | |||
| this.test = new StatVigorTest(Stat.STR) | |||
| this.test = new StatVigorTest(Stat.Power) | |||
| } | |||
| execute (user: Creature, target: Creature): LogEntry { | |||
| @@ -79,19 +79,25 @@ class CrushAction extends EatenAction { | |||
| export class Cafat extends Creature { | |||
| constructor () { | |||
| super(new ProperNoun('Cafat'), [TheyPronouns, FemalePronouns][Math.floor(Math.random() * 2)], { [Stat.STR]: 30, [Stat.DEX]: 15, [Stat.CON]: 25 }, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral, VoreType.Anal]), 150) | |||
| super(new ProperNoun('Cafat'), [TheyPronouns, FemalePronouns][Math.floor(Math.random() * 2)], { | |||
| [Stat.Toughness]: 30, | |||
| [Stat.Power]: 30, | |||
| [Stat.Speed]: 15, | |||
| [Stat.Willpower]: 25, | |||
| [Stat.Charm]: 20 | |||
| }, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral, VoreType.Anal]), 150) | |||
| this.vigors.Health = 200 | |||
| this.maxVigors.Health = 200 | |||
| this.vigors.Stamina = 250 | |||
| this.maxVigors.Stamina = 250 | |||
| this.vigors.Willpower = 150 | |||
| this.maxVigors.Willpower = 150 | |||
| this.vigors.Resolve = 150 | |||
| this.maxVigors.Resolve = 150 | |||
| const stomach = new Stomach(this, 100, new Damage( | |||
| { amount: 20, type: DamageType.Acid, target: Vigor.Health }, | |||
| { amount: 10, type: DamageType.Crush, target: Vigor.Stamina }, | |||
| { amount: 10, type: DamageType.Dominance, target: Vigor.Willpower } | |||
| { amount: 10, type: DamageType.Dominance, target: Vigor.Resolve } | |||
| )) | |||
| stomach.name = new ImproperNoun("upper stomach", "upper stomachs").all | |||
| @@ -101,7 +107,7 @@ export class Cafat extends Creature { | |||
| const lowerStomach = new InnerStomach(this, 100, new Damage( | |||
| { amount: 40, type: DamageType.Acid, target: Vigor.Health }, | |||
| { amount: 20, type: DamageType.Crush, target: Vigor.Stamina }, | |||
| { amount: 20, type: DamageType.Dominance, target: Vigor.Willpower } | |||
| { amount: 20, type: DamageType.Dominance, target: Vigor.Resolve } | |||
| ), stomach) | |||
| lowerStomach.name = new ImproperNoun("lower stomach", "lower stomachs").all | |||
| @@ -127,9 +133,9 @@ export class Cafat extends Creature { | |||
| this.actions.push(new TransferAction(lowerStomach, stomach)) | |||
| this.actions.push(new AttackAction(new Damage({ amount: 40, type: DamageType.Crush, target: Vigor.Health }))) | |||
| this.actions.push(new BellyCrushAction(new Damage({ amount: 10, type: DamageType.Crush, target: Vigor.Health }, { amount: 10, type: DamageType.Dominance, target: Vigor.Willpower }))) | |||
| this.actions.push(new BellyCrushAction(new Damage({ amount: 10, type: DamageType.Crush, target: Vigor.Health }, { amount: 10, type: DamageType.Dominance, target: Vigor.Resolve }))) | |||
| this.actions.push(new BelchAction(new Damage( | |||
| { amount: 100, target: Vigor.Willpower, type: DamageType.Acid } | |||
| { amount: 100, target: Vigor.Resolve, type: DamageType.Acid } | |||
| ))) | |||
| this.otherActions.push(new FeedAction(stomach)) | |||
| } | |||
| @@ -5,7 +5,7 @@ import { Stomach, Bowels, VoreType } from '../vore' | |||
| export class Player extends Creature { | |||
| constructor () { | |||
| super(new ProperNoun('The Dude'), TheyPronouns, { [Stat.STR]: 20, [Stat.DEX]: 20, [Stat.CON]: 20 }, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral, VoreType.Anal]), 50) | |||
| super(new ProperNoun('The Dude'), TheyPronouns, { Toughness: 20, Power: 20, Speed: 20, Willpower: 20, Charm: 20 }, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral, VoreType.Anal]), 50) | |||
| this.actions.push(new AttackAction(new Damage({ type: DamageType.Pierce, amount: 20, target: Vigor.Health }, { type: DamageType.Pierce, amount: 20, target: Vigor.Stamina }))) | |||
| @@ -34,22 +34,22 @@ class HypnoAction extends AttackAction { | |||
| ]) | |||
| constructor () { | |||
| super(new Damage({ amount: 30, type: DamageType.Dominance, target: Vigor.Willpower })) | |||
| this.test = new StatTest(Stat.CON) | |||
| super(new Damage({ amount: 30, type: DamageType.Dominance, target: Vigor.Resolve })) | |||
| this.test = new StatTest(Stat.Willpower) | |||
| this.name = "Hypnotize" | |||
| } | |||
| } | |||
| export class Wolf extends Creature { | |||
| constructor () { | |||
| super(new ImproperNoun('wolf', 'wolves'), MalePronouns, { [Stat.STR]: 15, [Stat.DEX]: 15, [Stat.CON]: 25 }, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral, VoreType.Anal]), 25) | |||
| super(new ImproperNoun('wolf', 'wolves'), MalePronouns, { Toughness: 20, Power: 20, Speed: 20, Willpower: 20, Charm: 20 }, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral, VoreType.Anal]), 25) | |||
| this.actions.push(new BiteAction()) | |||
| this.actions.push(new HypnoAction()) | |||
| const stomach = new Stomach(this, 50, new Damage( | |||
| { amount: 20, type: DamageType.Acid, target: Vigor.Health }, | |||
| { amount: 10, type: DamageType.Crush, target: Vigor.Stamina }, | |||
| { amount: 10, type: DamageType.Dominance, target: Vigor.Willpower } | |||
| { amount: 10, type: DamageType.Dominance, target: Vigor.Resolve } | |||
| )) | |||
| this.containers.push(stomach) | |||
| @@ -57,7 +57,7 @@ export class Wolf extends Creature { | |||
| const bowels = new Bowels(this, 50, new Damage( | |||
| { amount: 10, type: DamageType.Crush, target: Vigor.Health }, | |||
| { amount: 25, type: DamageType.Crush, target: Vigor.Stamina }, | |||
| { amount: 25, type: DamageType.Dominance, target: Vigor.Willpower } | |||
| { amount: 25, type: DamageType.Dominance, target: Vigor.Resolve } | |||
| )) | |||
| this.containers.push(bowels) | |||
| @@ -26,13 +26,13 @@ export class Creature extends Vore implements Combatant { | |||
| vigors = { | |||
| [Vigor.Health]: 100, | |||
| [Vigor.Stamina]: 100, | |||
| [Vigor.Willpower]: 100 | |||
| [Vigor.Resolve]: 100 | |||
| } | |||
| maxVigors = { | |||
| [Vigor.Health]: 100, | |||
| [Vigor.Stamina]: 100, | |||
| [Vigor.Willpower]: 100 | |||
| [Vigor.Resolve]: 100 | |||
| } | |||
| get disabled (): boolean { | |||
| @@ -85,7 +85,7 @@ export class Creature extends Vore implements Combatant { | |||
| if (this.vigors[Vigor.Stamina] <= -100) { | |||
| return "Unconscious" | |||
| } | |||
| if (this.vigors[Vigor.Willpower] <= -100) { | |||
| if (this.vigors[Vigor.Resolve] <= -100) { | |||
| return "Broken" | |||
| } | |||
| if (this.vigors[Vigor.Health] <= 0) { | |||
| @@ -94,7 +94,7 @@ export class Creature extends Vore implements Combatant { | |||
| if (this.vigors[Vigor.Stamina] <= 0) { | |||
| return "Exhausted" | |||
| } | |||
| if (this.vigors[Vigor.Willpower] <= 0) { | |||
| if (this.vigors[Vigor.Resolve] <= 0) { | |||
| return "Overpowered" | |||
| } | |||
| if (this.containedIn !== null) { | |||