It is currently 14 Oct 2019, 22:40
   
Text Size

Implementing Channel

Moderators: melvin, beholder, ubeefx, ShawnieBoy, Lodici, CCGHQ Admins

Implementing Channel

Postby ShawnieBoy » 02 Jul 2014, 17:27

I've begun working with the Channel ability word, starting with a simple card first, and have noticed something strange with the AI. Whichever AI I use, it always Channels as soon as it is possible, regardless of whether there's any valid target or not.

The main structure is pretty close to a Cycle activation, but can't work out why the AI's all excited about it...

Code: Select all
name=Shinen of Flight's Wings
image=http://mtgimage.com/card/shinen%20of%20flight%27s%20wings.jpg
value=2.500
rarity=C
type=Creature
subtype=Spirit
cost={4}{U}
pt=3/3
ability=Flying
timing=main
requires_groovy_code
oracle=Flying. Channel — {U}, Discard Shinen of Flight's Wings: Target creature gains flying until end of turn.
Code: Select all
[
    new MagicCardActivation(
        [MagicCondition.HAND_CONDITION],
        new MagicActivationHints(MagicTiming.Pump),
        "Channel"
    ) {
        @Override
        public Iterable<MagicEvent> getCostEvent(final MagicCard source) {
            return [
                new MagicPayManaCostEvent(source, "{U}"),
                new MagicDiscardSelfEvent(source)
            ];
        }
        @Override
        public MagicEvent getEvent(final MagicSource source) {
            return new MagicEvent(
                source,
                MagicTargetChoice.POS_TARGET_CREATURE,
                MagicFlyingTargetPicker.create(),
                this,
                "Target creature\$ gains flying until end of turn."
            );
        }
        @Override
        public void executeEvent(final MagicGame game, final MagicEvent event) {
            event.processTargetPermanent(game,{
                final MagicPermanent creature ->
                game.doAction(new MagicGainAbilityAction(creature,MagicAbility.Flying));
            });
        }
    }
]
Also, as a future note - it will be a bit tricky to get this in the MagicAbility list (I know Channel isn't a keyword ability), as the text it would have to match would be the 'Discard SN' cost, which could possibly pop up on other cards doing different things.
User avatar
ShawnieBoy
Programmer
 
Posts: 601
Joined: 02 Apr 2012, 22:42
Location: UK
Has thanked: 80 times
Been thanked: 50 times

Re: Implementing Channel

Postby melvin » 03 Jul 2014, 02:00

There are a couple of issue.
1) The getEvent method should return an event that puts the ability on the stack, eg MagicNinjutsuActivation.java

2) getChoice method is missing so the engine is unable to tell that the effect requires a target choice, hence it allowed it to be cast when there is no valid target

I've added MagicChannelActivation that implements getCostEvent, getEvent, getChoice, cards only need extend this by implementing the getCardEvent and executeEvent methods. See https://code.google.com/p/magarena/sour ... 5d5484a401
User avatar
melvin
AI Programmer
 
Posts: 1057
Joined: 21 Mar 2010, 12:26
Location: Singapore
Has thanked: 36 times
Been thanked: 456 times

Re: Implementing Channel

Postby ShawnieBoy » 06 Jul 2014, 13:57

I've also been looking at Madness:

