Page 1 of 8

AbilityFactory TODO list

PostPosted: 07 Mar 2011, 17:44
by slapshot5
We have discussed in several different threads the remaining AbilityFactory classes that we would like to create.

Here is a central place we can track those ideas and related discussions.

-slapshot5

Re: AbilityFactory TODO list

PostPosted: 07 Mar 2011, 17:46
by slapshot5
First up, Sol, I think you mentioned you were looking into an AF for Animate things like manlands, Mimics, etc. If you haven't started it yet, (or haven't gotten too far along), I'll tackle that one.

-slapshot5

Re: AbilityFactory TODO list

PostPosted: 07 Mar 2011, 17:56
by Hellfish
We talked briefly about AF's for spell copying, clashing and attaching auras/equipment. I have no plans for either of those as of right now,though that may change.

Re: AbilityFactory TODO list

PostPosted: 07 Mar 2011, 18:15
by Chris H.
AF_Animate will be tricky. Someone added some code to some of the manlands which would allow the AI to get some use out of these types of spells/abilities.

I remember some comments from the user base. The AI would animate a land and then tap the same land to pay for it's animation cost or to pay for some other cost for another spell or ability.

I spent a little time tracking down the AI mana code and found where it creates a list of untapped mana for the AI to use. I planned to sort his list so that the non-manland mana would appear first and the manlands would appear last in the list.

This would help to reduce the chance that the AI would waste it's mana to no effect. It might be worth it to create a flag that prevent an animate till EOT animation from tapping for mana.

Re: AbilityFactory TODO list

PostPosted: 07 Mar 2011, 19:22
by Zirbert
Some damage prevention would be good - "Prevent the next (x) damage that would be dealt to", "Prevent all damage that would be dealt by (x)", etc.

Re: AbilityFactory TODO list

PostPosted: 07 Mar 2011, 19:53
by Sloth
We need an AF to handle abilities like those of Serendib Sorcerer and Liquimetal Coating. Something like "DefineCharacteristic".

Re: AbilityFactory TODO list

PostPosted: 07 Mar 2011, 20:56
by friarsol
I've been way too busy to work on anything lately. I have a list at home that I'll post tonight of what I think would be good things to do for AFs next.

Re: AbilityFactory TODO list

PostPosted: 08 Mar 2011, 02:28
by friarsol
Alright here's my list. This may be repeats of others already mentioned.

SetLife (mostly written below, someone else can feel free to finish it up)
AssignType (granting additional or overriding types Liquidmetal Coating)
AssignColor (granting additional or overriding colors)
SetPowerToughness (setting Power/Toughness Serendib Sorcerer)
Conditional AF (for Flip a Coin, or other things that need to check that state of something, basically just calls subAbilities if true/false)
Peek/Reveal (Impulse is what I would consider the standard Peek/Reveal card. You look at the X cards of your Library, put Y of them somewhere, then put the rest somewhere.)

I thought I had some more but I can't find them. Might have deleted it by accident.

