Przeglądaj źródła

Add header; add more vore actions

Containers can now offer actions to their contents.
vintage
Fen Dweller 5 lat temu
rodzic
commit
7a23dabdd1
11 zmienionych plików z 314 dodań i 93 usunięć
  1. +2
    -1
      .eslintrc.js
  2. +2
    -1
      public/index.html
  3. +48
    -0
      public/reset.css
  4. +6
    -9
      src/App.vue
  5. BIN
      src/assets/logo.png
  6. +8
    -4
      src/components/Combat.vue
  7. +29
    -0
      src/components/Header.vue
  8. +116
    -52
      src/game/combat.ts
  9. +7
    -5
      src/game/creatures/wolf.ts
  10. +7
    -3
      src/game/entity.ts
  11. +89
    -18
      src/game/vore.ts

+ 2
- 1
.eslintrc.js Wyświetl plik

@@ -15,6 +15,7 @@ module.exports = {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-useless-constructor': 'off',
'@typescript-eslint/no-unused-vars': 'off'
'@typescript-eslint/no-unused-vars': 'off',
'quotes': 'off'
}
}

+ 2
- 1
public/index.html Wyświetl plik

@@ -4,7 +4,8 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="icon" href="https://crux.sexy/images/feast.ico">
<link rel="stylesheet" href="./reset.css"</link>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>


+ 48
- 0
public/reset.css Wyświetl plik

@@ -0,0 +1,48 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

+ 6
- 9
src/App.vue Wyświetl plik

@@ -1,9 +1,6 @@
<template>
<div id="app">
<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>
<img alt="Feast logo" id="logo" src="./assets/feast.png">
<Header version="pre-alpha" />
<Combat :player="player" :enemy="enemy" />
</div>
</template>
@@ -11,12 +8,13 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import Combat from './components/Combat.vue'
import Header from './components/Header.vue'
import * as Creatures from '@/game/creatures'
import { Creature, POV } from '@/game/entity'

@Component({
components: {
Combat
Combat, Header
}
})
export default class App extends Vue {
@@ -28,6 +26,7 @@ export default class App extends Vue {
this.player.perspective = POV.First
this.enemy = new Creatures.Wolf()
console.log(this.player)
console.log(this.enemy)
}
}
</script>
@@ -35,6 +34,8 @@ export default class App extends Vue {
<style>
body, html {
background: #111;
width: 100vw;
height: 100vh;
}

#app {
@@ -44,9 +45,5 @@ body, html {
text-align: center;
color: #ddd;
background: #111;
margin-top: 60px;
}
#logo {
width: 30vw;
}
</style>

BIN
src/assets/logo.png Wyświetl plik

Przed Po
Szerokość: 200  |  Wysokość: 200  |  Rozmiar: 6.7 KiB

+ 8
- 4
src/components/Combat.vue Wyświetl plik

@@ -8,14 +8,14 @@
<div class="horiz-display">
<div>
<h2>Your moves</h2>
<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>
<button class="combat-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 class="combat-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>
<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>
<button v-for="action in enemy.validActions(enemy)" :key="'enemy-' + action.name" v-on:click="log(action.execute(enemy, enemy))">{{action.name}}</button>
<button class="combat-button" v-for="action in enemy.validActions(player)" :key="'enemy-' + action.name" v-on:click="log(action.execute(enemy, player))">{{action.name}}</button>
<button class="combat-button" v-for="action in enemy.validActions(enemy)" :key="'enemy-' + action.name" v-on:click="log(action.execute(enemy, enemy))">{{action.name}}</button>
</div>
</div>
<div id="log"></div>
@@ -71,4 +71,8 @@ a {
display: flex;
justify-content: center;
}
.combat-button {
width: 100px;
height: 100px;
}
</style>

+ 29
- 0
src/components/Header.vue Wyświetl plik

@@ -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>

+ 116
- 52
src/game/combat.ts Wyświetl plik

