Skip to content

Commit

Permalink
UPDATE: new Scout's Steed
Browse files Browse the repository at this point in the history
  • Loading branch information
lsocrate committed Jan 1, 2024
1 parent d5f6533 commit 942f116
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 103 deletions.
20 changes: 12 additions & 8 deletions server/game/GameActions/InitiateConflictAction.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
import type { AbilityContext } from '../AbilityContext';
import { ConflictTypes, EventNames } from '../Constants';
import type Player from '../player';
import { ProvinceCard } from '../ProvinceCard';
import { PlayerAction, type PlayerActionProperties } from './PlayerAction';

export interface InitiateConflictProperties extends PlayerActionProperties {
canPass?: boolean;
forcedDeclaredType?: ConflictTypes;
forceProvinceTarget?: ProvinceCard;
}

export class InitiateConflictAction extends PlayerAction {
export class InitiateConflictAction extends PlayerAction<InitiateConflictProperties> {
name = 'initiateConflict';
eventName = EventNames.OnConflictInitiated;
effect = 'declare a new conflict';
defaultProperties: InitiateConflictProperties = {
canPass: true
};
constructor(properties: InitiateConflictProperties | ((context: AbilityContext) => InitiateConflictProperties)) {
super(properties);
}

canAffect(player: Player, context: AbilityContext): boolean {
let { forcedDeclaredType } = this.getProperties(context) as InitiateConflictProperties;
const { forcedDeclaredType } = this.getProperties(context);
return super.canAffect(player, context) && player.hasLegalConflictDeclaration({ forcedDeclaredType });
}

defaultTargets(context: AbilityContext): Player[] {
return [context.player];
}

eventHandler(event, additionalProperties): void {
let properties = this.getProperties(event.context, additionalProperties) as InitiateConflictProperties;
event.context.game.initiateConflict(event.player, properties.canPass, properties.forcedDeclaredType);
eventHandler(event: any, additionalProperties: any): void {
const properties = this.getProperties(event.context, additionalProperties);
event.context.game.initiateConflict(
event.player,
properties.canPass,
properties.forcedDeclaredType,
properties.forceProvinceTarget
);
}
}
63 changes: 33 additions & 30 deletions server/game/cards/18.1-EL01/Unicorn/ScoutsSteed.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,48 @@
import { CardTypes, Locations, Players, PlayTypes } from '../../../Constants';
import { PlayCharacterAsIfFromHandIntoConflict } from '../../../PlayCharacterAsIfFromHand';
import { PlayDisguisedCharacterAsIfFromHandIntoConflict } from '../../../PlayDisguisedCharacterAsIfFromHand';
import AbilityDsl = require('../../../abilitydsl');
import DrawCard = require('../../../drawcard');
import AbilityDsl from '../../../abilitydsl';
import { CardTypes, Durations, Locations } from '../../../Constants';
import DrawCard from '../../../drawcard';

export default class ScoutsSteed extends DrawCard {
static id = 'scout-s-steed';

public setupCardAbilities() {
this.attachmentConditions({
myControl: true
});
this.attachmentConditions({ myControl: true });

this.reaction({
title: 'Pick a character to be able to play',
title: 'Call your steed and go out to explore!',
when: {
onConflictDeclared: (event, context) =>
context.source.parent && event.attackers.includes(context.source.parent),
onDefendersDeclared: (event, context) =>
context.source.parent && event.defenders.includes(context.source.parent),
onMoveToConflict: (event, context) => context.source.parent && event.card === context.source.parent
onCardPlayed: (event, context) => event.card === context.source
},
target: {
cardType: CardTypes.Character,
cardType: CardTypes.Province,
location: Locations.Provinces,
controller: Players.Self,
gameAction: AbilityDsl.actions.playCard((context) => ({
target: context.target,
source: this,
resetOnCancel: false,
playType: PlayTypes.PlayFromHand,
playAction: context.target
? [
new PlayCharacterAsIfFromHandIntoConflict(context.target),
new PlayDisguisedCharacterAsIfFromHandIntoConflict(context.target)
]
: undefined,
ignoredRequirements: ['phase']
}))
cardCondition: (card) => card.isFacedown() && card.canBeAttacked()
},
effect: 'play {0} into the conflict'
gameAction: AbilityDsl.actions.sequentialContext(
({ player, target: province, source: { parent: character } }) => ({
gameActions: [
AbilityDsl.actions.ready({ target: character }),
AbilityDsl.actions.cardLastingEffect({
target: character,
effect: AbilityDsl.effects.mustBeDeclaredAsAttacker(),
duration: Durations.UntilEndOfConflict
}),
AbilityDsl.actions.cardLastingEffect(() => ({
target: province,
targetLocation: Locations.Provinces,
effect: AbilityDsl.effects.cardCannot('break'),
duration: Durations.UntilEndOfConflict
})),
AbilityDsl.actions.initiateConflict({
target: player,
forceProvinceTarget: province,
canPass: false
})
]
})
),
effect: "ready {1} and send them on a journey! {0} cannot be broken during this conflict - it's just exploration for now",
effectArgs: (context) => [context.source.parent]
});
}
}
11 changes: 9 additions & 2 deletions server/game/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -1049,8 +1049,15 @@ class Game extends EventEmitter {
return new AbilityContext({ game: this, player: player });
}

initiateConflict(player, canPass, forcedDeclaredType) {
const conflict = new Conflict(this, player, player.opponent, null, null, forcedDeclaredType);
initiateConflict(player, canPass, forcedDeclaredType, forceProvinceTarget) {
const conflict = new Conflict(
this,
player,
player.opponent,
null,
forceProvinceTarget ?? null,
forcedDeclaredType
);
this.queueStep(new ConflictFlow(this, conflict, canPass));
}

Expand Down
92 changes: 29 additions & 63 deletions test/server/cards/18.1-EL01/Unicorn/ScoutsSteed.spec.js
Original file line number Diff line number Diff line change
@@ -1,81 +1,47 @@
describe('Scouts Steed', function() {
integration(function() {
beforeEach(function() {
describe('Scouts Steed', function () {
integration(function () {
beforeEach(function () {
this.setupTest({
phase: 'conflict',
player1: {
inPlay: ['kakita-toshimoko'],
dynastyDiscard: ['doji-whisperer'],
inPlay: ['moto-conqueror', 'shinjo-archer'],
hand: ['scout-s-steed']
},
player2: {
inPlay: ['wandering-ronin'],
hand: ['talisman-of-the-sun']
provinces: ['manicured-garden', 'fertile-fields']
}
});

this.toshimoko = this.player1.findCardByName('kakita-toshimoko');
this.steed = this.player1.findCardByName('scout-s-steed');
this.whisperer = this.player1.placeCardInProvince('doji-whisperer', 'province 1');
this.scoutsSteed = this.player1.findCardByName('scout-s-steed');
this.motoConqueror = this.player1.findCardByName('moto-conqueror');
this.shinjoArcher = this.player1.findCardByName('shinjo-archer');

this.wanderingRonin = this.player2.findCardByName('wandering-ronin');
this.talismanOfTheSun = this.player2.findCardByName('talisman-of-the-sun');
this.motoConqueror.bow();
this.shinjoArcher.bow();

this.player1.playAttachment(this.steed, this.toshimoko);

this.shamefulDisplay1 = this.player2.provinces['province 1'].provinceCard;
this.shamefulDisplay2 = this.player2.provinces['province 2'].provinceCard;
this.manicuredFaceup = this.player2.findCardByName('manicured-garden');
this.fertileFacedown = this.player2.findCardByName('fertile-fields');
this.manicuredFaceup.facedown = false;
this.fertileFacedown.facedown = true;
});

it('should not give cavalry and trigger after attacking', function() {
this.noMoreActions();
expect(this.toshimoko.hasTrait('cavalry')).toBe(false);
this.initiateConflict({
attackers: [this.toshimoko]
});
it('readies the character and declare an attack with them', function () {
this.player1.clickCard(this.scoutsSteed);
this.player1.clickCard(this.shinjoArcher);
expect(this.player1).toHavePrompt('Triggered Abilities');
expect(this.player1).toBeAbleToSelect(this.steed);
});

it('should let you play a character into the conflict', function() {
this.noMoreActions();
this.initiateConflict({
attackers: [this.toshimoko]
});
this.player1.clickCard(this.steed);
expect(this.player1).toHavePrompt('Choose a character');
expect(this.player1).toBeAbleToSelect(this.whisperer);
this.player1.clickCard(this.whisperer);
expect(this.player1).toHavePromptButton('0');
expect(this.player1).toHavePromptButton('1');
expect(this.player1).toHavePromptButton('2');

this.player1.clickPrompt('2');
expect(this.whisperer.location).toBe('play area');
expect(this.whisperer.isParticipating()).toBe(true);
expect(this.whisperer.fate).toBe(2);
expect(this.getChatLogs(10)).toContain('player1 uses Scout\'s Steed to play Doji Whisperer into the conflict');
expect(this.getChatLogs(10)).toContain('player1 plays Doji Whisperer into the conflict with 2 additional fate');
});

it('if you cancel should not let you play the character', function() {
this.noMoreActions();
this.initiateConflict({
attackers: [this.toshimoko]
});
this.player1.clickCard(this.steed);
expect(this.player1).toHavePrompt('Choose a character');
expect(this.player1).toBeAbleToSelect(this.whisperer);
this.player1.clickCard(this.whisperer);
this.player1.clickPrompt('Cancel');
expect(this.whisperer.location).toBe('province 1');
expect(this.getChatLogs(10)).toContain('player1 uses Scout\'s Steed to play Doji Whisperer into the conflict');
this.player2.clickPrompt('Done');

this.player2.pass();
expect(this.player1).toHavePrompt('Conflict Action Window');
this.player1.clickCard(this.whisperer);
expect(this.player1).toHavePrompt('Conflict Action Window');
this.player1.clickCard(this.scoutsSteed);
expect(this.player1).toHavePrompt('Choose a province');
expect(this.player1).not.toBeAbleToSelect(this.manicuredFaceup);
expect(this.player1).toBeAbleToSelect(this.fertileFacedown);

this.player1.clickCard(this.fertileFacedown);
expect(this.getChatLogs(3)).toContain(
"player1 uses Scout's Steed to ready Shinjo Archer and send them on a journey! Fertile Fields cannot be broken during this conflict - it's just exploration for now"
);
expect(this.player1).toHavePrompt('Military Air Conflict');
expect(this.player1).not.toHavePromptButton('Pass Conflict');
expect(this.game.currentConflict.attackers).toContain(this.shinjoArcher);
});
});
});

0 comments on commit 942f116

Please sign in to comment.