| @@ -8,7 +8,7 @@ | |||||
| <script lang="ts"> | <script lang="ts"> | ||||
| import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | ||||
| import { Action, GroupAction } from '@/game/combat' | |||||
| import { Action, Encounter } from '@/game/combat' | |||||
| import { Creature } from '@/game/creature' | import { Creature } from '@/game/creature' | ||||
| import { nilLog } from '@/game/interface' | import { nilLog } from '@/game/interface' | ||||
| @@ -23,28 +23,21 @@ export default class ActionButton extends Vue { | |||||
| @Prop() | @Prop() | ||||
| target!: Creature | target!: Creature | ||||
| @Prop() | |||||
| encounter!: Encounter | |||||
| @Prop() | @Prop() | ||||
| combatants!: Array<Creature> | combatants!: Array<Creature> | ||||
| @Emit("execute") | @Emit("execute") | ||||
| execute () { | execute () { | ||||
| if ((this.action as GroupAction).executeGroup !== undefined) { | |||||
| const action = (this.action as GroupAction) | |||||
| this.$emit('executed', (this.action as GroupAction).executeGroup(this.user, action.allowedGroup(this.user, this.combatants))) | |||||
| } else { | |||||
| this.$emit('executed', this.user.executeAction(this.action, this.target)) | |||||
| } | |||||
| this.$emit('executed', this.user.executeAction(this.action, this.action.targets(this.target, this.encounter))) | |||||
| this.undescribe() | this.undescribe() | ||||
| } | } | ||||
| @Emit("describe") | @Emit("describe") | ||||
| describe () { | describe () { | ||||
| if ((this.action as GroupAction).describeGroup !== undefined) { | |||||
| const action = (this.action as GroupAction) | |||||
| this.$emit('described', action.describeGroup(this.user, action.allowedGroup(this.user, this.combatants))) | |||||
| } else { | |||||
| this.$emit('described', this.action.describe(this.user, this.target)) | |||||
| } | |||||
| this.$emit('described', this.action.describe(this.user, this.target)) | |||||
| } | } | ||||
| @Emit("undescribe") | @Emit("undescribe") | ||||
| @@ -11,7 +11,7 @@ | |||||
| <script lang="ts"> | <script lang="ts"> | ||||
| import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | ||||
| import { Action, GroupAction } from '@/game/combat' | |||||
| import { Action } from '@/game/combat' | |||||
| import { Creature } from '@/game/creature' | import { Creature } from '@/game/creature' | ||||
| import { Place, Direction, Choice, World } from '@/game/world' | import { Place, Direction, Choice, World } from '@/game/world' | ||||
| import tippy from 'tippy.js' | import tippy from 'tippy.js' | ||||
| @@ -19,12 +19,12 @@ | |||||
| </div> | </div> | ||||
| <div v-if="running" class="left-actions"> | <div v-if="running" class="left-actions"> | ||||
| <div v-if="encounter.currentMove === left" class="vert-display"> | <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" /> | |||||
| <i class="action-label fas fa-user-friends" v-if="left.validActions(right).length > 0 && left !== right"></i> | |||||
| <ActionButton @described="described" @executed="executedLeft" v-for="(action, index) in left === right ? [] : left.validActions(right)" :key="'left-' + action.name + '-' + index" :action="action" :user="left" :target="right" :combatants="combatants" /> | |||||
| <i class="action-label fas fa-users" v-if="left.validGroupActions(right, encounter).length > 0 && left !== right"></i> | |||||
| <ActionButton @described="described" @executed="executedLeft" v-for="(action, index) in left === right ? [] : left.validGroupActions(right, encounter)" :key="'left-' + action.name + '-' + index" :action="action" :user="left" :target="right" :encounter="encounter" :combatants="combatants" /> | |||||
| <i class="action-label fas fa-user-friends" v-if="left.validSoloActions(right, encounter).length > 0 && left !== right"></i> | |||||
| <ActionButton @described="described" @executed="executedLeft" v-for="(action, index) in left === right ? [] : left.validSoloActions(right, encounter)" :key="'left-' + action.name + '-' + index" :action="action" :user="left" :target="right" :encounter="encounter" :combatants="combatants" /> | |||||
| <i class="action-label fas fa-user" v-if="left.validActions(left).length > 0"></i> | <i class="action-label fas fa-user" v-if="left.validActions(left).length > 0"></i> | ||||
| <ActionButton @described="described" @executed="executedLeft" v-for="(action, index) in left.validActions(left)" :key="'left-' + action.name + '-' + index" :action="action" :user="left" :target="left" :combatants="combatants" /> | |||||
| <ActionButton @described="described" @executed="executedLeft" v-for="(action, index) in left.validActions(left)" :key="'left-' + action.name + '-' + index" :action="action" :user="left" :target="left" :encounter="encounter" :combatants="combatants" /> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div class="right-fader"> | <div class="right-fader"> | ||||
| @@ -32,12 +32,12 @@ | |||||
| </div> | </div> | ||||
| <div v-if="running" class="right-actions"> | <div v-if="running" class="right-actions"> | ||||
| <div v-if="encounter.currentMove === right" class="vert-display"> | <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" /> | |||||
| <i class="action-label fas fa-user-friends" v-if="right.validActions(left).length > 0 && right !== left"></i> | |||||
| <ActionButton @described="described" @executed="executedRight" v-for="(action, index) in right === left ? [] : right.validActions(left)" :key="'right-' + action.name + '-' + index" :action="action" :user="right" :target="left" :combatants="combatants" /> | |||||
| <i class="action-label fas fa-users" v-if="right.validGroupActions(left, encounter).length > 0 && right !== left"></i> | |||||
| <ActionButton @described="described" @executed="executedRight" v-for="(action, index) in right === left ? [] : right.validGroupActions(left, encounter)" :key="'right-' + action.name + '-' + index" :action="action" :user="right" :target="left" :encounter="encounter" :combatants="combatants" /> | |||||
| <i class="action-label fas fa-user-friends" v-if="right.validSoloActions(left, encounter).length > 0 && right !== left"></i> | |||||
| <ActionButton @described="described" @executed="executedRight" v-for="(action, index) in right === left ? [] : right.validSoloActions(left, encounter)" :key="'right-' + action.name + '-' + index" :action="action" :user="right" :target="left" :encounter="encounter" :combatants="combatants" /> | |||||
| <i class="action-label fas fa-user" v-if="right.validActions(right).length > 0"></i> | <i class="action-label fas fa-user" v-if="right.validActions(right).length > 0"></i> | ||||
| <ActionButton @described="described" @executed="executedRight" v-for="(action, index) in right.validActions(right)" :key="'right-' + action.name + '-' + index" :action="action" :user="right" :target="right" :combatants="combatants" /> | |||||
| <ActionButton @described="described" @executed="executedRight" v-for="(action, index) in right.validActions(right)" :key="'right-' + action.name + '-' + index" :action="action" :user="right" :target="right" :encounter="encounter" :combatants="combatants" /> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div v-show="actionDescVisible && encounter.winner === null" class="action-description"> | <div v-show="actionDescVisible && encounter.winner === null" class="action-description"> | ||||
| @@ -11,7 +11,7 @@ | |||||
| <script lang="ts"> | <script lang="ts"> | ||||
| import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | ||||
| import { Action, GroupAction } from '@/game/combat' | |||||
| import { Action } from '@/game/combat' | |||||
| import { Creature } from '@/game/creature' | import { Creature } from '@/game/creature' | ||||
| import { Place, Direction } from '@/game/world' | import { Place, Direction } from '@/game/world' | ||||
| import tippy from 'tippy.js' | import tippy from 'tippy.js' | ||||
| @@ -61,12 +61,12 @@ export class AI { | |||||
| ) | ) | ||||
| if (chosen !== undefined) { | if (chosen !== undefined) { | ||||
| return chosen.action.try(actor, chosen.target) | |||||
| return chosen.action.try(actor, chosen.action.targets(chosen.target, encounter)) | |||||
| } | } | ||||
| // if we filtered out EVERY action, we should just give up and pass | // if we filtered out EVERY action, we should just give up and pass | ||||
| return new PassAction().try(actor, actor) | |||||
| return new PassAction().try(actor, [actor]) | |||||
| } | } | ||||
| } | } | ||||
| @@ -107,6 +107,10 @@ export interface CombatTest { | |||||
| fail: (user: Creature, target: Creature) => LogEntry; | fail: (user: Creature, target: Creature) => LogEntry; | ||||
| } | } | ||||
| export interface Targeter { | |||||
| targets (primary: Creature, encounter: Encounter): Array<Creature>; | |||||
| } | |||||
| /** | /** | ||||
| * An instance of damage. Contains zero or more [[DamageInstance]] objects | * An instance of damage. Contains zero or more [[DamageInstance]] objects | ||||
| */ | */ | ||||
| @@ -375,7 +379,6 @@ export enum Side { | |||||
| */ | */ | ||||
| export interface Combatant { | export interface Combatant { | ||||
| actions: Array<Action>; | actions: Array<Action>; | ||||
| groupActions: Array<GroupAction>; | |||||
| side: Side; | side: Side; | ||||
| } | } | ||||
| @@ -400,13 +403,20 @@ export abstract class Action { | |||||
| return this.name.toString() | return this.name.toString() | ||||
| } | } | ||||
| try (user: Creature, target: Creature): LogEntry { | |||||
| const failReason = this.tests.find(test => !test.test(user, target)) | |||||
| if (failReason !== undefined) { | |||||
| return failReason.fail(user, target) | |||||
| } else { | |||||
| return this.execute(user, target) | |||||
| } | |||||
| try (user: Creature, targets: Array<Creature>): LogEntry { | |||||
| const results = targets.map(target => { | |||||
| const failReason = this.tests.find(test => !test.test(user, target)) | |||||
| if (failReason !== undefined) { | |||||
| return { failed: true, target: target, log: failReason.fail(user, target) } | |||||
| } else { | |||||
| return { failed: false, target: target, log: this.execute(user, target) } | |||||
| } | |||||
| }) | |||||
| return new LogLines( | |||||
| ...results.map(result => result.log), | |||||
| this.executeAll(user, results.filter(result => !result.failed).map(result => result.target)) | |||||
| ) | |||||
| } | } | ||||
| describe (user: Creature, target: Creature, verbose = true): LogEntry { | describe (user: Creature, target: Creature, verbose = true): LogEntry { | ||||
| @@ -424,11 +434,21 @@ export abstract class Action { | |||||
| return this.tests.reduce((total, test) => total * test.odds(user, target), 1) | return this.tests.reduce((total, test) => total * test.odds(user, target), 1) | ||||
| } | } | ||||
| targets (primary: Creature, encounter: Encounter): Array<Creature> { | |||||
| return [primary] | |||||
| } | |||||
| executeAll (user: Creature, targets: Array<Creature>): LogEntry { | |||||
| return nilLog | |||||
| } | |||||
| abstract execute (user: Creature, target: Creature): LogEntry | abstract execute (user: Creature, target: Creature): LogEntry | ||||
| } | } | ||||
| export class CompositionAction extends Action { | export class CompositionAction extends Action { | ||||
| public consequences: Array<Consequence>; | public consequences: Array<Consequence>; | ||||
| public groupConsequences: Array<GroupConsequence>; | |||||
| public targeters: Array<Targeter>; | |||||
| constructor ( | constructor ( | ||||
| name: TextLike, | name: TextLike, | ||||
| @@ -436,11 +456,15 @@ export class CompositionAction extends Action { | |||||
| properties: { | properties: { | ||||
| conditions?: Array<Condition>; | conditions?: Array<Condition>; | ||||
| consequences?: Array<Consequence>; | consequences?: Array<Consequence>; | ||||
| groupConsequences?: Array<GroupConsequence>; | |||||
| tests?: Array<CombatTest>; | tests?: Array<CombatTest>; | ||||
| targeters?: Array<Targeter>; | |||||
| } | } | ||||
| ) { | ) { | ||||
| super(name, desc, properties.conditions ?? [], properties.tests ?? []) | super(name, desc, properties.conditions ?? [], properties.tests ?? []) | ||||
| this.consequences = properties.consequences ?? [] | this.consequences = properties.consequences ?? [] | ||||
| this.groupConsequences = properties.groupConsequences ?? [] | |||||
| this.targeters = properties.targeters ?? [] | |||||
| } | } | ||||
| execute (user: Creature, target: Creature): LogEntry { | execute (user: Creature, target: Creature): LogEntry { | ||||
| @@ -449,6 +473,12 @@ export class CompositionAction extends Action { | |||||
| ) | ) | ||||
| } | } | ||||
| executeAll (user: Creature, targets: Array<Creature>): LogEntry { | |||||
| return new LogLines( | |||||
| ...this.groupConsequences.map(consequence => consequence.apply(user, targets.filter(target => consequence.applicable(user, target)))) | |||||
| ) | |||||
| } | |||||
| describe (user: Creature, target: Creature): LogEntry { | describe (user: Creature, target: Creature): LogEntry { | ||||
| return new LogLines( | return new LogLines( | ||||
| ...this.consequences.map(consequence => consequence.describe(user, target)).concat( | ...this.consequences.map(consequence => consequence.describe(user, target)).concat( | ||||
| @@ -457,6 +487,10 @@ export class CompositionAction extends Action { | |||||
| ) | ) | ||||
| ) | ) | ||||
| } | } | ||||
| targets (primary: Creature, encounter: Encounter) { | |||||
| return this.targeters.flatMap(targeter => targeter.targets(primary, encounter)).unique() | |||||
| } | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -471,22 +505,6 @@ export interface Actionable { | |||||
| actions: Array<Action>; | actions: Array<Action>; | ||||
| } | } | ||||
| export abstract class GroupAction extends Action { | |||||
| constructor (name: TextLike, desc: TextLike, conditions: Array<Condition>) { | |||||
| super(name, desc, conditions) | |||||
| } | |||||
| allowedGroup (user: Creature, targets: Array<Creature>): Array<Creature> { | |||||
| return targets.filter(target => this.allowed(user, target)) | |||||
| } | |||||
| executeGroup (user: Creature, targets: Array<Creature>): LogEntry { | |||||
| return new LogLines(...targets.map(target => this.execute(user, target))) | |||||
| } | |||||
| abstract describeGroup (user: Creature, targets: Array<Creature>): LogEntry | |||||
| } | |||||
| /** | /** | ||||
| * Individual status effects, items, etc. should override some of these hooks. | * Individual status effects, items, etc. should override some of these hooks. | ||||
| * Some hooks just produce a log entry. | * Some hooks just produce a log entry. | ||||
| @@ -770,3 +788,16 @@ export abstract class Consequence { | |||||
| abstract describe (user: Creature, target: Creature): LogEntry | abstract describe (user: Creature, target: Creature): LogEntry | ||||
| abstract apply (user: Creature, target: Creature): LogEntry | abstract apply (user: Creature, target: Creature): LogEntry | ||||
| } | } | ||||
| export abstract class GroupConsequence { | |||||
| constructor (public conditions: Condition[]) { | |||||
| } | |||||
| applicable (user: Creature, target: Creature): boolean { | |||||
| return this.conditions.every(cond => cond.allowed(user, target)) | |||||
| } | |||||
| abstract describe (user: Creature, targets: Array<Creature>): LogEntry | |||||
| abstract apply (user: Creature, targets: Array<Creature>): LogEntry | |||||
| } | |||||
| @@ -40,15 +40,7 @@ export abstract class DamageAction extends Action { | |||||
| ) | ) | ||||
| } | } | ||||
| try (user: Creature, target: Creature): LogEntry { | |||||
| const effectResults = target.effects.map(effect => effect.preAttack(target, user)) | |||||
| if (effectResults.some(result => result.prevented)) { | |||||
| return new LogLines(...effectResults.map(result => result.log)) | |||||
| } else { | |||||
| return super.try(user, target) | |||||
| } | |||||
| } | |||||
| // TODO: remove me or replace the logic for damage prevention | |||||
| execute (user: Creature, target: Creature): LogEntry { | execute (user: Creature, target: Creature): LogEntry { | ||||
| const damage = this.damage.calc(user, target) | const damage = this.damage.calc(user, target) | ||||
| @@ -117,7 +117,7 @@ export class ConsumeConsequence extends Consequence { | |||||
| describe (user: Creature, target: Creature): LogEntry { | describe (user: Creature, target: Creature): LogEntry { | ||||
| return new LogLine( | return new LogLine( | ||||
| `Devours the target.` | |||||
| `${this.container.consumeVerb.singular.capital} ${target.name.objective}, sending ${target.pronouns.objective} ${this.container.consumePreposition} ${user.name.possessive} ${this.container.name}.` | |||||
| ) | ) | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,22 @@ | |||||
| import { GroupConsequence, Condition } from '../combat' | |||||
| import { Creature } from '../creature' | |||||
| import { LogEntry, nilLog } from '../interface' | |||||
| import { GroupLine } from '../language' | |||||
| /** | |||||
| * Renders some text. | |||||
| */ | |||||
| export class LogGroupConsequence extends GroupConsequence { | |||||
| constructor (private line: GroupLine<Creature>, conditions: Condition[] = []) { | |||||
| super(conditions) | |||||
| } | |||||
| apply (user: Creature, targets: Array<Creature>): LogEntry { | |||||
| return this.line(user, targets) | |||||
| } | |||||
| describe (user: Creature, targets: Array<Creature>): LogEntry { | |||||
| return nilLog | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| import { Encounter, Targeter } from '../combat' | |||||
| import { Creature } from '../creature' | |||||
| export class SoloTargeter implements Targeter { | |||||
| targets (primary: Creature, encounter: Encounter): Array<Creature> { | |||||
| return [primary] | |||||
| } | |||||
| } | |||||
| export class SideTargeter implements Targeter { | |||||
| targets (primary: Creature, encounter: Encounter): Array<Creature> { | |||||
| return encounter.combatants.filter(combatant => primary.side === combatant.side) | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,4 @@ | |||||
| import { Damage, Stats, Action, Vigor, Side, GroupAction, VisibleStatus, ImplicitStatus, StatusEffect, DamageType, Effective, VoreStat, VoreStats, DamageInstance, Stat, Vigors } from '@/game/combat' | |||||
| import { Damage, Stats, Action, Vigor, Side, VisibleStatus, ImplicitStatus, StatusEffect, DamageType, Effective, VoreStat, VoreStats, DamageInstance, Stat, Vigors, Encounter } from '@/game/combat' | |||||
| import { Noun, Pronoun, SoloLine, Verb } from '@/game/language' | import { Noun, Pronoun, SoloLine, Verb } from '@/game/language' | ||||
| import { LogEntry, LogLines, LogLine } from '@/game/interface' | import { LogEntry, LogLines, LogLine } from '@/game/interface' | ||||
| import { VoreContainer, VoreType, Container } from '@/game/vore' | import { VoreContainer, VoreType, Container } from '@/game/vore' | ||||
| @@ -53,7 +53,6 @@ export class Creature extends Entity { | |||||
| statusEffects: Array<StatusEffect> = []; | statusEffects: Array<StatusEffect> = []; | ||||
| perks: Array<Perk> = []; | perks: Array<Perk> = []; | ||||
| groupActions: Array<GroupAction> = []; | |||||
| items: Array<Item> = []; | items: Array<Item> = []; | ||||
| /* eslint-disable-next-line */ | /* eslint-disable-next-line */ | ||||
| wallet: { [key in Currency]: number } = Object.keys(Currency).reduce((total: any, key) => { total[key] = 0; return total }, {}); | wallet: { [key in Currency]: number } = Object.keys(Currency).reduce((total: any, key) => { total[key] = 0; return total }, {}); | ||||
| @@ -218,16 +217,10 @@ export class Creature extends Entity { | |||||
| this.perks.push(perk) | this.perks.push(perk) | ||||
| } | } | ||||
| executeAction (action: Action, target: Creature): LogEntry { | |||||
| const preActionResults = this.effects.map(effect => effect.preAction(this)) | |||||
| const preReceiveActionResults = target.effects.map(effect => effect.preReceiveAction(target, this)) | |||||
| // TODO replace the logic for getting blocked or prevented from acting | |||||
| const blocking = preActionResults.concat(preReceiveActionResults).filter(result => result.prevented) | |||||
| if (blocking.length > 0) { | |||||
| return new LogLines(...blocking.map(result => result.log)) | |||||
| } else { | |||||
| return action.try(this, target) | |||||
| } | |||||
| executeAction (action: Action, targets: Array<Creature>): LogEntry { | |||||
| return action.try(this, targets) | |||||
| } | } | ||||
| removeEffect (effect: StatusEffect): LogEntry { | removeEffect (effect: StatusEffect): LogEntry { | ||||
| @@ -297,12 +290,12 @@ export class Creature extends Entity { | |||||
| }) | }) | ||||
| } | } | ||||
| validGroupActions (targets: Array<Creature>): Array<GroupAction> { | |||||
| const choices = this.groupActions | |||||
| validSoloActions (target: Creature, encounter: Encounter): Array<Action> { | |||||
| return this.validActions(target).filter(action => action.targets(target, encounter).length === 1) | |||||
| } | |||||
| return choices.filter(action => { | |||||
| return targets.some(target => action.allowed(this, target)) | |||||
| }) | |||||
| validGroupActions (target: Creature, encounter: Encounter): Array<Action> { | |||||
| return this.validActions(target).filter(action => action.targets(target, encounter).length > 1) | |||||
| } | } | ||||
| destroyLine: SoloLine<Creature> = (victim) => new LogLine( | destroyLine: SoloLine<Creature> = (victim) => new LogLine( | ||||
| @@ -1,6 +1,11 @@ | |||||
| import { FavorEscapedPrey, VoreAI } from '@/game/ai' | import { FavorEscapedPrey, VoreAI } from '@/game/ai' | ||||
| import { DamageType, Side, Stat, StatDamageFormula, Vigor } from '@/game/combat' | |||||
| import { CompositionAction, DamageType, Side, Stat, StatDamageFormula, Vigor } from '@/game/combat' | |||||
| import { PairCondition, TogetherCondition } from '@/game/combat/conditions' | |||||
| import { ConsumeConsequence } from '@/game/combat/consequences' | |||||
| import { LogGroupConsequence } from '@/game/combat/groupConsequences' | |||||
| import { SideTargeter } from '@/game/combat/targeters' | |||||
| import { Creature } from '@/game/creature' | import { Creature } from '@/game/creature' | ||||
| import { LogLine } from '@/game/interface' | |||||
| import { ImproperNoun, MalePronouns, ProperNoun } from '@/game/language' | import { ImproperNoun, MalePronouns, ProperNoun } from '@/game/language' | ||||
| import { anyVore, Stomach } from '@/game/vore' | import { anyVore, Stomach } from '@/game/vore' | ||||
| @@ -28,12 +33,29 @@ export default class Inazuma extends Creature { | |||||
| this.ai.addDecider(new FavorEscapedPrey()) | this.ai.addDecider(new FavorEscapedPrey()) | ||||
| this.addVoreContainer(new Stomach( | |||||
| const stomach = new Stomach( | |||||
| this, | this, | ||||
| 2, | 2, | ||||
| new StatDamageFormula([ | new StatDamageFormula([ | ||||
| { fraction: 1, stat: Stat.Power, target: Vigor.Health, type: DamageType.Acid } | { fraction: 1, stat: Stat.Power, target: Vigor.Health, type: DamageType.Acid } | ||||
| ]) | ]) | ||||
| ) | |||||
| this.actions.push(new CompositionAction( | |||||
| "Mass Devour", | |||||
| "Eat everyone!", | |||||
| { | |||||
| conditions: [new PairCondition(), new TogetherCondition()], | |||||
| targeters: [new SideTargeter()], | |||||
| consequences: [new ConsumeConsequence(stomach)], | |||||
| groupConsequences: [new LogGroupConsequence( | |||||
| (user, targets) => new LogLine(`With a mighty GULP!, all ${targets.length} of ${user.name.possessive} prey are swallowed down.`) | |||||
| )] | |||||
| } | |||||
| )) | )) | ||||
| this.addVoreContainer(stomach) | |||||
| this.ai = null | |||||
| } | } | ||||
| } | } | ||||
| @@ -6,6 +6,7 @@ export type SoloLine<T> = (user: T) => LogEntry | |||||
| export type SoloLineArgs<T, V> = (user: T, args: V) => LogEntry | export type SoloLineArgs<T, V> = (user: T, args: V) => LogEntry | ||||
| export type PairLine<T> = (user: T, target: T) => LogEntry | export type PairLine<T> = (user: T, target: T) => LogEntry | ||||
| export type PairLineArgs<T, V> = (user: T, target: T, args: V) => LogEntry | export type PairLineArgs<T, V> = (user: T, target: T, args: V) => LogEntry | ||||
| export type GroupLine<T> = (user: T, targets: Array<T>) => LogEntry | |||||
| enum NounKind { | enum NounKind { | ||||
| Specific, | Specific, | ||||
| @@ -5,7 +5,7 @@ declare global { | |||||
| interface Array<T> { | interface Array<T> { | ||||
| joinGeneral (item: T, endItem: T|null): Array<T>; | joinGeneral (item: T, endItem: T|null): Array<T>; | ||||
| /* eslint-disable-next-line */ | /* eslint-disable-next-line */ | ||||
| unique (predicate: (elem: T) => any): Array<T>; | |||||
| unique (predicate?: (elem: T) => any): Array<T>; | |||||
| } | } | ||||
| } | } | ||||
| @@ -19,11 +19,12 @@ Array.prototype.joinGeneral = function (item, endItem = null) { | |||||
| } | } | ||||
| /* eslint-disable-next-line */ | /* eslint-disable-next-line */ | ||||
| Array.prototype.unique = function<T> (predicate: (elem: T) => any): Array<T> { | |||||
| Array.prototype.unique = function<T> (predicate?: (elem: T) => any): Array<T> { | |||||
| const set = new Set() | const set = new Set() | ||||
| const result: Array<T> = [] as T[] | const result: Array<T> = [] as T[] | ||||
| this.forEach(elem => { | this.forEach(elem => { | ||||
| const predResult = predicate(elem) | |||||
| // if there is no predicate, just use the identity function | |||||
| const predResult = (predicate ?? (x => x))(elem) | |||||
| if (!set.has(predResult)) { | if (!set.has(predResult)) { | ||||
| set.add(predResult) | set.add(predResult) | ||||
| result.push(elem) | result.push(elem) | ||||