@@ -39,46 +39,42 @@ export enum Stat {

export type Stats = {[key in Stat]: number}

export enum State {
Normal = 'Normal',
Grappled = 'Grappled',
Grappling = 'Grappling',
Eaten = 'Eaten'
}

export interface Combatant {
actions: Array<Action>;
}

export abstract class Action {
allowed (user: Creature, target: Creature) {
return this.userStates.has(user.state) && this.targetStates.has(target.state)
}
abstract allowed (user: Creature, target: Creature): boolean
abstract execute(user: Creature, target: Creature): LogEntry

abstract execute(user: Creature, target: Creature): LogEntry
constructor (public name: string, public desc: string) {

constructor (public name: string, public desc: string, public userStates: Set<State>, public targetStates: Set<State>) {
}

}
toString (): string {
return this.name
}
}

toString (): string {
return this.name
}
export interface Actionable {
actions: Array<Action>;
}

abstract class SelfAction extends Action {
allowed (user: Creature, target: Creature) {
if (user === target) {
return super.allowed(user, target)
} else {
return false
}
return user === target
}
}

abstract class PairAction extends Action {
allowed (user: Creature, target: Creature) {
if (user !== target) {
return user !== target
}
}

abstract class TogetherAction extends PairAction {
allowed (user: Creature, target: Creature) {
if (user.containedIn === target.containedIn) {
return super.allowed(user, target)
} else {
return false
@@ -86,7 +82,7 @@ abstract class PairAction extends Action {
}
}

export class AttackAction extends PairAction {
export class AttackAction extends TogetherAction {
protected lines: POVActionPicker = {
[POV.First]: {
[POV.First]: (user, target) => new LogLines('You bite...yourself?'),
@@ -99,7 +95,7 @@ export class AttackAction extends PairAction {
}

constructor (protected damage: Damage) {
super('Attack', 'Attack the enemy', new Set([State.Normal]), new Set([State.Normal]))
super('Attack', 'Attack the enemy')
}

execute (user: Creature, target: Creature): LogEntry {
@@ -108,7 +104,7 @@ export class AttackAction extends PairAction {
}
}

export class DevourAction extends PairAction {
export class DevourAction extends TogetherAction {
protected lines: POVActionPicker = {
[POV.First]: {
[POV.First]: (user, target) => new LogLines('You devour...yourself?'),
@@ -121,11 +117,11 @@ export class DevourAction extends PairAction {
}

constructor (protected container: Container) {
super('Devour', 'Try to consume your foe', new Set([State.Normal]), new Set([State.Normal]))
super('Devour', 'Try to consume your foe')
this.name += ` (${container.name})`
}

execute (user: Creature, target: Creature): LogEntry {
target.state = State.Eaten
return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), this.container.consume(target))
}
}
@@ -142,48 +138,116 @@ export class StruggleAction extends PairAction {
}
}

constructor () {
super('Struggle', 'Try to escape your predator', new Set([State.Eaten]), new Set([State.Normal]))
allowed (user: Creature, target: Creature) {
if (user.containedIn === this.container) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (public container: Container) {
super('Struggle', 'Try to escape your predator')
}

execute (user: Creature, target: Creature): LogEntry {
if (user.containedIn) {
user.state = State.Normal
return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), user.containedIn.release(user))
} else { return new LogLines("The prey wasn't actually eaten...") }
}
}

export class DigestAction extends SelfAction {
protected lines: POVActionPicker = {
[POV.First]: {
[POV.First]: (user, target) => new LogLines('You rub your stomach'),
[POV.Third]: (user, target) => new LogLines("You can't digest for other people...")
},
[POV.Third]: {
[POV.First]: (user, target) => new LogLines("Other people can't digest for you..."),
[POV.Third]: (user, target) => new LogLines(user.name.capital + ' rubs ' + user.pronouns.possessive + ' gut.')
}
protected lines: POVActionPicker = {
[POV.First]: {
[POV.First]: (user, target) => new LogLines('You rub your stomach'),
[POV.Third]: (user, target) => new LogLines("You can't digest for other people...")
},
[POV.Third]: {
[POV.First]: (user, target) => new LogLines("Other people can't digest for you..."),
[POV.Third]: (user, target) => new LogLines(user.name.capital + ' rubs ' + user.pronouns.possessive + ' gut.')
}
}

allowed (user: Creature, target: Creature) {
if (user.containers.some(container => {
return container.contents.length > 0
})) {
return super.allowed(user, target)
} else {
return false
}
allowed (user: Creature, target: Creature) {
if (this.container.owner === user && this.container.contents.length > 0) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (protected container: Container) {
super('Digest', 'Digest all of your current prey')
this.name += ` (${container.name})`
}

execute (user: Creature, target: Creature): LogEntry {
const results = new CompositeLog(...user.containers.map(container => container.tick(60)))
return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), results)
}
}

constructor () {
super('Digest', 'Digest all of your current prey', new Set([State.Normal]), new Set([State.Normal]))
export class ReleaseAction extends PairAction {
protected lines: POVActionPicker = {
[POV.First]: {
[POV.First]: (user, target) => new LogLines(`You can't release yourself`),
[POV.Third]: (user, target) => new LogLines(`You hork up ${target.name}}`)
},
[POV.Third]: {
[POV.First]: (user, target) => new LogLines(`${user.name.capital} horks you up`),
[POV.Third]: (user, target) => new LogLines(`${user.name.capital} horks up ${target.name.capital}`)
}
}

execute (user: Creature, target: Creature): LogEntry {
const results = new CompositeLog(...user.containers.map(container => container.tick(60)))
return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), results)
allowed (user: Creature, target: Creature) {
if (target.containedIn === this.container) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (protected container: Container) {
super('Release', 'Release one of your prey')
this.name += ` (${container.name})`
}

execute (user: Creature, target: Creature): LogEntry {
const results = this.container.release(target)
return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), results)
}
}

export class TransferAction extends PairAction {
protected lines: POVActionPicker = {
[POV.First]: {
[POV.First]: (user, target) => new LogLines(),
[POV.Third]: (user, target) => new LogLines(`You push your prey from your ${this.from.name} to your ${this.to.name}`)
},
[POV.Third]: {
[POV.First]: (user, target) => new LogLines(`You're shoved from ${user.name}'s ${this.from.name} to their ${this.to.name}`),
[POV.Third]: (user, target) => new LogLines(`${user.name.capital} pushes ${target.name} from their ${this.from.name} to their ${this.to.name}`)
}
}

allowed (user: Creature, target: Creature) {
if (target.containedIn === this.from) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (protected from: Container, protected to: Container) {
super('Transfer', `Shove your prey from your ${from.name} to your ${to.name}`)
}

execute (user: Creature, target: Creature): LogEntry {
this.from.release(target)
this.to.consume(target)
return this.lines[user.perspective][target.perspective](user, target)
}
}

export interface CombatTest {


+ 7
- 5
src/game/creatures/wolf.ts Wyświetl plik

@@ -1,7 +1,7 @@
import { Creature, POV } from '../entity'
import { Stat, Damage, DamageType, AttackAction, DevourAction, StruggleAction, DigestAction } from '../combat'
import { Stat, Damage, DamageType, AttackAction, StruggleAction, TransferAction } from '../combat'
import { MalePronouns, ImproperNoun } from '../language'
import { VoreType, Stomach } from '../vore'
import { VoreType, Stomach, Bowels } from '../vore'
import { LogLines } from '../interface'

class BiteAction extends AttackAction {
@@ -21,8 +21,10 @@ 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.push(stomach)
this.actions.push(new DevourAction(stomach))
this.actions.push(new StruggleAction())
this.actions.push(new DigestAction())
const bowels = new Bowels(this, 50, new Damage({ amount: 50, type: DamageType.Acid }, { amount: 500, type: DamageType.Crush }))

this.containers.push(bowels)

this.actions.push(new TransferAction(bowels, stomach))
}
}

+ 7
- 3
src/game/entity.ts Wyświetl plik

@@ -1,4 +1,4 @@
import { DamageType, Damage, Combatant, Stats, State, Action } from './combat'
import { DamageType, Damage, Combatant, Stats, Action } from './combat'
import { Noun, Pronoun } from './language'

import { Pred, Prey, Container, VoreType } from './vore'
@@ -24,7 +24,6 @@ export class Creature implements Mortal, Pred, Prey, Combatant {
maxHealth = 100
resistances: Map<DamageType, number> = new Map()
perspective: POV = POV.Third
state: State = State.Normal
containers: Array<Container> = []
actions: Array<Action> = [];
private baseBulk: number;
@@ -55,7 +54,12 @@ export class Creature implements Mortal, Pred, Prey, Combatant {
}

validActions (target: Creature): Array<Action> {
return this.actions.filter(action => {
let choices = this.actions.concat(this.containers.flatMap(container => container.actions))

if (this.containedIn !== null) {
choices = choices.concat(this.containedIn.actions)
}
return choices.filter(action => {
return action.allowed(this, target)
})
}


+ 89
- 18
src/game/vore.ts Wyświetl plik

@@ -1,7 +1,6 @@
import { Entity, Mortal, POV } from './entity'
import { Damage } from './combat'
import { Damage, Actionable, Action, DevourAction, DigestAction, ReleaseAction, StruggleAction } from './combat'
import { LogLines, LogEntry, CompositeLog } from './interface'
import { ProperNoun } from './language'
export enum VoreType {Oral}

export interface Prey extends Mortal {
@@ -15,21 +14,23 @@ export interface Pred extends Entity {
containers: Array<Container>;
}

export interface Container {
name: string;
voreTypes: Set<VoreType>;
contents: Array<Prey>;
capacity: number;
fullness: number;
canTake: (prey: Prey) => boolean;
consume: (prey: Prey) => LogEntry;
release: (prey: Prey) => LogEntry;
struggle: (prey: Prey) => LogEntry;
tick: (dt: number) => LogEntry;
describe: () => LogEntry;
digest: (prey: Prey) => LogEntry;
absorb: (prey: Prey) => LogEntry;
dispose: (preys: Prey[]) => LogEntry;
export interface Container extends Actionable {
name: string;
owner: Pred;
voreTypes: Set<VoreType>;
contents: Array<Prey>;
capacity: number;
fullness: number;
canTake: (prey: Prey) => boolean;
consume: (prey: Prey) => LogEntry;
release: (prey: Prey) => LogEntry;
struggle: (prey: Prey) => LogEntry;
tick: (dt: number) => LogEntry;
describe: () => LogEntry;
digest: (prey: Prey) => LogEntry;
absorb: (prey: Prey) => LogEntry;
dispose: (preys: Prey[]) => LogEntry;
actions: Array<Action>;
}

abstract class NormalContainer implements Container {
@@ -112,14 +113,23 @@ abstract class NormalContainer implements Container {
return new LogLines('GLORP')
}

constructor (public name: string, protected owner: Pred, public voreTypes: Set<VoreType>, public capacity: number, private damage: Damage) {
actions: Array<Action>

constructor (public name: string, public owner: Pred, public voreTypes: Set<VoreType>, public capacity: number, private damage: Damage) {
this.contents = []

this.actions = []
}
}

export class Stomach extends NormalContainer {
constructor (owner: Pred, capacity: number, damage: Damage) {
super('Stomach', owner, new Set([VoreType.Oral]), capacity, damage)

this.actions.push(new DevourAction(this))
this.actions.push(new DigestAction(this))
this.actions.push(new ReleaseAction(this))
this.actions.push(new StruggleAction(this))
}

consume (prey: Prey): LogEntry {
@@ -172,3 +182,64 @@ export class Stomach extends NormalContainer {
}
}
}

export class Bowels extends NormalContainer {
constructor (owner: Pred, capacity: number, damage: Damage) {
super('Bowels', owner, new Set([VoreType.Oral]), capacity, damage)

this.actions.push(new DevourAction(this))
this.actions.push(new DigestAction(this))
this.actions.push(new ReleaseAction(this))
this.actions.push(new StruggleAction(this))
}

consume (prey: Prey): LogEntry {
super.consume(prey)

const predPOV = this.owner.perspective
const preyPOV = prey.perspective
if (predPOV === POV.First && preyPOV === POV.Third) {
return new LogLines(prey.name.capital + ' slides down into your bowels')
} else if (predPOV === POV.Third && preyPOV === POV.First) {
return new LogLines(this.owner.name.capital + "'s guts swell as you slush down into " + this.owner.pronouns.possessive + ' bowels')
} else if (predPOV === POV.Third && preyPOV === POV.Third) {
return new LogLines(this.owner.name.capital + "'s belly fills with the struggling form of " + prey.name)
} else {
return new LogLines('FIX ME!')
}
}

digest (prey: Prey): LogEntry {
super.digest(prey)

const predPOV = this.owner.perspective
const preyPOV = prey.perspective

if (predPOV === POV.First && preyPOV === POV.Third) {
return new LogLines('Your bowels finishes off ' + prey.name)
} else if (predPOV === POV.Third && preyPOV === POV.First) {
return new LogLines(this.owner.name.capital + ' digests you')
} else if (predPOV === POV.Third && preyPOV === POV.Third) {
return new LogLines(this.owner.name.capital + ' finishes digesting ' + prey.name)
} else {
return new LogLines('FIX ME!')
}
}

absorb (prey: Prey): LogEntry {
super.absorb(prey)

const predPOV = this.owner.perspective
const preyPOV = prey.perspective

if (predPOV === POV.First && preyPOV === POV.Third) {
return new LogLines("Your bowels melts down what's left of " + prey.name)
} else if (predPOV === POV.Third && preyPOV === POV.First) {
return new LogLines(this.owner.name.capital + ' finishes absorbing you')
} else if (predPOV === POV.Third && preyPOV === POV.Third) {
return new LogLines(this.owner.name.capital + ' fully absorbs ' + prey.name)
} else {
return new LogLines('FIX ME!')
}
}
}

Ładowanie…
Anuluj
Zapisz