UNPKG

@firestone-hs/replay-parser

Version:

This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.9.

1,299 lines (1,259 loc) 246 kB
import { Map } from 'immutable'; import { GameTag, CardType, PlayState, Zone, AllCardsService as AllCardsService$1, GameType, Mulligan, Step, MetaTags, BlockType, ChoiceType, isBattlegrounds } from '@firestone-hs/reference-data'; import equal from 'deep-equal'; import { __awaiter } from 'tslib'; import * as i0 from '@angular/core'; import { Injectable, NgModule } from '@angular/core'; import { Observable } from 'rxjs'; import { SaxesParser } from 'saxes'; class ActionParserConfig { constructor() { this.showEnchantments = false; } } class Entity { constructor() { this.tags = {}; } static create(base, newAttributes) { // Merge tags const newTags = newAttributes && newAttributes.tags ? newAttributes.tags : {}; const tags = base.tags ? Object.assign(Object.assign({}, base.tags), newTags) : newTags; const newEntity = Object.assign(new Entity(), base, newAttributes, { tags }); return newEntity; } static default(card) { var _a; let tags = {}; for (const strTag of (_a = card === null || card === void 0 ? void 0 : card.mechanics) !== null && _a !== void 0 ? _a : []) { const tag = GameTag[strTag]; if (tag) { tags[strTag] = 1; } } return Entity.create({ cardID: card.id, tags: tags, }); } getCardType() { return this.getTag(GameTag.CARDTYPE); } getZone() { return this.getTag(GameTag.ZONE); } getTag(tag) { return !this.tags ? -1 : this.tags[GameTag[tag]]; } isRevealed() { // There are many tags that are set only when ShowEntity triggers. This is only // one of the possible choices const revealed = (this.getTag(GameTag.COST) && this.getTag(GameTag.COST) !== -1) || // For some reasons it happens that the cost is not always set? (this.getTag(GameTag.CARDTYPE) && this.getTag(GameTag.CARDTYPE) !== -1); // // console.log('revealed', revealed, this.id, this.cardID, this.tags.toJS()); return revealed; } zone() { return this.getTag(GameTag.ZONE); } updateDamage(damage) { const base = this; return Object.assign(new Entity(), this, { damageForThisAction: damage }); } update(definition) { const newAttributes = {}; if (definition.cardID) { newAttributes.cardID = definition.cardID; } if (definition.name) { newAttributes.name = definition.name; } if (definition.tags) { newAttributes.tags = definition.tags; if (newAttributes.tags.PLAYSTATE === 8) { newAttributes.tags.CONCEDED = 1; } } return Entity.create(this, newAttributes); } updateTag(tag, value) { const newTags = Object.assign(Object.assign({}, this.tags), { [GameTag[tag]]: value }); const base = this; return Object.assign(new Entity(), base, { tags: newTags }); } } class PlayerEntity extends Entity { static create(base, newAttributes) { // Merge tags const newTags = newAttributes && newAttributes.tags ? newAttributes.tags : {}; const tags = base.tags ? Object.assign(Object.assign({}, base.tags), newTags) : newTags; const newEntity = Object.assign(new PlayerEntity(), base, newAttributes, { tags }); return newEntity; } update(definition) { const newAttributes = {}; if (definition.cardID) { newAttributes.cardID = definition.cardID; } if (definition.name) { newAttributes.name = definition.name; } if (definition.tags) { newAttributes.tags = definition.tags; if (newAttributes.tags.PLAYSTATE === 8) { newAttributes.tags.CONCEDED = 1; } } return PlayerEntity.create(this, newAttributes); } updateDamage(damage) { const base = this; return Object.assign(new PlayerEntity(), base, { damageForThisAction: damage }); } updateTag(tag, value) { const newTags = Object.assign(Object.assign({}, this.tags), { [GameTag[tag]]: value }); const base = this; return Object.assign(new PlayerEntity(), base, { tags: newTags }); } } const deepEqual = (a, b) => equal(a, b, { strict: false, }); class ActionHelper { static getOwner(entities, entityId) { const ownerId = entityId; let owner = entities.get(ownerId); if (!(owner instanceof PlayerEntity)) { const controllerId = entities.get(entityId).getTag(GameTag.CONTROLLER); owner = entities .filter((entity) => entity instanceof PlayerEntity) .filter((entity) => entity.playerId === controllerId) .first(); } return owner; } static getCardId(entities, entityId, allEntitiesSoFar) { const entity = entities.get(entityId); if (entity && entity.cardID) { return entity.cardID; } if (allEntitiesSoFar) { const entitySoFar = allEntitiesSoFar.get(entityId); if (entitySoFar && entitySoFar.cardID) { return entitySoFar.cardID; } } // Otherwise, this can happen when we're targeting a player entity, which doesn't have a card id if (!(entity instanceof PlayerEntity)) { // Since we don't always know the entity id, it is often correct to say we don't know return null; } const heroEntityId = entity.getTag(GameTag.HERO_ENTITY); return entities.get(heroEntityId).cardID; } static combineActions(actions, shouldMerge, combiner, shouldSwap) { let previousResult = actions; let result = ActionHelper.doCombine(previousResult, shouldMerge, combiner, shouldSwap); while (!deepEqual(result, previousResult)) { previousResult = result; result = ActionHelper.doCombine(previousResult, shouldMerge, combiner); } return result; } static getTag(tags, name) { if (!tags) { return null; } const defender = tags === null || tags === void 0 ? void 0 : tags.find((tag) => tag.tag === name); return defender ? defender.value : 0; } static mergeIntoFirstAction(first, second, newElements) { const result = first.updateAction(newElements); // const concat = [...(first.damages || []), ...(second.damages || [])] as ReadonlyArray<Damage>; const finalDamages = first.damages.mergeWith((prev, next) => prev + next, second.damages); return result.updateAction({ damages: finalDamages, }); } static doCombine(actions, shouldMerge, combiner, shouldSwap) { const result = []; let previousAction; // // console.log('considering actions to merge', actions); for (let i = 0; i < actions.length; i++) { const currentAction = actions[i]; // // console.log( // 'reduce 150', // previousAction && previousAction.entities.get(150) && previousAction.entities.get(150).tags.toJS(), // currentAction && currentAction.entities.get(150) && currentAction.entities.get(150).tags.toJS(), // previousAction, // currentAction, // ); if (shouldMerge(previousAction, currentAction)) { // // console.log('merging'); const index = result.indexOf(previousAction); previousAction = combiner(previousAction, currentAction); // // console.log( // 'new previous action', // previousAction.entities.get(150) && previousAction.entities.get(150).tags.toJS(), // previousAction, // ); result[index] = previousAction; } else if (shouldSwap && shouldSwap(previousAction, currentAction)) { // // console.log('swapping', previousAction, currentAction); const index = result.indexOf(previousAction); const previousEntities = previousAction.entities; const previousIndex = previousAction.index; const previousTs = previousAction.timestamp; const currentEntities = currentAction.entities; const currentIndex = currentAction.index; const currentTs = currentAction.timestamp; result[index] = Object.assign(currentAction, { entities: previousEntities, index: previousIndex, timestamp: previousTs, }); result[index + 1] = Object.assign(previousAction, { entities: currentEntities, index: currentIndex, timestamp: currentTs, }); } else { // // console.log('doing nothing'); previousAction = currentAction; result.push(currentAction); } } // // console.log( // 'finished', // result[result.length - 1].entities.get(150) && result[result.length - 1].entities.get(150).tags.toJS(), // ); return result; } } class Action { constructor(allCards) { this.allCards = allCards; this.crossedEntities = []; this.options = []; // This is part of the global action, because damage actions can be merged // into non-damage ones this.damages = Map(); } updateAction(newAction) { return Object.assign(this.getInstance(), this, newAction); } generateTargetsText(allEntitiesSoFar) { if (!this.targetIds || this.targetIds.length === 0) { return null; } const originCardId = ActionHelper.getCardId(this.entities, this.originId, allEntitiesSoFar); const originCardName = this.allCards.getCard(originCardId).name; const targetCardIds = this.targetIds.map((entityId) => ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar)); const cardIds = targetCardIds.map((cardId) => this.allCards.getCard(cardId)); const targetCardNames = cardIds.some((card) => !card || !card.name) ? `${cardIds.length} cards` : cardIds.map((card) => card.name).join(', '); let damageText = ''; if (this.damages) { damageText = this.damages .map((amount, entityId) => { const entityCardId = ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar); const entityCard = this.allCards.getCard(entityCardId); return `${entityCard.name} takes ${amount} damage`; }) .join(', '); } const textRaw = `\t${originCardName} targets ${targetCardNames}. \n${damageText}`; return textRaw; } } class ActionButtonUsedAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new ActionButtonUsedAction(allCards), newAction); } update(entities) { return Object.assign(new ActionButtonUsedAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { const ownerName = ActionHelper.getOwner(this.entities, this.entityId).name; const cardId = ActionHelper.getCardId(this.entities, this.entityId, allEntitiesSoFar); const card = this.allCards.getCard(cardId); const verb = this.buildVerb(card); let actionTarget = ''; if (this.targetIds && this.targetIds.length > 0) { const targetCardIds = this.targetIds.map((entityId) => ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar)); const cardIds = targetCardIds.map((cardId) => this.allCards.getCard(cardId)); const targetCardNames = cardIds.some((card) => !card || !card.name) ? `${cardIds.length} cards` : cardIds.map((card) => card.name).join(', '); actionTarget = ` on ${targetCardNames}`; } const textRaw = `\t${ownerName} ${verb} ${card.name}${actionTarget}` // Sugar for Battlegrounds .replace('uses Drag To Buy on', 'buys') .replace('uses Drag To Buy Spell on', 'buys') .replace('uses Drag To Sell on', 'sells'); return Object.assign(new ActionButtonUsedAction(this.allCards), this, { textRaw, }); } getInstance() { return new ActionButtonUsedAction(this.allCards); } buildVerb(card) { // // console.log('building verb for', card, card.name && card.name.toLowerCase().indexOf('tavern tier')); if (!card.name) { return 'uses'; } if (card.name.toLowerCase().indexOf('tavern tier') !== -1) { return 'upgrades to'; } return 'uses'; } } class AttachingEnchantmentAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new AttachingEnchantmentAction(allCards), newAction); } update(entities) { return Object.assign(new AttachingEnchantmentAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { const creatorCardId = ActionHelper.getCardId(this.entities, this.originId, allEntitiesSoFar); const creatorCard = this.allCards.getCard(creatorCardId); const enchantmentCard = this.allCards.getCard(this.enchantmentCardId); const targetCardNames = this.targetIds .map(targetId => this.entities.get(targetId)) // There are some weird cases where the entity is not defined in the XML itself .filter(targetEntity => targetEntity) .map(targetEntity => targetEntity.cardID ? this.allCards.getCard(targetEntity.cardID).name : // Enchantments sometimes target the player itself, not the hero targetEntity.name) .join(', '); const textRaw = `\t${creatorCard.name} enchants ${targetCardNames} with ${enchantmentCard.name}`; return Object.assign(new AttachingEnchantmentAction(this.allCards), this, { textRaw, }); } getInstance() { return new AttachingEnchantmentAction(this.allCards); } } class AttackAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new AttackAction(allCards), newAction); } update(entities) { return Object.assign(new AttackAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { const originCardId = ActionHelper.getCardId(this.entities, this.originId, allEntitiesSoFar); const targetCardId = ActionHelper.getCardId(this.entities, this.targetId, allEntitiesSoFar); const originCard = this.allCards.getCard(originCardId); const targetCard = this.allCards.getCard(targetCardId); let damageText = ''; if (this.damages) { damageText = this.damages .map((amount, entityId) => { const entityCardId = ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar); const entityCard = this.allCards.getCard(entityCardId); return `${entityCard.name} takes ${amount} damage`; }) .join(', '); } const textRaw = `\t${originCard.name} attacks ${targetCard.name}. ${damageText}`; return Object.assign(new AttackAction(this.allCards), this, { textRaw, }); } getInstance() { return new AttackAction(this.allCards); } } class BaconBattleOverAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new BaconBattleOverAction(allCards), newAction); } update(entities) { return Object.assign(this.getInstance(), this, { entities, }); } enrichWithText(allEntitiesSoFar) { return Object.assign(new BaconBattleOverAction(this.allCards), this, { textRaw: 'Battle over', }); } getInstance() { return new BaconBattleOverAction(this.allCards); } } class BaconBoardVisualStateAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new BaconBoardVisualStateAction(allCards), newAction); } update(entities) { return Object.assign(this.getInstance(), this, { entities, }); } enrichWithText(allEntitiesSoFar) { const textRaw = this.newState === 1 ? 'Recruit' : 'Combat'; return Object.assign(new BaconBoardVisualStateAction(this.allCards), this, { textRaw, }); } getInstance() { return new BaconBoardVisualStateAction(this.allCards); } } class BaconOpponentRevealedAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new BaconOpponentRevealedAction(allCards), newAction); } update(entities) { return Object.assign(this.getInstance(), this, { entities, }); } enrichWithText() { return Object.assign(new BaconOpponentRevealedAction(this.allCards), this, { textRaw: 'Opponents revealed!', }); } getInstance() { return new BaconOpponentRevealedAction(this.allCards); } } class CardBurnAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new CardBurnAction(allCards), newAction); } update(entities) { return Object.assign(this.getInstance(), this, { entities }); } enrichWithText(allEntitiesSoFar) { const ownerNames = this.burnedCardIds .map((entityId) => ActionHelper.getOwner(this.entities, entityId)) .map((playerEntity) => { if (!playerEntity) { console.warn('[card-burn-action] no player entity', playerEntity, this.burnedCardIds, this.entities.get(this.burnedCardIds[0]).tags); return ''; } return playerEntity.name; }) .filter((name, index, self) => self.indexOf(name) === index); if (ownerNames.length !== 1) { console.warn('[card-burn-action] Invalid grouping of cards ' + ownerNames + ', ' + this.burnedCardIds); return this; } const ownerName = ownerNames[0]; const drawnCards = this.burnedCardIds .map((entityId) => ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar)) .map((cardId) => this.allCards.getCard(cardId)); let drawInfo = ''; // We don't have the mulligan info, so we just display the amount of cards being mulliganed if (drawnCards.some((card) => !card || !card.name)) { drawInfo = `${drawnCards.length} cards`; } else { drawInfo = drawnCards.map((card) => card.name).join(', '); } const textRaw = `\t${ownerName} burns ` + drawInfo; return Object.assign(new CardBurnAction(this.allCards), this, { textRaw, }); } getInstance() { return new CardBurnAction(this.allCards); } } class CardDiscardAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new CardDiscardAction(allCards), newAction); } update(entities) { return Object.assign(this.getInstance(), this, { entities }); } enrichWithText(allEntitiesSoFar) { const playerEntity = this.data.map((entityId) => ActionHelper.getOwner(this.entities, entityId)); if (!playerEntity || playerEntity.length === 0) { console.warn('[discard-action] could not find player owner', this.data); return this; } const ownerNames = this.data .map((entityId) => ActionHelper.getOwner(this.entities, entityId)) .map((entity) => { if (!entity) { console.warn('[discard-action] no player entity', entity, this.data, this.entities.get(this.data[0]).tags); return ''; } return entity.name; }) .filter((name, index, self) => self.indexOf(name) === index); if (ownerNames.length !== 1) { console.warn('[discard-action] Invalid grouping of cards ' + ownerNames + ', ' + this.data); return this; } const ownerName = ownerNames[0]; const discardedCards = this.data .map((entityId) => ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar)) .map((cardId) => this.allCards.getCard(cardId)); let discardInfo = ''; if (discardedCards.some((card) => !card || !card.name)) { discardInfo = `${discardedCards.length} cards`; } else { discardInfo = discardedCards.map((card) => card.name).join(', '); } const textRaw = `\t${ownerName} discards ` + discardInfo; return Object.assign(this.getInstance(), this, { textRaw }); } getInstance() { return new CardDiscardAction(this.allCards); } } class CardDrawAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new CardDrawAction(allCards), newAction); } update(entities) { return Object.assign(new CardDrawAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { const playerEntity = this.data.map((entityId) => ActionHelper.getOwner(this.entities, entityId)); if (!playerEntity || playerEntity.length === 0) { console.warn('[card-draw-action] could not find player owner', this.data); return this; } const ownerNames = this.data .map((entityId) => ActionHelper.getOwner(this.entities, entityId)) .map((entity) => { if (!entity) { console.warn('[card-draw-action] no player entity', entity, this.data, this.entities.get(this.data[0]).tags); return ''; } return entity.name; }) .filter((name, index, self) => self.indexOf(name) === index); if (ownerNames.length !== 1) { console.warn('[card-draw-action] Invalid grouping of cards ' + ownerNames + ', ' + this.data); return this; } const ownerName = ownerNames[0]; const drawnCards = this.data .map((entityId) => ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar)) .map((cardId) => this.allCards.getCard(cardId)); let drawInfo = ''; // We don't have the mulligan info, so we just display the amount of cards being mulliganed if (drawnCards.some((card) => !card || !card.name)) { drawInfo = `${drawnCards.length} cards`; } else { drawInfo = drawnCards.map((card) => card.name).join(', '); } const textRaw = `\t${ownerName} draws ` + drawInfo; return Object.assign(new CardDrawAction(this.allCards), this, { textRaw, }); } getInstance() { return new CardDrawAction(this.allCards); } } class CardPlayedFromHandAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new CardPlayedFromHandAction(allCards), newAction); } update(entities) { return Object.assign(new CardPlayedFromHandAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { const ownerName = ActionHelper.getOwner(this.entities, this.entityId).name; const cardEntity = this.entities.get(this.entityId); const cardId = ActionHelper.getCardId(this.entities, this.entityId, allEntitiesSoFar); const card = this.allCards.getCard(cardId); const cardName = card ? card.name : 'one card'; let playVerb = 'plays'; if (cardEntity.getTag(GameTag.CARDTYPE) === CardType.WEAPON) { playVerb = 'equips'; } const targetText = super.generateTargetsText(allEntitiesSoFar); const targetTextToDisplay = targetText && targetText.length > 0 ? `\n${targetText}` : ''; const textRaw = `\t${ownerName} ${playVerb} ${cardName}${targetTextToDisplay}`; // if (this.entityId === 38) { // console.warn('defile action', ownerName, cardEntity, cardId, card, cardName, targetText); // } // // console.log('enriching card played from hand action text', targetText, textRaw); return Object.assign(new CardPlayedFromHandAction(this.allCards), this, { textRaw, }); } getInstance() { return new CardPlayedFromHandAction(this.allCards); } } class CardTargetAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new CardTargetAction(allCards), newAction); } update(entities) { return Object.assign(new CardTargetAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { const originCardId = ActionHelper.getCardId(this.entities, this.originId, allEntitiesSoFar); const targetCardIds = this.targetIds.map(entityId => ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar)); const originCardName = this.allCards.getCard(originCardId).name; const targetCardNames = targetCardIds .map(cardId => this.allCards.getCard(cardId)) // There are some weird cases where the entity is not defined in the XML itself .filter(card => card) .map(card => card.name) .join(', '); const textRaw = `\t${originCardName} targets ${targetCardNames}`; return Object.assign(new CardTargetAction(this.allCards), this, { textRaw, }); } getInstance() { return new CardTargetAction(this.allCards); } } class Damage { } class DamageAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new DamageAction(allCards), newAction); } update(entities) { return Object.assign(new DamageAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { const textRaw = '\t' + this.damages .map((amount, entityId) => { const entityCardId = ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar); const entityCard = this.allCards.getCard(entityCardId); return `${entityCard.name} takes ${amount} damage`; }) .join(', '); return Object.assign(new DamageAction(this.allCards), this, { textRaw, }); } getInstance() { return new DamageAction(this.allCards); } } class DiscoverAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new DiscoverAction(allCards), newAction); } update(entities) { return Object.assign(new DiscoverAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { const owner = this.entities.get(this.ownerId); const offeredCards = this.choices .map(entityId => ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar)) .map(cardId => this.allCards.getCard(cardId)); let offerInfo = ''; // We don't have the mulligan info, so we just display the amount of cards being mulliganed if (offeredCards.some(card => !card || !card.name)) { offerInfo = `${offeredCards.length} cards`; } else { offerInfo = offeredCards.map(card => card.name).join(', '); } const chosenCards = this.chosen && this.chosen.length > 0 && this.chosen .map(entityId => ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar)) .map(cardId => this.allCards.getCard(cardId)); if (!chosenCards) { console.warn('Trying to do a discover action with an empty chosen array, aborting', this.originId, this.ownerId, this.choices, this.chosen); return this; } let choiceInfo; // We don't have the mulligan info, so we just display the amount of cards being mulliganed if (chosenCards.some(card => !card || !card.name)) { choiceInfo = `${chosenCards.length} cards`; } else { choiceInfo = chosenCards.map(card => card.name).join(', '); } const chosenText = choiceInfo && ` and picks ${choiceInfo}`; const textRaw = `\t${owner.name} discovers ${offerInfo}${chosenText}`; return Object.assign(new DiscoverAction(this.allCards), this, { textRaw, }); } getInstance() { return new DiscoverAction(this.allCards); } } class DiscoveryPickAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new DiscoveryPickAction(allCards), newAction); } update(entities) { return Object.assign(new DiscoveryPickAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { const ownerEntity = ActionHelper.getOwner(this.entities, this.owner); const choiceCardId = ActionHelper.getCardId(this.entities, this.choice, allEntitiesSoFar); let chosenCardText = 'one card'; if (choiceCardId) { chosenCardText = this.allCards.getCard(choiceCardId).name; } const textRaw = `\t${ownerEntity.name} picks ${chosenCardText}`; return Object.assign(new DiscoveryPickAction(this.allCards), this, { textRaw, }); } getInstance() { return new DiscoveryPickAction(this.allCards); } } class EndGameAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new EndGameAction(allCards), newAction); } update(entities) { return Object.assign(this.getInstance(), this, { entities }); } enrichWithText(allEntitiesSoFar) { let concededText = ''; for (const status of this.winStatus) { if (status[1] === PlayState.CONCEDED) { const name = ActionHelper.getOwner(this.entities, status[0]).name; concededText = `${name} conceded, `; } } let winText = ''; for (const status of this.winStatus) { if (status[1] !== PlayState.CONCEDED && status[1] !== PlayState.TIED && status[0] === this.entityId) { const name = ActionHelper.getOwner(this.entities, this.entityId).name; const statusString = status[1] === PlayState.WON ? 'won' : 'lost'; winText = `${name} ${statusString}!`; } } let textRaw = ''; if (!winText && this.winStatus.some(status => status[1] === PlayState.TIED)) { textRaw = 'Both players tied'; } else { textRaw = `${concededText}${winText}`; } return Object.assign(this.getInstance(), this, { textRaw }); } getInstance() { return new EndGameAction(this.allCards); } } class FatigueDamageAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new FatigueDamageAction(allCards), newAction); } update(entities) { return Object.assign(this.getInstance(), this, { entities }); } enrichWithText(allEntitiesSoFar) { const playerName = ActionHelper.getOwner(this.entities, this.controller).name; const textRaw = `\t${playerName} takes ${this.amount} fatigue damage`; return Object.assign(this.getInstance(), this, { textRaw }); } getInstance() { return new FatigueDamageAction(this.allCards); } } class HealingAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new HealingAction(allCards), newAction); } update(entities) { return Object.assign(new HealingAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { const textRaw = '\t' + this.damages .map((amount, entityId) => { const entityCardId = ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar); const entityCard = this.allCards.getCard(entityCardId); return `${entityCard.name} heals for ${-amount}`; }) .join(', '); return Object.assign(new HealingAction(this.allCards), this, { textRaw, }); } getInstance() { return new HealingAction(this.allCards); } } class LocationActivatedAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new LocationActivatedAction(allCards), newAction); } update(entities) { return Object.assign(new LocationActivatedAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { var _a; const playerEntity = ActionHelper.getOwner(this.entities, this.originId); if (!playerEntity) { return this; } const ownerName = playerEntity.name; const drawnCard = ActionHelper.getCardId(this.entities, this.originId, allEntitiesSoFar); const cardName = ((_a = this.allCards.getCard(drawnCard)) === null || _a === void 0 ? void 0 : _a.name) || 'Unknown Card'; const textRaw = `\t${ownerName} activates ${cardName}`; return Object.assign(new LocationActivatedAction(this.allCards), this, { textRaw, }); } getInstance() { return new LocationActivatedAction(this.allCards); } } class MinionDeathAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new MinionDeathAction(allCards), newAction); } update(entities) { return Object.assign(new MinionDeathAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { const deadMinionNames = this.deadMinions .map(entityId => ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar)) .map(cardId => this.allCards.getCard(cardId)) .map(card => card.name) .join(', '); const textRaw = `\t${deadMinionNames} die`; return Object.assign(new MinionDeathAction(this.allCards), this, { textRaw, }); } getInstance() { return new MinionDeathAction(this.allCards); } } class MulliganCardAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new MulliganCardAction(allCards), newAction); } update(entities) { return Object.assign(new MulliganCardAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { const textRaw = this.buildMulliganText(this.playerMulligan, allEntitiesSoFar) + '\n' + this.buildMulliganText(this.opponentMulligan, allEntitiesSoFar); return Object.assign(new MulliganCardAction(this.allCards), this, { textRaw, }); } buildMulliganText(cards, allEntitiesSoFar) { if (!cards || cards.length === 0) { return ''; } const ownerNames = cards .map((entityId) => ActionHelper.getOwner(this.entities, entityId)) .map((playerEntity) => playerEntity.name) .filter((name, index, self) => self.indexOf(name) === index); if (ownerNames.length !== 1) { console.warn('Invalid grouping of cards ' + ownerNames + ', ' + cards); return ''; } const ownerName = ownerNames[0]; const mulliganedCards = cards .map((entityId) => ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar)) .map((cardId) => this.allCards.getCard(cardId)); let mulliganInfo = ''; // We don't have the mulligan info, so we just display the amount of cards being mulliganed if (mulliganedCards.some((card) => !card || !card.name)) { mulliganInfo = `${mulliganedCards.length} cards`; } else { mulliganInfo = mulliganedCards.map((card) => card.name).join(', '); } const textRaw = `\t${ownerName} mulligans ${mulliganInfo}`; return textRaw; } getInstance() { return new MulliganCardAction(this.allCards); } } class MulliganCardChoiceAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new MulliganCardChoiceAction(allCards), newAction); } update(entities) { return Object.assign(new MulliganCardChoiceAction(this.allCards), this, { entities }); } enrichWithText(allEntitiesSoFar) { const textRaw = ''; return Object.assign(new MulliganCardChoiceAction(this.allCards), this, { textRaw }); } getInstance() { return new MulliganCardChoiceAction(this.allCards); } } class OptionsAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new OptionsAction(allCards), newAction); } update(entities) { return Object.assign(this.getInstance(), this, { entities }); } enrichWithText(allEntitiesSoFar) { const textRaw = ''; return Object.assign(this.getInstance(), this, { textRaw }); } getInstance() { return new OptionsAction(this.allCards); } } class PowerTargetAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new PowerTargetAction(allCards), newAction); } update(entities) { return Object.assign(new PowerTargetAction(this.allCards), this, { entities }); } enrichWithText(allEntitiesSoFar) { const originCardId = ActionHelper.getCardId(this.entities, this.originId, allEntitiesSoFar); const targetCardIds = this.targetIds.map(entityId => ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar)); const originCardName = this.allCards.getCard(originCardId).name; const cardIds = targetCardIds.map(cardId => this.allCards.getCard(cardId)); const targetCardNames = cardIds.some(card => !card || !card.name) ? `${cardIds.length} cards` : cardIds.map(card => card.name).join(', '); let damageText = ''; if (this.damages) { damageText = this.damages .map((amount, entityId) => { const entityCardId = ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar); const entityCard = this.allCards.getCard(entityCardId); return `${entityCard.name} takes ${amount} damage`; }) .join(', '); } const textRaw = `\t${originCardName} targets ${targetCardNames}. ${damageText}`; return Object.assign(new PowerTargetAction(this.allCards), this, { textRaw }); } getInstance() { return new PowerTargetAction(this.allCards); } } class QuestCompletedAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new QuestCompletedAction(allCards), newAction); } update(entities) { return Object.assign(this.getInstance(), this, { entities }); } enrichWithText(allEntitiesSoFar) { const questCardId = ActionHelper.getCardId(this.entities, this.originId, allEntitiesSoFar); const questName = this.allCards.getCard(questCardId).name; const playerName = ActionHelper.getOwner(this.entities, this.originId).name; const textRaw = `\t${playerName} completes ${questName}!`; return Object.assign(this.getInstance(), this, { textRaw }); } getInstance() { return new QuestCompletedAction(this.allCards); } } class SecretPlayedFromHandAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new SecretPlayedFromHandAction(allCards), newAction); } update(entities) { return Object.assign(new SecretPlayedFromHandAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { const owner = ActionHelper.getOwner(this.entities, this.entityId); const ownerName = owner ? owner.name : ''; const cardId = ActionHelper.getCardId(this.entities, this.entityId, allEntitiesSoFar); const card = this.allCards.getCard(cardId); const textRaw = `\t${ownerName} plays a secret! ${card.name}`; return Object.assign(new SecretPlayedFromHandAction(this.allCards), this, { textRaw, }); } getInstance() { return new SecretPlayedFromHandAction(this.allCards); } } class SecretRevealedAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new SecretRevealedAction(allCards), newAction); } update(entities) { return Object.assign(new SecretRevealedAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { const cardId = ActionHelper.getCardId(this.entities, this.entityId, allEntitiesSoFar); const cardName = this.allCards.getCard(cardId).name; const textRaw = `\t... which triggers ${cardName}!`; return Object.assign(new SecretRevealedAction(this.allCards), this, { textRaw, }); } getInstance() { return new SecretRevealedAction(this.allCards); } } class StartTurnAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new StartTurnAction(allCards), newAction); } update(entities) { return Object.assign(this.getInstance(), this, { entities }); } enrichWithText(allEntitiesSoFar) { const textRaw = this.isHeroSelection ? 'Hero selection' : this.isMulligan ? 'Start of mulligan' : 'Start of turn ' + this.turn; return Object.assign(this.getInstance(), this, { textRaw }); } getInstance() { return new StartTurnAction(this.allCards); } } class SummonAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new SummonAction(allCards), newAction); } update(entities) { return Object.assign(new SummonAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { const originCardId = ActionHelper.getCardId(this.entities, this.originId, allEntitiesSoFar); // // console.log('enriching summon', this.originId, originCardId); const originCardName = this.allCards.getCard(originCardId).name; const summonCardNames = this.entityIds .map(entityId => ActionHelper.getCardId(this.entities, entityId, allEntitiesSoFar)) .map(cardId => this.allCards.getCard(cardId).name) .join(', '); const textRaw = `\t${originCardName} summons ${summonCardNames}`; return Object.assign(new SummonAction(this.allCards), this, { textRaw, }); } getInstance() { return new SummonAction(this.allCards); } } class TradeAction extends Action { constructor(allCards) { super(allCards); } static create(newAction, allCards) { return Object.assign(new TradeAction(allCards), newAction); } update(entities) { return Object.assign(new TradeAction(this.allCards), this, { entities, }); } enrichWithText(allEntitiesSoFar) { var _a; const playerEntity = ActionHelper.getOwner(this.entities, this.originId); if (!playerEntity) { console.warn('[card-draw-action] could not find player owner', this.originId); return this; } const ownerName = playerEntity.name; const drawnCard = ActionHelper.getCardId(this.entities, this.originId, allEntitiesSoFar); const cardName = ((_a = this.allCards.getCard(drawnCard)) === null || _a === void 0 ? void 0 : _a.name) || 'Unknown Card'; const textRaw = `\t${ownerName} trades ${cardName}`; return Object.assign(new TradeAction(this.allCards), this, { textRaw, }); } getInstance() { return new TradeAction(this.allCards); } } class Turn { } class ActionTurn extends Turn { static create(base) { return Object.assign(new ActionTurn(), base); } update(newTurn) { return Object.assign(new ActionTurn(), this, newTurn); } } class Game { constructor() { this.players = []; this.turns = Map(); this.entitiesBeforeMulligan = Map(); } static createGame(baseGame, newAttributes) { return Object.assign(new Game(), baseGame, newAttributes); } update(base) { return Object.assign(new Game(), this, base); } getLatestParsedState() { if (this.turns.size === 0 || this.turns.last().actions.length === 0) { return this.entitiesBeforeMulligan; } const lastTurn = this.turns.get(this.turns.size - 1); // // console.log('last turn', lastTurn, this.turns.toJS()); return lastTurn.actions[lastTurn.actions.length - 1].entities; } } class GameEntity extends Entity { static create(base, newAttributes) { // Merge tags const newTags = newAttributes && newAttributes.tags ? newAttributes.tags : {}; const tags = base.tags ? Object.assign(Object.assign({}, base.tags), newTags) : newTags; const newEntity = Object.assign(new GameEntity(), base, newAttributes, { tags }); return newEntity; } update(definition) { const newAttributes = {}; if (definition.cardID) { newAttributes.cardID = definition.cardID; } if (definition.name) { newAttributes.name = definition.name; } if (definition.tags) { newAttributes.tags = definition.tags; if (newAttributes.tags.PLAYSTATE === 8) { newAttributes.tags.CONCEDED = 1; } } return GameEntity.create(this, newAttributes); } updateTag(tag, value) { const newTags = Object.assign(Object.assign({}, this.tags), { [GameTag[tag]]: value }); const base = this; return Object.