It is currently 16 Apr 2024, 17:45
   
Text Size

Card Development Questions

Post MTG Forge Related Programming Questions Here

Moderators: timmermac, Blacksmith, KrazyTheFox, Agetian, friarsol, CCGHQ Admins

Re: Card Development Questions

Postby Marek14 » 27 Jun 2016, 09:16

BTW, Hanmac, what is your opinion about including more info in the card detail panel?

At this point, we have storm count display on storm cards. I think that numerous other cards could use extra info.

For example, a list of card types in graveyard on Tarmogoyf, so you could see whether Lightning Bolt kills it or not without opening up all graveyard.
Marek14
Tester
 
Posts: 2759
Joined: 07 Jun 2008, 07:54
Has thanked: 0 time
Been thanked: 296 times

Re: Card Development Questions

Postby Hanmac » 27 Jun 2016, 09:38

@swordshine okay i need to test it with Control Magic and Humility ...
what are the expected Effects?

PS: i did the Oyster with Effect before i did try it like the Rust Tick without:
Code: Select all
Name:Giant Oyster
ManaCost:2 U U
Types:Creature Oyster
PT:0/3
K:You may choose not to untap CARDNAME during your untap step.
A:AB$ Effect | Cost$ T | ValidTgts$ Creature.tapped | TgtPrompt$ Select target tapped creature | IsCurse$ True | RememberObjects$ Targeted | ImprintCards$ Self | Duration$ Permanent | Triggers$ DrawPhase,Untaps,LeavesPlay,OutOfSight | StaticAbilities$ StayTapped | SVars$ DrawPhase,Untaps,LeavesPlay,OutOfSight,TrigCounter,StayTapped,RemoveCounters,ExileEffect | References$ DrawPhase,Untaps,LeavesPlay,OutOfSight,TrigCounter,StayTapped,RemoveCounters,ExileEffect | SpellDescription$ For as long as CARDNAME remains tapped, target tapped creature doesn't untap during its controller's untap step, and at the beginning of each of your draw steps, put a -1/-1 counter on that creature.

SVar:DrawPhase:Mode$ Phase | Phase$ Draw | ValidPlayer$ You | TriggerZones$ Command | Execute$ TrigCounter | TriggerDescription$ At the beginning of each of your draw steps, put a -1/-1 counter on that creature.
SVar:TrigCounter:DB$ PutCounter | Defined$ Remembered | CounterType $ M1M1 | CounterNum$ 1 | IsCurse$ True

SVar:StayTapped:Mode$ Continuous | EffectZone$ Command | Affected$ Creature.IsRemembered | AddHiddenKeyword$ CARDNAME doesn't untap during your untap step.

SVar:Untaps:Mode$ Untaps | ValidCard$ Card.IsImprinted | Execute$ RemoveCounters | Static$ True | TriggerDescription$ When CARDNAME leaves the battlefield or becomes untapped, remove all -1/-1 counters from the creature.

SVar:OutOfSight:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.IsRemembered | Execute$ ExileEffect | Static$ True

SVar:LeavesPlay:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.IsImprinted | Execute$ RemoveCounters | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME leaves the battlefield or becomes untapped, remove all -1/-1 counters from the creature.

SVar:RemoveCounters:DB$ RemoveCounter | Defined$ Card.IsRemembered | CounterType$ M1M1 | CounterNum$ All | SubAbility$ ExileEffect

SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile

SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/giant_oyster.jpg
Oracle:You may choose not to untap Giant Oyster during your untap step.\n{T}: For as long as Giant Oyster remains tapped, target tapped creature doesn't untap during its controller's untap step, and at the beginning of each of your draw steps, put a -1/-1 counter on that creature. When Giant Oyster leaves the battlefield or becomes untapped, remove all -1/-1 counters from the creature.
maybe it might be a better way ?

Also swordshine, what do you think about my idea with doing that stuff with ExiledWithSource for Linked Abilities and later for all cards with exile?

===
@Marek14:
yeah showing the List of Types in the Graveyard might be interesting, also for Delirium cards.
Same is True for Spell Mastery.

what i would like is if the CardTypes line is to long, that it does show them on mousehover (might work with title attribute)

hey what about showing other stuff in the card detail panel? like for the Edition a Symbol?
like from viewtopic.php?f=15&t=7010
Hanmac
 
Posts: 954
Joined: 06 May 2013, 18:44
Has thanked: 229 times
Been thanked: 158 times

Re: Card Development Questions

Postby Marek14 » 27 Jun 2016, 09:52

