Skip to content

Commit

Permalink
UPDATE: New Mioko's Song
Browse files Browse the repository at this point in the history
  • Loading branch information
lsocrate committed Dec 31, 2023
1 parent 47e2b35 commit 14a1fb1
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 68 deletions.
9 changes: 7 additions & 2 deletions server/game/ProvinceCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { EffectNames, Locations } from './Constants';
import AbilityDsl from './abilitydsl';
import BaseCard from './basecard';
import type Player from './player';
import type DrawCard from './drawcard';

type CardData = {
strength: number;
Expand Down Expand Up @@ -202,7 +203,7 @@ export class ProvinceCard extends BaseCard {
return;
}

for (const dynastyCard of this.controller.getDynastyCardsInProvince(this.location)) {
for (const dynastyCard of this.cardsInSelf()) {
if (!dynastyCard) {
// Why?
continue;
Expand Down Expand Up @@ -268,7 +269,7 @@ export class ProvinceCard extends BaseCard {
menu.push({ command: 'move_conflict', text: 'Move Conflict' });
}

if (this.controller.getDynastyCardsInProvince(this.location).length <= 0) {
if (this.cardsInSelf().length <= 0) {
menu.push({ command: 'refill', text: 'Refill Province' });
}
}
Expand Down Expand Up @@ -312,4 +313,8 @@ export class ProvinceCard extends BaseCard {
}
return super.isFacedown();
}

cardsInSelf(): DrawCard[] {
return this.controller.getDynastyCardsInProvince(this.location);
}
}
12 changes: 6 additions & 6 deletions server/game/basecard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import type DrawCard = require('./drawcard');
import Ring = require('./ring');
import type { CardEffect } from './Effects/types';

type Faction = 'neutral' | 'crab' | 'crane' | 'dragon' | 'lion' | 'phoenix' | 'scorpion' | 'unicorn' | 'shadowlands';

type PrintedKeyword =
| 'ancestral'
| 'corrupted'
Expand Down Expand Up @@ -566,11 +568,9 @@ class BaseCard extends EffectSource {
return set;
}

isFaction(faction: string): boolean {
let copyEffect = this.mostRecentEffect(EffectNames.CopyCharacter);
let cardFaction = copyEffect ? copyEffect.printedFaction : this.printedFaction;

faction = faction.toLowerCase();
isFaction(faction: Faction): boolean {
const copyEffect = this.mostRecentEffect(EffectNames.CopyCharacter);
const cardFaction = copyEffect ? copyEffect.printedFaction : this.printedFaction;
if (faction === 'neutral') {
return cardFaction === faction && !this.anyEffect(EffectNames.AddFaction);
}
Expand Down Expand Up @@ -1111,7 +1111,7 @@ class BaseCard extends EffectSource {
break;
}
case EffectNames.AttachmentFactionRestriction: {
const factions = effect.getValue<string[]>(this as any);
const factions = effect.getValue<Faction[]>(this as any);
if (!factions.some((faction) => parent.isFaction(faction))) {
return false;
}
Expand Down
43 changes: 26 additions & 17 deletions server/game/cards/18.1-EL01/Crane/MiokosSong.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
import { CardTypes, Durations, Phases } from '../../../Constants';
import { CardTypes, Locations, Players } from '../../../Constants';
import { StrongholdCard } from '../../../StrongholdCard';
import AbilityDsl from '../../../abilitydsl';
import { ProvinceCard } from '../../../ProvinceCard';

export default class MiokosSong extends StrongholdCard {
static id = 'mioko-s-song';

setupCardAbilities() {
this.action({
title: 'Gain a fate',
phase: Phases.Conflict,
this.persistentEffect({
match: (card, context) =>
card.controller === context.player &&
card.type === CardTypes.Character &&
card.isDishonored &&
card.isFaction('crane'),
effect: AbilityDsl.effects.modifyBothSkills(1)
});

this.reaction({
title: 'Sabotage the opponent a lot',
when: {
onCardPlayed: (event, context) =>
event.player === context.player && event.card.type === CardTypes.Character
},
cost: [
AbilityDsl.costs.bowSelf(),
AbilityDsl.costs.dishonor({ cardCondition: (card) => card.type === CardTypes.Character })
AbilityDsl.costs.dishonor({ cardCondition: (card, context: any) => card == context.event.card })
],
gameAction: AbilityDsl.actions.multiple([
AbilityDsl.actions.gainFate((context) => ({
target: context.player,
amount: 1
})),
AbilityDsl.actions.cardLastingEffect((context) => ({
effect: AbilityDsl.effects.addKeyword('courtesy'),
target: context.costs.dishonor,
duration: Durations.UntilEndOfRound
target: {
location: Locations.Provinces,
controller: Players.Opponent,
cardType: CardTypes.Province,
gameAction: AbilityDsl.actions.moveCard((context) => ({
destination: Locations.DynastyDiscardPile,
target: (context.target as ProvinceCard).cardsInSelf()
}))
]),
effect: 'gain a fate and give {1} Courtesy until the end of the round',
effectArgs: (context) => [context.costs.dishonor]
}
});
}
}
114 changes: 71 additions & 43 deletions test/server/cards/18.1-EL01/Crane/MiokosSong.spec.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,82 @@
describe('Mioko\'s Song', function() {
integration(function() {
beforeEach(function() {
this.setupTest({
phase: 'conflict',
player1: {
inPlay: ['doji-challenger', 'kakita-yoshi', 'doji-kuwanan', 'doji-whisperer'],
hand: ['festival-for-the-fortunes', 'way-of-the-crane'],
stronghold: ['mioko-s-song'],
dynastyDiscard: ['favorable-ground']
},
player2: {
inPlay: ['savvy-politician'],
hand: ['assassination']
}
const { GameModes } = require('../../../../../build/server/GameModes');

describe("Mioko's Song", function () {
integration(function () {
describe('Static ability', function () {
beforeEach(function () {
this.setupTest({
player1: {
stronghold: ['mioko-s-song'],
inPlay: ['daidoji-ahma', 'kakita-yoshi', 'hantei-daisetsu']
}
});

this.ahma = this.player1.findCardByName('daidoji-ahma');
this.yoshi = this.player1.findCardByName('kakita-yoshi');
this.daisetsu = this.player1.findCardByName('hantei-daisetsu');

this.ahma.dishonor();
this.yoshi.dishonor();
this.daisetsu.dishonor();
this.noMoreActions();
});

this.sh = this.player1.findCardByName('mioko-s-song');
this.crane = this.player1.findCardByName('way-of-the-crane');
this.ground = this.player1.findCardByName('favorable-ground');
this.whisperer = this.player1.findCardByName('doji-whisperer');
this.yoshi = this.player1.findCardByName('kakita-yoshi');
this.festival = this.player1.findCardByName('festival-for-the-fortunes');
this.challenger = this.player1.findCardByName('doji-challenger');
this.kuwanan = this.player1.findCardByName('doji-kuwanan');
this.savvy = this.player2.findCardByName('savvy-politician');

this.player1.placeCardInProvince(this.ground, 'province 1');
this.assassination = this.player2.findCardByName('assassination');
it('gives skill bonus to dishonored crane characters', function () {
expect(this.ahma.getMilitarySkill()).toBe(1);
expect(this.ahma.getPoliticalSkill()).toBe(2);
expect(this.yoshi.getMilitarySkill()).toBe(0);
expect(this.yoshi.getPoliticalSkill()).toBe(4);
expect(this.daisetsu.getMilitarySkill()).toBe(0);
expect(this.daisetsu.getPoliticalSkill()).toBe(1);
});
});

it('should dishonor someone to gain a fate and give target Courtesy', function() {
let fate = this.player1.fate;
this.player1.clickCard(this.sh);
this.player1.clickCard(this.whisperer);
describe('New Ability Strong', function () {
beforeEach(function () {
this.setupTest({
gameMode: GameModes.Emerald,
phase: 'dynasty',
player1: {
stronghold: ['mioko-s-song'],
dynastyDiscard: ['kakita-yoshi', 'hantei-daisetsu'],
inPlay: ['doji-whisperer']
},
player2: {
dynastyDiscard: ['aranat'],
provinces: ['shameful-display']
}
});

this.miokosSong = this.player1.findCardByName('mioko-s-song');
this.whisperer = this.player1.findCardByName('doji-whisperer');
this.yoshi = this.player1.placeCardInProvince('kakita-yoshi', 'province 1');
this.daisetsu = this.player1.moveCard('hantei-daisetsu', 'province 1');

this.shamefulDisplay = this.player2.findCardByName('shameful-display', 'province 1');
this.aranat = this.player2.findCardByName('aranat');
this.player2.moveCard(this.aranat, 'province 1');
});

it('discards all cards from opponent province', function () {
this.player1.clickCard(this.yoshi);
this.player1.clickPrompt('0');
expect(this.player1).toHavePrompt('Triggered Abilities');
expect(this.player1).toBeAbleToSelect(this.miokosSong);

expect(this.whisperer.isDishonored).toBe(true);
expect(this.whisperer.hasKeyword('courtesy')).toBe(true);
expect(this.getChatLogs(5)).toContain('player1 uses Mioko\'s Song, bowing Mioko\'s Song and dishonoring Doji Whisperer to gain a fate and give Doji Whisperer Courtesy until the end of the round');
expect(this.player1.fate).toBe(fate + 1);
this.player1.clickCard(this.miokosSong);
expect(this.player1).toHavePrompt('Choose a province');

this.noMoreActions();
this.player1.clickCard(this.shamefulDisplay);
expect(this.player1).toHavePrompt('Select character to dishonor');
expect(this.player1).toBeAbleToSelect(this.yoshi);
expect(this.player1).not.toBeAbleToSelect(this.whisperer);

this.initiateConflict({
attackers: [this.whisperer],
defenders: []
this.player1.clickCard(this.yoshi);
expect(this.yoshi.isDishonored).toBe(true);
expect(this.getChatLogs(5)).toContain(
"player1 uses Mioko's Song, bowing Mioko's Song and dishonoring Kakita Yoshi to move Adept of the Waves and Aranat to player2's dynasty discard pile"
);
});
this.player2.clickCard(this.assassination);
this.player2.clickCard(this.whisperer);
expect(this.player1.fate).toBe(fate + 2);
expect(this.getChatLogs(5)).toContain('player1 gains a fate due to Doji Whisperer\'s Courtesy');
});
});
});

0 comments on commit 14a1fb1

Please sign in to comment.