| @@ -7,6 +7,7 @@ | |||||
| <link rel="icon" href="https://crux.sexy/images/feast.ico"> | <link rel="icon" href="https://crux.sexy/images/feast.ico"> | ||||
| <link rel="stylesheet" href="./reset.css"></link> | <link rel="stylesheet" href="./reset.css"></link> | ||||
| <title><%= htmlWebpackPlugin.options.title %></title> | <title><%= htmlWebpackPlugin.options.title %></title> | ||||
| <script src="https://kit.fontawesome.com/10a16c6083.js" crossorigin="anonymous"></script> | |||||
| </head> | </head> | ||||
| <body> | <body> | ||||
| <noscript> | <noscript> | ||||
| @@ -32,7 +32,7 @@ export default class App extends Vue { | |||||
| <style> | <style> | ||||
| body, html { | body, html { | ||||
| background: #111; | |||||
| background: #181818; | |||||
| width: 100%; | width: 100%; | ||||
| height: 100%; | height: 100%; | ||||
| } | } | ||||
| @@ -45,6 +45,8 @@ body, html { | |||||
| color: #ddd; | color: #ddd; | ||||
| background: #111; | background: #111; | ||||
| width: 100%; | width: 100%; | ||||
| max-width: 1000pt; | |||||
| margin: auto; | |||||
| height: 100%; | height: 100%; | ||||
| } | } | ||||
| </style> | </style> | ||||
| @@ -0,0 +1,39 @@ | |||||
| <template> | |||||
| <button class="action-button" @click="execute"> | |||||
| {{ action.name }} | |||||
| </button> | |||||
| </template> | |||||
| <script lang="ts"> | |||||
| import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | |||||
| import { Action } from '@/game/combat' | |||||
| import { Creature } from '@/game/entity' | |||||
| @Component({}) | |||||
| export default class ActionButton extends Vue { | |||||
| @Prop() | |||||
| action!: Action | |||||
| @Prop() | |||||
| user!: Creature | |||||
| @Prop() | |||||
| target!: Creature | |||||
| @Emit("execute") | |||||
| execute () { | |||||
| this.$emit('executed', this.action.execute(this.user, this.target)) | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style scoped> | |||||
| .action-button { | |||||
| width: 100px; | |||||
| height: 100px; | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,237 @@ | |||||
| <template> | |||||
| <div class="combat-layout"> | |||||
| <Statblock class="player-stats" :subject="player" /> | |||||
| <Statblock class="enemy-stats" :subject="enemy" /> | |||||
| <div id="log"> | |||||
| </div> | |||||
| <div class="player-actions"> | |||||
| <h2>Your moves</h2> | |||||
| <div class="vert-display"> | |||||
| <ActionButton @executed="executed" v-for="action in player.validActions(enemy)" :key="'player' + action.name" :action="action" :user="player" :target="enemy" /> | |||||
| <ActionButton @executed="executed" v-for="action in player.validActions(player)" :key="'player' + action.name" :action="action" :user="player" :target="enemy" /> | |||||
| </div> | |||||
| <div>{{actionDescription}}</div> | |||||
| </div> | |||||
| <div class="enemy-actions"> | |||||
| <h2>Enemy moves</h2> | |||||
| <div class="vert-display"> | |||||
| <ActionButton @executed="executedEnemy" v-for="action in enemy.validActions(player)" :key="'player' + action.name" :action="action" :user="enemy" :target="player" /> | |||||
| <ActionButton @executed="executedEnemy" v-for="action in enemy.validActions(enemy)" :key="'player' + action.name" :action="action" :user="enemy" :target="player" /> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script lang="ts"> | |||||
| import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | |||||
| import { Creature, POV } from '@/game/entity' | |||||
| import { LogEntry } from '@/game/interface' | |||||
| import Statblock from './Statblock.vue' | |||||
| import ActionButton from './ActionButton.vue' | |||||
| @Component( | |||||
| { | |||||
| components: { Statblock, ActionButton } | |||||
| } | |||||
| ) | |||||
| export default class Combat extends Vue { | |||||
| @Prop({ type: Creature, required: true }) | |||||
| player!: Creature | |||||
| @Prop({ type: Creature, required: true }) | |||||
| enemy!: Creature | |||||
| actionDescription = '' | |||||
| constructor () { | |||||
| super() | |||||
| } | |||||
| @Emit("executed") | |||||
| executed (entry: LogEntry) { | |||||
| const log = document.querySelector("#log") | |||||
| if (log !== null) { | |||||
| const holder = document.createElement("div") | |||||
| entry.render().forEach(element => { | |||||
| holder.appendChild(element) | |||||
| }) | |||||
| holder.classList.add("player-move") | |||||
| log.appendChild(holder) | |||||
| log.scrollTo({ top: 10000000000, left: 0 }) | |||||
| } | |||||
| } | |||||
| @Emit("executedEnemy") | |||||
| executedEnemy (entry: LogEntry) { | |||||
| const log = document.querySelector("#log") | |||||
| if (log !== null) { | |||||
| const holder = document.createElement("div") | |||||
| entry.render().forEach(element => { | |||||
| holder.appendChild(element) | |||||
| }) | |||||
| holder.classList.add("enemy-move") | |||||
| log.appendChild(holder) | |||||
| log.scrollTo({ top: 10000000000, left: 0 }) | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | |||||
| <style scoped> | |||||
| .combat-layout { | |||||
| display: grid; | |||||
| grid-template-rows: 20% [main-row-start] 30% 30% [main-row-end] 20%; | |||||
| grid-template-columns: 20% [main-col-start] 60% [main-col-end] 20%; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| } | |||||
| .player-stats { | |||||
| grid-area: 1 / 1 / 2 / 2 | |||||
| } | |||||
| .enemy-stats { | |||||
| grid-area: 1 / 3 / 2 / 4; | |||||
| } | |||||
| #log { | |||||
| grid-area: main-row-start / main-col-start / main-row-end / main-col-end; | |||||
| overflow-y: auto; | |||||
| font-size: 16pt; | |||||
| width: 100%; | |||||
| max-height: 100%; | |||||
| align-self: end; | |||||
| } | |||||
| .player-actions { | |||||
| grid-area: 3 / 1 / 4 / 2; | |||||
| } | |||||
| .enemy-actions { | |||||
| grid-area: 3 / 3 / 4 / 4; | |||||
| } | |||||
| h3 { | |||||
| margin: 40px 0 0; | |||||
| } | |||||
| ul { | |||||
| list-style-type: none; | |||||
| padding: 0; | |||||
| } | |||||
| li { | |||||
| display: inline-block; | |||||
| margin: 0 10px; | |||||
| } | |||||
| a { | |||||
| color: #42b983; | |||||
| } | |||||
| .horiz-display { | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| } | |||||
| .vert-display { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| align-items: center; | |||||
| } | |||||
| </style> | |||||
| <style> | |||||
| .log-damage { | |||||
| font-weight: bold; | |||||
| } | |||||
| #log > div { | |||||
| color: #888; | |||||
| padding-top: 8pt; | |||||
| padding-bottom: 8pt; | |||||
| } | |||||
| div.player-move, | |||||
| div.enemy-move { | |||||
| color: #888; | |||||
| } | |||||
| div.player-move { | |||||
| text-align: start; | |||||
| margin-left: 48pt; | |||||
| } | |||||
| div.enemy-move { | |||||
| text-align: end; | |||||
| margin-right: 48pt; | |||||
| } | |||||
| #log > div.enemy-move:nth-last-child(7) { | |||||
| color: #988; | |||||
| } | |||||
| #log > div.enemy-move:nth-last-child(6) { | |||||
| padding-top: 8pt; | |||||
| color: #a88; | |||||
| } | |||||
| #log > div.enemy-move:nth-last-child(5) { | |||||
| padding-top: 16pt; | |||||
| color: #b88; | |||||
| } | |||||
| #log > div.enemy-move:nth-last-child(4) { | |||||
| padding-top: 24pt; | |||||
| color: #c88; | |||||
| } | |||||
| #log > div.enemy-move:nth-last-child(3) { | |||||
| padding-top: 32pt; | |||||
| color: #d88; | |||||
| } | |||||
| #log > div.enemy-move:nth-last-child(2) { | |||||
| padding-top: 40pt; | |||||
| color: #e88; | |||||
| } | |||||
| #log > div.enemy-move:nth-last-child(1) { | |||||
| padding-top: 48pt; | |||||
| color: #f88; | |||||
| } | |||||
| #log > div.player-move:nth-last-child(7) { | |||||
| color: #898; | |||||
| } | |||||
| #log > div.player-move:nth-last-child(6) { | |||||
| padding-top: 8pt; | |||||
| color: #8a8; | |||||
| } | |||||
| #log > div.player-move:nth-last-child(5) { | |||||
| padding-top: 16pt; | |||||
| color: #8b8; | |||||
| } | |||||
| #log > div.player-move:nth-last-child(4) { | |||||
| padding-top: 24pt; | |||||
| color: #8c8; | |||||
| } | |||||
| #log > div.player-move:nth-last-child(3) { | |||||
| padding-top: 32pt; | |||||
| color: #8d8; | |||||
| } | |||||
| #log > div.player-move:nth-last-child(2) { | |||||
| padding-top: 40pt; | |||||
| color: #8e8; | |||||
| } | |||||
| #log > div.player-move:nth-last-child(1) { | |||||
| padding-top: 48pt; | |||||
| color: #8f8; | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,42 @@ | |||||
| <template> | |||||
| <div v-if="container.fullness > 0" class="statblock"> | |||||
| <h3>{{container.name}}</h3> | |||||
| <div>Fullness: {{container.fullness}} / {{container.capacity}}</div> | |||||
| <div v-for="(prey, index) in container.contents" :key="'prey-' + index">{{prey.name}}</div> | |||||
| </div> | |||||
| </template> | |||||
| <script lang="ts"> | |||||
| import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | |||||
| import { Creature, POV } from '@/game/entity' | |||||
| import { Stats, Stat } from '@/game/combat' | |||||
| import { Container } from '@/game/vore' | |||||
| @Component | |||||
| export default class ContainerView extends Vue { | |||||
| @Prop({ required: true }) | |||||
| container!: Container | |||||
| constructor () { | |||||
| super() | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | |||||
| <style scoped> | |||||
| h3 { | |||||
| margin: 40px 0 0; | |||||
| } | |||||
| ul { | |||||
| list-style-type: none; | |||||
| padding: 0; | |||||
| } | |||||
| li { | |||||
| display: inline-block; | |||||
| margin: 0 10px; | |||||
| } | |||||
| a { | |||||
| color: #42b983; | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,29 @@ | |||||
| <template> | |||||
| <div id="header"> | |||||
| <div> | |||||
| This is the (extremely early alpha of the) new Feast. If you're looking for the old version, <a href="https://classic.feast.crux.sexy">go here!</a> | |||||
| </div> | |||||
| <div>Version: {{version}}</div> | |||||
| </div> | |||||
| </template> | |||||
| <script lang="ts"> | |||||
| import { Component, Vue, Prop } from 'vue-property-decorator' | |||||
| @Component({}) | |||||
| export default class Header extends Vue { | |||||
| @Prop() version!: string | |||||
| } | |||||
| </script> | |||||
| <style scoped> | |||||
| #header { | |||||
| width: 100%; | |||||
| background: #222; | |||||
| top: 0%; | |||||
| padding-top: 32pt; | |||||
| padding-bottom: 32pt; | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,54 @@ | |||||
| <template> | |||||
| <div class="statblock"> | |||||
| <h2 v-if="subject.perspective === firstperson">You</h2> | |||||
| <h2 v-if="subject.perspective !== firstperson">{{subject.name.all.capital}}</h2> | |||||
| <div>Health: {{subject.health.toFixed(0)}} / {{subject.maxHealth.toFixed(0)}}</div> | |||||
| <div v-for="stat in Object.keys(subject.stats)" v-bind:key="stat">{{stat}}: {{subject.stats[stat]}}</div> | |||||
| <div>Status: {{subject.status}}</div> | |||||
| <ContainerView v-for="container in subject.containers" :key="container.name" :container="container" /> | |||||
| </div> | |||||
| </template> | |||||
| <script lang="ts"> | |||||
| import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | |||||
| import { Creature, POV } from '@/game/entity' | |||||
| import { Stats, Stat } from '@/game/combat' | |||||
| import ContainerView from './ContainerView.vue' | |||||
| @Component({ | |||||
| components: { | |||||
| ContainerView | |||||
| } | |||||
| }) | |||||
| export default class Statblock extends Vue { | |||||
| @Prop({ type: Creature, required: true }) | |||||
| subject!: Creature | |||||
| firstperson: POV = POV.First | |||||
| constructor () { | |||||
| super() | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | |||||
| <style scoped> | |||||
| h2 { | |||||
| margin-bottom: 16pt; | |||||
| font-size: 200%; | |||||
| } | |||||
| ul { | |||||
| list-style-type: none; | |||||
| padding: 0; | |||||
| } | |||||
| li { | |||||
| display: inline-block; | |||||
| margin: 0 10px; | |||||
| } | |||||
| a { | |||||
| color: #42b983; | |||||
| } | |||||
| .statblock { | |||||
| margin: 16px; | |||||
| } | |||||
| </style> | |||||
| @@ -2,8 +2,11 @@ | |||||
| <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>Health: {{subject.health.toFixed(0)}} / {{subject.maxHealth.toFixed(0)}}</div> | |||||
| <div v-for="stat in Object.keys(subject.stats)" v-bind:key="stat">{{stat}}: {{subject.stats[stat]}}</div> | |||||
| <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> | |||||
| <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>Status: {{subject.status}}</div> | <div>Status: {{subject.status}}</div> | ||||
| <ContainerView v-for="container in subject.containers" :key="container.name" :container="container" /> | <ContainerView v-for="container in subject.containers" :key="container.name" :container="container" /> | ||||
| </div> | </div> | ||||
| @@ -12,7 +15,7 @@ | |||||
| <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 } from '@/game/combat' | |||||
| import { Stats, Stat, StatIcons, Vigor } from '@/game/combat' | |||||
| import ContainerView from './ContainerView.vue' | import ContainerView from './ContainerView.vue' | ||||
| @Component({ | @Component({ | ||||
| @@ -24,6 +27,9 @@ export default class Statblock extends Vue { | |||||
| @Prop({ type: Creature, required: true }) | @Prop({ type: Creature, required: true }) | ||||
| subject!: Creature | subject!: Creature | ||||
| private statIcons = StatIcons | |||||
| private vigor = Vigor | |||||
| firstperson: POV = POV.First | firstperson: POV = POV.First | ||||
| constructor () { | constructor () { | ||||
| super() | super() | ||||
| @@ -51,4 +57,9 @@ a { | |||||
| .statblock { | .statblock { | ||||
| margin: 16px; | margin: 16px; | ||||
| } | } | ||||
| .stat-line { | |||||
| font-size: 24pt; | |||||
| padding-top: 4pt; | |||||
| padding-bottom: 4pt; | |||||
| } | |||||
| </style> | </style> | ||||
| @@ -75,8 +75,9 @@ export enum DamageType { | |||||
| Acid = "Acid"} | Acid = "Acid"} | ||||
| export interface DamageInstance { | export interface DamageInstance { | ||||
| type: DamageType; | |||||
| amount: number; | |||||
| type: DamageType; | |||||
| amount: number; | |||||
| target: Vigor; | |||||
| } | } | ||||
| export class Damage { | export class Damage { | ||||
| @@ -92,7 +93,8 @@ export class Damage { | |||||
| this.damages.forEach(damage => { | this.damages.forEach(damage => { | ||||
| results.push({ | results.push({ | ||||
| type: damage.type, | type: damage.type, | ||||
| amount: damage.amount * factor | |||||
| amount: damage.amount * factor, | |||||
| target: damage.target | |||||
| }) | }) | ||||
| }) | }) | ||||
| @@ -104,6 +106,12 @@ export class Damage { | |||||
| } | } | ||||
| } | } | ||||
| export enum Vigor { | |||||
| Health, | |||||
| Stamina, | |||||
| Willpower | |||||
| } | |||||
| export enum Stat { | export enum Stat { | ||||
| STR = 'Strength', | STR = 'Strength', | ||||
| DEX = 'Dexterity', | DEX = 'Dexterity', | ||||
| @@ -112,6 +120,12 @@ export enum Stat { | |||||
| export type Stats = {[key in Stat]: number} | 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' | |||||
| } | |||||
| export interface Combatant { | export interface Combatant { | ||||
| actions: Array<Action>; | actions: Array<Action>; | ||||
| } | } | ||||
| @@ -1,18 +1,18 @@ | |||||
| import { Creature, POV } from '../entity' | import { Creature, POV } from '../entity' | ||||
| import { ProperNoun, TheyPronouns } from '../language' | import { ProperNoun, TheyPronouns } from '../language' | ||||
| import { Stat, Damage, AttackAction, DamageType } from '../combat' | |||||
| import { Stat, Damage, AttackAction, DamageType, Vigor } from '../combat' | |||||
| import { Stomach, Bowels, VoreType } from '../vore' | 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]), new Set([VoreType.Oral, VoreType.Anal]), 50) | super(new ProperNoun('The Dude'), TheyPronouns, { [Stat.STR]: 20, [Stat.DEX]: 20, [Stat.CON]: 20 }, new Set([VoreType.Oral]), new Set([VoreType.Oral, VoreType.Anal]), 50) | ||||
| this.actions.push(new AttackAction(new Damage({ type: DamageType.Pierce, amount: 20 }))) | |||||
| this.actions.push(new AttackAction(new Damage({ type: DamageType.Pierce, amount: 20, target: Vigor.Health }))) | |||||
| const stomach = new Stomach(this, 100, new Damage({ amount: 100000000000, type: DamageType.Acid }, { amount: 10, type: DamageType.Crush })) | |||||
| const stomach = new Stomach(this, 100, new Damage({ amount: 100000000000, type: DamageType.Acid, target: Vigor.Health }, { amount: 10, type: DamageType.Crush, target: Vigor.Health })) | |||||
| this.containers.push(stomach) | this.containers.push(stomach) | ||||
| const bowels = new Bowels(this, 100, new Damage({ amount: 20, type: DamageType.Crush })) | |||||
| const bowels = new Bowels(this, 100, new Damage({ amount: 20, type: DamageType.Crush, target: Vigor.Health })) | |||||
| this.containers.push(bowels) | this.containers.push(bowels) | ||||
| this.perspective = POV.First | this.perspective = POV.First | ||||
| @@ -1,11 +1,11 @@ | |||||
| import { Creature, POV } from '../entity' | import { Creature, POV } from '../entity' | ||||
| import { Stat, Damage, DamageType, AttackAction, StruggleAction, TransferAction } from '../combat' | |||||
| import { Stat, Damage, DamageType, AttackAction, TransferAction, Vigor } from '../combat' | |||||
| import { MalePronouns, ImproperNoun } from '../language' | import { MalePronouns, ImproperNoun } from '../language' | ||||
| import { VoreType, Stomach, Bowels } from '../vore' | import { VoreType, Stomach, Bowels } from '../vore' | ||||
| class BiteAction extends AttackAction { | class BiteAction extends AttackAction { | ||||
| constructor () { | constructor () { | ||||
| super(new Damage({ amount: 10, type: DamageType.Slash })) | |||||
| super(new Damage({ amount: 10, type: DamageType.Slash, target: Vigor.Health })) | |||||
| } | } | ||||
| } | } | ||||
| @@ -14,10 +14,10 @@ export class Wolf extends Creature { | |||||
| super(new ImproperNoun('wolf', 'wolves'), MalePronouns, { [Stat.STR]: 10, [Stat.DEX]: 10, [Stat.CON]: 10 }, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral]), 25) | super(new ImproperNoun('wolf', 'wolves'), MalePronouns, { [Stat.STR]: 10, [Stat.DEX]: 10, [Stat.CON]: 10 }, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral]), 25) | ||||
| this.actions.push(new BiteAction()) | this.actions.push(new BiteAction()) | ||||
| const stomach = new Stomach(this, 50, new Damage({ amount: 50, type: DamageType.Acid }, { amount: 500, type: DamageType.Crush })) | |||||
| const stomach = new Stomach(this, 50, new Damage({ amount: 50, type: DamageType.Acid, target: Vigor.Health }, { amount: 500, type: DamageType.Crush, target: Vigor.Health })) | |||||
| this.containers.push(stomach) | this.containers.push(stomach) | ||||
| const bowels = new Bowels(this, 50, new Damage({ amount: 50, type: DamageType.Acid }, { amount: 500, type: DamageType.Crush })) | |||||
| const bowels = new Bowels(this, 50, new Damage({ amount: 50, type: DamageType.Acid, target: Vigor.Health }, { amount: 500, type: DamageType.Crush, target: Vigor.Health })) | |||||
| this.containers.push(bowels) | this.containers.push(bowels) | ||||
| @@ -1,4 +1,4 @@ | |||||
| import { DamageType, Damage, Combatant, Stats, Action } from './combat' | |||||
| import { DamageType, Damage, Combatant, Stats, Action, Vigor } from './combat' | |||||
| import { Noun, Pronoun } from './language' | import { Noun, Pronoun } from './language' | ||||
| import { Pred, Prey, Container, VoreType } from './vore' | import { Pred, Prey, Container, VoreType } from './vore' | ||||
| @@ -12,17 +12,28 @@ export interface Entity { | |||||
| } | } | ||||
| export interface Mortal extends Entity { | export interface Mortal extends Entity { | ||||
| health: number; | |||||
| maxHealth: number; | |||||
| resistances: Map<DamageType, number>; | |||||
| takeDamage: (damage: Damage) => void; | |||||
| stats: Stats; | |||||
| status: string; | |||||
| vigors: {[key in Vigor]: number}; | |||||
| maxVigors: {[key in Vigor]: number}; | |||||
| resistances: Map<DamageType, number>; | |||||
| takeDamage: (damage: Damage) => void; | |||||
| stats: Stats; | |||||
| status: string; | |||||
| } | } | ||||
| export class Creature implements Mortal, Pred, Prey, Combatant { | export class Creature implements Mortal, Pred, Prey, Combatant { | ||||
| health = 100 | |||||
| maxHealth = 100 | |||||
| vigors = { | |||||
| [Vigor.Health]: 100, | |||||
| [Vigor.Stamina]: 100, | |||||
| [Vigor.Willpower]: 100 | |||||
| } | |||||
| maxVigors = { | |||||
| [Vigor.Health]: 100, | |||||
| [Vigor.Stamina]: 100, | |||||
| [Vigor.Willpower]: 100 | |||||
| } | |||||
| resistances: Map<DamageType, number> = new Map() | resistances: Map<DamageType, number> = new Map() | ||||
| perspective: POV = POV.Third | perspective: POV = POV.Third | ||||
| containers: Array<Container> = [] | containers: Array<Container> = [] | ||||
| @@ -40,24 +51,30 @@ export class Creature implements Mortal, Pred, Prey, Combatant { | |||||
| } | } | ||||
| toString (): string { | toString (): string { | ||||
| return this.name + ': ' + this.health + ' HP' | |||||
| return this.name.toString() | |||||
| } | } | ||||
| takeDamage (damage: Damage): void { | takeDamage (damage: Damage): void { | ||||
| damage.damages.forEach(instance => { | damage.damages.forEach(instance => { | ||||
| const resistance: number|undefined = this.resistances.get(instance.type) | const resistance: number|undefined = this.resistances.get(instance.type) | ||||
| if (resistance !== undefined) { | if (resistance !== undefined) { | ||||
| this.health -= instance.amount * resistance | |||||
| this.vigors[instance.target] -= instance.amount * resistance | |||||
| } else { | } else { | ||||
| this.health -= instance.amount | |||||
| this.vigors[instance.target] -= instance.amount | |||||
| } | } | ||||
| }) | }) | ||||
| } | } | ||||
| get status (): string { | get status (): string { | ||||
| if (this.health < 0) { | |||||
| if (this.vigors[Vigor.Health] < 0) { | |||||
| return "DEAD" | |||||
| } | |||||
| if (this.vigors[Vigor.Stamina] < 0) { | |||||
| return "Unconscious" | return "Unconscious" | ||||
| } | } | ||||
| if (this.vigors[Vigor.Willpower] < 0) { | |||||
| return "Too horny" | |||||
| } | |||||
| if (this.containedIn !== null) { | if (this.containedIn !== null) { | ||||
| return "Devoured" | return "Devoured" | ||||
| } | } | ||||
| @@ -1,5 +1,5 @@ | |||||
| import { Entity, Mortal, POV } from './entity' | import { Entity, Mortal, POV } from './entity' | ||||
| import { Damage, Actionable, Action, DevourAction, DigestAction, ReleaseAction, StruggleAction } from './combat' | |||||
| import { Damage, Actionable, Action, DevourAction, DigestAction, ReleaseAction, StruggleAction, Vigor } from './combat' | |||||
| import { LogLines, LogEntry, CompositeLog } from './interface' | import { LogLines, LogEntry, CompositeLog } from './interface' | ||||
| import { POVSolo, POVPair, POVPairArgs } from './language' | import { POVSolo, POVPair, POVPairArgs } from './language' | ||||
| @@ -86,9 +86,9 @@ abstract class NormalContainer implements Container { | |||||
| const absorbed: Array<Prey> = [] | const absorbed: Array<Prey> = [] | ||||
| this.contents.forEach(prey => { | this.contents.forEach(prey => { | ||||
| const start = prey.health | |||||
| const start = prey.vigors[Vigor.Health] | |||||
| prey.takeDamage(this.damage.scale(dt / 3600)) | prey.takeDamage(this.damage.scale(dt / 3600)) | ||||
| const end = prey.health | |||||
| const end = prey.vigors[Vigor.Health] | |||||
| if (start > 0 && end <= 0) { | if (start > 0 && end <= 0) { | ||||
| digested.push(prey) | digested.push(prey) | ||||
| @@ -103,7 +103,7 @@ abstract class NormalContainer implements Container { | |||||
| const absorbedEntries = new CompositeLog(...absorbed.map(prey => this.absorb(prey))) | const absorbedEntries = new CompositeLog(...absorbed.map(prey => this.absorb(prey))) | ||||
| this.contents = this.contents.filter(prey => { | this.contents = this.contents.filter(prey => { | ||||
| return prey.health > -100 | |||||
| return prey.vigors[Vigor.Health] > -100 | |||||
| }) | }) | ||||
| return new CompositeLog(this.tickLines.run(this.owner), digestedEntries, absorbedEntries) | return new CompositeLog(this.tickLines.run(this.owner), digestedEntries, absorbedEntries) | ||||