Hanmac wrote:===
@Marek14:
yeah showing the List of Types in the Graveyard might be interesting, also for Delirium cards.
Same is True for Spell Mastery.

what i would like is if the CardTypes line is to long, that it does show them on mousehover (might work with title attribute)

hey what about showing other stuff in the card detail panel? like for the Edition a Symbol?
like from viewtopic.php?f=15&t=7010
For Giant Oyster:
Change of control in Giant Oyster wouldn't end the effect (that ends on leaving the battlefield or untap), and the control of the effect stays the same -- so until the Oyster untaps, the effect would still trigger in your draw step.
Humility also shouldn't affect Giant Oyster's effect -- the only thing that happens would be that you couldn't use the ability again. Both delayed triggers created by Giant Oyster should still work, even under Humility.

Now, as for showing edition symbols, I don't personally think it's needed since I play with images, and I actually find it better to see the set code than the symbol -- there is lots of sets and some symbols (like of various commander sets) are a bit hard to remember.

I think that card detail panel should contain any information that can't be normally seen by a casual look on the game window.

For example, cards like Courser of Kruphix used to show top card of your library on mousing over. This was removed when new windows were implemented so you can open your library with a click and see the top card, if visible, but I believe it was a mistake to remove the functionality. If the information is visible in two places, that's not bad in any way.
Marek14
Tester
 
Posts: 2759
Joined: 07 Jun 2008, 07:54
Has thanked: 0 time
Been thanked: 296 times

Re: Card Development Questions

Postby swordshine » 27 Jun 2016, 10:23

Thank you Marek for explaining the issues in Giant Oyster :D
@Hanmac
I would be very happy to see Linked Abilities to be implemented. "ExiledWithSource" is a good idea, though I'm not quite sure if cards like Quicksilver Elemental can work well with Linked Abilities.
swordshine
 
Posts: 682
Joined: 11 Jul 2010, 02:37
Has thanked: 116 times
Been thanked: 87 times

Re: Card Development Questions

Postby Hanmac » 27 Jun 2016, 10:56

i will look about the Oyster later ... i am currently reworking many other cards because of ExiledWithSource and other bugs i found.

(like Disciple of Deceit does not work for split cards, i rewrote some stuff so i can use "Card.SharesCMCWith Discarded", but now i found that sharesCMCWith had a problem when a split card is discarded ... the card state was wrong, so i needed to get it from game)

same for Thought Prison also didn't respect split cards. (i used "shares" or "same converted mana cost" with excluding Transmute because that does not appear on split Cards)

hm i need to see how i will commit that massive changes ;P

===

@swordshine: about Linked Abilites: i did test it with Gain Abilities like from Experiment Kraj and Myr Welder.

i also did test it with Cloner like Dimir Doppelganger.
i don't know if its Intended to do, but when i have a Doppelganger transform into something it does Forget all other stuff from it was before.
That is good in my case so the second Card the clone does turn into can not access other cards exiled by the previous version.

about Quicksilver Elemental, that does have other problems too (with spending mana) so its not that problematic if it gets another problem ...
then we might need to find another way to combine the effects :/

hm might be useful for when a card does gain abilities so see where they do come from. (so see which are maybe linked)

PS: hm while i do rewrite some stuff, someone else might look at the "May Play" where Source of the Effect are somehow get set (and be seen in the GUI)
then we can add Trigger (or extend existing ones) which does react to that when a "May Play" is used and can do a once per turn effect.
Hanmac
 
Posts: 954
Joined: 06 May 2013, 18:44
Has thanked: 229 times
Been thanked: 158 times

Re: Card Development Questions

Postby Marek14 » 27 Jun 2016, 12:44

For Dimir Doppelganger, it's OK if it forgets everything when it changes -- its changes are irreversible, so the remembered stuff can be never relevant again (unless it's something like Banisher Priest, but there the return is governed by an effect, not by an ability of the card).

A worse case is Cytoshape -- if you change, say, Fiend Hunter into something else (even another Fiend Hunter!) it will lose access to its remembered card and won't return it if it dies, but once the effect passes, his memory will return.

EDIT: OK, Hanmac, seems you've chosen the worst possible moment to work on the exiling cards :) They will now apparently need some meldproofing.
Marek14
Tester
 
Posts: 2759
Joined: 07 Jun 2008, 07:54
Has thanked: 0 time
Been thanked: 296 times

Re: Card Development Questions

Postby Hanmac » 02 Jul 2016, 12:42

oh i didn't see your comment about Meld ;P
yeah it had reasons why i try to finish some of my work BEFORE we try to implement Meld.

