瀏覽代碼

Add Goldeneye; add composition-based actions

master
Fen Dweller 5 年之前
父節點
當前提交
13dce9ef14
共有 10 個文件被更改,包括 405 次插入38 次删除
  1. +1
    -0
      src/App.vue
  2. +11
    -23
      src/components/Combat.vue
  3. +90
    -7
      src/game/combat.ts
  4. +10
    -0
      src/game/combat/conditions.ts
  5. +57
    -0
      src/game/combat/consequences.ts
  6. +36
    -2
      src/game/combat/effects.ts
  7. +4
    -2
      src/game/creature.ts
  8. +3
    -1
      src/game/creatures.ts
  9. +190
    -0
      src/game/creatures/goldeneye.ts
  10. +3
    -3
      src/game/vore.ts

+ 1
- 0
src/App.vue 查看文件

@@ -53,6 +53,7 @@ export default class App extends Vue {
this.$data.encounters.push(new Encounter({ name: 'Dragon' }, this.makeParty().concat([new Creatures.Dragon()])))
this.$data.encounters.push(new Encounter({ name: 'Wolves' }, this.makeParty().concat([new Creatures.Wolf(), new Creatures.Wolf(), new Creatures.Wolf(), new Creatures.Wolf()])))
this.$data.encounters.push(new Encounter({ name: 'Large Wah' }, this.makeParty().concat([new Creatures.Shingo()])))
this.$data.encounters.push(new Encounter({ name: 'Goldeneye' }, this.makeParty().concat([new Creatures.Goldeneye()])))

this.$data.encounter = this.$data.encounters[0]



+ 11
- 23
src/components/Combat.vue 查看文件

@@ -91,33 +91,24 @@ export default class Combat extends Vue {

@Emit("executedLeft")
executedLeft (entry: LogEntry) {
const log = this.$el.querySelector(".log")
if (log !== null) {
const before = log.querySelector("div.log-entry")
const holder = document.createElement("div")
holder.classList.add("log-entry")
this.writeLog(entry, "left-move")

entry.render().forEach(element => {
holder.appendChild(element)
})
this.writeLog(this.encounter.nextMove(), "left-move")
this.pickNext()
}

holder.classList.add("left-move")
const hline = document.createElement("div")
hline.classList.add("log-separator")
log.insertBefore(hline, before)
log.insertBefore(holder, hline)
// TODO these need to render on the correct side

log.scrollTo({ top: 0, left: 0 })
}
@Emit("executedRight")
executedRight (entry: LogEntry) {
this.writeLog(entry, "right-move")

this.encounter.nextMove()
this.writeLog(this.encounter.nextMove(), "right-move")
this.pickNext()
}

@Emit("executedRight")
executedRight (entry: LogEntry) {
writeLog (entry: LogEntry, cls: string) {
const log = this.$el.querySelector(".log")

if (log !== null) {
const before = log.querySelector("div.log-entry")
const holder = document.createElement("div")
@@ -127,7 +118,7 @@ export default class Combat extends Vue {
holder.appendChild(element)
})

holder.classList.add("right-move")
holder.classList.add(cls)
const hline = document.createElement("div")
hline.classList.add("log-separator")
log.insertBefore(hline, before)
@@ -135,9 +126,6 @@ export default class Combat extends Vue {

log.scrollTo({ top: 0, left: 0 })
}

this.encounter.nextMove()
this.pickNext()
}

pickNext () {


+ 90
- 7
src/game/combat.ts 查看文件

@@ -1,5 +1,5 @@
import { Creature } from "./creature"
import { TextLike, DynText, ToBe, LiveText } from './language'
import { TextLike, DynText, ToBe, LiveText, PairLineArgs, PairLine } from './language'
import { LogEntry, LogLines, FAElem, LogLine, FormatEntry, FormatOpt, PropElem, nilLog } from './interface'

export enum DamageType {
@@ -277,7 +277,7 @@ export class FractionDamageFormula implements DamageFormula {
}
} else if (factor.target in Vigor) {
return {
amount: Math.max(factor.fraction * user.vigors[factor.target as Vigor]),
amount: Math.max(factor.fraction * target.vigors[factor.target as Vigor]),
target: factor.target,
type: factor.type
}
@@ -343,6 +343,32 @@ export abstract class Action {
abstract describe (user: Creature, target: Creature): LogEntry
}

export type TestBundle = {
test: CombatTest;
fail: PairLine<Creature>;
}

export class CompositionAction extends Action {
private consequences: Array<Consequence>;
private tests: Array<TestBundle>;

constructor (name: TextLike, desc: TextLike, properties: { conditions?: Array<Condition>; consequences?: Array<Consequence>; tests?: Array<TestBundle> }) {
super(name, desc, properties.conditions ?? [])
this.consequences = properties.consequences ?? []
this.tests = properties.tests ?? []
}

execute (user: Creature, target: Creature): LogEntry {
return new LogLines(
...this.consequences.filter(consequence => consequence.applicable(user, target)).map(consequence => consequence.apply(user, target))
)
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`No descriptions yet...`)
}
}

/**
* A Condition describes whether or not something is permissible between two [[Creature]]s
*/
@@ -376,10 +402,19 @@ export abstract class GroupAction extends Action {
* Some hooks return results along with a log entry.
*/
export class Effective {
/**
* Executes when the effect is initially applied
*/
onApply (creature: Creature): LogEntry { return nilLog }

/**
* Executes when the effect is removed
*/
onRemove (creature: Creature): LogEntry { return nilLog }

/**
* Executes before the creature tries to perform an action
*/
preAction (creature: Creature): { prevented: boolean; log: LogEntry } {
return {
prevented: false,
@@ -387,16 +422,42 @@ export class Effective {
}
}

/**
* Executes before another creature tries to perform an action that targets this creature
*/
preReceiveAction (creature: Creature, attacker: Creature): { prevented: boolean; log: LogEntry } {
return {
prevented: false,
log: nilLog
}
}

/**
* Executes before the creature receives damage (or healing)
*/
preDamage (creature: Creature, damage: Damage): Damage {
return damage
}

/**
* Executes before the creature is attacked
*/
preAttack (creature: Creature, attacker: Creature): { prevented: boolean; log: LogEntry } {
return {
prevented: false,
log: nilLog
}
}

/**
* Executes when a creature's turn starts
*/
preTurn (creature: Creature): { prevented: boolean; log: LogEntry } {
return {
prevented: false,
log: nilLog
}
}
}
/**
* A displayable status effect
@@ -454,14 +515,14 @@ export class Encounter {
this.nextMove()
}

nextMove (): void {
nextMove (): LogEntry {
this.initiatives.set(this.currentMove, 0)
const times = new Map<Creature, number>()

this.combatants.forEach(combatant => {
// this should never be undefined
const currentProgress = this.initiatives.get(combatant) ?? 0
const remaining = (this.turnTime - currentProgress) / Math.max(combatant.stats.Speed, 1)
const remaining = (this.turnTime - currentProgress) / Math.sqrt(Math.max(combatant.stats.Speed, 1))
times.set(combatant, remaining)
})

@@ -471,18 +532,40 @@ export class Encounter {

return closestTime <= nextTime ? closest : next
}, this.combatants[0])
const closestRemaining = (this.turnTime - (this.initiatives.get(this.currentMove) ?? 0)) / Math.max(this.currentMove.stats.Speed, 1)
const closestRemaining = (this.turnTime - (this.initiatives.get(this.currentMove) ?? 0)) / Math.sqrt(Math.max(this.currentMove.stats.Speed, 1))

this.combatants.forEach(combatant => {
// still not undefined...
const currentProgress = this.initiatives.get(combatant) ?? 0
this.initiatives.set(combatant, currentProgress + closestRemaining * Math.max(combatant.stats.Speed, 1))
this.initiatives.set(combatant, currentProgress + closestRemaining * Math.sqrt(Math.max(combatant.stats.Speed, 1)))
})

// TODO: still let the creature use drained-vigor moves

if (this.currentMove.disabled) {
this.nextMove()
return this.nextMove()
} else {
const effectResults = this.currentMove.effects.map(effect => effect.preTurn(this.currentMove)).filter(effect => effect.prevented)

if (effectResults.some(result => result.prevented)) {
return new LogLines(
...effectResults.map(result => result.log).concat([this.nextMove()])
)
}
}

return nilLog
}
}

export abstract class Consequence {
constructor (public conditions: Condition[]) {

}

applicable (user: Creature, target: Creature): boolean {
return this.conditions.every(cond => cond.allowed(user, target))
}

abstract apply (user: Creature, target: Creature): LogEntry
}

+ 10
- 0
src/game/combat/conditions.ts 查看文件

@@ -87,3 +87,13 @@ export class EnemyCondition implements Condition {
return user.side !== target.side
}
}

export class ContainerFullCondition implements Condition {
constructor (private container: Container) {

}

allowed (user: Creature, target: Creature): boolean {
return this.container.contents.length > 0
}
}

+ 57
- 0
src/game/combat/consequences.ts 查看文件

@@ -0,0 +1,57 @@
import { Consequence, DamageFormula, Condition, StatusEffect } from '../combat'
import { Creature } from '../creature'
import { LogEntry, LogLines, LogLine } from '../interface'
import { Verb, PairLine } from '../language'

/**
* Takes a function, and thus can do anything.
*/
export class ArbitraryConsequence extends Consequence {
constructor (public apply: (user: Creature, target: Creature) => LogEntry, conditions: Condition[] = []) {
super(conditions)
}
}

/**
* Renders some text.
*/

export class LogConsequence extends Consequence {
constructor (private line: PairLine<Creature>, conditions: Condition[] = []) {
super(conditions)
}

apply (user: Creature, target: Creature): LogEntry {
return this.line(user, target)
}
}
/**
* Deals damage.
*/
export class DamageConsequence extends Consequence {
constructor (private damageFormula: DamageFormula, conditions: Condition[] = []) {
super(conditions)
}

apply (user: Creature, target: Creature): LogEntry {
const damage = this.damageFormula.calc(user, target)
return new LogLines(
new LogLine(`${target.name.capital} ${target.name.conjugate(new Verb('take'))} `, damage.renderShort(), ` damage!`),
target.takeDamage(damage)
)
}
}

/**
* Applies a status effect
*/

export class StatusConsequence extends Consequence {
constructor (private statusMaker: () => StatusEffect, conditions: Condition[] = []) {
super(conditions)
}

apply (user: Creature, target: Creature): LogEntry {
return target.applyEffect(this.statusMaker())
}
}

+ 36
- 2
src/game/combat/effects.ts 查看文件

@@ -1,4 +1,4 @@
import { StatusEffect, Damage, DamageType, Action } from '../combat'
import { StatusEffect, Damage, DamageType, Action, Condition } from '../combat'
import { DynText, LiveText, ToBe, Verb } from '../language'
import { Creature } from "../creature"
import { LogLine, LogEntry, LogLines, FAElem, nilLog } from '../interface'
@@ -38,7 +38,7 @@ export class StunEffect extends StatusEffect {
return new LogLine(`${creature.name.capital} ${creature.name.conjugate(new ToBe())} no longer stunned.`)
}

preAction (creature: Creature): { prevented: boolean; log: LogEntry } {
preTurn (creature: Creature): { prevented: boolean; log: LogEntry } {
if (--this.duration <= 0) {
return {
prevented: true,
@@ -96,3 +96,37 @@ export class PredatorCounterEffect extends StatusEffect {
}
}
}

export class UntouchableEffect extends StatusEffect {
constructor () {
super('Untouchable', 'Cannot be attacked', 'fas fa-times')
}

preAttack (creature: Creature, attacker: Creature) {
return {
prevented: true,
log: new LogLine(`${creature.name.capital} cannot be attacked.`)
}
}
}

export class DazzlingEffect extends StatusEffect {
constructor (private conditions: Condition[]) {
super('Dazzling', 'Stuns enemies who try to affect this creature', 'fas fa-spinner')
}

preReceiveAction (creature: Creature, attacker: Creature) {
if (this.conditions.every(cond => cond.allowed(creature, attacker))) {
attacker.applyEffect(new StunEffect(1))
return {
prevented: true,
log: new LogLine(`${attacker.name.capital} can't act against ${creature.name.objective}!`)
}
} else {
return {
prevented: false,
log: nilLog
}
}
}
}

+ 4
- 2
src/game/creature.ts 查看文件

@@ -38,8 +38,10 @@ export class Creature extends Vore implements Combatant {
}

executeAction (action: Action, target: Creature): LogEntry {
const effectResults = this.effects.map(effect => effect.preAction(this))
const blocking = effectResults.filter(result => result.prevented)
const preActionResults = this.effects.map(effect => effect.preAction(this))
const preReceiveActionResults = target.effects.map(effect => effect.preReceiveAction(target, this))

const blocking = preActionResults.concat(preReceiveActionResults).filter(result => result.prevented)
if (blocking.length > 0) {
return new LogLines(...blocking.map(result => result.log))
} else {


+ 3
- 1
src/game/creatures.ts 查看文件

@@ -6,4 +6,6 @@ import { Withers } from './creatures/withers'
import { Kenzie } from './creatures/kenzie'
import { Dragon } from './creatures/dragon'
import { Shingo } from './creatures/shingo'
export { Wolf, Player, Cafat, Human, Withers, Kenzie, Dragon, Shingo }
import { Goldeneye } from './creatures/goldeneye'

export { Wolf, Player, Cafat, Human, Withers, Kenzie, Dragon, Shingo, Goldeneye }

+ 190
- 0
src/game/creatures/goldeneye.ts 查看文件

@@ -0,0 +1,190 @@
import { Creature } from "../creature"
import { Damage, DamageType, ConstantDamageFormula, Vigor, Side, GroupAction, FractionDamageFormula, DamageFormula, UniformRandomDamageFormula, CompositionAction, StatusEffect } from '../combat'
import { MalePronouns, ImproperNoun, Verb, ProperNoun, ToBe, SoloLineArgs } from '../language'
import { VoreType, NormalContainer, Vore, InnerVoreContainer, Container } from '../vore'
import { TransferAction } from '../combat/actions'
import { LogEntry, LogLine, LogLines } from '../interface'
import { ContainerFullCondition, CapableCondition, EnemyCondition, TogetherCondition } from '../combat/conditions'
import { UntouchableEffect, DazzlingEffect, StunEffect } from '../combat/effects'
import { DamageConsequence, StatusConsequence, LogConsequence } from '../combat/consequences'

class GoldeneyeCrop extends NormalContainer {
consumeVerb: Verb = new Verb('swallow')
releaseVerb: Verb = new Verb('free')
struggleVerb: Verb = new Verb('struggle', 'struggles', 'struggling', 'struggled')

constructor (owner: Vore) {
super(
new ImproperNoun('crop').all,
owner,
new Set([VoreType.Oral]),
300
)
}
}

class Taunt extends GroupAction {
damage: DamageFormula = new UniformRandomDamageFormula(
new Damage(
{ amount: 50, target: Vigor.Resolve, type: DamageType.Dominance }
),
0.5
)

constructor () {
super(
"Taunt",
"Demoralize your enemies",
[
new EnemyCondition(),
new CapableCondition()
]
)
}

describeGroup (user: Creature, targets: Creature[]): LogEntry {
return new LogLine(`Demoralize your foes`)
}

execute (user: Creature, target: Creature): LogEntry {
return target.takeDamage(this.damage.calc(user, target))
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`Demoralize your foes`)
}
}
class Flaunt extends GroupAction {
constructor (public container: Container) {
super(
"Flaunt " + container.name,
"Show off your " + container.name,
[new ContainerFullCondition(container), new CapableCondition(), new EnemyCondition(), new TogetherCondition()]
)
}

groupLine: SoloLineArgs<Creature, { container: Container }> = (user, args) => new LogLine(
`${user.name.capital} ${user.name.conjugate(new Verb('show'))} off ${user.pronouns.possessive} squirming ${args.container.name}, ${user.pronouns.possessive} doomed prey writhing beneath ${user.pronouns.possessive} pelt.`
)

describeGroup (user: Creature, targets: Creature[]): LogEntry {
return new LogLine(`Flaunt your bulging ${this.container.name} for all your foes to see`)
}

execute (user: Creature, target: Creature): LogEntry {
const fracDamage = new FractionDamageFormula([
{ fraction: 0.25, target: Vigor.Resolve, type: DamageType.Dominance }
])
const flatDamage = new ConstantDamageFormula(
new Damage(
{ amount: 50, target: Vigor.Resolve, type: DamageType.Dominance }
)
)

const damage = fracDamage.calc(user, target).combine(flatDamage.calc(user, target))

return new LogLines(
new LogLine(`${target.name.capital} ${target.name.conjugate(new ToBe())} shaken for `, damage.renderShort(), '.'),
target.takeDamage(damage)
)
}

executeGroup (user: Creature, targets: Array<Creature>): LogEntry {
return new LogLines(...[this.groupLine(user, { container: this.container })].concat(targets.map(target => this.execute(user, target))))
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`Flaunt your bulging gut for all your foes to see`)
}
}

class GoldeneyeStomach extends InnerVoreContainer {
consumeVerb: Verb = new Verb('swallow')
releaseVerb: Verb = new Verb('free')
struggleVerb: Verb = new Verb('struggle', 'struggles', 'struggling', 'struggled')

constructor (owner: Vore, crop: GoldeneyeCrop) {
super(
new ImproperNoun('stomach').all,
owner,
new Set([VoreType.Oral]),
900,
new Damage(
{ amount: 1000, target: Vigor.Health, type: DamageType.Acid }
),
crop
)
}
}

export class Goldeneye extends Creature {
constructor () {
super(
new ProperNoun("Goldeneye"),
new ImproperNoun('gryphon', 'gryphons'),
MalePronouns,
{ Toughness: 200, Power: 200, Speed: 200, Willpower: 200, Charm: 200 },
new Set(),
new Set([VoreType.Oral]),
2000
)

this.title = "Not really a gryphon"
this.desc = "Not really survivable, either."

this.side = Side.Monsters
this.applyEffect(new DazzlingEffect([
new EnemyCondition(),
new TogetherCondition()
]))

const crop = new GoldeneyeCrop(this)
const stomach = new GoldeneyeStomach(this, crop)

this.containers.push(stomach)
this.otherContainers.push(crop)

this.actions.push(
new TransferAction(
crop,
stomach
)
)

this.groupActions.push(new Flaunt(stomach))
this.groupActions.push(new Taunt())

this.actions.push(new CompositionAction(
"Stomp",
"Big step",
{
conditions: [
new TogetherCondition(),
new EnemyCondition()
],
consequences: [
new LogConsequence(
(user, target) => new LogLine(
`${user.name.capital} ${user.name.conjugate(new Verb('stomp'))} on ${target.name.objective} with crushing force!`
)
),
new DamageConsequence(
new FractionDamageFormula([
{ fraction: 0.75, target: Vigor.Health, type: DamageType.Pure }
])
),
new DamageConsequence(
new ConstantDamageFormula(
new Damage(
{ amount: 50, target: Vigor.Health, type: DamageType.Crush }
)
)
),
new StatusConsequence(
() => new StunEffect(3)
)
]
}
))
}
}

+ 3
- 3
src/game/vore.ts 查看文件

@@ -195,7 +195,7 @@ export abstract class NormalContainer implements Container {
}

export abstract class InnerContainer extends NormalContainer {
constructor (name: Noun, owner: Vore, voreTypes: Set<VoreType>, capacity: number, private escape: VoreContainer) {
constructor (name: Noun, owner: Vore, voreTypes: Set<VoreType>, capacity: number, private escape: Container) {
super(name, owner, voreTypes, capacity)

this.actions = []
@@ -276,8 +276,8 @@ export abstract class NormalVoreContainer extends NormalContainer implements Vor
}
}

abstract class InnerVoreContainer extends NormalVoreContainer {
constructor (name: Noun, owner: Vore, voreTypes: Set<VoreType>, capacity: number, damage: Damage, private escape: VoreContainer) {
export abstract class InnerVoreContainer extends NormalVoreContainer {
constructor (name: Noun, owner: Vore, voreTypes: Set<VoreType>, capacity: number, damage: Damage, private escape: Container) {
super(name, owner, voreTypes, capacity, damage)

this.actions = []


Loading…
取消
儲存