Code: Select all
name=Ichor Slick
image=http://mtgimage.com/card/ichor%20slick.jpg
value=2.500
rarity=C
type=Sorcery
cost={2}{B}
effect=Target creature gets -3/-3 until end of turn.
ability=Cycling {2}
timing=main
requires_groovy_code
oracle=Target creature gets -3/-3 until end of turn. Cycling {2}. Madness {3}{B}
Code: Select all
[   
    new MagicWhenPutIntoGraveyardTrigger(MagicTrigger.REPLACEMENT) {
        @Override
        public MagicEvent executeTrigger(final MagicGame game, final MagicPermanent permanent, final MagicMoveCardAction act) {
            final MagicCard card = act.card;
            return act.from(MagicLocationType.OwnersHand) && act.to(MagicLocationType.Graveyard) ?
                new MagicEvent(
                    permanent,
                    new MagicMayChoice(
                        new MagicPayManaCostChoice(MagicManaCost.create("{3}{B}"))
                    ),
                    card,
                    this,
                    "PN may\$ cast SN for its madness cost instead of putting it into his or her graveyard."
                ):
            MagicEvent.NONE;
        }
        @Override
        public void executeEvent(final MagicGame game, final MagicEvent event) {
            if (event.isYes()) {
                game.doAction(new MagicPlayCardAction(event.getRefCard()));
            }
        }
    }
]
I'm assuming that the problem is that the Cycling and Discard actions don't reference a game for the executeEvent to refer to.

Essentially Madness is a discard replacement effect with an optional mana payment choice. Discard occurs normally unless the payment is payed, then the card is played instead.
User avatar
ShawnieBoy
Programmer
 
Posts: 601
Joined: 02 Apr 2012, 22:42
Location: UK
Has thanked: 80 times
Been thanked: 50 times

Re: Implementing Channel

Postby melvin » 07 Jul 2014, 01:15

Your implementation looks reasonable. What is the issue you are facing?
User avatar
melvin
AI Programmer
 
Posts: 1057
Joined: 21 Mar 2010, 12:26
Location: Singapore
Has thanked: 36 times
Been thanked: 456 times

Re: Implementing Channel

Postby ShawnieBoy » 07 Jul 2014, 13:19

melvin wrote:Your implementation looks reasonable. What is the issue you are facing?
Oh yes, that would have helped...

Having a return of MagicEvent.NONE doesn't quite work as it's a replacement trigger. And as the card is in the process of going to the graveyard you get this:

Code: Select all
Exception from controller.runGame: getGame called for MagicPlayer.NONE
java.lang.RuntimeException: getGame called for MagicPlayer.NONE
   at magic.model.MagicPlayer$1.getGame(MagicPlayer.java:41)
   at magic.model.MagicPermanent.getGame(MagicPermanent.java:342)
   at magic.model.stack.MagicItemOnStack.<init>(MagicItemOnStack.java:37)
   at magic.model.stack.MagicItemOnStack.<init>(MagicItemOnStack.java:41)
   at magic.model.stack.MagicTriggerOnStack.<init>(MagicTriggerOnStack.java:13)
   at magic.model.MagicGame.executeTrigger(MagicGame.java:1267)
   at magic.model.action.MagicMoveCardAction.doAction(MagicMoveCardAction.java:69)
   at magic.model.MagicGame.doAction(MagicGame.java:525)
   at magic.model.action.MagicDiscardCardAction.doAction(MagicDiscardCardAction.java:31)
   at magic.model.MagicGame.doAction(MagicGame.java:525)
   at magic.model.event.MagicDiscardSelfEvent$1.executeEvent(MagicDiscardSelfEvent.java:20)
   at magic.model.event.MagicEvent.executeEvent(MagicEvent.java:631)
   at magic.model.MagicGame.executeEvent(MagicGame.java:722)
   at magic.model.action.MagicExecuteFirstEventAction.doAction(MagicExecuteFirstEventAction.java:18)
   at magic.model.MagicGame.doAction(MagicGame.java:525)
   at magic.model.MagicGame.executeNextEvent(MagicGame.java:751)
   at magic.ai.MCTSAI.getNextChoices(MCTSAI.java:469)
   at magic.ai.MCTSAI.randomPlay(MCTSAI.java:432)
   at magic.ai.MCTSAI.access$000(MCTSAI.java:62)
   at magic.ai.MCTSAI$2.run(MCTSAI.java:199)
   at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
   at java.util.concurrent.FutureTask.run(Unknown Source)
   at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
   at java.lang.Thread.run(Unknown Source)
