@@ -46,6 +46,9 @@
+
@@ -66,7 +69,10 @@ import { NoAI } from '../game/ai'
return {
left: null,
right: null,
- combatants: null
+ combatants: null,
+ won: false,
+ continuing: false,
+ totalWon: false
}
}
}
@@ -80,6 +86,14 @@ export default class Combat extends Vue {
actionDescription = ''
+ get running () {
+ if (this.encounter.winner === null || (this.$data.continuing === true && this.encounter.totalWinner === null)) {
+ return true
+ } else {
+ return false
+ }
+ }
+
@Emit("described")
described (entry: LogEntry) {
const actionDesc = this.$el.querySelector(".action-description")
@@ -150,11 +164,22 @@ export default class Combat extends Vue {
pickNext () {
// Did one side win?
+ console.log(this.encounter.winner, this.encounter.totalWinner)
- if (this.encounter.winner !== null) {
+ if (this.encounter.totalWinner !== null && !this.$data.totalWon) {
+ this.$data.totalWon = true
+ this.$data.won = true
this.writeLog(
new LogLine(
- `game o-vore lmaoooooooo`
+ `game o-vore for good`
+ ),
+ "center"
+ )
+ } else if (this.encounter.winner !== null && !this.$data.won && !this.$data.continuing) {
+ this.$data.won = true
+ this.writeLog(
+ new LogLine(
+ `game o-vore`
),
"center"
)
@@ -282,8 +307,8 @@ export default class Combat extends Vue {
min-height: 100%;
}
-.exit-combat {
- grid-area: 2 / main-col-start / main-row-start / main-col-end;
+.exit-combat,
+.continue-combat {
width: 100%;
padding: 4pt;
flex: 0 1;
@@ -295,6 +320,14 @@ export default class Combat extends Vue {
font-size: 36px;
}
+.exit-combat {
+ grid-area: 2 / main-col-start / main-row-start / 3;
+}
+
+.continue-combat {
+ grid-area: 2 / 3 / main-row-start / main-col-end;
+}
+
.combat-layout {
position: relative;
display: grid;
diff --git a/src/components/Statblock.vue b/src/components/Statblock.vue
index 89de514..112140d 100644
--- a/src/components/Statblock.vue
+++ b/src/components/Statblock.vue
@@ -71,7 +71,7 @@
import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator'
import { Creature } from '@/game/creature'
import { POV } from '@/game/language'
-import { NoAI, RandomAI } from '@/game/ai'
+import { NoAI, RandomAI, VoreAI } from '@/game/ai'
import { Stats, Stat, StatIcons, StatDescs, Vigor, VigorIcons, VigorDescs, VoreStatDescs, VoreStatIcons, VisibleStatus } from '@/game/combat'
import ContainerView from './ContainerView.vue'
import tippy, { delegate, createSingleton } from 'tippy.js'
@@ -84,7 +84,7 @@ import 'tippy.js/dist/tippy.css'
data () {
return {
POV: POV,
- ais: [new NoAI(), new RandomAI()]
+ ais: [new NoAI(), new RandomAI(), new VoreAI()]
}
},
methods: {
diff --git a/src/game/ai.ts b/src/game/ai.ts
index 74e9acd..ab3740f 100644
--- a/src/game/ai.ts
+++ b/src/game/ai.ts
@@ -1,6 +1,7 @@
import { Creature } from './creature'
import { Encounter } from './combat'
import { LogEntry } from './interface'
+import { ReleaseAction, TransferAction } from './combat/actions'
export interface AI {
name: string;
@@ -28,3 +29,23 @@ export class RandomAI implements AI {
return chosen.action.execute(actor, chosen.target)
}
}
+
+/**
+ * The VoreAI tries to perform moves from its containers
+ */
+export class VoreAI extends RandomAI {
+ name = "Vore AI"
+ decide (actor: Creature, encounter: Encounter): LogEntry {
+ const actions = encounter.combatants.flatMap(enemy => actor.validActions(enemy).map(action => ({
+ target: enemy,
+ action: action
+ })))
+ const voreActions = actions.filter(action => actor.otherContainers.concat(actor.containers).some(container => container.actions.includes(action.action) || action instanceof TransferAction))
+ const aggressiveActions = voreActions.filter(action => !(action.action instanceof ReleaseAction))
+ const chosen = aggressiveActions[Math.floor(Math.random() * aggressiveActions.length)]
+ if (chosen === undefined) {
+ return super.decide(actor, encounter)
+ }
+ return chosen.action.execute(actor, chosen.target)
+ }
+}
diff --git a/src/game/combat.ts b/src/game/combat.ts
index d9e12c0..122142d 100644
--- a/src/game/combat.ts
+++ b/src/game/combat.ts
@@ -579,6 +579,9 @@ export class Encounter {
return nilLog
}
+ /**
+ * Combat is won once one side is completely disabled
+ */
get winner (): null|Side {
const remaining: Set
= new Set(this.combatants.filter(combatant => !combatant.disabled).map(combatant => combatant.side))
@@ -588,6 +591,19 @@ export class Encounter {
return null
}
}
+
+ /**
+ * Combat is completely won once one side is completely destroyed
+ */
+ get totalWinner (): null|Side {
+ const remaining: Set = new Set(this.combatants.filter(combatant => !combatant.destroyed).map(combatant => combatant.side))
+
+ if (remaining.size === 1) {
+ return Array.from(remaining)[0]
+ } else {
+ return null
+ }
+ }
}
export abstract class Consequence {
diff --git a/src/game/entity.ts b/src/game/entity.ts
index dd35e69..052e300 100644
--- a/src/game/entity.ts
+++ b/src/game/entity.ts
@@ -46,6 +46,8 @@ export abstract class Mortal extends Entity {
[Vigor.Resolve]: 100
}
+ destroyed = false;
+
constructor (name: Noun, kind: Noun, pronouns: Pronoun, public baseStats: Stats) {
super(name, kind, pronouns)
this.stats = Object.keys(Stat).reduce((base: any, key) => { base[key] = baseStats[key as Stat]; return base }, {})
@@ -109,6 +111,10 @@ export abstract class Mortal extends Entity {
}
})
+ if (this.vigors.Health <= -this.maxVigors.Health) {
+ this.destroyed = true
+ }
+
if (this.vigors.Health <= 0 && startHealth > 0) {
return this.destroy()
} else {
diff --git a/src/game/vore.ts b/src/game/vore.ts
index fe170f9..7513288 100644
--- a/src/game/vore.ts
+++ b/src/game/vore.ts
@@ -24,7 +24,6 @@ export abstract class Vore extends Mortal {
otherContainers: Array = []
containedIn: Container | null = null
- destroyed = false;
voreStats: VoreStats