Ability Factory
Post MTG Forge Related Programming Questions Here
Moderators: timmermac, Blacksmith, KrazyTheFox, Agetian, friarsol, CCGHQ Admins
20 posts
• Page 1 of 2 • 1, 2
Ability Factory
by friarsol » 04 Oct 2010, 21:33
I know Rob has talked about the Ability Factory before in the past, so I wanted to get some of the ideas in the same place, since I think right now they aren't really centralized (unless you count Rob's head).
Add/correct anything that looks weird to you. This is just what I'm picturing from what I remember.
<ability>:Cost$<abCost>:ValidTarget$<targets>:Damage$<numDamage>:Drawback$<Drawback>
The keywords that can appear before the $ would be things like:
Cost- Ability_Cost of the Ability
ValidTarget - To be filled in for the Target
Damage - Amount of damage dealt to target
Life - Amount of life gained/lost by ability
NumCounters - Number of Counters added/subtracted by ability
TypeCounter - Type of Counter added/subtracted by ability
Drawback - Drawback of the ability
StackDesc - Stack Description
SpellDesc - Spell Description
ActivatingZone - The Zone this ability can be activated from (Default is Battlefield)
Depending on the Ability certain keywords would be required.
abDamage requires ValidTarget and Damage.
abDestroy requires ValidTarget.
Add/correct anything that looks weird to you. This is just what I'm picturing from what I remember.
<ability>:Cost$<abCost>:ValidTarget$<targets>:Damage$<numDamage>:Drawback$<Drawback>
The keywords that can appear before the $ would be things like:
Cost- Ability_Cost of the Ability
ValidTarget - To be filled in for the Target
Damage - Amount of damage dealt to target
Life - Amount of life gained/lost by ability
NumCounters - Number of Counters added/subtracted by ability
TypeCounter - Type of Counter added/subtracted by ability
Drawback - Drawback of the ability
StackDesc - Stack Description
SpellDesc - Spell Description
ActivatingZone - The Zone this ability can be activated from (Default is Battlefield)
Depending on the Ability certain keywords would be required.
abDamage requires ValidTarget and Damage.
abDestroy requires ValidTarget.
- friarsol
- Global Moderator
- Posts: 7593
- Joined: 15 May 2010, 04:20
- Has thanked: 243 times
- Been thanked: 965 times
Re: Ability Factory
by Rob Cashwalker » 04 Oct 2010, 22:52
That's about it in a nutshell.
We can switch to a new delimiter... pipe comes to mind "|".
The parser will simply put all parameters into a map. Each ability will look for its own required parameters by name in the map. any parameter should have provision to take its value from an SVar.
I'm thinking these ability strings will be added to an ArrayList<String> in the card object when first read from the file. Then in CardFactory, instead of "hasKeyword", we'll loop through ability strings. (if size() > zero)
I want to try to begin this.. if I can't make it work on my first shot, I'll throw it up for others to work on.
We can switch to a new delimiter... pipe comes to mind "|".
The parser will simply put all parameters into a map. Each ability will look for its own required parameters by name in the map. any parameter should have provision to take its value from an SVar.
I'm thinking these ability strings will be added to an ArrayList<String> in the card object when first read from the file. Then in CardFactory, instead of "hasKeyword", we'll loop through ability strings. (if size() > zero)
I want to try to begin this.. if I can't make it work on my first shot, I'll throw it up for others to work on.
The Force will be with you, Always.
-
Rob Cashwalker - Programmer
- Posts: 2167
- Joined: 09 Sep 2008, 15:09
- Location: New York
- Has thanked: 5 times
- Been thanked: 40 times
Re: Ability Factory
by Rob Cashwalker » 08 Oct 2010, 13:33
I have just submitted my first draft of this.
AbilityFactory:
AbilityFactory:
- Code: Select all
package forge;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class AbilityFactory {
public SpellAbility getAbility(String abString, final Card hostCard){
final SpellAbility SA[] = {null};
final HashMap<String,String> mapParams = new HashMap<String,String>();
if (!(abString.length() > 0))
throw new RuntimeException("AbilityFactory : getAbility -- abString too short in " + hostCard.getName());
String a[] = abString.split("\\|");
if (!(a.length > 1))
throw new RuntimeException("AbilityFactory : getAbility -- a[] too short in " + hostCard.getName());
for (int i=0; i<a.length; i++)
{
String aa[] = a[i].split("\\$");
if (!(aa.length == 2))
throw new RuntimeException("AbilityFactory : getAbility -- aa.length not 2 in " + hostCard.getName());
mapParams.put(aa[0], aa[1]);
}
final boolean isAb[] = {false};
final boolean isSp[] = {false};
String abAPI = "";
String spAPI = "";
// additional ability types here
if (mapParams.containsKey("AB"))
{
isAb[0] = true;
abAPI = mapParams.get("AB");
}
else if (mapParams.containsKey("SP"))
{
isSp[0] = true;
spAPI = mapParams.get("SP");
}
else
throw new RuntimeException("AbilityFactory : getAbility -- no API in " + hostCard.getName());
if (!mapParams.containsKey("Cost"))
throw new RuntimeException("AbilityFactory : getAbility -- no Cost in " + hostCard.getName());
final Ability_Cost abCost = new Ability_Cost(mapParams.get("Cost"), hostCard.getName(), isAb[0]);
final boolean isTargeted[] = {false};
final boolean hasValid[] = {false};
final Target abTgt[] = {null};
if (mapParams.containsKey("ValidTgts"))
{
hasValid[0] = true;
isTargeted[0] = true;
abTgt[0] = new Target("V");
abTgt[0].setValidTgts(mapParams.get("ValidTgts").split(","));
abTgt[0].setVTSelection(mapParams.get("TgtPrompt"));
}
if (mapParams.containsKey("ValidCards"))
hasValid[0] = true;
if (mapParams.containsKey("Tgt"))
{
isTargeted[0] = true;
abTgt[0] = new Target(mapParams.get("Tgt"));
}
//final String SubAbility[] = {"none"};
final boolean hasSubAb[] = {false};
if (mapParams.containsKey("SubAbility"))
hasSubAb[0] = true;
//SubAbility[0] = mapParams;
final String spDescription[] = {"none"};
final boolean hasSpDesc[] = {false};
//String tmpSpDesc = mapParams.get("SpellDescription");
if (mapParams.containsKey("SpellDescription"))
{
hasSpDesc[0] = true;
spDescription[0] = abCost.toString() + mapParams.get("SpellDescription");
}
if (abAPI.equals("DealDamage"))
{
final int NumDmg[] = {-1};
final String NumDmgX[] = {"none"};
String tmpND = mapParams.get("NumDmg");
if (tmpND.length() > 0)
{
if (tmpND.matches("X"))
NumDmgX[0] = hostCard.getSVar(tmpND.substring(1));
else if (tmpND.matches("[0-9][0-9]?"))
NumDmg[0] = Integer.parseInt(tmpND);
}
final SpellAbility abDamage = new Ability_Activated(hostCard, abCost, abTgt[0])
{
private static final long serialVersionUID = -7560349014757367722L;
int damage = 0;
public int getNumDamage() {
if(NumDmg[0] != -1) return NumDmg[0];
if(!NumDmgX[0].equals("none")) return CardFactoryUtil.xCount(hostCard, NumDmgX[0]);
return 0;
}
boolean shouldTgtP() {
PlayerZone compHand = AllZone.getZone(Constant.Zone.Hand, Constant.Player.Computer);
CardList hand = new CardList(compHand.getCards());
if(hand.size() > 7) // anti-discard-at-EOT
return true;
if(AllZone.Human_Life.getLife() - damage < 10) // if damage from this spell would drop the human to less than 10 life
return true;
return false;
}
Card chooseTgtC() {
// Combo alert!!
PlayerZone compy = AllZone.getZone(Constant.Zone.Play, Constant.Player.Computer);
CardList cPlay = new CardList(compy.getCards());
if(cPlay.size() > 0) for(int i = 0; i < cPlay.size(); i++)
if(cPlay.get(i).getName().equals("Stuffy Doll")) return cPlay.get(i);
PlayerZone human = AllZone.getZone(Constant.Zone.Play, Constant.Player.Human);
CardList hPlay = new CardList(human.getCards());
hPlay = hPlay.filter(new CardListFilter() {
public boolean addCard(Card c) {
// will include creatures already dealt damage
return c.isCreature() && ((c.getNetDefense() + c.getDamage()) <= damage)
&& CardFactoryUtil.canTarget(hostCard, c);
}
});
if(hPlay.size() > 0) {
Card best = hPlay.get(0);
if(hPlay.size() > 1) {
for(int i = 1; i < hPlay.size(); i++) {
Card b = hPlay.get(i);
// choose best overall creature?
if(b.getSpellAbility().length > best.getSpellAbility().length
|| b.getKeyword().size() > best.getKeyword().size()
|| b.getNetAttack() > best.getNetAttack()) best = b;
}
}
return best;
}
return null;
}
@Override
public boolean canPlay(){
Cost_Payment pay = new Cost_Payment(abCost, this);
return (pay.canPayAdditionalCosts(abCost, this) && CardFactoryUtil.canUseAbility(hostCard) && super.canPlay());
}
@Override
public boolean canPlayAI() {
// temporarily disabled until better AI
if (abCost.getSacCost()) return false;
if (abCost.getSubCounter()) return false;
if (abCost.getLifeCost()) return false;
if (!ComputerUtil.canPayCost(this))
return false;
damage = getNumDamage();
Random r = new Random(); // prevent run-away activations
boolean rr = false;
if(r.nextFloat() <= Math.pow(.6667, hostCard.getAbilityUsed()))
rr = true;
if(abTgt[0].canTgtCreaturePlayer()) {
if(shouldTgtP()) {
setTargetPlayer(Constant.Player.Human);
return rr;
}
Card c = chooseTgtC();
if(c != null) {
setTargetCard(c);
return rr;
}
}
if(abTgt[0].canTgtPlayer()/* || TgtOpp[0] == true */) {
setTargetPlayer(Constant.Player.Human);
return rr;
}
if(abTgt[0].canTgtCreature()) {
Card c = chooseTgtC();
if(c != null) {
setTargetCard(c);
return rr;
}
}
return false;
}
@Override
public void resolve() {
int damage = getNumDamage();
String tgtP = "";
//if(TgtOpp[0] == true) {
// tgtP = AllZone.GameAction.getOpponent(card.getController());
// setTargetPlayer(tgtP);
//}
Card c = getTargetCard();
if(c != null) {
if(AllZone.GameAction.isCardInPlay(c) && CardFactoryUtil.canTarget(hostCard, c)) {
AllZone.GameAction.addDamage(c, hostCard, damage);
tgtP = c.getController();
if(hasSubAb[0])
CardFactoryUtil.doDrawBack(mapParams.get("SubAbility"), damage,
hostCard.getController(), AllZone.GameAction.getOpponent(hostCard.getController()),
tgtP, hostCard, c, this);
}
} else {
tgtP = getTargetPlayer();
AllZone.GameAction.addDamage(tgtP, hostCard, damage);
if(hasSubAb[0])
CardFactoryUtil.doDrawBack(mapParams.get("SubAbility"), damage,
hostCard.getController(), AllZone.GameAction.getOpponent(hostCard.getController()),
tgtP, hostCard, null, this);
}
}//resolve()
};//Ability_Activated
if (isTargeted[0])
abDamage.setTarget(abTgt[0]);
abDamage.setPayCosts(abCost);
if (hasSpDesc[0])
abDamage.setDescription(spDescription[0]);
SA[0] = abDamage;
}
// additional keywords here
return SA[0];
}
}
- Code: Select all
//**************************************************
// AbilityFactory cards
ArrayList<String> IA = card.getIntrinsicAbilities();
if (IA.size() > 0)
{
AbilityFactory AF = new AbilityFactory();
if (card.isInstant() || card.isSorcery())
card.clearSpellAbility();
for (int i=0; i<IA.size(); i++)
card.addSpellAbility(AF.getAbility(IA.get(i), card));
}
- Code: Select all
private ArrayList<String> intrinsicAbility = new ArrayList<String>();
...
public ArrayList<String> getIntrinsicAbilities()
{
return intrinsicAbility;
}
...
public void setIntrinsicAbilities(ArrayList<String> a)
{
intrinsicAbility = new ArrayList<String>(a);
}
...
public void addIntrinsicAbility(String s)
{
if (s.trim().length() != 0)
intrinsicAbility.add(s);
}
- Code: Select all
Name:Prodigal Pyromancer
ManaCost:2 R
Types:Creature Human Wizard
Text:no text
PT:1/1
# K:abDamageTgtCP T:1
A:AB$DealDamage|Cost$T|Tgt$TgtCP|NumDmg$1|SpellDescription$Deal 1 damage to target creature or player.
SVar:Rarity:Common
SVar:Picture:http://resources.wizards.com/magic/cards/plc/en-us/card122338.jpg
End
The Force will be with you, Always.
-
Rob Cashwalker - Programmer
- Posts: 2167
- Joined: 09 Sep 2008, 15:09
- Location: New York
- Has thanked: 5 times
- Been thanked: 40 times
Re: Ability Factory
by friarsol » 08 Oct 2010, 14:26
Looking good. I think it would help find things if all the different spells/abilities had their own function.
- Code: Select all
if (abAPI.equals("DealDamage"))
{
return createDealDamageAbility(<parameters>);
}
- friarsol
- Global Moderator
- Posts: 7593
- Joined: 15 May 2010, 04:20
- Has thanked: 243 times
- Been thanked: 965 times
Re: Ability Factory
by Rob Cashwalker » 08 Oct 2010, 14:36
Where would that function reside? It's just offloading the clutter from one class to another. (Yeah.. I know just like from CardFactory -> AbilityFactory) At some point we just have to stop the madness....
My only thought, is that DealDamage (and Pump, and Destroy, etc) would actually have its own class file. In that manner, the code can handle all variations of DealDamage, as an ability, spell, enters the battlefield effect, or SubAbility (aka Drawback).
My only thought, is that DealDamage (and Pump, and Destroy, etc) would actually have its own class file. In that manner, the code can handle all variations of DealDamage, as an ability, spell, enters the battlefield effect, or SubAbility (aka Drawback).
The Force will be with you, Always.
-
Rob Cashwalker - Programmer
- Posts: 2167
- Joined: 09 Sep 2008, 15:09
- Location: New York
- Has thanked: 5 times
- Been thanked: 40 times
Re: Ability Factory
by friarsol » 08 Oct 2010, 15:04
It's not just about offloading clutter or stopping the buck, it's about not having functions that are 600+ lines and difficult to debug. The main function should do two things: parse the string that it received, and call a function to actually create the SA. We're having difficulties with the Whenever keyword because too much is going on in one place without any offloading of work. If each piece that was being parsed was a separate function call, it would be less of a huge wall of text in the debugger.
The create functions can live in that same class, it just would call a different function to actually generate up the SA. Grouping similar abilities into their own classes works too. That would make any global updates to the keywords simpler. Either separate classes, or separate functions are both good, but one huge function to do all the work makes it harder to track down issues.
Oh I just thought of another thing, this function probably should be a static since the object doesn't really do anything. It just gets created so you can call the function.
The create functions can live in that same class, it just would call a different function to actually generate up the SA. Grouping similar abilities into their own classes works too. That would make any global updates to the keywords simpler. Either separate classes, or separate functions are both good, but one huge function to do all the work makes it harder to track down issues.
Oh I just thought of another thing, this function probably should be a static since the object doesn't really do anything. It just gets created so you can call the function.
- Code: Select all
card.addSpellAbility(AbilityFactory.getAbility(IA.get(i), card));
- friarsol
- Global Moderator
- Posts: 7593
- Joined: 15 May 2010, 04:20
- Has thanked: 243 times
- Been thanked: 965 times
Re: Ability Factory
by friarsol » 08 Oct 2010, 21:11
Are there changes needed in ReadCard.java to properly grab the line? Maybe they just didn't get committed with your other changes?
It looks like the Pyromancer has no abilities right now.
It looks like the Pyromancer has no abilities right now.
- friarsol
- Global Moderator
- Posts: 7593
- Joined: 15 May 2010, 04:20
- Has thanked: 243 times
- Been thanked: 965 times
Re: Ability Factory
by Rob Cashwalker » 08 Oct 2010, 21:30
DOH! Yeah, I missed it this morning. It just does almost exactly the same as the "K", but with "A" instead. Added to the IntrinsicAbilities list.
I'll put that up tonight when I get home.
You can always switch the comment, and put it back to work if you need to actually play the card.
I'll put that up tonight when I get home.
You can always switch the comment, and put it back to work if you need to actually play the card.
The Force will be with you, Always.
-
Rob Cashwalker - Programmer
- Posts: 2167
- Joined: 09 Sep 2008, 15:09
- Location: New York
- Has thanked: 5 times
- Been thanked: 40 times
Re: Ability Factory
by friarsol » 08 Oct 2010, 22:16
Nah I was trying to code up a new Ability and figured I could just use the new Factory code and was really confused for a few minutes until I decided to look at the Pyromancer and it was experiencing the same thing. I'll just wait until you can submit the remaining file(s) to test out the new code.
- friarsol
- Global Moderator
- Posts: 7593
- Joined: 15 May 2010, 04:20
- Has thanked: 243 times
- Been thanked: 965 times
Re: Ability Factory
by Rob Cashwalker » 09 Oct 2010, 01:48
I've uploaded it now.
I'm about to do some surgery on AbilityFactory, as I mentioned, pulling DealDamage into its own class.
I'm about to do some surgery on AbilityFactory, as I mentioned, pulling DealDamage into its own class.
The Force will be with you, Always.
-
Rob Cashwalker - Programmer
- Posts: 2167
- Joined: 09 Sep 2008, 15:09
- Location: New York
- Has thanked: 5 times
- Been thanked: 40 times
Re: Ability Factory
by Rob Cashwalker » 09 Oct 2010, 03:57
OK, the new layout has been submitted.
I decided to keep AbilityFactory.getAbility non-static. AbilityFactory now uses private static variables with public get methods.
getAbility parses the universal parameters into the private members. Then matches the API string. This in turn calls a class specific to that API, DealDamage for example, passing it a reference to itself.
This reference is used throughout DealDamage to retrieve the parsed information, and the raw mapParams.
The API class should implement static public methods for the specific sub types of SpellAbility, generally getAbility() and getSpell(). So far, DealDamage only has getAbility implemented, I need to look at spDamage more closely to merge it.
I reduced the DealDamage Ability_Activated to just a shell of the required abstract methods. They call upon helper methods which are mostly common to both the ability and spell version of the code.
AbilityFactory:
I decided to keep AbilityFactory.getAbility non-static. AbilityFactory now uses private static variables with public get methods.
getAbility parses the universal parameters into the private members. Then matches the API string. This in turn calls a class specific to that API, DealDamage for example, passing it a reference to itself.
This reference is used throughout DealDamage to retrieve the parsed information, and the raw mapParams.
The API class should implement static public methods for the specific sub types of SpellAbility, generally getAbility() and getSpell(). So far, DealDamage only has getAbility implemented, I need to look at spDamage more closely to merge it.
I reduced the DealDamage Ability_Activated to just a shell of the required abstract methods. They call upon helper methods which are mostly common to both the ability and spell version of the code.
AbilityFactory:
- Code: Select all
package forge;
import java.util.HashMap;
import java.util.Random;
public class AbilityFactory {
private static Card hostC = null;
public Card getHostCard()
{
return hostC;
}
private static HashMap<String,String> mapParams = new HashMap<String,String>();
public HashMap<String,String> getMapParams()
{
return mapParams;
}
private static boolean isAb = false;
private static boolean isSp = false;
public boolean isAbility()
{
return isAb;
}
public boolean isSpell()
{
return isSp;
}
private static Ability_Cost abCost = null;
public Ability_Cost getAbCost()
{
return abCost;
}
private static boolean isTargeted = false;
private static boolean hasValid = false;
private static Target abTgt = null;
public boolean isTargeted()
{
return isTargeted;
}
public boolean hasValid()
{
return hasValid;
}
public Target getAbTgt()
{
return abTgt;
}
private static boolean hasSubAb = false;
public boolean hasSubAbility()
{
return hasSubAb;
}
private static boolean hasSpDesc = false;
public boolean hasSpDescription()
{
return hasSpDesc;
}
public SpellAbility getAbility(String abString, final Card hostCard){
SpellAbility SA = null;
//final HashMap<String,String> mapParams = new HashMap<String,String>();
hostC = hostCard;
if (!(abString.length() > 0))
throw new RuntimeException("AbilityFactory : getAbility -- abString too short in " + hostCard.getName());
String a[] = abString.split("\\|");
if (!(a.length > 1))
throw new RuntimeException("AbilityFactory : getAbility -- a[] too short in " + hostCard.getName());
for (int i=0; i<a.length; i++)
{
String aa[] = a[i].split("\\$");
if (!(aa.length == 2))
throw new RuntimeException("AbilityFactory : getAbility -- aa.length not 2 in " + hostCard.getName());
mapParams.put(aa[0], aa[1]);
}
//final boolean isAb[] = {false};
//final boolean isSp[] = {false};
String abAPI = "";
String spAPI = "";
// additional ability types here
if (mapParams.containsKey("AB"))
{
isAb = true;
abAPI = mapParams.get("AB");
}
else if (mapParams.containsKey("SP"))
{
isSp = true;
spAPI = mapParams.get("SP");
}
else
throw new RuntimeException("AbilityFactory : getAbility -- no API in " + hostCard.getName());
if (!mapParams.containsKey("Cost"))
throw new RuntimeException("AbilityFactory : getAbility -- no Cost in " + hostCard.getName());
abCost = new Ability_Cost(mapParams.get("Cost"), hostCard.getName(), isAb);
//final boolean isTargeted[] = {false};
//final boolean hasValid[] = {false};
//final Target abTgt[] = {null};
if (mapParams.containsKey("ValidTgts"))
{
hasValid = true;
isTargeted = true;
abTgt = new Target("TgtV");
abTgt.setValidTgts(mapParams.get("ValidTgts").split(","));
abTgt.setVTSelection(mapParams.get("TgtPrompt"));
}
if (mapParams.containsKey("ValidCards"))
hasValid = true;
if (mapParams.containsKey("Tgt"))
{
isTargeted = true;
abTgt = new Target(mapParams.get("Tgt"));
}
//final String SubAbility[] = {"none"};
//final boolean hasSubAb[] = {false};
if (mapParams.containsKey("SubAbility"))
hasSubAb = true;
//SubAbility[0] = mapParams;
//final String spDescription[] = {"none"};
//final boolean hasSpDesc[] = {false};
//String tmpSpDesc = mapParams.get("SpellDescription");
if (mapParams.containsKey("SpellDescription"))
{
hasSpDesc = true;
//spDescription[0] = abCost.toString() + mapParams.get("SpellDescription");
}
if (abAPI.equals("DealDamage"))
{
final int NumDmg[] = {-1};
final String NumDmgX[] = {"none"};
String tmpND = mapParams.get("NumDmg");
if (tmpND.length() > 0)
{
if (tmpND.matches("X"))
NumDmgX[0] = hostCard.getSVar(tmpND.substring(1));
else if (tmpND.matches("[0-9][0-9]?"))
NumDmg[0] = Integer.parseInt(tmpND);
}
if (isAb)
SA = DealDamage.getAbility(this, NumDmg[0], NumDmgX[0]);
else if (isSp)
SA = DealDamage.getSpell(this);
}
// additional keywords here
// set universal properties of the SpellAbility
if (isTargeted)
SA.setTarget(abTgt);
SA.setPayCosts(abCost);
if (hasSpDesc)
SA.setDescription(abCost.toString() + mapParams.get("SpellDescription"));
return SA;
}
}
- Code: Select all
package forge;
import java.util.Random;
public class DealDamage {
private static AbilityFactory AF = null;
private static int nDamage = -1;
private static String XDamage = "none";
public static SpellAbility getAbility(final AbilityFactory af, final int NumDmg, final String NumDmgX)
{
AF = af;
nDamage = NumDmg;
XDamage = NumDmgX;
final SpellAbility abDamage = new Ability_Activated(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt())
{
private static final long serialVersionUID = -7560349014757367722L;
int damage = 0;
@Override
public boolean canPlay(){
return (Cost_Payment.canPayAdditionalCosts(AF.getAbCost(), this)
&& CardFactoryUtil.canUseAbility(AF.getHostCard())
&& super.canPlay());
}
@Override
public boolean canPlayAI() {
return doCanPlayAI(this);
}
@Override
public void resolve() {
doResolve(this);
AF.getHostCard().setAbilityUsed(AF.getHostCard().getAbilityUsed() + 1);
}
};//Ability_Activated
return abDamage;
}
public static SpellAbility getSpell(AbilityFactory AF)
{
SpellAbility spDealDamage = null;
return spDealDamage;
}
private static int getNumDamage() {
if(nDamage != -1) return nDamage;
if(!XDamage.equals("none"))
return CardFactoryUtil.xCount(AF.getHostCard(), XDamage);
return 0;
}
private static boolean shouldTgtP(int d) {
PlayerZone compHand = AllZone.getZone(Constant.Zone.Hand, Constant.Player.Computer);
CardList hand = new CardList(compHand.getCards());
if(hand.size() > 7) // anti-discard-at-EOT
return true;
if(AllZone.Human_Life.getLife() - d < 10) // if damage from this spell would drop the human to less than 10 life
return true;
return false;
}
private static Card chooseTgtC(final int d) {
// Combo alert!!
PlayerZone compy = AllZone.getZone(Constant.Zone.Play, Constant.Player.Computer);
CardList cPlay = new CardList(compy.getCards());
if(cPlay.size() > 0) for(int i = 0; i < cPlay.size(); i++)
if(cPlay.get(i).getName().equals("Stuffy Doll")) return cPlay.get(i);
PlayerZone human = AllZone.getZone(Constant.Zone.Play, Constant.Player.Human);
CardList hPlay = new CardList(human.getCards());
hPlay = hPlay.filter(new CardListFilter() {
public boolean addCard(Card c) {
// will include creatures already dealt damage
return c.isCreature() && ((c.getNetDefense() + c.getDamage()) <= d)
&& CardFactoryUtil.canTarget(AF.getHostCard(), c);
}
});
if(hPlay.size() > 0) {
Card best = hPlay.get(0);
if(hPlay.size() > 1) {
for(int i = 1; i < hPlay.size(); i++) {
Card b = hPlay.get(i);
// choose best overall creature?
if(b.getSpellAbility().length > best.getSpellAbility().length
|| b.getKeyword().size() > best.getKeyword().size()
|| b.getNetAttack() > best.getNetAttack()) best = b;
}
}
return best;
}
return null;
}
private static boolean doCanPlayAI(SpellAbility saMe)
{
// temporarily disabled until better AI
if (AF.getAbCost().getSacCost()) return false;
if (AF.getAbCost().getSubCounter()) return false;
if (AF.getAbCost().getLifeCost()) return false;
if (!ComputerUtil.canPayCost(saMe))
return false;
int damage = getNumDamage();
Random r = new Random(); // prevent run-away activations
boolean rr = false;
if(r.nextFloat() <= Math.pow(.6667, AF.getHostCard().getAbilityUsed()))
rr = true;
if(AF.getAbTgt().canTgtCreaturePlayer()) {
if(shouldTgtP(damage)) {
saMe.setTargetPlayer(Constant.Player.Human);
return rr;
}
Card c = chooseTgtC(damage);
if(c != null) {
saMe.setTargetCard(c);
return rr;
}
}
if(AF.getAbTgt().canTgtPlayer()/* || TgtOpp[0] == true */) {
saMe.setTargetPlayer(Constant.Player.Human);
return rr;
}
if(AF.getAbTgt().canTgtCreature()) {
Card c = chooseTgtC(damage);
if(c != null) {
saMe.setTargetCard(c);
return rr;
}
}
return false;
}
private static void doResolve(SpellAbility saMe)
{
int damage = getNumDamage();
String tgtP = "";
//if(TgtOpp[0] == true) {
// tgtP = AllZone.GameAction.getOpponent(card.getController());
// setTargetPlayer(tgtP);
//}
Card c = saMe.getTargetCard();
if(c != null) {
if(AllZone.GameAction.isCardInPlay(c) && CardFactoryUtil.canTarget(AF.getHostCard(), c)) {
AllZone.GameAction.addDamage(c, AF.getHostCard(), damage);
tgtP = c.getController();
if(AF.hasSubAbility())
CardFactoryUtil.doDrawBack(AF.getMapParams().get("SubAbility"), damage,
AF.getHostCard().getController(), AllZone.GameAction.getOpponent(AF.getHostCard().getController()),
tgtP, AF.getHostCard(), c, saMe);
}
} else {
tgtP = saMe.getTargetPlayer();
AllZone.GameAction.addDamage(tgtP, AF.getHostCard(), damage);
if(AF.hasSubAbility())
CardFactoryUtil.doDrawBack(AF.getMapParams().get("SubAbility"), damage,
AF.getHostCard().getController(), AllZone.GameAction.getOpponent(AF.getHostCard().getController()),
tgtP, AF.getHostCard(), null, saMe);
}
}
}
The Force will be with you, Always.
-
Rob Cashwalker - Programmer
- Posts: 2167
- Joined: 09 Sep 2008, 15:09
- Location: New York
- Has thanked: 5 times
- Been thanked: 40 times
Re: Ability Factory
by friarsol » 09 Oct 2010, 13:41
That's fine. The way you had it the other day it wasn't actually doing anything on it's own. But if you are going to pass it into the next function to be able to retrieve variables, that's reasonable use.Rob Cashwalker wrote:I decided to keep AbilityFactory.getAbility non-static. AbilityFactory now uses private static variables with public get methods.
I don't think static private variables in this class will work though. Everything that uses AbilityFactory will share those variables if they are static, making each card to create an AbilityFactory overwrite every other object already created that set those variables. Ability Factory should just set non-static variables and getters.
Should we come up with a naming mechanism for these classes so they are grouped Alphabetically? Something like AF_DealDamage, or similar?
- friarsol
- Global Moderator
- Posts: 7593
- Joined: 15 May 2010, 04:20
- Has thanked: 243 times
- Been thanked: 965 times
Re: Ability Factory
by Rob Cashwalker » 09 Oct 2010, 14:20
ahh... I knew what static meant in VB... I didn't realize it would be almost exactly the same in Java. I'll go ahead and make setters too.
I could go either way on a naming scheme....
I could go either way on a naming scheme....
The Force will be with you, Always.
-
Rob Cashwalker - Programmer
- Posts: 2167
- Joined: 09 Sep 2008, 15:09
- Location: New York
- Has thanked: 5 times
- Been thanked: 40 times
Re: Ability Factory
by friarsol » 09 Oct 2010, 22:09
I just submitted some code to fix the static variables, along with a few other issues that popped up when I had multiple cards using the Factory (or the same card using it twice). I tested the Pyromancer and it was still working. I didn't create any additional DealDamage cards, but you may want to create a second one that has different parameters (maybe more than 1 damage, or only targets creatures) to make sure they aren't clashing with each other. I had been getting a null pointer exception with Trigon of Corruption, since the Targeting of the second ability was "bleeding through" to the first ability, so just to confirm this is resolved for the DealDamage Factory.
Here's the data file for Trigon of Corruption
Here's the data file for Trigon of Corruption
- Code: Select all
Name:Trigon of Corruption
ManaCost:4
Types:Artifact
Text:no text
K:etbCounter:CHARGE:3
A:AB$PutCounter|Cost$B B T|CounterType$CHARGE|CounterNum$1|SpellDescription$Put a charge counter on CARDNAME.
A:AB$PutCounter|Cost$2 T SubCounter<1/CHARGE>|Tgt$TgtC|IsCurse$True|CounterType$M1M1|CounterNum$1|SpellDescription$Put a -1/-1 counter on target creature.
SVar:Rarity:Common
SVar:Picture:http://www.wizards.com/global/images/magic/general/trigon_of_corruption.jpg
End
- Code: Select all
package forge;
import java.util.HashMap;
import java.util.Random;
public class AbilityFactory_Counters {
// An AbilityFactory subclass for Putting or Removing Counters on Cards.
public static SpellAbility createAbilityPutCounters(final AbilityFactory AF){
final SpellAbility abPutCounter = new Ability_Activated(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()){
private static final long serialVersionUID = -1259638699008542484L;
final AbilityFactory af = AF;
final HashMap<String,String> params = af.getMapParams();
final int amount = calculateAmount(af.getHostCard(), params.get("CounterNum"));
final String type = params.get("CounterType");
@Override
public String getStackDescription(){
// when getStackDesc is called, just build exactly what is happening
Counters cType = Counters.valueOf(type);
StringBuilder sb = new StringBuilder();
String name = af.getHostCard().getName();
sb.append(name).append(" - Put ").append(amount).append(" ").append(cType.getName()).append(" counter on ");
Card tgt = getTargetCard();
if (tgt != null)
sb.append(tgt.getName());
else
sb.append(name);
return sb.toString();
}
public boolean canPlay(){
// super takes care of AdditionalCosts
return (CardFactoryUtil.canUseAbility(af.getHostCard()) && super.canPlay());
}
public boolean canPlayAI()
{
return putCanPlayAI(af, this, amount, type);
}
@Override
public void resolve() {
putResolve(af, this, amount, type);
}
};
return abPutCounter;
}
public static int calculateAmount(Card card, String counterNum){
int amount;
if (counterNum.matches("X"))
{
String calcX = card.getSVar(counterNum);
if (calcX.startsWith("Count$"))
{
String kk[] = calcX.split("\\$");
amount = kk[1].equals("none") ? 0 : CardFactoryUtil.xCount(card, kk[1]);
}
else
amount = 0;
}
else
amount = Integer.parseInt(counterNum);
return amount;
}
public static boolean putCanPlayAI(final AbilityFactory af, final SpellAbility sa, final int amount, final String type){
// AI needs to be expanded, since this function can be pretty complex based on what the expected targets could be
Random r = new Random();
Ability_Cost abCost = sa.getPayCosts();
Target abTgt = sa.getTarget();
final Card source = sa.getSourceCard();
CardList list;
Card choice = null;
String player = af.isCurse() ? Constant.Player.Human : Constant.Player.Computer;
list = new CardList(AllZone.getZone(Constant.Zone.Play, player).getCards());
list = list.filter(new CardListFilter() {
public boolean addCard(Card c) {
return AllZone.GameAction.canTarget(c, source);
}
});
if (abTgt != null){
if (abTgt.canTgtCreature()){
list = list.getType("creature");
}
else{
list = list.getValidCards(abTgt.getValidTgts());
}
if (list.size() == 0)
return false;
}
if (abCost != null){
// AI currently disabled for these costs
if (abCost.getSacCost()) return false;
if (abCost.getLifeCost()) return false;
if (abCost.getDiscardCost()) return false;
if (abCost.getSubCounter()){
// A card has a 25% chance per counter to be able to pass through here
// 8+ counters will always pass. 0 counters will never
int currentNum = source.getCounters(abCost.getCounterType());
double percent = .25 * (currentNum / abCost.getCounterNum());
if (percent <= r.nextFloat())
return false;
}
}
if (!ComputerUtil.canPayCost(sa))
return false;
// prevent run-away activations - first time will always return true
boolean chance = r.nextFloat() <= Math.pow(.6667, source.getAbilityUsed());
// Targeting
if (abTgt != null){
if (af.isCurse()){
if (type.equals("M1M1")){
// try to kill the best killable creature, or reduce the best one
CardList killable = list.filter(new CardListFilter() {
public boolean addCard(Card c) {
return c.getNetDefense() <= amount;
}
});
if (killable.size() > 0)
choice = CardFactoryUtil.AI_getBestCreature(killable);
else
choice = CardFactoryUtil.AI_getBestCreature(list);
}
else{
// improve random choice here
list.shuffle();
choice = list.get(0);
}
}
else{
if (type.equals("P1P1")){
choice = CardFactoryUtil.AI_getBestCreature(list);
}
else{
// The AI really should put counters on cards that can use it.
// Charge counters on things with Charge abilities, etc. Expand these above
list.shuffle();
choice = list.get(0);
}
}
if (choice == null)
return false;
sa.setTargetCard(choice);
}
else{
// Placeholder: No targeting necessary
int currCounters = sa.getSourceCard().getCounters(Counters.valueOf(type));
// each counter on the card is a 10% chance of not activating this ability.
if (r.nextFloat() < .1 * currCounters)
return false;
}
return ((r.nextFloat() < .6667) && chance);
}
public static void putResolve(final AbilityFactory af, final SpellAbility sa, int counterAmount, final String type){
HashMap<String,String> params = af.getMapParams();
String DrawBack = params.get("SubAbility");
Card card = af.getHostCard();
Card tgtCard = (sa.getTarget() == null) ? card : sa.getTargetCard();
tgtCard.addCounter(Counters.valueOf(type), counterAmount);
if (af.hasSubAbility())
CardFactoryUtil.doDrawBack(DrawBack, counterAmount, card.getController(), AllZone.GameAction.getOpponent(card.getController()), card.getController(), card, null, sa);
}
}
- friarsol
- Global Moderator
- Posts: 7593
- Joined: 15 May 2010, 04:20
- Has thanked: 243 times
- Been thanked: 965 times
Re: Ability Factory
by Rob Cashwalker » 10 Oct 2010, 01:02
Yeah, you just screwed up the file for me.
I was able to reconcile the CardFactory change. But I forced my revision on the AbilityFactory.
I did rename DealDamage to AbilityFactory_DealDamage to match your format. There are a number of changes to how the variables and methods are declared in the new AF_DD.
This likely breaks your Counters class. I wish the SVN Lock function was implemented, then this would have been avoided.
I was able to reconcile the CardFactory change. But I forced my revision on the AbilityFactory.
I did rename DealDamage to AbilityFactory_DealDamage to match your format. There are a number of changes to how the variables and methods are declared in the new AF_DD.
This likely breaks your Counters class. I wish the SVN Lock function was implemented, then this would have been avoided.
The Force will be with you, Always.
-
Rob Cashwalker - Programmer
- Posts: 2167
- Joined: 09 Sep 2008, 15:09
- Location: New York
- Has thanked: 5 times
- Been thanked: 40 times
20 posts
• Page 1 of 2 • 1, 2
Who is online
Users browsing this forum: No registered users and 18 guests