@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
JavaScript
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.