Returning the original trigger back with
Code: Select all
: act;
doesn't quite work either, as it's an action not an event.
The MagicGame needs to be returned, but the DiscardCardAction, which is the most likely cause of a card going from your hand to your graveyard, doesn't refer to a MagicGame.
User avatar
ShawnieBoy
Programmer
 
Posts: 601
Joined: 02 Apr 2012, 22:42
Location: UK
Has thanked: 80 times
Been thanked: 50 times

Re: Implementing Channel

Postby melvin » 08 Jul 2014, 00:30

Returning MagicEvent.NONE is always allowed in triggers, even for replacements.

Rather the issue is probably due to the following:
Code: Select all
new MagicEvent(
  permanent,
  ...
)
permanent could be MagicPermanent.NONE in some cases such as discard from hand to graveyard where there is no permanent involved. Better of using the card as the source of the event.

MagicPermanent.NONE has as its controller MagicPlayer.NONE, hence it failed during the call to getGame(MagicPermanent.java:342) because you're not suppose to call getGame on MagicPlayer.NONE. The message is a bit misleading, will fix it so that it says "getGame called for MagicPermanent.NONE"
User avatar
melvin
AI Programmer
 
Posts: 1057
Joined: 21 Mar 2010, 12:26
Location: Singapore
Has thanked: 36 times
Been thanked: 456 times

Re: Implementing Channel

Postby ShawnieBoy » 08 Jul 2014, 01:15

I was assuming that the trigger event had to refer to a permanent, even if there isn't one present. All the MagicWhenPutIntoGraveyardTrigger executeTriggers refer to permanents.

Thanks for that :)
User avatar
ShawnieBoy
Programmer
 
Posts: 601
Joined: 02 Apr 2012, 22:42
Location: UK
Has thanked: 80 times
Been thanked: 50 times

Re: Implementing Channel

Postby ShawnieBoy » 08 Jul 2014, 01:47

I think this could be the basis for a new Ability :)

Code: Select all
[   
    new MagicWhenPutIntoGraveyardTrigger(MagicTrigger.REPLACEMENT) {
        @Override
        public MagicEvent executeTrigger(final MagicGame game, final MagicPermanent permanent, final MagicMoveCardAction act) {
            final MagicCard card = act.card;
            return act.from(MagicLocationType.OwnersHand) && act.to(MagicLocationType.Graveyard) ?
                new MagicEvent(
                    card,
                    new MagicMayChoice(
                        new MagicPayManaCostChoice(MagicManaCost.create("{3}{B}"))
                    ),
                    card,
                    this,
                    "PN may\$ cast SN for its madness cost instead of putting it into his or her graveyard."
                ):
            MagicEvent.NONE;
        }
        @Override
        public void executeEvent(final MagicGame game, final MagicEvent event) {
            if (event.isYes()) {
                final MagicCardOnStack cardOnStack=new MagicCardOnStack(event.getRefCard(),event.getPlayer(),MagicPayedCost.NO_COST);
                game.doAction(new MagicPutItemOnStackAction(cardOnStack));
            }
        }
    }
]
Although I've just noticed that two cards hit the graveyard if Ichor Slick is cycled. It hits the graveyard before paying the mana to cycle it, and the 'May cast' from Madness is on the stack. Still need it to be 'held in limbo' before deciding where it should go.
User avatar
ShawnieBoy
Programmer
 
Posts: 601
Joined: 02 Apr 2012, 22:42
Location: UK
Has thanked: 80 times
Been thanked: 50 times

Re: Implementing Channel

Postby melvin » 08 Jul 2014, 03:59

It should be exiled if madness cost is paid.

Madness =
"If a player would discard this card, that player discards it, but may exile it instead of putting it into his or her graveyard" and
"When this card is exiled this way, its owner may cast it by paying [cost] rather than paying its mana cost. If that player doesn‘t, he or she puts this card into his or her graveyard."
User avatar
melvin
AI Programmer
 
