@@ -47,7 +48,7 @@ import { POV } from '@/game/language'
import { LogEntry } from '@/game/interface'
import Statblock from './Statblock.vue'
import ActionButton from './ActionButton.vue'
-import { Side } from '@/game/combat'
+import { Side, Encounter } from '@/game/combat'
@Component(
{
@@ -55,7 +56,8 @@ import { Side } from '@/game/combat'
data () {
return {
left: null,
- right: null
+ right: null,
+ combatants: null
}
},
methods: {
@@ -72,6 +74,16 @@ import { Side } from '@/game/combat'
if (target !== null) {
target.scrollBy({ top: 0, left: event.deltaY, behavior: 'smooth' })
}
+ },
+ doSelectLeft (combatant: Creature) {
+ if (combatant.side !== this.$props.encounter.currentMove.side) {
+ this.$data.left = combatant
+ }
+ },
+ doSelectRight (combatant: Creature) {
+ if (combatant.side !== this.$props.encounter.currentMove.side) {
+ this.$data.right = combatant
+ }
}
}
}
@@ -79,15 +91,16 @@ import { Side } from '@/game/combat'
export default class Combat extends Vue {
@Prop()
- combatants!: Array
+ encounter!: Encounter
Side = Side
actionDescription = ''
created () {
- this.$data.left = this.combatants.filter(x => x.side === Side.Heroes)[0]
- this.$data.right = this.combatants.filter(x => x.side === Side.Monsters)[0]
+ this.$data.left = this.encounter.combatants.filter(x => x.side === Side.Heroes)[0]
+ this.$data.right = this.encounter.combatants.filter(x => x.side === Side.Monsters)[0]
+ this.$data.combatants = this.encounter.combatants
}
mounted () {
@@ -112,6 +125,23 @@ export default class Combat extends Vue {
log.scrollTo({ top: log.scrollHeight, left: 0 })
}
+
+ this.encounter.nextMove()
+
+ if (this.encounter.currentMove.side === Side.Heroes) {
+ this.$data.left = this.encounter.currentMove
+ this.$el.querySelector("#left-stats ")
+ } else if (this.encounter.currentMove.side === Side.Monsters) {
+ this.$data.right = this.encounter.currentMove
+ }
+
+ // scroll to the newly selected creature
+ this.$nextTick(() => {
+ const creature: HTMLElement|null = this.$el.querySelector("[data-current-turn]")
+ if (creature !== null) {
+ creature.scrollIntoView()
+ }
+ })
}
@Emit("executedRight")
@@ -128,6 +158,22 @@ export default class Combat extends Vue {
log.scrollTo({ top: log.scrollHeight, left: 0 })
}
+
+ this.encounter.nextMove()
+
+ if (this.encounter.currentMove.side === Side.Heroes) {
+ this.$data.left = this.encounter.currentMove
+ } else if (this.encounter.currentMove.side === Side.Monsters) {
+ this.$data.right = this.encounter.currentMove
+ }
+
+ // scroll to the newly selected creature
+ this.$nextTick(() => {
+ const creature: HTMLElement|null = this.$el.querySelector("[data-current-turn]")
+ if (creature !== null) {
+ creature.scrollIntoView()
+ }
+ })
}
@Emit("described")
diff --git a/src/components/Statblock.vue b/src/components/Statblock.vue
index 44a2995..2798e02 100644
--- a/src/components/Statblock.vue
+++ b/src/components/Statblock.vue
@@ -3,6 +3,7 @@
+
@@ -13,6 +14,7 @@
{{ subject.desc }}
+ Initiative: {{ (initiative).toFixed(0) }}%
{{ status.topLeft }}
@@ -105,6 +107,9 @@ export default class Statblock extends Vue {
@Prop({ type: Creature, required: true })
subject!: Creature
+ @Prop()
+ initiative!: number
+
private vigorIcons = VigorIcons
private statIcons = StatIcons
private voreStatIcons = VoreStatIcons
@@ -283,6 +288,15 @@ a {
opacity: 0.20;
}
+.statblock[data-current-turn] .statblock-shader-current-turn {
+ background: #0f0;
+ opacity: 0.3;
+}
+
+.statblock[data-disabled] {
+ color: #888;
+}
+
.statblock[data-dead] .statblock-shader-dead {
background: red;
opacity: 0.50;
diff --git a/src/game/combat.ts b/src/game/combat.ts
index 71272a7..f98ffbe 100644
--- a/src/game/combat.ts
+++ b/src/game/combat.ts
@@ -320,6 +320,34 @@ export abstract class GroupAction extends Action {
}
}
+/**
+ * Individual status effects, items, etc. should override some of these hooks.
+ * Some hooks just produce a log entry.
+ * Some hooks return results along with a log entry.
+ */
+export class Effective {
+ onApply (creature: Creature): LogEntry { return nilLog }
+
+ onRemove (creature: Creature): LogEntry { return nilLog }
+
+ preAction (creature: Creature): { prevented: boolean; log: LogEntry } {
+ return {
+ prevented: false,
+ log: nilLog
+ }
+ }
+
+ preDamage (creature: Creature, damage: Damage): Damage {
+ return damage
+ }
+
+ preAttack (creature: Creature, attacker: Creature): { prevented: boolean; log: LogEntry } {
+ return {
+ prevented: false,
+ log: nilLog
+ }
+ }
+}
/**
* A displayable status effect
*/
@@ -346,38 +374,64 @@ export class ImplicitStatus implements VisibleStatus {
/**
* This kind of status is explicitly given to a creature.
- *
- * Individual status effects should override some of its hooks.
- * Some hooks just produce a log entry.
- * Some hooks return results along with a log entry.
*/
-export abstract class StatusEffect implements VisibleStatus {
+export abstract class StatusEffect extends Effective implements VisibleStatus {
constructor (public name: TextLike, public desc: TextLike, public icon: string) {
-
+ super()
}
get topLeft () { return '' }
get bottomRight () { return '' }
+}
- onApply (creature: Creature): LogEntry { return nilLog }
+/**
+ * An Encounter describes a fight: who is in it and whose turn it is
+ */
+export class Encounter {
+ private initiatives: Map
+ currentMove: Creature
+ turnTime = 100
- onRemove (creature: Creature): LogEntry { return nilLog }
+ constructor (public combatants: Creature[]) {
+ this.initiatives = new Map()
- preAction (creature: Creature): { prevented: boolean; log: LogEntry } {
- return {
- prevented: false,
- log: nilLog
- }
- }
+ combatants.forEach(combatant => this.initiatives.set(combatant, 0))
+ this.currentMove = combatants[0]
- preDamage (creature: Creature, damage: Damage): Damage {
- return damage
+ this.nextMove()
}
- preAttack (creature: Creature, attacker: Creature): { prevented: boolean; log: LogEntry } {
- return {
- prevented: false,
- log: nilLog
+ nextMove (): void {
+ this.initiatives.set(this.currentMove, 0)
+ const times = new Map()
+
+ 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)
+ times.set(combatant, remaining)
+ })
+
+ this.currentMove = this.combatants.reduce((closest, next) => {
+ const closestTime = times.get(closest) ?? 0
+ const nextTime = times.get(next) ?? 0
+
+ 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)
+
+ 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))
+ console.log(combatant.name.toString(), currentProgress, closestRemaining)
+ })
+
+ // TODO: still let the creature use drained-vigor moves
+
+ console.log(this.currentMove.name.toString())
+ if (this.currentMove.disabled) {
+ this.nextMove()
}
}
}
diff --git a/src/game/combat/actions.ts b/src/game/combat/actions.ts
index f342a63..ed734e7 100644
--- a/src/game/combat/actions.ts
+++ b/src/game/combat/actions.ts
@@ -2,10 +2,24 @@ import { StatTest, StatVigorTest } from './tests'
import { DynText, LiveText, TextLike, Verb, PairLine, PairLineArgs } from '../language'
import { Entity, Creature } from '../entity'
import { Damage, DamageFormula, Stat, Vigor, Action } from '../combat'
-import { LogLine, LogLines, LogEntry, CompositeLog } from '../interface'
+import { LogLine, LogLines, LogEntry, CompositeLog, nilLog } from '../interface'
import { VoreContainer, Container } from '../vore'
import { CapableCondition, UserDrainedVigorCondition, TogetherCondition, EnemyCondition, SoloCondition, PairCondition, ContainsCondition, ContainedByCondition } from './conditions'
+export class PassAction extends Action {
+ execute (user: Creature, target: Creature): LogEntry {
+ return nilLog
+ }
+
+ describe (user: Creature, target: Creature): LogEntry {
+ return new LogLine("Do nothing.")
+ }
+
+ constructor () {
+ super("Pass", "Do nothing", [new SoloCondition()])
+ }
+}
+
export class AttackAction extends Action {
protected test: StatTest
diff --git a/src/game/entity.ts b/src/game/entity.ts
index 7a00841..0a359f6 100644
--- a/src/game/entity.ts
+++ b/src/game/entity.ts
@@ -3,6 +3,7 @@ import { Noun, Pronoun, TextLike, POV } from './language'
import { LogEntry, LogLine, LogLines } from './interface'
import { Vore, VoreContainer, VoreType, Container } from './vore'
import { Item } from './items'
+import { PassAction } from './combat/actions'
export interface Entity {
name: Noun;
@@ -94,6 +95,7 @@ export class Creature extends Vore implements Combatant {
super()
const containers = this.containers
+ this.actions.push(new PassAction())
Object.entries(this.maxVigors).forEach(([key, val]) => {
this.vigors[key as Vigor] = val
})