It is currently 25 Apr 2024, 09:52
   
Text Size

Hypnotic Specter, Shadowmage Infiltrator, etc.

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

Hypnotic Specter, Shadowmage Infiltrator, etc.

Postby DennisBergkamp » 09 Oct 2008, 19:22

I finally managed to do this the right way, with a stacktrace. We can add tons and tons of new cards now :D

in Combat.java add the line "GameActionUtil.executePlayerCombatDamageEffects(att.get(i));", after the if, like so:
Code: Select all
public void setDefendingDamage()
  {
    defendingDamage = 0;
    CardList att = new CardList(getAttackers());
    //sum unblocked attackers' power
    for(int i = 0; i < att.size(); i++)
      if(! isBlocked(att.get(i))) {
        defendingDamage += att.get(i).getAttack();
        GameActionUtil.executePlayerCombatDamageEffects(att.get(i));
       
      }
  }
This will call the following method in GameActionUtil.java, which we will add there, and this method in turn calls pieces of code for the individual cards:

Code: Select all
public static void executePlayerCombatDamageEffects(Card c)
  {   
     if (c.getName().equals("Hypnotic Specter"))
        playerCombatDamage_Hypnotic_Specter(c);
     else if (c.getName().equals("Shadowmage Infiltrator") || c.getName().equals("Thieving Magpie"))
        playerCombatDamage_Shadowmage_Infiltrator(c);
          //etc. etc. etc. ... add more cards here .....


  }
 
  // ....

  private static void playerCombatDamage_Shadowmage_Infiltrator(Card c)
  {
     final String player = c.getController();
    
     Ability ability2 = new Ability(c, "0")
     {
        public void resolve()
        {
           //System.out.println("hello");
           AllZone.GameAction.drawCard(player);   
        }
     };//ability2
       
     ability2.setStackDescription(c.getName() + " - " + player + " draws a card.");
     AllZone.Stack.add(ability2);
        
  }
 
  private static void playerCombatDamage_Hypnotic_Specter(Card c)
  {
     final String player = c.getController();
     final String opponent = AllZone.GameAction.getOpponent(player);
              
    
     Ability ability = new Ability(c, "0")
     {
        public void resolve()
        {
           AllZone.GameAction.discardRandom(opponent);
        }
     };//ability
       
     ability.setStackDescription("Hypnotic Specter - " +opponent +" discards one card at random");
     AllZone.Stack.add(ability);
        
  }
User avatar
DennisBergkamp
AI Programmer
 
Posts: 2602
Joined: 09 Sep 2008, 15:46
Has thanked: 0 time
Been thanked: 0 time

Re: Hypnotic Specter, Shadowmage Infiltrator, etc.

Postby Jorbes » 10 Oct 2008, 10:52

Does this also take into consideration if there was 0 damage dealt?
Like when the attacker'ss power becomes 0 or when a card like "Fog" was played?

Also, you can't resolve the triggered ability untill after all combat damage has resolved and players have passed priority.

Right now, you seem to discard / draw cards during damage resolvement, but it's possible that a player can still prevent this event
after all damage has resolved.
Also, does this take replacement effects into consideration?

I dont know the code entirely,so pardon if I say stuff you already thought about.
Jorbes
DEVELOPER
 
Posts: 333
Joined: 10 Oct 2008, 10:46
Has thanked: 0 time
Been thanked: 2 times

Re: Hypnotic Specter, Shadowmage Infiltrator, etc.

Postby DennisBergkamp » 10 Oct 2008, 16:22

No, you're absolutely right. This stuff all resolves while combat damage is still on the stack... and I realize this, I just haven't found the exact place yet AFTER combat damage resolves :)
The good news is, once I do, I can just move the method executePlayerCombatDamageEffects() to the correct place.

However, to answer your first question, whenever the power of these creatures is set to 0 through some effect, in this case, my code won't trigger (I've tested this with Guiltfeeder). I originally even had "if (c.getAttack > 0 )" checks in my resolve() parts, but those turned out to be unnecessary. On the other hand, when I move executePlayerCombatDamageEffects() to a different place, I may well have to put those checks back into place.