Code: Select all
   // *************************************************************************
   // ************************** SET LIFE *************************************
   // *************************************************************************

   public static SpellAbility createAbilitySetLife(final AbilityFactory AF){

      final SpellAbility abSetLife = new Ability_Activated(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()){
         private static final long serialVersionUID = 8869422603616247307L;
         
         final AbilityFactory af = AF;
      
         @Override
         public String getStackDescription(){
            // when getStackDesc is called, just build exactly what is happening
            return setLifeStackDescription(af, this);
         }

         public boolean canPlayAI()
         {
            return setLifeCanPlayAI(af, this);
         }
         
         @Override
         public void resolve() {
            setLifeResolve(af, this);
         }

         @Override
         public boolean doTrigger(boolean mandatory) {
            return setLifeDoTriggerAI(af, this, mandatory);
         }
         
      };
      return abSetLife;
   }
   
   public static SpellAbility createSpellSetLife(final AbilityFactory AF){
      final SpellAbility spSetLife = new Spell(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()){
         private static final long serialVersionUID = 6631124959690157874L;
         
         final AbilityFactory af = AF;
      
         @Override
         public String getStackDescription(){
         // when getStackDesc is called, just build exactly what is happening
            return setLifeStackDescription(af, this);
         }
         
         public boolean canPlayAI()
         {
            // if X depends on abCost, the AI needs to choose which card he would sacrifice first
            // then call xCount with that card to properly calculate the amount
            // Or choosing how many to sacrifice
            return setLifeCanPlayAI(af, this);
         }
         
         @Override
         public void resolve() {
            setLifeResolve(af, this);
         }
         
      };
      return spSetLife;
   }
   
   public static SpellAbility createDrawbackSetLife(final AbilityFactory AF){
      final SpellAbility dbSetLife = new Ability_Sub(AF.getHostCard(), AF.getAbTgt()){
         private static final long serialVersionUID = 6631124959690157874L;
         
         final AbilityFactory af = AF;
      
         @Override
         public String getStackDescription(){
         // when getStackDesc is called, just build exactly what is happening
            return setLifeStackDescription(af, this);
         }
         
         public boolean canPlayAI()
         {
            // if X depends on abCost, the AI needs to choose which card he would sacrifice first
            // then call xCount with that card to properly calculate the amount
            // Or choosing how many to sacrifice
            return setLifeCanPlayAI(af, this);
         }
         
         @Override
         public void resolve() {
            setLifeResolve(af, this);
         }

         @Override
         public boolean chkAI_Drawback() {
            return true;
         }

         @Override
         public boolean doTrigger(boolean mandatory) {
            return setLifeDoTriggerAI(af, this, mandatory);
         }
         
      };
      return dbSetLife;
   }

   public static String setLifeStackDescription(AbilityFactory af, SpellAbility sa){
      StringBuilder sb = new StringBuilder();
      int amount = AbilityFactory.calculateAmount(af.getHostCard(), af.getMapParams().get("LifeAmount"), sa);

      if (!(sa instanceof Ability_Sub))
         sb.append(sa.getSourceCard().getName()).append(" -");

      sb.append(" ");

      ArrayList<Player> tgtPlayers;

      Target tgt = af.getAbTgt();
      if (tgt != null)
         tgtPlayers = tgt.getTargetPlayers();
      else
         tgtPlayers = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), af.getMapParams().get("Defined"), sa);
      
      for(Player player : tgtPlayers)
         sb.append(player).append(" ");
      
      sb.append("life total becomes ").append(amount).append(".");

      Ability_Sub abSub = sa.getSubAbility();
      if (abSub != null) {
         sb.append(abSub.getStackDescription());
      }

      return sb.toString();
   }
   
   public static boolean setLifeCanPlayAI(final AbilityFactory af, final SpellAbility sa){
      Random r = new Random();
      Ability_Cost abCost = sa.getPayCosts();
      final Card source = sa.getSourceCard();
      int life = AllZone.ComputerPlayer.getLife();
      String amountStr = af.getMapParams().get("LifeAmount");

      if (!ComputerUtil.canPayCost(sa))
         return false;

      if (!AllZone.ComputerPlayer.canGainLife())
         return false;

      // TODO handle proper calculation of X values based on Cost and what would be paid
      int amount;
      if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")){
         // Set PayX here to maximum value.
         int xPay = ComputerUtil.determineLeftoverMana(sa);
         source.setSVar("PayX", Integer.toString(xPay));
         amount = xPay;
      }
      else
         amount = AbilityFactory.calculateAmount(af.getHostCard(), amountStr, sa);

      // prevent run-away activations - first time will always return true
      boolean chance = r.nextFloat() <= Math.pow(.6667, source.getAbilityUsed());

      Target tgt = sa.getTarget();
      if (tgt != null){
         tgt.resetTargets();
         if (tgt.canOnlyTgtOpponent())
            tgt.addTarget(AllZone.HumanPlayer);
         else
            tgt.addTarget(AllZone.ComputerPlayer);      
      }
      else{
         
      }

      return ((r.nextFloat() < .6667) && chance);
   }
   
   public static boolean setLifeDoTriggerAI(AbilityFactory af, SpellAbility sa, boolean mandatory){
      if (!ComputerUtil.canPayCost(sa) && !mandatory)   // If there is a cost payment it's usually not mandatory
         return false;

      // If the Target is gaining life, target self.
      // if the Target is modifying how much life is gained, this needs to be handled better
      Target tgt = sa.getTarget();
      if (tgt != null){
         tgt.resetTargets();
         if (tgt.canOnlyTgtOpponent())
            tgt.addTarget(AllZone.HumanPlayer);
         else
            tgt.addTarget(AllZone.ComputerPlayer);      
      }

      Card source = sa.getSourceCard();
      String amountStr = af.getMapParams().get("LifeAmount");
      if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")){
         // Set PayX here to maximum value.
         int xPay = ComputerUtil.determineLeftoverMana(sa);
         source.setSVar("PayX", Integer.toString(xPay));
      }

      // check SubAbilities DoTrigger?
      Ability_Sub abSub = sa.getSubAbility();
      if (abSub != null) {
         return abSub.doTrigger(mandatory);
      }

      return true;
   }
   
   public static void setLifeResolve(final AbilityFactory af, final SpellAbility sa){
      HashMap<String,String> params = af.getMapParams();
      Card card = af.getHostCard();
      int lifeAmount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("LifeAmount"), sa);
      ArrayList<Player> tgtPlayers;

      Target tgt = af.getAbTgt();
      if (tgt != null && !params.containsKey("Defined"))
         tgtPlayers = tgt.getTargetPlayers();
      else
         tgtPlayers = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa);
      
      for(Player p : tgtPlayers)
         if (tgt == null || p.canTarget(af.getHostCard()))
            p.gainLife(lifeAmount, sa.getSourceCard());
      
      if (af.hasSubAbility()){
         Ability_Sub abSub = sa.getSubAbility();
         if (abSub != null){
              abSub.resolve();
           }
         else{
            String DrawBack = params.get("SubAbility");
            if (af.hasSubAbility())
                CardFactoryUtil.doDrawBack(DrawBack, lifeAmount, card.getController(), card.getController().getOpponent(), tgtPlayers.get(0), card, null, sa);
         }
      }
   }

