- Removing Static from Member Variables of AbilityFactory so they are not shared between cards.

- Create a new AbilityFactory for each time AF is used to prevent issues with Factory not being fresh.
- Added AbilityFactory_Counters.java for AFs that deal with putting or removing counters from cards
- Added createAbilityPutCounters for Abilities that put Counters on cards.
- Added Amok, Fume Spitter, and Trigon of Corruption as Samples of using PutCounter
This commit is contained in:
jendave
2011-08-06 08:56:40 +00:00
parent 58fd4d34ae
commit f16b280138
7 changed files with 266 additions and 43 deletions

4
.gitattributes vendored
View File

@@ -122,6 +122,7 @@ res/cardsfolder/ambassador_oak.txt -text svneol=native#text/plain
res/cardsfolder/ambitions_cost.txt -text svneol=native#text/plain
res/cardsfolder/ambush_party.txt -text svneol=native#text/plain
res/cardsfolder/amnesia.txt -text svneol=native#text/plain
res/cardsfolder/amok.txt -text svneol=native#text/plain
res/cardsfolder/amrou_kithkin.txt -text svneol=native#text/plain
res/cardsfolder/amrou_scout.txt -text svneol=native#text/plain
res/cardsfolder/amrou_seekers.txt -text svneol=native#text/plain
@@ -1496,6 +1497,7 @@ res/cardsfolder/fruition.txt -text svneol=native#text/plain
res/cardsfolder/fugitive_wizard.txt -text svneol=native#text/plain
res/cardsfolder/fugue.txt -text svneol=native#text/plain
res/cardsfolder/fulminator_mage.txt -text svneol=native#text/plain
res/cardsfolder/fume_spitter.txt -text svneol=native#text/plain
res/cardsfolder/funeral_charm.txt -text svneol=native#text/plain
res/cardsfolder/fungal_shambler.txt -text svneol=native#text/plain
res/cardsfolder/furious_assault.txt -text svneol=native#text/plain
@@ -4191,6 +4193,7 @@ res/cardsfolder/trevas_attendant.txt -text svneol=native#text/plain
res/cardsfolder/tribal_flames.txt -text svneol=native#text/plain
res/cardsfolder/tribal_forcemage.txt -text svneol=native#text/plain
res/cardsfolder/trickster_mage.txt -text svneol=native#text/plain
res/cardsfolder/trigon_of_corruption.txt -text svneol=native#text/plain
res/cardsfolder/trinket_mage.txt -text svneol=native#text/plain
res/cardsfolder/trip_noose.txt -text svneol=native#text/plain
res/cardsfolder/trip_wire.txt -text svneol=native#text/plain
@@ -5049,6 +5052,7 @@ src/com/cloudgarden/layout/AnchorLayout.java -text svneol=native#text/plain
src/com/esotericsoftware/minlog/Log.java svneol=native#text/plain
src/forge/Ability.java svneol=native#text/plain
src/forge/AbilityFactory.java -text svneol=native#text/plain
src/forge/AbilityFactory_Counters.java -text svneol=native#text/plain
src/forge/Ability_Activated.java svneol=native#text/plain
src/forge/Ability_Cost.java -text svneol=native#text/plain
src/forge/Ability_Hand.java svneol=native#text/plain

9
res/cardsfolder/amok.txt Normal file
View File

@@ -0,0 +1,9 @@
Name:Amok
ManaCost:1 R
Types:Enchantment
Text:no text
A:AB$PutCounter|Cost$1 Discard<1/Random>|Tgt$TgtC|CounterType$P1P1|CounterNum$1|SpellDescription$Put a +1/+1 counter on target creature.
SVar:Rarity:Rare
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/amok.jpg
End

View File

@@ -0,0 +1,10 @@
Name:Fume Spitter
ManaCost:B
Types:Creature Horror
PT:1/1
Text:no text
A:AB$PutCounter|Cost$Sac<1/CARDNAME>|Tgt$TgtC|IsCurse$True|CounterType$M1M1|CounterNum$1|SpellDescription$Put a -1/-1 counter on target creature.
SVar:Rarity:Common
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/fume_spitter.jpg
End

View File

@@ -0,0 +1,12 @@
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:RemAIDeck:True
# I've run some tests where the AI does use both abilities of this card, but it can be flakey.
SVar:Picture:http://www.wizards.com/global/images/magic/general/trigon_of_corruption.jpg
End

View File

@@ -1,26 +1,25 @@
package forge;
import java.util.HashMap;
import java.util.Random;
public class AbilityFactory {
private static Card hostC = null;
private Card hostC = null;
public Card getHostCard()
{
return hostC;
}
private static HashMap<String,String> mapParams = new HashMap<String,String>();
private 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;
private boolean isAb = false;
private boolean isSp = false;
public boolean isAbility()
{
@@ -32,16 +31,16 @@ public class AbilityFactory {
return isSp;
}
private static Ability_Cost abCost = null;
private 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;
private boolean isTargeted = false;
private boolean hasValid = false;
private Target abTgt = null;
public boolean isTargeted()
{
@@ -58,7 +57,12 @@ public class AbilityFactory {
return abTgt;
}
private static boolean hasSubAb = false;
private boolean isCurse = false;
public boolean isCurse(){
return isCurse;
}
private boolean hasSubAb = false;
public boolean hasSubAbility()
{
@@ -75,8 +79,6 @@ public class AbilityFactory {
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))
@@ -97,11 +99,9 @@ public class AbilityFactory {
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"))
{
@@ -121,17 +121,10 @@ public class AbilityFactory {
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"))
@@ -140,26 +133,30 @@ public class AbilityFactory {
if (mapParams.containsKey("Tgt"))
{
isTargeted = true;
abTgt = new Target(mapParams.get("Tgt"));
}
if (isTargeted)
{
if (hasValid)
abTgt = new Target("TgtV", mapParams.get("TgtPrompt"), mapParams.get("ValidTgts").split(","));
else
abTgt = new Target(mapParams.get("Tgt"));
}
else{
abTgt = null;
}
if (mapParams.containsKey("IsCurse")){
isCurse = true;
}
//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};
@@ -183,13 +180,23 @@ public class AbilityFactory {
}
// additional keywords here
if (abAPI.equals("PutCounter")){
if (isAb)
SA = AbilityFactory_Counters.createAbilityPutCounters(this);
if (isSp){
// todo: createSpellPutCounters
}
}
// set universal properties of the SpellAbility
if (isTargeted)
SA.setTarget(abTgt);
SA.setPayCosts(abCost);
if (isSp){
// Ability_Activated sets abTgt and abCost in the constructor so this only needs to be set for Spells
// Once Spells are more compatible with Tgt and abCost this block should be removed
if (isTargeted)
SA.setTarget(abTgt);
SA.setPayCosts(abCost);
}
if (hasSpDesc)
SA.setDescription(abCost.toString() + mapParams.get("SpellDescription"));

View File

@@ -0,0 +1,181 @@
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);
}
}

View File

@@ -6093,14 +6093,14 @@ public class CardFactory implements NewConstants {
// 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++)
for (int i=0; i<IA.size(); i++){
AbilityFactory AF = new AbilityFactory();
card.addSpellAbility(AF.getAbility(IA.get(i), card));
}
}