i am still not sure what would be the best way to implement Meld.
Probably a property like "getMeldComponents" if we do the Meld as a third card instead of Alternate State.
(also Meld is not Transform, so we need to prevent such cards from Transforming)

i added my stuff for ExiledWith to the java classes, i will add the updated scripts later. (i did wrote it that it doesn't break existing cards)

i also added StrictlySelf for stuff like the Tree of Redemption,
hm while i think about it, we might need to add some AI logic, for this and the other Tree. (like for the green one, only switch if the toughness are greater than AI's life, and AI can gain life.)
HM i noticed there is already some AI it, but imo its not perfect yet ... (like to add checks if AI's can gain life, or its life can't change, or life is turned into life lost)
Also the new tree might then some logic for cards that does turn lifegain into life lost. (to hurt the opponent) hm i also would like to add some special case for Triskaidekaphobia, hm i wish that card would get some AI too)

===

Marek14 wrote:OK, changed it. If this is correct, Abomination of Gudul should be changed in the same way.

The same incorrect structure is used on Force Away, Izzet Keyrune, Jeskai Ascendancy, Jeskai Elder, Mask of Memory, Murder of Crows and Salvage Drone.
i updated the code of the existing cards to add the Optional to the Trigger instead of the carddraw ... but that didn't work for Force Away because that is no trigger :/

someone an idea to do it better?

===
hm i might checkout if i can do the Emerge stuff ... i want to try it similar than offering .. and i might reuse some of the variables.
Hanmac
 
Posts: 954
Joined: 06 May 2013, 18:44
Has thanked: 229 times
Been thanked: 158 times

Re: Card Development Questions

Postby Hanmac » 12 Jul 2016, 18:57

While my recent changes and card fixes, i got the thinking, why not add AI for Triskaidekaphobia? if someone is interested to see that code, i will post it there so you guys can say something about it and comment it before i commit it.

Thats the Chunk from my CharmAI part.

Code: Select all
if ("Triskaidekaphobia".equals(sa.getHostCard().getName())) {
    AbilitySub gain = choices.get(0);
    AbilitySub lose = choices.get(1);
    FCollection<Player> opponents = ai.getOpponents();

    boolean oppTainted = false;
    boolean allyTainted = ai.isCardInPlay("Tainted Remedy");
    final int aiLife = ai.getLife();

    //Check if Opponent controls Tainted Remedy
    for (Player p : opponents) {
        if (p.isCardInPlay("Tainted Remedy")) {
            oppTainted = true;
            break;
        }
    }
    // if ai or ally of ai does control Tainted Remedy, prefer gain life instead of lose
    if (!allyTainted) {
        for (Player p : ai.getAllies()) {
            if (p.isCardInPlay("Tainted Remedy")) {
                allyTainted = true;
                break;
            }
        }
    }
   
    if (!ai.canLoseLife() || ai.cantLose()) {
        // ai cant lose life, or cant lose the game, don't think about others
        chosenList.add(allyTainted ? gain : lose);
    } else if (oppTainted || ai.getGame().isCardInPlay("Rain of Gore")) {
        // Rain of Gore does negate lifegain, so don't benefit the others
        // same for if a oppoent does control Tainted Remedy
        // but if ai cant gain life, the effects are negated
        chosenList.add(ai.canGainLife() ? lose : gain);
    } else if (ai.getGame().isCardInPlay("Sulfuric Vortex")) {
        // no life gain, but extra life loss.
        if (aiLife >= 17)
            chosenList.add(lose);
        // try to prevent to get to 13 with extra lose
        else if (aiLife < 13 || ((aiLife - 13) % 2) == 1) {
            chosenList.add(gain);
        } else {
            chosenList.add(lose);
        }
    } else if (ai.canGainLife() && aiLife <= 5) {
        // critical Life try to gain more
        chosenList.add(gain);
    } else if(!ai.canGainLife() && aiLife == 14 ) {
        // ai cant gain life, but try to avoid falling to 13
        // but if a oppoent does control Tainted Remedy its irrelevant
        chosenList.add(oppTainted ? lose : gain);
    } else if (allyTainted) {
        // Tainted Remedy negation logic, try gain instead of lose
        // because negation does turn it into lose for opponents
        boolean oppCritical = false;
        // an oppoent is Critical = 14, and can't gain life, try to lose life instead
        // but only if ai doesn't kill itself with that.
        if (aiLife != 14) {
            for (Player p : opponents) {
                if (p.getLife() == 14 && !p.canGainLife() && p.canLoseLife()) {
                    oppCritical = true;
                    break;
                }
            }
        }
        chosenList.add(aiLife == 12 || oppCritical ? lose : gain);
    } else {
        // normal logic, try to gain life if its critical
        boolean oppCritical = false;
        // an oppoent is Critical = 12, and can gain life, try to gain life instead
        // but only if ai doesn't kill itself with that.
        if (aiLife != 12) {
            for (Player p : opponents) {
                if (p.getLife() == 12 && p.canGainLife()) {
                    oppCritical = true;
                    break;
                }
            }
        }
        chosenList.add(aiLife == 14 || aiLife <= 10 || oppCritical ? gain : lose);
    }
    return chosenList;
}
i did try to add comments into the code so its more understandable what the code is doing. i did checks for current Life of ai and opponents, also did checks if they can gain life, or lose life. (Life for example if AI can't lose life or can't lose the game, just do the life loss).
I did try to apply the great cards that does interact with life into the thinking.

  • Tainted Remedy good for me if on my side, bad for opponents (might improve the logic to check if opponent is also opponent of the ally with that card)
  • Rain of Gore is bad on the field, because it does turn my life gain into life loss. Thats why only choose life loss if its on the field.
  • Sulfuric Vortex instead of a static ability, that is a replacement effect. (so its not checked for ai.canGainLife) but because the Sulfur Vortex is not SO bad when trying to gain life, try it if life are low. (no life gain is still better than life loss) BUT because the Vortex does still deals damage itself, try to prevent to get hit by it and fall to 13.

PS: i was wondering, if Charm is the right API type and if something like GenericChoice wouldn't be better?
Hanmac
 
Posts: 954
Joined: 06 May 2013, 18:44
Has thanked: 229 times
Been thanked: 158 times

Re: Card Development Questions

Postby friarsol » 12 Jul 2016, 19:28

Hanmac wrote:PS: i was wondering, if Charm is the right API type and if something like GenericChoice wouldn't be better?
It's not generic choice. You have to choose the mode as it goes on the stack, so it's a Charm.
friarsol
Global Moderator
 
Posts: 7593
Joined: 15 May 2010, 04:20
Has thanked: 243 times
Been thanked: 965 times

Re: Card Development Questions

Postby mcrawford620 » 14 Jul 2016, 04:01

Does the AI not do well with "Tap a creature you control" abilities? I see several but not all of the card scripts with abilities like that marked as RemAIDeck.

I.e., Ondu War Cleric or Spawnbinder Mage (marked as RemAIDeck) versus Malakir Soothsayer or Stoneforge Acolyte (not so marked).

I'm just wondering if they should all be marked that way or not. I don't see anything else in the cards that would be tough for the AI, but I can see how knowing when to tap your own guys could be tricky.
mcrawford620
 
Posts: 112
Joined: 25 Jun 2012, 16:59
Has thanked: 55 times
Been thanked: 25 times

Re: Card Development Questions

Postby Hanmac » 25 Jul 2016, 10:57

i added CostAdjustment class similar to ManaCostAdjustment.

And i want you other guys look at this.

Difference:
  • CostAdjustment works inside CostPayment on a Cost object with CostPart objects before they are paid.
  • ManaCostAdjustment works with ManaCostBeingPaid and with ManaCost object.

what can it do:
  • different from ManaCostAdjustment, it can raise the cost with NonManaCosts like "Sacrifice a Swamp"
  • has logic for ForEachShard, which does look at the ManaSymbols of the Cost.
  • has logic for Strive and Escalate to be easier calculated. (that might help when i later turn them into better keywords)

what it can't yet:
  • there is no current way to reduce the Cost yet. (even if its only ManaCosts, non-ManaCosts should not be reduced)
  • special cost reduction like Offering, Emerge, Delve and Convoke probably need to reworked because i did see massive code duplication that i can't explain yet.

because of the con points it currently listens to RaiseCost2 because it didn't replaced ManaCostAdjustment yet.

===
while looking i don't know but Stuff like MultiKicker and Replicate might be turned into RaiseCost later too.

one of the cards i did thanks to it. (using ForEachShard)
Code: Select all
Name:Drought
ManaCost:2 W W
Types:Enchantment
K:At the beginning of your upkeep, sacrifice CARDNAME unless you pay W W
S:Mode$ RaiseCost2 | Type$ Spell | ForEachShard$ Black | Cost$ Sac<1/Swamp> | EffectZone$ All | Description$ Spells cost an additional "Sacrifice a Swamp" to cast for each black mana symbol in their mana costs.
S:Mode$ RaiseCost2 | Type$ Ability | ForEachShard$ Black | Cost$ Sac<1/Swamp> | EffectZone$ All | Description$ Activated abilities cost an additional "Sacrifice a Swamp" to activate for each black mana symbol in their activation costs.
SVar:Picture:http://www.wizards.com/global/images/magic/general/drought.jpg
Oracle:At the beginning of your upkeep, sacrifice Drought unless you pay {W}{W}.\nSpells cost an additional "Sacrifice a Swamp" to cast for each black mana symbol in their mana costs.\nActivated abilities cost an additional "Sacrifice a Swamp" to activate for each black mana symbol in their activation costs.
Hanmac
 
Posts: 954
Joined: 06 May 2013, 18:44
Has thanked: 229 times
Been thanked: 158 times

Re: Card Development Questions

Postby friarsol » 25 Jul 2016, 12:36

How temporary is it? RaiseCost2 is pretty clunky of a name. Wouldn't it work just as fine to use 'RaiseCost' and if it doesn't have a 'Cost' param to skip it? Then we can start migrating things over from ManaCostAdjustment to just CostAdjustment we would just need a way to streamline the differences between existing RaiseCosts and new ones? Potentially a good way to do that is to change the existing 'Color' param to 'Mana' since that would be more descriptive of what it's doing, and would put a nice partition between the old style and the new one for quick determination when it'll be run.

But you seem to be making lots of good progress in different areas, this should certainly give us a handful more cards.
friarsol
Global Moderator
 
Posts: 7593
Joined: 15 May 2010, 04:20
Has thanked: 243 times
Been thanked: 965 times

Re: Card Development Questions

Postby Hanmac » 25 Jul 2016, 12:49

hm i wanted to use RaiseCost2 so i don't break anything yet,
because for now i have no idea how to add the ReduceCost into it.

i changed it to RaiseCost, but do look for "Cost" parameter.

i think we can migrade the RaiseCost stuff, and for the others we might see how we can do them ... if i got no better idea, i have CostAdjustment absorb the stuff for reduce and Set cost into a different method.
(so one method for the raise stuff, and one for the other stuff)

for example Strive can look like this until i make a keyword for it:
Code: Select all
S:Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Amount$ Strive | Cost$ 2 G | EffectZone$ All | Description$ Strive - CARDNAME cost {2}{G} more to cast for each target beyond the first.
A
PS: i love Keywords ;P
Hanmac
 
Posts: 954
Joined: 06 May 2013, 18:44
Has thanked: 229 times
Been thanked: 158 times

Re: Card Development Questions

Postby Hanmac » 26 Jul 2016, 10:34

CostAdjustment did absorb ManaCostAdjustment.
i did keep the parameter the same for now, so no change on the cards needed.

so all cards should work the same, and the ones that did use Color for Raise are updated.

hm i might do port some more stuff to keywords like Affinity, but that's not important.
Hanmac
 
Posts: 954
Joined: 06 May 2013, 18:44
Has thanked: 229 times
Been thanked: 158 times

Re: Card Development Questions

Postby Hanmac » 27 Jul 2016, 08:13

Now its time i want to rework the MayPlay feature to add counting into it.
(for cards where the MayPlay effect can be only used once per turn, like Karador, Ghost Chieftain and Gisa and Geralf)

first i did some statistic:
grep "May be played" -R | wc -l #=> 49
grep "MayPlay" -R | wc -l #=> 25
grep "MayPlay" -R | grep "May be played" | wc -l #=> 5
means:
44 Cards use the "May be played" Keyword
20 Cards use the "MayPlay" Flag in StaticEffectContinuous
and 5 for WHATEVER REASON does use both. (i am surprised that Forge is not confused more?)

i think the best way would be to use only one of them, probably the "MayPlay" flag because its more powerful.

then i need to add some counting, i thought about adding it to StaticAbility or StaticEffect, which one would be better for that?

then i need to find the right place where the cast Options are created and selected. Because there i do need to output which Effect does cause this MayPlay option, and when this option is selected, then it should increase a counter somewhere.

Someone options about that and other ideas that might be helpful for me?
Hanmac
 
Posts: 954
Joined: 06 May 2013, 18:44
Has thanked: 229 times
Been thanked: 158 times

PreviousNext

Return to Developer's Corner

Who is online

Users browsing this forum: No registered users and 51 guests


Who is online

In total there are 51 users online :: 0 registered, 0 hidden and 51 guests (based on users active over the past 10 minutes)
Most users ever online was 4143 on 23 Jan 2024, 08:21

Users browsing this forum: No registered users and 51 guests

Login Form