| @@ -1,8 +1,6 @@ | |||
| <template> | |||
| <div id="app"> | |||
| <img alt="Vue logo" src="./assets/logo.png"> | |||
| <Statblock :subject="player" /> | |||
| <Statblock :subject="enemy" /> | |||
| <Combat :player="player" :enemy="enemy" /> | |||
| </div> | |||
| </template> | |||
| @@ -10,14 +8,12 @@ | |||
| <script lang="ts"> | |||
| import { Component, Vue } from 'vue-property-decorator' | |||
| import Combat from './components/Combat.vue' | |||
| import Statblock from './components/Statblock.vue' | |||
| import * as Creatures from '@/game/creatures' | |||
| import { Creature, POV } from '@/game/entity' | |||
| @Component({ | |||
| components: { | |||
| Combat, | |||
| Statblock | |||
| Combat | |||
| } | |||
| }) | |||
| export default class App extends Vue { | |||
| @@ -28,6 +24,7 @@ export default class App extends Vue { | |||
| this.player = new Creatures.Wolf() | |||
| this.player.perspective = POV.First | |||
| this.enemy = new Creatures.Wolf() | |||
| console.log(this.player) | |||
| } | |||
| } | |||
| </script> | |||
| @@ -1,9 +1,12 @@ | |||
| <template> | |||
| <div class="hello"> | |||
| <h1>VORE TIME</h1> | |||
| <Statblock :subject="player" /> | |||
| <Statblock :subject="enemy" /> | |||
| <h2>Your moves</h2> | |||
| <button v-for="action in player.validActions(enemy)" :key="'player-' + action.name" v-on:click="log(action.execute(player, enemy))">{{action.name}}</button> | |||
| <button v-for="action in player.validActions(player)" :key="'palyer-' + action.name" v-on:click="log(action.execute(player, player))">{{action.name}}</button> | |||
| <button @mouseleave="actionDescription= ''" @mouseover="actionDescription = action.desc" v-for="action in player.validActions(enemy)" :key="'player-' + action.name" v-on:click="log(action.execute(player, enemy))">{{action.name}}</button> | |||
| <button @mouseleave="actionDescription= ''" @mouseover="actionDescription = action.desc" v-for="action in player.validActions(player)" :key="'player-' + action.name" v-on:click="log(action.execute(player, player))">{{action.name}}</button> | |||
| <div>{{actionDescription}}</div> | |||
| <div id="log"></div> | |||
| <h2>Enemy moves</h2> | |||
| <button v-for="action in enemy.validActions(player)" :key="'enemy-' + action.name" v-on:click="log(action.execute(enemy, player))">{{action.name}}</button> | |||
| @@ -15,8 +18,13 @@ | |||
| import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | |||
| import { Creature, POV } from '@/game/entity' | |||
| import { log, LogEntry } from '@/game/interface' | |||
| import Statblock from './Statblock.vue' | |||
| @Component | |||
| @Component( | |||
| { | |||
| components: { Statblock } | |||
| } | |||
| ) | |||
| export default class Combat extends Vue { | |||
| @Prop({ type: Creature, required: true }) | |||
| player!: Creature | |||
| @@ -24,6 +32,8 @@ export default class Combat extends Vue { | |||
| @Prop({ type: Creature, required: true }) | |||
| enemy!: Creature | |||
| actionDescription = '' | |||
| private log: (entry: LogEntry) => void; | |||
| constructor () { | |||
| @@ -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> | |||
| @@ -3,9 +3,10 @@ | |||
| <h2>Stats</h2> | |||
| <div v-if="subject.perspective === firstperson">Player</div> | |||
| <div v-if="subject.perspective !== firstperson">Name: {{subject.name.all.capital}}</div> | |||
| <div>Health: {{subject.health.toFixed(0)}}</div> | |||
| <div v-for="stat in Object.keys(subject.stats)" :key="stat">{{stat}}: {{subject.stats[stat]}}</div> | |||
| <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.state}}</div> | |||
| <ContainerView v-for="container in subject.containers" :key="container.name" :container="container" /> | |||
| </div> | |||
| </template> | |||
| @@ -13,8 +14,13 @@ | |||
| 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 | |||
| @Component({ | |||
| components: { | |||
| ContainerView | |||
| } | |||
| }) | |||
| export default class Statblock extends Vue { | |||
| @Prop({ type: Creature, required: true }) | |||
| subject!: Creature | |||
| @@ -51,15 +51,14 @@ export interface Combatant { | |||
| } | |||
| export abstract class Action { | |||
| public name: string; | |||
| allowed (user: Creature, target: Creature) { | |||
| return this.userStates.has(user.state) && this.targetStates.has(target.state) | |||
| } | |||
| abstract execute(user: Creature, target: Creature): LogEntry | |||
| constructor (name: string, public userStates: Set<State>, public targetStates: Set<State>) { | |||
| this.name = name | |||
| constructor (public name: string, public desc: string, public userStates: Set<State>, public targetStates: Set<State>) { | |||
| } | |||
| toString (): string { | |||
| @@ -100,7 +99,7 @@ export class AttackAction extends PairAction { | |||
| } | |||
| constructor (protected damage: Damage) { | |||
| super('Attack', new Set([State.Normal]), new Set([State.Normal])) | |||
| super('Attack', 'Attack the enemy', new Set([State.Normal]), new Set([State.Normal])) | |||
| } | |||
| execute (user: Creature, target: Creature): LogEntry { | |||
| @@ -122,7 +121,7 @@ export class DevourAction extends PairAction { | |||
| } | |||
| constructor (protected container: Container) { | |||
| super('Devour', new Set([State.Normal]), new Set([State.Normal])) | |||
| super('Devour', 'Try to consume your foe', new Set([State.Normal]), new Set([State.Normal])) | |||
| } | |||
| execute (user: Creature, target: Creature): LogEntry { | |||
| @@ -144,7 +143,7 @@ export class StruggleAction extends PairAction { | |||
| } | |||
| constructor () { | |||
| super('Struggle', new Set([State.Eaten]), new Set([State.Normal])) | |||
| super('Struggle', 'Try to escape your predator', new Set([State.Eaten]), new Set([State.Normal])) | |||
| } | |||
| execute (user: Creature, target: Creature): LogEntry { | |||
| @@ -168,8 +167,8 @@ export class DigestAction extends SelfAction { | |||
| } | |||
| allowed (user: Creature, target: Creature) { | |||
| if (Array.from(user.containers).some(container => { | |||
| return container.contents.size > 0 | |||
| if (user.containers.some(container => { | |||
| return container.contents.length > 0 | |||
| })) { | |||
| return super.allowed(user, target) | |||
| } else { | |||
| @@ -178,11 +177,11 @@ export class DigestAction extends SelfAction { | |||
| } | |||
| constructor () { | |||
| super('Digest', new Set([State.Normal]), new Set([State.Normal])) | |||
| super('Digest', 'Digest all of your current prey', new Set([State.Normal]), new Set([State.Normal])) | |||
| } | |||
| execute (user: Creature, target: Creature): LogEntry { | |||
| const results = new CompositeLog(...Array.from(user.containers).map(container => container.tick(60))) | |||
| const results = new CompositeLog(...user.containers.map(container => container.tick(60))) | |||
| return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), results) | |||
| } | |||
| } | |||
| @@ -10,7 +10,7 @@ export class Player extends Creature { | |||
| const stomach = new Stomach(this, 100, new Damage({ amount: 10, type: DamageType.Acid }, { amount: 10, type: DamageType.Crush })) | |||
| this.containers.add(stomach) | |||
| this.containers.push(stomach) | |||
| this.actions.push(new DevourAction(stomach)) | |||
| this.perspective = POV.First | |||
| @@ -20,7 +20,7 @@ export class Wolf extends Creature { | |||
| const stomach = new Stomach(this, 50, new Damage({ amount: 50, type: DamageType.Acid }, { amount: 500, type: DamageType.Crush })) | |||
| this.containers.add(stomach) | |||
| this.containers.push(stomach) | |||
| this.actions.push(new DevourAction(stomach)) | |||
| this.actions.push(new StruggleAction()) | |||
| this.actions.push(new DigestAction()) | |||
| @@ -13,6 +13,7 @@ export interface Entity { | |||
| export interface Mortal extends Entity { | |||
| health: number; | |||
| maxHealth: number; | |||
| resistances: Map<DamageType, number>; | |||
| takeDamage: (damage: Damage) => void; | |||
| stats: Stats; | |||
| @@ -20,15 +21,22 @@ export interface Mortal extends Entity { | |||
| export class Creature implements Mortal, Pred, Prey, Combatant { | |||
| health = 100 | |||
| maxHealth = 100 | |||
| resistances: Map<DamageType, number> = new Map() | |||
| perspective: POV = POV.Third | |||
| state: State = State.Normal | |||
| containers: Set<Container> = new Set(); | |||
| containers: Array<Container> = [] | |||
| actions: Array<Action> = []; | |||
| containedIn: Container|null = null; | |||
| private baseBulk: number; | |||
| get bulk (): number { | |||
| return this.baseBulk | |||
| } | |||
| constructor (public name: Noun, public pronouns: Pronoun, public stats: Stats, public preyPrefs: Set<VoreType>, public predPrefs: Set<VoreType>, public bulk: number) { | |||
| containedIn: Container|null = null; | |||
| constructor (public name: Noun, public pronouns: Pronoun, public stats: Stats, public preyPrefs: Set<VoreType>, public predPrefs: Set<VoreType>, bulk: number) { | |||
| this.baseBulk = bulk | |||
| } | |||
| toString (): string { | |||
| @@ -37,8 +45,9 @@ export class Creature implements Mortal, Pred, Prey, Combatant { | |||
| takeDamage (damage: Damage): void { | |||
| damage.damages.forEach(instance => { | |||
| if (this.resistances.has(instance.type)) { | |||
| this.health -= instance.amount * this.resistances.get(instance.type)! | |||
| const resistance: number|undefined = this.resistances.get(instance.type) | |||
| if (resistance !== undefined) { | |||
| this.health -= instance.amount * resistance | |||
| } else { | |||
| this.health -= instance.amount | |||
| } | |||
| @@ -46,7 +55,6 @@ export class Creature implements Mortal, Pred, Prey, Combatant { | |||
| } | |||
| validActions (target: Creature): Array<Action> { | |||
| console.log(this) | |||
| return this.actions.filter(action => { | |||
| return action.allowed(this, target) | |||
| }) | |||
| @@ -34,5 +34,10 @@ export class CompositeLog implements LogEntry { | |||
| } | |||
| export function log (entry: LogEntry): void { | |||
| entry.render().forEach(elem => document.querySelector('#log')?.appendChild(elem)) | |||
| entry.render().forEach(elem => { | |||
| document.querySelector('#log')!.insertBefore(elem, document?.querySelector('#log > *')) | |||
| }) | |||
| const entries: Array<HTMLElement> = Array.from(document.querySelectorAll('#log > *')) | |||
| entries.slice(5).forEach(element => element.remove()) | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| import { Entity, Mortal, POV } from './entity' | |||
| import { Damage } from './combat' | |||
| import { LogLines, LogEntry, CompositeLog } from './interface' | |||
| import { ProperNoun } from './language' | |||
| export enum VoreType {Oral} | |||
| export interface Prey extends Mortal { | |||
| @@ -11,13 +12,13 @@ export interface Prey extends Mortal { | |||
| export interface Pred extends Entity { | |||
| predPrefs: Set<VoreType>; | |||
| containers: Set<Container>; | |||
| containers: Array<Container>; | |||
| } | |||
| export interface Container { | |||
| name: string; | |||
| voreTypes: Set<VoreType>; | |||
| contents: Set<Prey>; | |||
| contents: Array<Prey>; | |||
| capacity: number; | |||
| fullness: number; | |||
| canTake: (prey: Prey) => boolean; | |||
| @@ -32,7 +33,7 @@ export interface Container { | |||
| } | |||
| abstract class NormalContainer implements Container { | |||
| contents: Set<Prey> | |||
| contents: Array<Prey> | |||
| get fullness (): number { | |||
| return Array.from(this.contents.values()).reduce((total: number, prey: Prey) => total + prey.bulk, 0) | |||
| @@ -49,14 +50,14 @@ abstract class NormalContainer implements Container { | |||
| } | |||
| consume (prey: Prey): LogEntry { | |||
| this.contents.add(prey) | |||
| this.contents.push(prey) | |||
| prey.containedIn = this | |||
| return new LogLines('MUNCH') | |||
| } | |||
| release (prey: Prey): LogEntry { | |||
| prey.containedIn = null | |||
| this.contents.delete(prey) | |||
| this.contents = this.contents.filter(victim => victim !== prey) | |||
| return new LogLines('ANTI-MUNCH') | |||
| } | |||
| @@ -82,9 +83,9 @@ abstract class NormalContainer implements Container { | |||
| const digestedEntries = new CompositeLog(...digested.map(prey => this.digest(prey))) | |||
| const absorbedEntries = new CompositeLog(...digested.map(prey => this.absorb(prey))) | |||
| this.contents = new Set(Array.from(this.contents.values()).filter(prey => { | |||
| return prey.health > 0 | |||
| })) | |||
| this.contents = this.contents.filter(prey => { | |||
| return prey.health > -100 | |||
| }) | |||
| return new CompositeLog(digestedEntries, absorbedEntries) | |||
| } | |||
| @@ -112,7 +113,7 @@ abstract class NormalContainer implements Container { | |||
| } | |||
| constructor (public name: string, protected owner: Pred, public voreTypes: Set<VoreType>, public capacity: number, private damage: Damage) { | |||
| this.contents = new Set() | |||
| this.contents = [] | |||
| } | |||
| } | |||