Re: AbilityFactory TODO list

PostPosted: 08 Mar 2011, 04:21
by slapshot5
SetLife was the other one I keep putting off. I'll take your code and finish that one up.

-slapshot5

Re: AbilityFactory TODO list

PostPosted: 08 Mar 2011, 13:35
by slapshot5
Chris H. wrote:I remember some comments from the user base. The AI would animate a land and then tap the same land to pay for it's animation cost or to pay for some other cost for another spell or ability.

I spent a little time tracking down the AI mana code and found where it creates a list of untapped mana for the AI to use. I planned to sort his list so that the non-manland mana would appear first and the manlands would appear last in the list.
Seems like in the Pay Mana code, we could add the following checks:
1. if this land is also a creature, don't tap it for mana
2. if a spell (or maybe specifically an animate spell even) is targeting a land, or has Defined$ Self, don't use that land to lay the cost.

That should help. Fortunately, most everything for Animate is Defined$ Self. There are very few things I have found like Kamahl, Fist of Krosa.

-slapshot5

Re: AbilityFactory TODO list

PostPosted: 08 Mar 2011, 13:49
by friarsol
slapshot5 wrote:That should help. Fortunately, most everything for Animate is Defined$ Self. There are very few things I have found like Kamahl, Fist of Krosa.
There are a few AnimateAlls but we can handle those afterwards.

Oh I also wanted an Effect AF. I'm not sure how the AI would work for it yet, but basically it would create an Effect "permanent", which is sorta like a token Card, but generally without any types. It would be similar to Token since you could give it abilities and whatnot. We could have Emblems use this AF, as well as Effects that last a certain amount of time that we have no way to keep track of right now. Like Dauntless Escort's ability.

Re: AbilityFactory TODO list

PostPosted: 08 Mar 2011, 15:32
by Replika
An Effect AF would be very nice to handle abilities that have to stay even if its source dies, like Ethersworn Shieldmage.

I would also like a "Repeat this until" to do things like Treasure Hunt or the often requested Ad Nauseam.

Re: AbilityFactory TODO list

PostPosted: 08 Mar 2011, 17:46
by slapshot5
I was going to enchance the generic aura code to Enchant <valid>, but since it should be an AF eventually, I'll just go to that instead.

Has anyone thought about what the Enchant/Equip AF would look like? I see them both going into an AbilityFactory_Attach.java file.

Then, do we add a fourth category when getting abilties in AbilityFactory?
Code: Select all
if(API.equals("Enchant")) {
         if(isAb)
            SA = AbilityFactory_Attach.createAbilityEnchant(this);
         else if(isSp)
            SA = AbilityFactory_Attach.createSpellEnchant(this);
         else if(isDb)
            SA = AbilityFactory_Attach.createDrawbackEnchant(this);
         else if(isAttachment)
            SA = AbilityFactory_Attach.createAttachmentEnchant(this);
      }
That way, we can handle the Aura itself, plus permanent abilities that say attach target aura to target creature (for example) and spell that say "move target enchantment from one creature to another).

Is there a better/different way anyone had thought of?

-slapshot5

Re: AbilityFactory TODO list

PostPosted: 08 Mar 2011, 18:03
by Hellfish
I found some old notes I'd made about a generic attach AF a while ago.Don't remember why I didn't implement it fully, probably AI problems.

Equip:
Code: Select all
AB$Attach | Cost$ 2 | DefinedAttachee$ Self | ValidAttachTo$ Creature.YouCtrl | PreCostDesc$ Equip -
Aura Finesse:
Code: Select all
SP$Attach | Cost$ U | ValidAttachee$ Aura.YouCtrl | ValidAttachTo$ Creature | SubAbility$SVar=DBDraw | SpellDescription$ Attach target Aura you control to target creature.
Handling all these cases in a single AF. ValidAttachee and ValidAttachTo are interchangeable with DefinedAttachee(as seen in the Equip example) and DefinedAttachTo(For use with Triggered-variables). I made a note to scavenge useful code from the eqPump code in CardFactoryUtil.java, row 1525 and onwards.

Re: AbilityFactory TODO list

PostPosted: 09 Mar 2011, 14:36
by Rob Cashwalker
Something else to consider before going crazy adding new mechanics:

Charms and Commands

A Charm AF would wrap around three sub abilities, checking each AI, then chooses one from the positive returns, albeit randomly....