mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28:01 +00:00
- Initial SubAbility work: DealDamage, GainLife, Draw, Mill all can now be called as a SubAbility and can call SubAbilities.
- Target_Selection will now Target for Parent ability and each subAbility that requires it - Stack will fizzle if ALL Targets are illegal on resolution - Merged Orcish Cannonade, Psionic Entity to AF+SubAbility - Added Lunge - Target Prompt now Optional in AFs
This commit is contained in:
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -2751,6 +2751,7 @@ res/cardsfolder/lull.txt -text svneol=native#text/plain
|
||||
res/cardsfolder/lumbering_satyr.txt -text svneol=native#text/plain
|
||||
res/cardsfolder/lumengrid_warden.txt -text svneol=native#text/plain
|
||||
res/cardsfolder/luminous_angel.txt -text svneol=native#text/plain
|
||||
res/cardsfolder/lunge.txt -text svneol=native#text/plain
|
||||
res/cardsfolder/lure.txt -text svneol=native#text/plain
|
||||
res/cardsfolder/lurking_informant.txt -text svneol=native#text/plain
|
||||
res/cardsfolder/lurking_nightstalker.txt -text svneol=native#text/plain
|
||||
@@ -5790,6 +5791,7 @@ src/forge/Ability_Cost.java -text svneol=native#text/plain
|
||||
src/forge/Ability_Mana.java -text svneol=native#text/plain
|
||||
src/forge/Ability_Reflected_Mana.java svneol=native#text/plain
|
||||
src/forge/Ability_Static.java -text svneol=native#text/plain
|
||||
src/forge/Ability_Sub.java -text svneol=native#text/plain
|
||||
src/forge/Ability_Tap.java svneol=native#text/plain
|
||||
src/forge/Ability_Triggered.java svneol=native#text/plain
|
||||
src/forge/AllZone.java svneol=native#text/plain
|
||||
|
||||
9
res/cardsfolder/lunge.txt
Normal file
9
res/cardsfolder/lunge.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Name:Lunge
|
||||
ManaCost:2 R
|
||||
Types:Instant
|
||||
Text:no text
|
||||
A:SP$DealDamage | Cost$ 2 R | Tgt$ C | NumDmg$2 | SubAbility$SVar=DB1 | SpellDescription$ Lunge deals 2 damage to target creature and 2 damage to target player.
|
||||
SVar:DB1:DB$DealDamage | NumDmg$ 2 | Tgt$ P
|
||||
SVar:Rarity:Common
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/lunge.jpg
|
||||
End
|
||||
@@ -2,8 +2,9 @@ Name:Orcish Cannonade
|
||||
ManaCost:1 R R
|
||||
Types:Instant
|
||||
Text:no text
|
||||
K:spDamageTgtCP:2:Drawback$DamageYou/3:Orcish Cannonade deals 2 damage to target creature or player and 3 damage to you.
|
||||
K:Draw a card.
|
||||
A:SP$DealDamage | Cost$ 1 R R | Tgt$ CP | NumDmg$2 | SubAbility$SVar=DB1 | SpellDescription$ Orcish Cannonade deals 2 damage to target creature or player and 3 damage to you. Draw a card.
|
||||
SVar:DB1:DB$DealDamage | NumDmg$ 3 | Affected$ You | SubAbility$SVar=DB2
|
||||
SVar:DB2:DB$Draw | NumCards$ 1
|
||||
SVar:Rarity:Common
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/orcish_cannonade.jpg
|
||||
End
|
||||
@@ -3,7 +3,8 @@ ManaCost:4 U
|
||||
Types:Creature Illusion
|
||||
Text:no text
|
||||
PT:2/2
|
||||
K:abDamageTgtCP T:2:Drawback$DamageSelf/3:Psionic Entity deals 2 damage to target creature or player and 3 damage to itself.:Psionic Entity - deal damage to creature or player and itself.
|
||||
A:AB$DealDamage | Cost$ T | Tgt$ CP | NumDmg$2 | SubAbility$SVar=DB1 | SpellDescription$ Psionic Entity deals 2 damage to target creature or player and 3 damage to itself.
|
||||
SVar:DB1:DB$DealDamage | NumDmg$ 3 | Affected$ Self
|
||||
SVar:Rarity:Rare
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/psionic_entity.jpg
|
||||
End
|
||||
|
||||
@@ -20,6 +20,7 @@ public class AbilityFactory {
|
||||
|
||||
private boolean isAb = false;
|
||||
private boolean isSp = false;
|
||||
private boolean isDb = false;
|
||||
|
||||
public boolean isAbility()
|
||||
{
|
||||
@@ -31,6 +32,10 @@ public class AbilityFactory {
|
||||
return isSp;
|
||||
}
|
||||
|
||||
public boolean isDrawback() {
|
||||
return isDb;
|
||||
}
|
||||
|
||||
private Ability_Cost abCost = null;
|
||||
|
||||
public Ability_Cost getAbCost()
|
||||
@@ -120,14 +125,18 @@ public class AbilityFactory {
|
||||
isSp = true;
|
||||
API = mapParams.get("SP");
|
||||
}
|
||||
else if (mapParams.containsKey("DB")) {
|
||||
isDb = true;
|
||||
API = mapParams.get("DB");
|
||||
}
|
||||
else
|
||||
throw new RuntimeException("AbilityFactory : getAbility -- no API in " + hostCard.getName());
|
||||
|
||||
|
||||
if (!isDb){
|
||||
if (!mapParams.containsKey("Cost"))
|
||||
throw new RuntimeException("AbilityFactory : getAbility -- no Cost in " + hostCard.getName());
|
||||
abCost = new Ability_Cost(mapParams.get("Cost"), hostCard.getName(), isAb);
|
||||
|
||||
}
|
||||
|
||||
if (mapParams.containsKey("ValidTgts"))
|
||||
{
|
||||
@@ -145,8 +154,11 @@ public class AbilityFactory {
|
||||
int min = mapParams.containsKey("TargetMin") ? Integer.parseInt(mapParams.get("TargetMin")) : 1;
|
||||
int max = mapParams.containsKey("TargetMax") ? Integer.parseInt(mapParams.get("TargetMax")) : 1;
|
||||
|
||||
if (hasValid)
|
||||
abTgt = new Target(mapParams.get("TgtPrompt"), mapParams.get("ValidTgts").split(","), min, max);
|
||||
if (hasValid){
|
||||
// TgtPrompt now optional
|
||||
String prompt = mapParams.containsKey("TgtPrompt") ? mapParams.get("TgtPrompt") : "Select target " + mapParams.get("ValidTgts");
|
||||
abTgt = new Target(prompt, mapParams.get("ValidTgts").split(","), min, max);
|
||||
}
|
||||
else
|
||||
abTgt = new Target(mapParams.get("Tgt"), min, max);
|
||||
|
||||
@@ -163,26 +175,15 @@ public class AbilityFactory {
|
||||
|
||||
if (API.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);
|
||||
|
||||
else if (tmpND.matches("[0-9][0-9]?"))
|
||||
NumDmg[0] = Integer.parseInt(tmpND);
|
||||
}
|
||||
|
||||
AbilityFactory_DealDamage dd = new AbilityFactory_DealDamage();
|
||||
AbilityFactory_DealDamage dd = new AbilityFactory_DealDamage(this);
|
||||
|
||||
if (isAb)
|
||||
SA = dd.getAbility(this, NumDmg[0], NumDmgX[0]);
|
||||
SA = dd.getAbility();
|
||||
else if (isSp)
|
||||
SA = dd.getSpell(this, NumDmg[0], NumDmgX[0]);
|
||||
|
||||
|
||||
SA = dd.getSpell();
|
||||
else if (isDb)
|
||||
SA = dd.getDrawback();
|
||||
SA.setSubAbility(dd.getSubAbility());
|
||||
}
|
||||
|
||||
if (API.equals("PutCounter")){
|
||||
@@ -240,9 +241,12 @@ public class AbilityFactory {
|
||||
if (API.equals("GainLife")){
|
||||
if (isAb)
|
||||
SA = AbilityFactory_AlterLife.createAbilityGainLife(this);
|
||||
if (isSp){
|
||||
else if (isSp)
|
||||
SA = AbilityFactory_AlterLife.createSpellGainLife(this);
|
||||
}
|
||||
else if (isDb)
|
||||
SA = AbilityFactory_AlterLife.createSpellGainLife(this);
|
||||
if(hasSubAbility())
|
||||
SA.setSubAbility(getSubAbility());
|
||||
}
|
||||
|
||||
if (API.equals("LoseLife")){
|
||||
@@ -297,17 +301,23 @@ public class AbilityFactory {
|
||||
if (API.equals("Draw")){
|
||||
if (isAb)
|
||||
SA = AbilityFactory_ZoneAffecting.createAbilityDraw(this);
|
||||
if (isSp){
|
||||
else if (isSp)
|
||||
SA = AbilityFactory_ZoneAffecting.createSpellDraw(this);
|
||||
}
|
||||
else if (isDb)
|
||||
SA = AbilityFactory_ZoneAffecting.createDrawbackDraw(this);
|
||||
if(hasSubAbility())
|
||||
SA.setSubAbility(getSubAbility());
|
||||
}
|
||||
|
||||
if (API.equals("Mill")){
|
||||
if (isAb)
|
||||
SA = AbilityFactory_ZoneAffecting.createAbilityMill(this);
|
||||
if (isSp){
|
||||
else if (isSp)
|
||||
SA = AbilityFactory_ZoneAffecting.createSpellMill(this);
|
||||
}
|
||||
else if (isDb)
|
||||
SA = AbilityFactory_ZoneAffecting.createDrawbackMill(this);
|
||||
if(hasSubAbility())
|
||||
SA.setSubAbility(getSubAbility());
|
||||
}
|
||||
|
||||
if (API.equals("Destroy")){
|
||||
@@ -402,6 +412,27 @@ public class AbilityFactory {
|
||||
return SA;
|
||||
}
|
||||
|
||||
// Easy creation of SubAbilities
|
||||
public Ability_Sub getSubAbility(){
|
||||
Ability_Sub abSub = null;
|
||||
|
||||
String sSub = getMapParams().get("SubAbility");
|
||||
|
||||
if (sSub.startsWith("SVar="))
|
||||
sSub = getHostCard().getSVar(sSub.split("=")[1]);
|
||||
|
||||
if (sSub.startsWith("DB$"))
|
||||
{
|
||||
AbilityFactory afDB = new AbilityFactory();
|
||||
abSub = (Ability_Sub)afDB.getAbility(sSub, getHostCard());
|
||||
}
|
||||
else{
|
||||
// Older style Drawback. May not be necessary?
|
||||
}
|
||||
|
||||
return abSub;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -18,15 +18,7 @@ public class AbilityFactory_AlterLife {
|
||||
@Override
|
||||
public String getStackDescription(){
|
||||
// when getStackDesc is called, just build exactly what is happening
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String name = af.getHostCard().getName();
|
||||
int amount = calculateAmount(af.getHostCard(), params.get("LifeAmount"), this);
|
||||
String player = "You gain ";
|
||||
if (af.getAbTgt() != null)
|
||||
player = getTargetPlayer() + " gains ";
|
||||
sb.append(name).append(" - ").append(player).append(amount).append(" life.");
|
||||
|
||||
return sb.toString();
|
||||
return gainLifeStackDescription(af, this);
|
||||
}
|
||||
|
||||
public boolean canPlay(){
|
||||
@@ -59,15 +51,7 @@ public class AbilityFactory_AlterLife {
|
||||
@Override
|
||||
public String getStackDescription(){
|
||||
// when getStackDesc is called, just build exactly what is happening
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String name = af.getHostCard().getName();
|
||||
int amount = calculateAmount(af.getHostCard(), params.get("LifeAmount"), this);
|
||||
String player = "You gain ";
|
||||
if (af.getAbTgt() != null)
|
||||
player = getTargetPlayer() + " gains ";
|
||||
sb.append(name).append(" - ").append(player).append(amount).append(" life.");
|
||||
|
||||
return sb.toString();
|
||||
return gainLifeStackDescription(af, this);
|
||||
}
|
||||
|
||||
public boolean canPlay(){
|
||||
@@ -93,6 +77,42 @@ public class AbilityFactory_AlterLife {
|
||||
return spGainLife;
|
||||
}
|
||||
|
||||
public static SpellAbility createDrawbackGainLife(final AbilityFactory AF){
|
||||
final SpellAbility dbGainLife = new Ability_Sub(AF.getHostCard(), AF.getAbTgt()){
|
||||
private static final long serialVersionUID = 6631124959690157874L;
|
||||
|
||||
final AbilityFactory af = AF;
|
||||
final HashMap<String,String> params = af.getMapParams();
|
||||
|
||||
@Override
|
||||
public String getStackDescription(){
|
||||
// when getStackDesc is called, just build exactly what is happening
|
||||
return gainLifeStackDescription(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 gainLifeCanPlayAI(af, this, params.get("LifeAmount"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve() {
|
||||
int amount = calculateAmount(af.getHostCard(), params.get("LifeAmount"), this);
|
||||
gainLifeResolve(af, this, amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAI_Drawback() {
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
return dbGainLife;
|
||||
}
|
||||
|
||||
public static SpellAbility createAbilityLoseLife(final AbilityFactory AF){
|
||||
final SpellAbility abLoseLife = new Ability_Activated(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()){
|
||||
private static final long serialVersionUID = 1129762905315395160L;
|
||||
@@ -182,13 +202,6 @@ public class AbilityFactory_AlterLife {
|
||||
return abLoseLife;
|
||||
}
|
||||
|
||||
public static SpellAbility createAbilitySyphonLife(final AbilityFactory AF){ // tgt loses X life, you gain X life
|
||||
return null;
|
||||
}
|
||||
|
||||
public static SpellAbility createSpellSyphonLife(final AbilityFactory AF){ // tgt loses X life, you gain X life
|
||||
return null;
|
||||
}
|
||||
|
||||
public static int calculateAmount(Card card, String lifeAmount, SpellAbility ability){
|
||||
if (lifeAmount.matches("X"))
|
||||
@@ -212,6 +225,29 @@ public class AbilityFactory_AlterLife {
|
||||
return Integer.parseInt(lifeAmount);
|
||||
}
|
||||
|
||||
public static String gainLifeStackDescription(AbilityFactory af, SpellAbility sa){
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int amount = calculateAmount(af.getHostCard(), af.getMapParams().get("LifeAmount"), sa);
|
||||
|
||||
if (!(sa instanceof Ability_Sub))
|
||||
sb.append(sa.getSourceCard().getName()).append(" - ");
|
||||
else
|
||||
sb.append(" ");
|
||||
|
||||
String player = "You gain ";
|
||||
if (af.getAbTgt() != null)
|
||||
player = sa.getTargetPlayer() + " gains ";
|
||||
sb.append(player).append(amount).append(" life.");
|
||||
|
||||
Ability_Sub abSub = sa.getSubAbility();
|
||||
if (abSub != null) {
|
||||
abSub.setParent(sa);
|
||||
sb.append(abSub.getStackDescription());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static boolean gainLifeCanPlayAI(final AbilityFactory af, final SpellAbility sa, final String amountStr){
|
||||
Random r = new Random();
|
||||
Ability_Cost abCost = sa.getPayCosts();
|
||||
@@ -260,7 +296,6 @@ public class AbilityFactory_AlterLife {
|
||||
|
||||
public static void gainLifeResolve(final AbilityFactory af, final SpellAbility sa, int lifeAmount){
|
||||
HashMap<String,String> params = af.getMapParams();
|
||||
String DrawBack = params.get("SubAbility");
|
||||
Card card = af.getHostCard();
|
||||
|
||||
ArrayList<Player> tgtPlayers;
|
||||
@@ -277,9 +312,21 @@ public class AbilityFactory_AlterLife {
|
||||
if (tgt == null || p.canTarget(af.getHostCard()))
|
||||
p.gainLife(lifeAmount, sa.getSourceCard());
|
||||
|
||||
|
||||
if (af.hasSubAbility()){
|
||||
Ability_Sub abSub = sa.getSubAbility();
|
||||
if (abSub != null){
|
||||
if (abSub.getParent() == null)
|
||||
abSub.setParent(sa);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean loseLifeCanPlayAI(final AbilityFactory af, final SpellAbility sa, final String amountStr){
|
||||
Random r = new Random();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package forge;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
package forge;
|
||||
|
||||
public class AbilityFactory_DealDamage {
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
|
||||
public class AbilityFactory_DealDamage {
|
||||
private AbilityFactory AF = null;
|
||||
|
||||
private int nDamage = -1;
|
||||
@@ -13,16 +13,55 @@ public class AbilityFactory_DealDamage {
|
||||
|
||||
private boolean TgtOpp = false;
|
||||
|
||||
public SpellAbility getAbility(final AbilityFactory af, final int NumDmg, final String NumDmgX)
|
||||
{
|
||||
AF = af;
|
||||
nDamage = NumDmg;
|
||||
XDamage = NumDmgX;
|
||||
private Ability_Sub subAbAF = null;
|
||||
private boolean hasSubAbAF = false;
|
||||
private String subAbStr = "none";
|
||||
private boolean hasSubAbStr = false;
|
||||
|
||||
if(af.getMapParams().containsKey("Tgt"))
|
||||
public Ability_Sub getSubAbility() { return subAbAF; }
|
||||
|
||||
public AbilityFactory_DealDamage(AbilityFactory newAF)
|
||||
{
|
||||
AF = newAF;
|
||||
|
||||
String tmpND = AF.getMapParams().get("NumDmg");
|
||||
if (tmpND.length() > 0)
|
||||
{
|
||||
if (tmpND.matches("[xX]"))
|
||||
XDamage = AF.getHostCard().getSVar(tmpND.substring(1));
|
||||
|
||||
else if (tmpND.matches("[0-9][0-9]?"))
|
||||
nDamage = Integer.parseInt(tmpND);
|
||||
}
|
||||
|
||||
if(AF.getMapParams().containsKey("Tgt"))
|
||||
if (AF.getMapParams().get("Tgt").equals("TgtOpp"))
|
||||
TgtOpp = true;
|
||||
|
||||
if(AF.hasSubAbility())
|
||||
{
|
||||
String sSub = AF.getMapParams().get("SubAbility");
|
||||
|
||||
if (sSub.startsWith("SVar="))
|
||||
sSub = AF.getHostCard().getSVar(sSub.split("=")[1]);
|
||||
|
||||
if (sSub.startsWith("DB$"))
|
||||
{
|
||||
AbilityFactory afDB = new AbilityFactory();
|
||||
subAbAF = (Ability_Sub)afDB.getAbility(sSub, AF.getHostCard());
|
||||
hasSubAbAF = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
subAbStr = sSub;
|
||||
hasSubAbStr = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SpellAbility getAbility()
|
||||
{
|
||||
final SpellAbility abDamage = new Ability_Activated(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt())
|
||||
{
|
||||
private static final long serialVersionUID = -7560349014757367722L;
|
||||
@@ -54,16 +93,8 @@ public class AbilityFactory_DealDamage {
|
||||
return abDamage;
|
||||
}
|
||||
|
||||
public SpellAbility getSpell(final AbilityFactory af, final int NumDmg, final String NumDmgX)
|
||||
public SpellAbility getSpell()
|
||||
{
|
||||
AF = af;
|
||||
nDamage = NumDmg;
|
||||
XDamage = NumDmgX;
|
||||
|
||||
if(af.getMapParams().containsKey("Tgt"))
|
||||
if (AF.getMapParams().get("Tgt").equals("TgtOpp"))
|
||||
TgtOpp = true;
|
||||
|
||||
final SpellAbility spDealDamage = new Spell(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()) {
|
||||
private static final long serialVersionUID = 7239608350643325111L;
|
||||
|
||||
@@ -95,6 +126,32 @@ public class AbilityFactory_DealDamage {
|
||||
return spDealDamage;
|
||||
}
|
||||
|
||||
public SpellAbility getDrawback()
|
||||
{
|
||||
final SpellAbility dbDealDamage = new Ability_Sub(AF.getHostCard(), AF.getAbTgt()) {
|
||||
private static final long serialVersionUID = 7239608350643325111L;
|
||||
|
||||
@Override
|
||||
public boolean chkAI_Drawback() {
|
||||
return doCanPlayAI(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStackDescription() {
|
||||
return damageStackDescription(AF, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve() {
|
||||
doResolve(this);
|
||||
}
|
||||
|
||||
}; // Spell
|
||||
|
||||
return dbDealDamage;
|
||||
}
|
||||
|
||||
|
||||
private int getNumDamage(SpellAbility saMe) {
|
||||
if(nDamage != -1) return nDamage;
|
||||
|
||||
@@ -237,19 +294,16 @@ public class AbilityFactory_DealDamage {
|
||||
String name = af.getHostCard().getName();
|
||||
int damage = getNumDamage(sa);
|
||||
|
||||
ArrayList<Object> tgts;
|
||||
Target tgt = AF.getAbTgt();
|
||||
if (tgt != null)
|
||||
tgts = tgt.getTargets();
|
||||
else{
|
||||
tgts = new ArrayList<Object>();
|
||||
if (TgtOpp)
|
||||
tgts.add(AF.getHostCard().getController().getOpponent());
|
||||
}
|
||||
ArrayList<Object> tgts = findTargets(sa);
|
||||
|
||||
if (!(sa instanceof Ability_Sub))
|
||||
sb.append(name).append(" - ");
|
||||
|
||||
sb.append("Deals ").append(damage).append(" damage to ");
|
||||
for(int i = 0; i < tgts.size(); i++){
|
||||
if (i != 0)
|
||||
sb.append(" ");
|
||||
|
||||
Object o = tgts.get(0);
|
||||
if (o instanceof Player){
|
||||
sb.append(((Player)o).getName());
|
||||
@@ -257,27 +311,47 @@ public class AbilityFactory_DealDamage {
|
||||
else{
|
||||
sb.append(((Card)o).getName());
|
||||
}
|
||||
sb.append(" ");
|
||||
|
||||
}
|
||||
sb.append(". ");
|
||||
|
||||
if (hasSubAbAF){
|
||||
subAbAF.setParent(sa);
|
||||
sb.append(subAbAF.getStackDescription());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private ArrayList<Object> findTargets(SpellAbility saMe){
|
||||
Target tgt = AF.getAbTgt();
|
||||
ArrayList<Object> tgts;
|
||||
if (tgt != null)
|
||||
tgts = tgt.getTargets();
|
||||
else{
|
||||
tgts = new ArrayList<Object>();
|
||||
if (TgtOpp){
|
||||
tgts.add(saMe.getActivatingPlayer().getOpponent());
|
||||
}
|
||||
else if (AF.getMapParams().containsKey("Affected")){
|
||||
String affected = AF.getMapParams().get("Affected");
|
||||
if (affected.equals("You"))
|
||||
tgts.add(saMe.getActivatingPlayer());
|
||||
else if (affected.equals("Self"))
|
||||
tgts.add(saMe.getSourceCard());
|
||||
}
|
||||
}
|
||||
return tgts;
|
||||
}
|
||||
|
||||
private void doResolve(SpellAbility saMe)
|
||||
{
|
||||
int damage = getNumDamage(saMe);
|
||||
|
||||
ArrayList<Object> tgts;
|
||||
Target tgt = AF.getAbTgt();
|
||||
if (tgt != null)
|
||||
tgts = tgt.getTargets();
|
||||
else{
|
||||
tgts = new ArrayList<Object>();
|
||||
if (TgtOpp)
|
||||
tgts.add(AF.getHostCard().getController().getOpponent());
|
||||
}
|
||||
ArrayList<Object> tgts = findTargets(saMe);
|
||||
boolean targeted = (AF.getAbTgt() != null) || TgtOpp;
|
||||
|
||||
if (tgts.size() == 0){
|
||||
if (tgts == null || tgts.size() == 0){
|
||||
System.out.println("No targets?");
|
||||
return;
|
||||
}
|
||||
@@ -285,16 +359,23 @@ public class AbilityFactory_DealDamage {
|
||||
for(Object o : tgts){
|
||||
if (o instanceof Card){
|
||||
Card c = (Card)o;
|
||||
if(AllZone.GameAction.isCardInPlay(c) && CardFactoryUtil.canTarget(AF.getHostCard(), c))
|
||||
if(AllZone.GameAction.isCardInPlay(c) && (!targeted || CardFactoryUtil.canTarget(AF.getHostCard(), c)))
|
||||
c.addDamage(damage, AF.getHostCard());
|
||||
}
|
||||
else if (o instanceof Player){
|
||||
Player p = (Player) o;
|
||||
if (p.canTarget(AF.getHostCard()))
|
||||
if (!targeted || p.canTarget(AF.getHostCard()))
|
||||
p.addDamage(damage, AF.getHostCard());
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSubAbAF) {
|
||||
if (subAbAF.getParent() == null)
|
||||
subAbAF.setParent(saMe);
|
||||
subAbAF.resolve();
|
||||
}
|
||||
|
||||
else if (hasSubAbStr){
|
||||
Object obj = tgts.get(0);
|
||||
|
||||
Player pl = null;
|
||||
@@ -307,10 +388,10 @@ public class AbilityFactory_DealDamage {
|
||||
else{
|
||||
pl = (Player)obj;
|
||||
}
|
||||
CardFactoryUtil.doDrawBack(subAbStr, damage, AF.getHostCard().getController(),
|
||||
AF.getHostCard().getController().getOpponent(), pl, AF.getHostCard(), c, saMe);
|
||||
|
||||
if(AF.hasSubAbility())
|
||||
CardFactoryUtil.doDrawBack(AF.getMapParams().get("SubAbility"), damage,
|
||||
AF.getHostCard().getController(), AF.getHostCard().getController().getOpponent(),
|
||||
pl, AF.getHostCard(), c, saMe);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,17 +14,7 @@ public class AbilityFactory_ZoneAffecting {
|
||||
@Override
|
||||
public String getStackDescription(){
|
||||
// when getStackDesc is called, just build exactly what is happening
|
||||
Player player = af.getAbTgt() == null ? getActivatingPlayer() : getTargetPlayer();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(getSourceCard().getName());
|
||||
sb.append(" - ");
|
||||
sb.append(player.toString());
|
||||
sb.append(" draws (");
|
||||
sb.append(af.getMapParams().get("NumCards"));
|
||||
sb.append(")");
|
||||
|
||||
return sb.toString();
|
||||
return drawStackDescription(af, this);
|
||||
}
|
||||
|
||||
public boolean canPlay(){
|
||||
@@ -55,17 +45,7 @@ public class AbilityFactory_ZoneAffecting {
|
||||
@Override
|
||||
public String getStackDescription(){
|
||||
// when getStackDesc is called, just build exactly what is happening
|
||||
Player player = af.getAbTgt() == null ? getActivatingPlayer() : getTargetPlayer();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(getSourceCard().getName());
|
||||
sb.append(" - ");
|
||||
sb.append(player.toString());
|
||||
sb.append(" draws (");
|
||||
sb.append(af.getMapParams().get("NumCards"));
|
||||
sb.append(")");
|
||||
|
||||
return sb.toString();
|
||||
return drawStackDescription(af, this);
|
||||
}
|
||||
|
||||
public boolean canPlay(){
|
||||
@@ -87,6 +67,56 @@ public class AbilityFactory_ZoneAffecting {
|
||||
return spDraw;
|
||||
}
|
||||
|
||||
public static SpellAbility createDrawbackDraw(final AbilityFactory AF){
|
||||
final SpellAbility dbDraw = new Ability_Sub(AF.getHostCard(), AF.getAbTgt()){
|
||||
private static final long serialVersionUID = -4990932993654533449L;
|
||||
|
||||
final AbilityFactory af = AF;
|
||||
|
||||
@Override
|
||||
public String getStackDescription(){
|
||||
// when getStackDesc is called, just build exactly what is happening
|
||||
return drawStackDescription(af, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve() {
|
||||
drawResolve(af, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAI_Drawback() {
|
||||
// TODO Auto-generated method stub
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
return dbDraw;
|
||||
}
|
||||
|
||||
public static String drawStackDescription(AbilityFactory af, SpellAbility sa){
|
||||
Player player = af.getAbTgt() == null ? sa.getActivatingPlayer() : sa.getTargetPlayer();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (!(sa instanceof Ability_Sub))
|
||||
sb.append(sa.getSourceCard().getName()).append(" - ");
|
||||
else
|
||||
sb.append(" ");
|
||||
|
||||
sb.append(player.toString());
|
||||
sb.append(" draws (");
|
||||
sb.append(af.getMapParams().get("NumCards"));
|
||||
sb.append(").");
|
||||
|
||||
Ability_Sub abSub = sa.getSubAbility();
|
||||
if (abSub != null){
|
||||
abSub.setParent(sa);
|
||||
sb.append(abSub.getStackDescription());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static boolean drawCanPlayAI(final AbilityFactory af, SpellAbility sa){
|
||||
// AI cannot use this properly until he can use SAs during Humans turn
|
||||
if (!ComputerUtil.canPayCost(sa))
|
||||
@@ -161,13 +191,28 @@ public class AbilityFactory_ZoneAffecting {
|
||||
}
|
||||
|
||||
for(Player p : tgtPlayers)
|
||||
if (tgt == null || p.canTarget(af.getHostCard()))
|
||||
if (tgt == null || p.canTarget(af.getHostCard())){
|
||||
if (params.containsKey("NextUpkeep"))
|
||||
for(int i = 0; i < numCards; i++)
|
||||
p.addSlowtripList(source);
|
||||
else
|
||||
p.drawCards(numCards);
|
||||
|
||||
}
|
||||
|
||||
if (af.hasSubAbility()){
|
||||
Ability_Sub abSub = sa.getSubAbility();
|
||||
if (abSub != null){
|
||||
if (abSub.getParent() == null)
|
||||
abSub.setParent(sa);
|
||||
abSub.resolve();
|
||||
}
|
||||
else{
|
||||
String DrawBack = params.get("SubAbility");
|
||||
if (af.hasSubAbility())
|
||||
CardFactoryUtil.doDrawBack(DrawBack, 0, source.getController(), source.getController().getOpponent(), source.getController(), source, null, sa);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -181,18 +226,7 @@ public class AbilityFactory_ZoneAffecting {
|
||||
|
||||
@Override
|
||||
public String getStackDescription(){
|
||||
// when getStackDesc is called, just build exactly what is happening
|
||||
Player player = af.getAbTgt() == null ? getActivatingPlayer() : getTargetPlayer();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(getSourceCard().getName());
|
||||
sb.append(" - Mills ");
|
||||
sb.append(af.getMapParams().get("NumCards"));
|
||||
sb.append(" Cards from ");
|
||||
sb.append(player.toString());
|
||||
sb.append("'s library.");
|
||||
|
||||
return sb.toString();
|
||||
return millStackDescription(this, af);
|
||||
}
|
||||
|
||||
public boolean canPlay(){
|
||||
@@ -220,6 +254,11 @@ public class AbilityFactory_ZoneAffecting {
|
||||
|
||||
final AbilityFactory af = AF;
|
||||
|
||||
@Override
|
||||
public String getStackDescription(){
|
||||
return millStackDescription(this, af);
|
||||
}
|
||||
|
||||
public boolean canPlay(){
|
||||
// super takes care of AdditionalCosts
|
||||
return super.canPlay();
|
||||
@@ -239,6 +278,61 @@ public class AbilityFactory_ZoneAffecting {
|
||||
return spMill;
|
||||
}
|
||||
|
||||
public static SpellAbility createDrawbackMill(final AbilityFactory AF){
|
||||
final SpellAbility dbMill = new Ability_Sub(AF.getHostCard(), AF.getAbTgt()){
|
||||
private static final long serialVersionUID = -4990932993654533449L;
|
||||
|
||||
final AbilityFactory af = AF;
|
||||
|
||||
@Override
|
||||
public String getStackDescription(){
|
||||
return millStackDescription(this, af);
|
||||
}
|
||||
|
||||
public boolean canPlayAI()
|
||||
{
|
||||
return millCanPlayAI(af, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve() {
|
||||
millResolve(af, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAI_Drawback() {
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
return dbMill;
|
||||
}
|
||||
|
||||
public static String millStackDescription(SpellAbility sa, AbilityFactory af){
|
||||
// when getStackDesc is called, just build exactly what is happening
|
||||
Player player = af.getAbTgt() == null ? sa.getActivatingPlayer() : sa.getTargetPlayer();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (!(sa instanceof Ability_Sub))
|
||||
sb.append(sa.getSourceCard().getName()).append(" - ");
|
||||
else
|
||||
sb.append(" ");
|
||||
|
||||
sb.append("Mills ");
|
||||
sb.append(af.getMapParams().get("NumCards"));
|
||||
sb.append(" Card(s) from ");
|
||||
sb.append(player.toString());
|
||||
sb.append("'s library.");
|
||||
|
||||
Ability_Sub abSub = sa.getSubAbility();
|
||||
if (abSub != null){
|
||||
abSub.setParent(sa);
|
||||
sb.append(abSub.getStackDescription());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static boolean millCanPlayAI(final AbilityFactory af, SpellAbility sa){
|
||||
// AI cannot use this properly until he can use SAs during Humans turn
|
||||
if (!ComputerUtil.canPayCost(sa))
|
||||
@@ -304,6 +398,7 @@ public class AbilityFactory_ZoneAffecting {
|
||||
HashMap<String,String> params = af.getMapParams();
|
||||
|
||||
Card source = sa.getSourceCard();
|
||||
// todo: handle deciding what X would be around here for Psychic Drain type cards
|
||||
int numCards = Integer.parseInt(params.get("NumCards"));
|
||||
|
||||
ArrayList<Player> tgtPlayers;
|
||||
@@ -320,9 +415,18 @@ public class AbilityFactory_ZoneAffecting {
|
||||
if (tgt == null || p.canTarget(af.getHostCard()))
|
||||
p.mill(numCards);
|
||||
|
||||
if (af.hasSubAbility()){
|
||||
Ability_Sub abSub = sa.getSubAbility();
|
||||
if (abSub != null){
|
||||
if (abSub.getParent() == null)
|
||||
abSub.setParent(sa);
|
||||
abSub.resolve();
|
||||
}
|
||||
else{
|
||||
String DrawBack = params.get("SubAbility");
|
||||
if (af.hasSubAbility())
|
||||
CardFactoryUtil.doDrawBack(DrawBack, 0, source.getController(), source.getController().getOpponent(), tgtPlayers.get(0), source, null, sa);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
29
src/forge/Ability_Sub.java
Normal file
29
src/forge/Ability_Sub.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package forge;
|
||||
|
||||
abstract public class Ability_Sub extends SpellAbility implements java.io.Serializable {
|
||||
private static final long serialVersionUID = 4650634415821733134L;
|
||||
|
||||
private SpellAbility parent = null;
|
||||
|
||||
public Ability_Sub(Card sourceCard, Target tgt) {
|
||||
super(SpellAbility.Ability, sourceCard);
|
||||
setTarget(tgt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPlay() {
|
||||
// this should never be on the Stack by itself
|
||||
return false;
|
||||
}
|
||||
|
||||
abstract public boolean chkAI_Drawback();
|
||||
|
||||
public void setParent(SpellAbility parent) {
|
||||
this.parent = parent;
|
||||
this.setActivatingPlayer(parent.getActivatingPlayer());
|
||||
}
|
||||
|
||||
public SpellAbility getParent() {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
@@ -440,32 +440,7 @@ public class MagicStack extends MyObservable {
|
||||
|
||||
AllZone.Phase.resetPriority(); // ActivePlayer gains priority first after Resolve
|
||||
Card source = sa.getSourceCard();
|
||||
boolean fizzle = false;
|
||||
|
||||
Target tgt = sa.getTarget();
|
||||
if (tgt != null){
|
||||
fizzle = true;
|
||||
// With multi-targets, as long as one target is still legal, we'll try to go through as much as possible
|
||||
ArrayList<Object> tgts = tgt.getTargets();
|
||||
for(Object o : tgts){
|
||||
if (o instanceof Player){
|
||||
Player p = (Player)o;
|
||||
fizzle &= !(p.canTarget(sa.getTargetCard()));
|
||||
}
|
||||
if (o instanceof Card){
|
||||
Card card = (Card)o;
|
||||
fizzle &= !(CardFactoryUtil.isTargetStillValid(sa, card));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (sa.getTargetCard() != null) {
|
||||
// Fizzling will only work for Abilities that use the Target class,
|
||||
// since the info isn't available otherwise
|
||||
fizzle = !CardFactoryUtil.isTargetStillValid(sa, sa.getTargetCard());
|
||||
}
|
||||
else if (sa.getTargetPlayer() != null) {
|
||||
fizzle = !sa.getTargetPlayer().canTarget(source);
|
||||
}
|
||||
boolean fizzle = hasFizzled(sa, source);
|
||||
|
||||
if (!fizzle) {
|
||||
final Card crd = source;
|
||||
@@ -546,6 +521,42 @@ public class MagicStack extends MyObservable {
|
||||
GuiDisplayUtil.updateGUI();
|
||||
}
|
||||
|
||||
public boolean hasFizzled(SpellAbility sa, Card source){
|
||||
boolean fizzle = false;
|
||||
|
||||
Target tgt = sa.getTarget();
|
||||
if (tgt != null){
|
||||
fizzle = true;
|
||||
// With multi-targets, as long as one target is still legal, we'll try to go through as much as possible
|
||||
ArrayList<Object> tgts = tgt.getTargets();
|
||||
for(Object o : tgts){
|
||||
if (o instanceof Player){
|
||||
Player p = (Player)o;
|
||||
fizzle &= !(p.canTarget(sa.getTargetCard()));
|
||||
}
|
||||
if (o instanceof Card){
|
||||
Card card = (Card)o;
|
||||
fizzle &= !(CardFactoryUtil.isTargetStillValid(sa, card));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (sa.getTargetCard() != null) {
|
||||
// Fizzling will only work for Abilities that use the Target class,
|
||||
// since the info isn't available otherwise
|
||||
fizzle = !CardFactoryUtil.isTargetStillValid(sa, sa.getTargetCard());
|
||||
}
|
||||
else if (sa.getTargetPlayer() != null) {
|
||||
fizzle = !sa.getTargetPlayer().canTarget(source);
|
||||
}
|
||||
|
||||
Ability_Sub abSub = sa.getSubAbility();
|
||||
if (abSub != null)
|
||||
fizzle &= hasFizzled(abSub, source);
|
||||
|
||||
return fizzle;
|
||||
}
|
||||
|
||||
|
||||
public SpellAbility pop() {
|
||||
SpellAbility sp = (SpellAbility) stack.remove(0);
|
||||
this.updateObservers();
|
||||
|
||||
@@ -48,6 +48,7 @@ public abstract class SpellAbility {
|
||||
protected Target chosenTarget = null;
|
||||
|
||||
private SpellAbility_Restriction restrictions = new SpellAbility_Restriction();
|
||||
private Ability_Sub subAbility = null;
|
||||
|
||||
private CardList sacrificedCards = null;
|
||||
|
||||
@@ -322,6 +323,14 @@ public abstract class SpellAbility {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setSubAbility(Ability_Sub subAbility) {
|
||||
this.subAbility = subAbility;
|
||||
}
|
||||
|
||||
public Ability_Sub getSubAbility() {
|
||||
return this.subAbility;
|
||||
}
|
||||
|
||||
public Card getTargetCard() {
|
||||
if(targetCard == null){
|
||||
Target tgt = this.getTarget();
|
||||
|
||||
@@ -28,13 +28,14 @@ public class SpellAbility_Requirements {
|
||||
// freeze Stack. No abilities should go onto the stack while I'm filling requirements.
|
||||
AllZone.Stack.freezeStack();
|
||||
|
||||
if (select.doesTarget()){
|
||||
// Skip to paying if parent ability doesn't target and has no subAbilities.
|
||||
if (!select.doesTarget() && ability.getSubAbility() == null)
|
||||
startPaying();
|
||||
else{
|
||||
select.setRequirements(this);
|
||||
select.resetTargets();
|
||||
select.chooseTargets();
|
||||
}
|
||||
else
|
||||
startPaying();
|
||||
}
|
||||
|
||||
public void finishedTargeting(){
|
||||
|
||||
@@ -47,10 +47,24 @@ public class Target_Selection {
|
||||
req.finishedTargeting();
|
||||
return false;
|
||||
}
|
||||
else if (bDoneTarget && target.isMinTargetsChosen() || target.isMaxTargetsChosen()){
|
||||
else if (!doesTarget() || bDoneTarget && target.isMinTargetsChosen() || target.isMaxTargetsChosen()){
|
||||
Ability_Sub abSub = ability.getSubAbility();
|
||||
|
||||
if (abSub == null){
|
||||
// if no more SubAbilities finish targeting
|
||||
req.finishedTargeting();
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
// Has Sub Ability
|
||||
Target_Selection ts = new Target_Selection(abSub.getTarget(), abSub);
|
||||
ts.setRequirements(req);
|
||||
ts.resetTargets();
|
||||
boolean flag = ts.chooseTargets();
|
||||
|
||||
return flag;
|
||||
}
|
||||
}
|
||||
|
||||
//targets still needed
|
||||
changeInput.stopSetNext(input_targetValid(ability, target.getValidTgts(), target.getVTSelection(), this, req));
|
||||
|
||||
Reference in New Issue
Block a user