| @@ -44,6 +44,7 @@ body, html { | |||||
| background: #181818; | background: #181818; | ||||
| width: 100%; | width: 100%; | ||||
| height: 100%; | height: 100%; | ||||
| overflow-x: hidden; | |||||
| } | } | ||||
| #app { | #app { | ||||
| @@ -2,14 +2,18 @@ | |||||
| <div class="statblock"> | <div class="statblock"> | ||||
| <h2 v-if="subject.perspective === firstperson">You</h2> | <h2 v-if="subject.perspective === firstperson">You</h2> | ||||
| <h2 v-if="subject.perspective !== firstperson">{{subject.name.all.capital}}</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> | <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> | <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> | <br> | ||||
| <div>Status: {{subject.status}}</div> | <div>Status: {{subject.status}}</div> | ||||
| <ContainerView v-for="container in subject.containers" :key="container.name.toString()" :container="container" /> | <ContainerView v-for="container in subject.containers" :key="container.name.toString()" :container="container" /> | ||||
| @@ -19,9 +23,8 @@ | |||||
| <script lang="ts"> | <script lang="ts"> | ||||
| import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | ||||
| import { Creature, POV } from '@/game/entity' | 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' | import ContainerView from './ContainerView.vue' | ||||
| @Component({ | @Component({ | ||||
| components: { | components: { | ||||
| ContainerView | ContainerView | ||||
| @@ -31,6 +34,7 @@ export default class Statblock extends Vue { | |||||
| @Prop({ type: Creature, required: true }) | @Prop({ type: Creature, required: true }) | ||||
| subject!: Creature | subject!: Creature | ||||
| private vigorIcons = VigorIcons | |||||
| private statIcons = StatIcons | private statIcons = StatIcons | ||||
| private vigor = Vigor | private vigor = Vigor | ||||
| @@ -61,11 +65,43 @@ a { | |||||
| .statblock { | .statblock { | ||||
| margin: 16px; | margin: 16px; | ||||
| } | } | ||||
| .stat-line { | .stat-line { | ||||
| width: 100%; | |||||
| display: flex; | |||||
| justify-content: space-around; | |||||
| flex-wrap: wrap; | |||||
| } | |||||
| .stat-entry { | |||||
| position: relative; | |||||
| font-size: 2vh; | font-size: 2vh; | ||||
| padding-top: 4pt; | padding-top: 4pt; | ||||
| padding-bottom: 4pt; | padding-bottom: 4pt; | ||||
| display: flex; | display: flex; | ||||
| flex-direction: column; | |||||
| justify-content: space-evenly; | 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> | </style> | ||||
| @@ -21,29 +21,33 @@ export interface DamageInstance { | |||||
| export enum Vigor { | export enum Vigor { | ||||
| Health = "Health", | Health = "Health", | ||||
| Stamina = "Stamina", | Stamina = "Stamina", | ||||
| Willpower = "Willpower" | |||||
| Resolve = "Resolve" | |||||
| } | } | ||||
| export const VigorIcons: {[key in Vigor]: string} = { | export const VigorIcons: {[key in Vigor]: string} = { | ||||
| [Vigor.Health]: "fas fa-heart", | [Vigor.Health]: "fas fa-heart", | ||||
| [Vigor.Stamina]: "fas fa-bolt", | [Vigor.Stamina]: "fas fa-bolt", | ||||
| [Vigor.Willpower]: "fas fa-brain" | |||||
| [Vigor.Resolve]: "fas fa-brain" | |||||
| } | } | ||||
| export type Vigors = {[key in Vigor]: number} | export type Vigors = {[key in Vigor]: number} | ||||
| export enum Stat { | 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 type Stats = {[key in Stat]: number} | ||||
| export const StatIcons: {[key in Stat]: string} = { | 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 { | export interface CombatTest { | ||||
| test: (user: Creature, target: Creature) => boolean; | test: (user: Creature, target: Creature) => boolean; | ||||
| @@ -311,7 +315,7 @@ export class AttackAction extends TogetherAction { | |||||
| constructor (protected damage: Damage) { | constructor (protected damage: Damage) { | ||||
| super('Attack', 'Attack the enemy', [new CapableCondition()]) | 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 { | execute (user: Creature, target: Creature): LogEntry { | ||||
| @@ -348,7 +352,7 @@ export class DevourAction extends TogetherAction { | |||||
| constructor (protected container: Container) { | constructor (protected container: Container) { | ||||
| super(new DynText('Devour (', new LiveText(container, x => x.name.all), ')'), 'Try to consume your foe', [new CapableCondition()]) | 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 { | execute (user: Creature, target: Creature): LogEntry { | ||||
| @@ -382,9 +386,9 @@ export class FeedAction extends TogetherAction { | |||||
| } | } | ||||
| constructor (protected container: Container) { | 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.name += ` (${container.name})` | ||||
| this.test = new StatTest(Stat.STR) | |||||
| this.test = new StatTest(Stat.Power) | |||||
| } | } | ||||
| execute (user: Creature, target: Creature): LogEntry { | execute (user: Creature, target: Creature): LogEntry { | ||||
| @@ -411,7 +415,7 @@ export class StruggleAction extends PairAction { | |||||
| constructor (public container: Container) { | constructor (public container: Container) { | ||||
| super(new DynText('Struggle (', new LiveText(container, x => x.name.all), ')'), 'Try to escape from your foe', [new CapableCondition()]) | 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 { | execute (user: Creature, target: Creature): LogEntry { | ||||
| @@ -79,19 +79,25 @@ class CrushAction extends EatenAction { | |||||
| export class Cafat extends Creature { | export class Cafat extends Creature { | ||||
| constructor () { | 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.vigors.Health = 200 | ||||
| this.maxVigors.Health = 200 | this.maxVigors.Health = 200 | ||||
| this.vigors.Stamina = 250 | this.vigors.Stamina = 250 | ||||
| this.maxVigors.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( | const stomach = new Stomach(this, 100, new Damage( | ||||
| { amount: 20, type: DamageType.Acid, target: Vigor.Health }, | { amount: 20, type: DamageType.Acid, target: Vigor.Health }, | ||||
| { amount: 10, type: DamageType.Crush, target: Vigor.Stamina }, | { 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 | 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( | const lowerStomach = new InnerStomach(this, 100, new Damage( | ||||
| { amount: 40, type: DamageType.Acid, target: Vigor.Health }, | { amount: 40, type: DamageType.Acid, target: Vigor.Health }, | ||||
| { amount: 20, type: DamageType.Crush, target: Vigor.Stamina }, | { 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) | ), stomach) | ||||
| lowerStomach.name = new ImproperNoun("lower stomach", "lower stomachs").all | 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 TransferAction(lowerStomach, stomach)) | ||||
| this.actions.push(new AttackAction(new Damage({ amount: 40, type: DamageType.Crush, target: Vigor.Health }))) | 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( | 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)) | this.otherActions.push(new FeedAction(stomach)) | ||||
| } | } | ||||
| @@ -5,7 +5,7 @@ import { Stomach, Bowels, VoreType } from '../vore' | |||||
| export class Player extends Creature { | export class Player extends Creature { | ||||
| constructor () { | 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 }))) | 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 () { | 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" | this.name = "Hypnotize" | ||||
| } | } | ||||
| } | } | ||||
| export class Wolf extends Creature { | export class Wolf extends Creature { | ||||
| constructor () { | 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 BiteAction()) | ||||
| this.actions.push(new HypnoAction()) | this.actions.push(new HypnoAction()) | ||||
| const stomach = new Stomach(this, 50, new Damage( | const stomach = new Stomach(this, 50, new Damage( | ||||
| { amount: 20, type: DamageType.Acid, target: Vigor.Health }, | { amount: 20, type: DamageType.Acid, target: Vigor.Health }, | ||||
| { amount: 10, type: DamageType.Crush, target: Vigor.Stamina }, | { 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) | this.containers.push(stomach) | ||||
| @@ -57,7 +57,7 @@ export class Wolf extends Creature { | |||||
| const bowels = new Bowels(this, 50, new Damage( | const bowels = new Bowels(this, 50, new Damage( | ||||
| { amount: 10, type: DamageType.Crush, target: Vigor.Health }, | { amount: 10, type: DamageType.Crush, target: Vigor.Health }, | ||||
| { amount: 25, type: DamageType.Crush, target: Vigor.Stamina }, | { 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) | this.containers.push(bowels) | ||||
| @@ -26,13 +26,13 @@ export class Creature extends Vore implements Combatant { | |||||
| vigors = { | vigors = { | ||||
| [Vigor.Health]: 100, | [Vigor.Health]: 100, | ||||
| [Vigor.Stamina]: 100, | [Vigor.Stamina]: 100, | ||||
| [Vigor.Willpower]: 100 | |||||
| [Vigor.Resolve]: 100 | |||||
| } | } | ||||
| maxVigors = { | maxVigors = { | ||||
| [Vigor.Health]: 100, | [Vigor.Health]: 100, | ||||
| [Vigor.Stamina]: 100, | [Vigor.Stamina]: 100, | ||||
| [Vigor.Willpower]: 100 | |||||
| [Vigor.Resolve]: 100 | |||||
| } | } | ||||
| get disabled (): boolean { | get disabled (): boolean { | ||||
| @@ -85,7 +85,7 @@ export class Creature extends Vore implements Combatant { | |||||
| if (this.vigors[Vigor.Stamina] <= -100) { | if (this.vigors[Vigor.Stamina] <= -100) { | ||||
| return "Unconscious" | return "Unconscious" | ||||
| } | } | ||||
| if (this.vigors[Vigor.Willpower] <= -100) { | |||||
| if (this.vigors[Vigor.Resolve] <= -100) { | |||||
| return "Broken" | return "Broken" | ||||
| } | } | ||||
| if (this.vigors[Vigor.Health] <= 0) { | if (this.vigors[Vigor.Health] <= 0) { | ||||
| @@ -94,7 +94,7 @@ export class Creature extends Vore implements Combatant { | |||||
| if (this.vigors[Vigor.Stamina] <= 0) { | if (this.vigors[Vigor.Stamina] <= 0) { | ||||
| return "Exhausted" | return "Exhausted" | ||||
| } | } | ||||
| if (this.vigors[Vigor.Willpower] <= 0) { | |||||
| if (this.vigors[Vigor.Resolve] <= 0) { | |||||
| return "Overpowered" | return "Overpowered" | ||||
| } | } | ||||
| if (this.containedIn !== null) { | if (this.containedIn !== null) { | ||||