Implementing Channel
Moderators: ubeefx, beholder, melvin, ShawnieBoy, Lodici, CCGHQ Admins
Implementing Channel
by 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...
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));
});
}
}
]
-
ShawnieBoy - Programmer
- Posts: 601
- Joined: 02 Apr 2012, 22:42
- Location: UK
- Has thanked: 80 times
- Been thanked: 50 times
Re: Implementing Channel
by 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
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
-
melvin - AI Programmer
- Posts: 1062
- Joined: 21 Mar 2010, 12:26
- Location: Singapore
- Has thanked: 36 times
- Been thanked: 459 times
Re: Implementing Channel
by ShawnieBoy » 06 Jul 2014, 13:57
I've also been looking at Madness:
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.
- 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()));
}
}
}
]
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.
-
ShawnieBoy - Programmer
- Posts: 601
- Joined: 02 Apr 2012, 22:42
- Location: UK
- Has thanked: 80 times
- Been thanked: 50 times
Re: Implementing Channel
by melvin » 07 Jul 2014, 01:15
Your implementation looks reasonable. What is the issue you are facing?
-
melvin - AI Programmer
- Posts: 1062
- Joined: 21 Mar 2010, 12:26
- Location: Singapore
- Has thanked: 36 times
- Been thanked: 459 times
Re: Implementing Channel
by ShawnieBoy » 07 Jul 2014, 13:19
Oh yes, that would have helped...melvin wrote:Your implementation looks reasonable. What is the issue you are facing?
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)
- Code: Select all
: act;
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.
-
ShawnieBoy - Programmer
- Posts: 601
- Joined: 02 Apr 2012, 22:42
- Location: UK
- Has thanked: 80 times
- Been thanked: 50 times
Re: Implementing Channel
by 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:
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"
Rather the issue is probably due to the following:
- Code: Select all
new MagicEvent(
permanent,
...
)
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"
-
melvin - AI Programmer
- Posts: 1062
- Joined: 21 Mar 2010, 12:26
- Location: Singapore
- Has thanked: 36 times
- Been thanked: 459 times
Re: Implementing Channel
by 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![Smile :)](./images/smilies/icon_smile.gif)
Thanks for that
![Smile :)](./images/smilies/icon_smile.gif)
-
ShawnieBoy - Programmer
- Posts: 601
- Joined: 02 Apr 2012, 22:42
- Location: UK
- Has thanked: 80 times
- Been thanked: 50 times
Re: Implementing Channel
by ShawnieBoy » 08 Jul 2014, 01:47
I think this could be the basis for a new Ability ![Smile :)](./images/smilies/icon_smile.gif)
![Smile :)](./images/smilies/icon_smile.gif)
- 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));
}
}
}
]
-
ShawnieBoy - Programmer
- Posts: 601
- Joined: 02 Apr 2012, 22:42
- Location: UK
- Has thanked: 80 times
- Been thanked: 50 times
Re: Implementing Channel
by 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."
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."
-
melvin - AI Programmer
- Posts: 1062
- Joined: 21 Mar 2010, 12:26
- Location: Singapore
- Has thanked: 36 times
- Been thanked: 459 times
Re: Implementing Channel
by 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 >.<
And yes, forgot that Madness was edited in Time Spiral block >.<
-
ShawnieBoy - Programmer
- Posts: 601
- Joined: 02 Apr 2012, 22:42
- Location: UK
- Has thanked: 80 times
- Been thanked: 50 times
Re: Implementing Channel
by melvin » 08 Jul 2014, 10:36
Replacement triggers can modify the destination with act.setLocation, if not the move action takes place unchanged.
-
melvin - AI Programmer
- Posts: 1062
- Joined: 21 Mar 2010, 12:26
- Location: Singapore
- Has thanked: 36 times
- Been thanked: 459 times
Re: Implementing Channel
by 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.
-
ShawnieBoy - Programmer
- Posts: 601
- Joined: 02 Apr 2012, 22:42
- Location: UK
- Has thanked: 80 times
- Been thanked: 50 times
Re: Implementing Channel
by 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));
}
}
}
]
-
ShawnieBoy - Programmer
- Posts: 601
- Joined: 02 Apr 2012, 22:42
- Location: UK
- Has thanked: 80 times
- Been thanked: 50 times
13 posts
• Page 1 of 1
Who is online
Users browsing this forum: No registered users and 4 guests