Posts: 1057
Joined: 21 Mar 2010, 12:26
Location: Singapore
Has thanked: 36 times
Been thanked: 456 times

Re: Implementing Channel

Postby ShawnieBoy » 08 Jul 2014, 04:27

I was assuming that the replacement to the card movement to the graveyard would prevent the card going to the graveyard at all. Looking at this, it seems as though the replacement isn't replacing anything, just adding to it.

And yes, forgot that Madness was edited in Time Spiral block >.<
User avatar
ShawnieBoy
Programmer
 
Posts: 601
Joined: 02 Apr 2012, 22:42
Location: UK
Has thanked: 80 times
Been thanked: 50 times

Re: Implementing Channel

Postby melvin » 08 Jul 2014, 10:36

Replacement triggers can modify the destination with act.setLocation, if not the move action takes place unchanged.
User avatar
melvin
AI Programmer
 
Posts: 1057
Joined: 21 Mar 2010, 12:26
Location: Singapore
Has thanked: 36 times
Been thanked: 456 times

Re: Implementing Channel

Postby ShawnieBoy » 08 Jul 2014, 13:36

Hmm, looking at it, and not seeing a simple way of passing information about an action from an event. I may be better to always assume that the first may choice is always yes (As in, "Do you want to put this card into exile instead of your graveyard?") The results from the second choice still result in the only two possible results: It goes to the graveyard; or it it cast.
User avatar
ShawnieBoy
Programmer
 
Posts: 601
Joined: 02 Apr 2012, 22:42
Location: UK
Has thanked: 80 times
Been thanked: 50 times

Re: Implementing Channel

Postby ShawnieBoy » 08 Jul 2014, 14:29

Ok, think I've cracked it:
Code: Select all
[   
    new MagicWhenPutIntoGraveyardTrigger(MagicTrigger.REPLACEMENT) {
        @Override
        public MagicEvent executeTrigger(final MagicGame game, final MagicPermanent permanent, final MagicMoveCardAction act) {
            if (act.from(MagicLocationType.OwnersHand) && act.to(MagicLocationType.Graveyard)) {
                game.executeTrigger(MagicTriggerType.WhenOtherPutIntoGraveyard, act); //Activate discard triggers
                act.setToLocation(MagicLocationType.Exile); //Change discard location
                final MagicCard card = act.card;
                return new MagicEvent(
                    card,
                    new MagicMayChoice(
                        new MagicPayManaCostChoice(MagicManaCost.create("{3}{B}"))
                    ),
                    card,
                    this,
                    "PN may\$ cast SN for its madness cost instead of putting it into his or her graveyard."
                );
            } else {
                return MagicEvent.NONE;
            }
        }
        @Override
        public void executeEvent(final MagicGame game, final MagicEvent event) {
            game.doAction(new MagicRemoveCardAction(event.getRefCard(),MagicLocationType.Exile));
            if (event.isYes()) {
                final MagicCardOnStack cardOnStack=new MagicCardOnStack(event.getRefCard(),event.getPlayer(),MagicPayedCost.NO_COST);
                game.doAction(new MagicPutItemOnStackAction(cardOnStack));
            } else {
                game.doAction(new MagicMoveCardAction(event.getRefCard(),MagicLocationType.Exile,MagicLocationType.Graveyard));
            }
        }
    }
]

User avatar
ShawnieBoy
Programmer
 
Posts: 601
Joined: 02 Apr 2012, 22:42
Location: UK
Has thanked: 80 times
Been thanked: 50 times


Return to Development

Who is online

Users browsing this forum: No registered users and 1 guest


Who is online

In total there is 1 user online :: 0 registered, 0 hidden and 1 guest (based on users active over the past 10 minutes)
Most users ever online was 287 on 31 Mar 2019, 04:11

Users browsing this forum: No registered users and 1 guest

Login Form