Parcourir la source

Add a vore AI; allow combat to continue until everyone's destroyed

master
Fen Dweller il y a 5 ans
Parent
révision
51eb4b0edc
6 fichiers modifiés avec 85 ajouts et 10 suppressions
  1. +40
    -7
      src/components/Combat.vue
  2. +2
    -2
      src/components/Statblock.vue
  3. +21
    -0
      src/game/ai.ts
  4. +16
    -0
      src/game/combat.ts
  5. +6
    -0
      src/game/entity.ts
  6. +0
    -1
      src/game/vore.ts

+ 40
- 7
src/components/Combat.vue Voir le fichier

@@ -17,7 +17,7 @@
<div class="left-fader">

</div>
<div class="left-actions">
<div v-if="running" class="left-actions">
<div v-if="encounter.currentMove === left" class="vert-display">
<i class="action-label fas fa-users" v-if="left.validGroupActions(combatants).length > 0"></i>
<ActionButton @described="described" @executed="executedLeft" v-for="(action, index) in left.validGroupActions(combatants)" :key="'left-' + action.name + '-' + index" :action="action" :user="left" :target="right" :combatants="combatants" />
@@ -31,7 +31,7 @@
<div class="right-fader">

</div>
<div class="right-actions">
<div v-if="running" class="right-actions">
<div v-if="encounter.currentMove === right" class="vert-display">
<i class="action-label fas fa-users" v-if="right.validGroupActions(combatants).length > 0"></i>
<ActionButton @described="described" @executed="executedRight" v-for="(action, index) in right.validGroupActions(combatants)" :key="'right-' + action.name + '-' + index" :action="action" :user="right" :target="left" :combatants="combatants" />
@@ -46,6 +46,9 @@
<button @click="$emit('leaveCombat')" v-if="encounter.winner !== null" class="exit-combat">
Exit Combat
</button>
<button @click="continuing = true; pickNext()" v-if="encounter.winner !== null && !continuing" class="continue-combat">
Continue
</button>
</div>
</template>

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


+ 2
- 2
src/components/Statblock.vue Voir le fichier

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


+ 21
- 0
src/game/ai.ts Voir le fichier

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

+ 16
- 0
src/game/combat.ts Voir le fichier

@@ -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<Side> = 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<Side> = 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 {


+ 6
- 0
src/game/entity.ts Voir le fichier

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


+ 0
- 1
src/game/vore.ts Voir le fichier

@@ -24,7 +24,6 @@ export abstract class Vore extends Mortal {
otherContainers: Array<Container> = []

containedIn: Container | null = null
destroyed = false;

voreStats: VoreStats



Chargement…
Annuler
Enregistrer