I posted this code because, in the current state of MTGForge (were no combat damage can be prevented through cards like Fog), to my knowledge gameplay-wise there won't be any difference between the correct and my (incorrect) implementation. Although maybe Planeswalkers would change things, I haven't looked at that yet.

But of course, you're 100% right, we should try to implement this the correct way. Maybe Forge (or someone else) could help us put this trigger in exactly the right place :) ?
User avatar
DennisBergkamp
AI Programmer
 
Posts: 2602
Joined: 09 Sep 2008, 15:46
Has thanked: 0 time
Been thanked: 0 time

Re: Hypnotic Specter, Shadowmage Infiltrator, etc.

Postby DennisBergkamp » 11 Oct 2008, 17:45

I've looked into this issue some more... I guess the place to put this is Input_CombatDamage.java.
It's a little trickier than just calling the method there though, since I don't think there's a list of unblocked attackers in that class.

But it should be doable :)
User avatar
DennisBergkamp
AI Programmer
 
Posts: 2602
Joined: 09 Sep 2008, 15:46
Has thanked: 0 time
Been thanked: 0 time

Re: Hypnotic Specter, Shadowmage Infiltrator, etc.

Postby GandoTheBard » 11 Oct 2008, 22:40

Im guessing that you need to bring those details with you as parameters of some sort. Isnt there a way to add members to prototypes of a class to take care of things like this?

You might have to rewrite the combat classes but imho that wouldnt necessarily be a bad thing.
visit my personal homepage here: http://outofthebrokensky.com

Listen to my podcast with famed AJ_Impy "Freed from the Real" on http://puremtgo.com
User avatar
GandoTheBard
Tester
 
Posts: 1043
Joined: 06 Sep 2008, 18:43
Has thanked: 0 time
Been thanked: 0 time

Re: Hypnotic Specter, Shadowmage Infiltrator, etc.

Postby DennisBergkamp » 13 Oct 2008, 16:42

Here's a second attempt:

In GameActionUtil.java:

Code: Select all
public static void executePlayerCombatDamageEffects(Card c)
  {   
     if (c.getName().equals("Hypnotic Specter"))
        playerCombatDamage_Hypnotic_Specter(c);
     else if (c.getName().equals("Shadowmage Infiltrator") || c.getName().equals("Thieving Magpie"))
        playerCombatDamage_Shadowmage_Infiltrator(c);
     else if (c.getName().equals("Guiltfeeder"))
        playerCombatDamage_Guiltfeeder(c);
  }
 
  private static void playerCombatDamage_Guiltfeeder(Card c)
  {
     final String player = c.getController();
     final String opponent = AllZone.GameAction.getOpponent(player);
    
     Ability ability2 = new Ability(c, "0")
     {
        public void resolve()
        {
          
           PlayerZone graveyard = AllZone.getZone(Constant.Zone.Graveyard, opponent);
           CardList cardsInGrave = new CardList(graveyard.getCards());
           PlayerLife life = AllZone.GameAction.getPlayerLife(opponent);
          
           life.subtractLife(cardsInGrave.size());          
          
        }
     };//ability2
       
     ability2.setStackDescription(c.getName() + " - " + opponent + " loses life equal to cards in graveyard.");
     AllZone.Stack.add(ability2);
    
        
  }
 
  private static void playerCombatDamage_Shadowmage_Infiltrator(Card c)
  {
     final String player = c.getController();
    
     if (c.getAttack() > 0)
     {
        Ability ability2 = new Ability(c, "0")
        {
           public void resolve()
           {
            
              AllZone.GameAction.drawCard(player);   
           }
        };//ability2
          
        ability2.setStackDescription(c.getName() + " - " + player + " draws a card.");
        AllZone.Stack.add(ability2);
     }
        
  }
 
  private static void playerCombatDamage_Hypnotic_Specter(Card c)
  {
     final String player = c.getController();
     final String opponent = AllZone.GameAction.getOpponent(player);
              
     if (c.getAttack() > 0)
     {
        Ability ability = new Ability(c, "0")
        {
           public void resolve()
           {
              AllZone.GameAction.discardRandom(opponent);
           }
        };//ability
          
        ability.setStackDescription("Hypnotic Specter - " +opponent +" discards one card at random");
        AllZone.Stack.add(ability);
     }
  }
In Combat.java:

Code: Select all
public Card[] getUnblockedAttackers()
  {
    CardList out = new CardList();
    Iterator it = unblockedMap.keySet().iterator();
    while(it.hasNext())
      out.add((Card)it.next());

    return out.toArray();
  }//getUnblockedAttackers()
   
  public void addUnblockedAttacker(Card c)
  {
     unblockedMap.put(c, new CardList());
  }
Then, in setAssignedDamage(), also in Combat.java:

Code: Select all
public void setAssignedDamage()
  {
    setDefendingDamage();

    CardList block;
    CardList attacking = new CardList(getAttackers());
    for(int i = 0; i < attacking.size(); i++)
    {
      block = getBlockers(attacking.get(i));

      //attacker always gets all blockers' attack
      attacking.get(i).setAssignedDamage(CardListUtil.sumAttack(block));
      if(block.size() == 0)//this damage is assigned to a player by setPlayerDamage()
      {
         //GameActionUtil.executePlayerCombatDamageEffects(attacking.get(i));
         addUnblockedAttacker(attacking.get(i));
      }
      //rest of code...
In Input_CombatDamage.java in the method damageCreatureAndPlayer():

Code: Select all
PlayerLife life = AllZone.GameAction.getPlayerLife(AllZone.Combat.getDefendingPlayer());
    life.subtractLife(AllZone.Combat.getDefendingDamage());

    life = AllZone.GameAction.getPlayerLife(AllZone.Combat.getAttackingPlayer());
    life.subtractLife(AllZone.Combat.getAttackingDamage());
    life.subtractLife(AllZone.pwCombat.getAttackingDamage());
   
    CardList unblocked = new CardList(AllZone.Combat.getUnblockedAttackers());
    for(int j = 0; j < unblocked.size(); j++)
    {
       //System.out.println("Unblocked Creature: " +unblocked.get(j).getName());
       GameActionUtil.executePlayerCombatDamageEffects(unblocked.get(j));       
    }
    //rest of code...
Oh, and in cards.txt:

Code: Select all
Guiltfeeder
3 B B
Creature Horror
Whenever Guiltfeeder attacks and isn't blocked, defending player loses 1 life for each card in his or her graveyard.
0/4
Fear

Hypnotic Specter
1 B B
Creature Specter
Whenever Hypnotic Specter deals damage to an opponent, that player discards a card at random.
2/2
Flying

Thieving Magpie
2 U U
Creature Bird
Whenever Thieving Magpie deals damage to an opponent, you draw a card.
1/3
Flying

Shadowmage Infiltrator
1 U B
Creature Wizard
Whenever Shadowmage Infiltrator deals combat damage to a player, you may draw a card.
1/3
Fear
This is a much better implementation, since stuff happens after combat damage actually has resolved. Of course I still don't take into account things like Fog, replacement effects, etc.
Then again, neither of these are in MTGForge right now.
User avatar
DennisBergkamp
AI Programmer
 
Posts: 2602
Joined: 09 Sep 2008, 15:46
Has thanked: 0 time
Been thanked: 0 time

Re: Hypnotic Specter, Shadowmage Infiltrator, etc.

Postby GandoTheBard » 13 Oct 2008, 19:20

Code: Select all
public static void executePlayerCombatDamageEffects(Card c)
  {   
     if (c.getName().equals("Hypnotic Specter"))
        playerCombatDamage_Hypnotic Specter(c);
     else if (c.getName().equals("Shadowmage Infiltrator") || c.getName().equals("Thieving Magpie"))
        playerCombatDamage_Shadowmage Infiltrator(c);
     else if (c.getName().equals("Guiltfeeder"))
        playerCombatDamage_Guiltfeeder(c);
  }
 
Imho this is the exact wrong approach to this. You need to set up a keyword check elsewhere to flag if there is a triggered effect after damage resolution and then use the keyword from the card to set up the trigger. Checking for individual cards means that new cards have to be hard coded rather than scripted later on.
visit my personal homepage here: http://outofthebrokensky.com

Listen to my podcast with famed AJ_Impy "Freed from the Real" on http://puremtgo.com
User avatar
GandoTheBard
Tester
 
Posts: 1043
Joined: 06 Sep 2008, 18:43
Has thanked: 0 time
Been thanked: 0 time

Re: Hypnotic Specter, Shadowmage Infiltrator, etc.

Postby jpb » 13 Oct 2008, 19:28

V1 is beyond all hope of doing cards the right way. All cards are hardcoded in V1. Hopefully V2 will be better.
jpb
 
Posts: 132
Joined: 05 Sep 2008, 13:12
Has thanked: 0 time
Been thanked: 0 time

Re: Hypnotic Specter, Shadowmage Infiltrator, etc.

Postby DennisBergkamp » 13 Oct 2008, 21:32

Actually, keywords might work, but we could only do that for a very limited amount of cards... I'm not sure if it would be worth it, especially not in V1 (as jpb said).
Hypnotic Specter would be something like damagesPlayer:discardRandom, whereas Shadowmage Infiltrator / Thieving Magpie damagesPlayer:draw. Or whatever. But how many other cards are there really with those exact abilities?

Having said that, there ARE a lot of creatures that have triggers on combat damage. It is kind of beyond the scope of this post though to come up with keywords that abstract all of these abilities (that's something Forge might be working on right now for V2, a huge task indeed). I merely wanted to see if I could actually create these creatures, and have their abilities trigger at the right time in the right conditions. I know it's not perfect (trample, Fog-like effects, and possibly other factors) but I'm still pretty satisfied with them :) It's just fun attacking the AI with an unblocked Hypnotic Specter or Shadowmage Infiltrator.
User avatar
DennisBergkamp
AI Programmer
 
Posts: 2602
Joined: 09 Sep 2008, 15:46
Has thanked: 0 time
Been thanked: 0 time

Re: Hypnotic Specter, Shadowmage Infiltrator, etc.

Postby Rob Cashwalker » 14 Oct 2008, 05:01

Thank you Gando!

Yes, a keyword is usually the answer, and quite frankly, V1 can handle them just fine.

A quick glance through the archives, we should easily be able to produce a hundred cards that all do SOMETHING when they deal damage to an opponent or player, and sometimes specifically combat damage.... Since for the most part, we can ignore a creature doing direct damage without some other means to do it, we could just stick to combat damage for implementation. Just tally up the different effects and start with the most popular one, like drawing a card.

Use a series of keywords like the following:

OnOppDamage_Draw:1
OnOppDamage_OppDiscard:1
OnOppDamage_OppChooseDiscard:1

Then in the combat code, simply check the keywords list for any keyword that startsWith("OnOppDamage_") and call your helper functions which checks the specific keyword for each different implementation for the various specific effects. The Numbers after the colon would be variable inputs... like if some card lets you draw 2.... Use the String.split method using ":" as the delimiter, or use additional parameters split by appropriate delimiters, if necessary.
The Force will be with you, Always.
User avatar
Rob Cashwalker
Programmer
 
Posts: 2167
Joined: 09 Sep 2008, 15:09
Location: New York
Has thanked: 5 times
Been thanked: 40 times

Re: Hypnotic Specter, Shadowmage Infiltrator, etc.

Postby DennisBergkamp » 14 Oct 2008, 15:40

We would need a LOT of different keywords though. I'm looking through some of the triggered cards right now, and I just don't know if it's worth the trouble, since there are so many cards that all have a slightly different ability.

However, one thing that I think is a definite advantage using keywords is for cards like Synapse Sliver (and probably a lot of others). Keywords would probably be the only easy way of implementing that card, we would just add the keyword (just to use Rob's example) "OnOppDamage_Draw:1" to each sliver.

I'll think about this some more, and maybe I'll have a shot at doing the keyword thing.
User avatar
DennisBergkamp
AI Programmer
 
Posts: 2602
Joined: 09 Sep 2008, 15:46
Has thanked: 0 time
Been thanked: 0 time

Re: Hypnotic Specter, Shadowmage Infiltrator, etc.

Postby Rob Cashwalker » 14 Oct 2008, 19:57

Drawing a card and "Looting" (draw/discard) seems to be by far the most popular action based on opponent damage. Followed up by having the Opponent discard, which has it's variations in "random", "revealed and chosen" or "opponents choice". Variations like that can be handled as variable parameters (after the colon, for example).

Another thought, is to put this code in Card Factory, and use the format mtgrares put in for the Pump spells, which I've taken and ran with.... That being that a SpellAbility is created by reading the keywords, removing the keyword from the list, then parsing the keyword so "OnOpDamage_DoThis" doesn't show up on the card. This parsing then only has to be done once, and then the SpellAbility can be attached to the card with a similar code as setting a ComesIntoPlay ability. Then during the combat calculation routine, all you have to do is call the card's OnOppDamage ability, which would have no ill effect if the ability didn't exist.... Some tricks might need to be played to get game state information over to the (theoretically "finalized") Ability object's code, for example, if the ability triggers of how much damage is dealt....

While we're monkeying around in the combat class, how about Lifelink? Looking at it more and more, First Strike would be just a matter of copying the loop code, but only assign damage from First Strikers and Double Strikers.
The Force will be with you, Always.
User avatar
Rob Cashwalker
Programmer
 
Posts: 2167
Joined: 09 Sep 2008, 15:09
Location: New York
Has thanked: 5 times
Been thanked: 40 times

Re: Hypnotic Specter, Shadowmage Infiltrator, etc.

Postby jpb » 18 Oct 2008, 04:32

This code did not compile for me. I added this to combat.java in order to get it to compile.

Code: Select all
private HashMap<Card,CardList> unblockedMap = new HashMap<Card,CardList>();
Is this what you had in mind for declaring unblockedMap?

In GameActionUtil.java there was spaces in two method names; I replaced them with underscores.
jpb
 
Posts: 132
Joined: 05 Sep 2008, 13:12
Has thanked: 0 time
Been thanked: 0 time

Re: Hypnotic Specter, Shadowmage Infiltrator, etc.

Postby DennisBergkamp » 18 Oct 2008, 17:22

Ahh, I forgot to paste that declaration in...
I'm not sure (since that code is on my desktop), but I think I did something like this:

Code: Select all
private Map unblockedMap = new HashMap();
But whatever works, if it compiles and those creatures work, then that's fine.
Weird thing with those spaces in the methods... maybe the card highlighting feature of these forums caused that?
User avatar
DennisBergkamp
AI Programmer
 
Posts: 2602
Joined: 09 Sep 2008, 15:46
Has thanked: 0 time
Been thanked: 0 time

Re: Hypnotic Specter, Shadowmage Infiltrator, etc.

Postby jpb » 18 Oct 2008, 17:33

Yeah, I think it's a bug in the forum. It shouldn't touch anything in a code block. I noticed on another post that the same thing happened.
jpb
 
Posts: 132
Joined: 05 Sep 2008, 13:12
Has thanked: 0 time
Been thanked: 0 time


Return to Forge

Who is online

Users browsing this forum: No registered users and 164 guests

cron

Who is online

In total there are 164 users online :: 0 registered, 0 hidden and 164 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 164 guests

Login Form