GameActionUtil: add getOptionalCostValues to return OptionalCost with its Cost objects

SpellAbility: add canPlayWithOptionalCost
PlayerControllerHuman: got formated
This commit is contained in:
Hanmac
2017-06-21 04:23:30 +00:00
parent d2b1c17f4e
commit e29ec6fb83
10 changed files with 930 additions and 491 deletions

View File

@@ -929,4 +929,11 @@ public class PlayerControllerAi extends PlayerController {
} }
return result; return result;
} }
@Override
public List<OptionalCostValue> chooseOptionalCosts(SpellAbility choosen,
List<OptionalCostValue> optionalCostValues) {
// TODO Auto-generated method stub
return null;
}
} }

View File

@@ -221,6 +221,113 @@ public final class GameActionUtil {
return alternatives; return alternatives;
} }
public static List<OptionalCostValue> getOptionalCostValues(final SpellAbility sa) {
final List<OptionalCostValue> costs = Lists.newArrayList();
if (sa == null || !sa.isSpell()) {
return costs;
}
final Card source = sa.getHostCard();
for (String keyword : source.getKeywords()) {
if (keyword.startsWith("Buyback")) {
final Cost cost = new Cost(keyword.substring(8), false);
costs.add(new OptionalCostValue(OptionalCost.Buyback, cost));
} else if (keyword.equals("Conspire")) {
final String conspireCost = "tapXType<2/Creature.SharesColorWith/" +
"untapped creature you control that shares a color with " + source.getName() + ">";
final Cost cost = new Cost(conspireCost, false);
costs.add(new OptionalCostValue(OptionalCost.Conspire, cost));
} else if (keyword.startsWith("Entwine")) {
String[] k = keyword.split(":");
final Cost cost = new Cost(k[1], false);
costs.add(new OptionalCostValue(OptionalCost.Entwine, cost));
} else if (keyword.startsWith("Kicker")) {
String[] sCosts = TextUtil.split(keyword.substring(6), ':');
boolean generic = "Generic".equals(sCosts[sCosts.length - 1]);
// If this is a "generic kicker" (Undergrowth), ignore value for kicker creations
int numKickers = sCosts.length - (generic ? 1 : 0);
for (int j = 0; j < numKickers; j++) {
final Cost cost = new Cost(sCosts[j], false);
OptionalCost type = null;
if (!generic) {
type = j == 0 ? OptionalCost.Kicker1 : OptionalCost.Kicker2;
} else {
type = OptionalCost.Generic;
}
costs.add(new OptionalCostValue(type, cost));
}
} else if (keyword.equals("Retrace")) {
final Cost cost = new Cost("Discard<1/Land>", false);
costs.add(new OptionalCostValue(OptionalCost.Retrace, cost));
}
// Surge while having OptionalCost is none of them
}
return costs;
}
public static SpellAbility addOptionalCosts(final SpellAbility sa, List<OptionalCostValue> list) {
if (sa == null || list.isEmpty()) {
return sa;
}
final SpellAbility result = sa.copy();
for (OptionalCostValue v : list) {
// need to copy cost, otherwise it does alter the original
result.setPayCosts(result.getPayCosts().copy().add(v.getCost()));
result.addOptionalCost(v.getType());
// add some extra logic, try to move it to other parts
switch (v.getType()) {
case Conspire:
result.addConspireInstance();
break;
case Retrace:
result.getRestrictions().setZone(ZoneType.Graveyard);
break;
}
}
return result;
}
public static List<SpellAbility> getAdditionalCostSpell(final SpellAbility sa) {
final List<SpellAbility> abilities = Lists.newArrayList(sa);
if (!sa.isSpell()) {
return abilities;
}
final Card source = sa.getHostCard();
for (String keyword : source.getKeywords()) {
if (keyword.startsWith("AlternateAdditionalCost")) {
final List<SpellAbility> newAbilities = Lists.newArrayList();
String[] costs = TextUtil.split(keyword, ':');
final SpellAbility newSA = sa.copy();
newSA.setBasicSpell(false);
final Cost cost1 = new Cost(costs[1], false);
newSA.setDescription(sa.getDescription() + " (Additional cost " + cost1.toSimpleString() + ")");
newSA.setPayCosts(cost1.add(sa.getPayCosts()));
if (newSA.canPlay()) {
newAbilities.add(newSA);
}
//second option
final SpellAbility newSA2 = sa.copy();
newSA2.setBasicSpell(false);
final Cost cost2 = new Cost(costs[2], false);
newSA2.setDescription(sa.getDescription() + " (Additional cost " + cost2.toSimpleString() + ")");
newSA2.setPayCosts(cost2.add(sa.getPayCosts()));
if (newSA2.canPlay()) {
newAbilities.add(newSA2);
}
abilities.clear();
abilities.addAll(newAbilities);
}
}
return abilities;
}
/** /**
* get optional additional costs. * get optional additional costs.
* *
@@ -229,44 +336,17 @@ public final class GameActionUtil {
* @return an ArrayList<SpellAbility>. * @return an ArrayList<SpellAbility>.
*/ */
public static List<SpellAbility> getOptionalCosts(final SpellAbility original) { public static List<SpellAbility> getOptionalCosts(final SpellAbility original) {
final List<SpellAbility> abilities = Lists.newArrayList(); final List<SpellAbility> abilities = getAdditionalCostSpell(original);
final Card source = original.getHostCard(); final Card source = original.getHostCard();
abilities.add(original);
if (!original.isSpell()) { if (!original.isSpell()) {
return abilities; return abilities;
} }
// Buyback, Kicker // Buyback, Kicker
for (String keyword : source.getKeywords()) { for (String keyword : source.getKeywords()) {
if (keyword.startsWith("AlternateAdditionalCost")) { if (keyword.startsWith("Buyback")) {
final List<SpellAbility> newAbilities = Lists.newArrayList();
String[] costs = TextUtil.split(keyword, ':');
for (SpellAbility sa : abilities) {
final SpellAbility newSA = sa.copy();
newSA.setBasicSpell(false);
final Cost cost1 = new Cost(costs[1], false);
newSA.setDescription(sa.getDescription() + " (Additional cost " + cost1.toSimpleString() + ")");
newSA.setPayCosts(cost1.add(sa.getPayCosts()));
if (newSA.canPlay()) {
newAbilities.add(newSA);
}
//second option
final SpellAbility newSA2 = sa.copy();
newSA2.setBasicSpell(false);
final Cost cost2 = new Cost(costs[2], false);
newSA2.setDescription(sa.getDescription() + " (Additional cost " + cost2.toSimpleString() + ")");
newSA2.setPayCosts(cost2.add(sa.getPayCosts()));
if (newSA2.canPlay()) {
newAbilities.add(newAbilities.size(), newSA2);
}
}
abilities.clear();
abilities.addAll(newAbilities);
} else if (keyword.startsWith("Buyback")) {
for (int i = 0; i < abilities.size(); i++) { for (int i = 0; i < abilities.size(); i++) {
final SpellAbility newSA = abilities.get(i).copy(); final SpellAbility newSA = abilities.get(i).copy();
newSA.setBasicSpell(false); newSA.setBasicSpell(false);

View File

@@ -6874,13 +6874,16 @@ public class Card extends GameEntity implements Comparable<Card> {
final Collection<SpellAbility> toRemove = Lists.newArrayListWithCapacity(abilities.size()); final Collection<SpellAbility> toRemove = Lists.newArrayListWithCapacity(abilities.size());
for (final SpellAbility sa : abilities) { for (final SpellAbility sa : abilities) {
sa.setActivatingPlayer(player); sa.setActivatingPlayer(player);
// fix things like retrace
// check only if SA can't be cast normally
if (sa.canPlay(true)) {
continue;
}
if ((removeUnplayable && !sa.canPlay()) || !sa.isPossible()) { if ((removeUnplayable && !sa.canPlay()) || !sa.isPossible()) {
toRemove.add(sa); toRemove.add(sa);
} }
} }
for (final SpellAbility sa : toRemove) { abilities.removeAll(toRemove);
abilities.remove(sa);
}
if (getState(CardStateName.Original).getType().isLand() && player.canPlayLand(this)) { if (getState(CardStateName.Original).getType().isLand() && player.canPlayLand(this)) {
game.PLAY_LAND_SURROGATE.setHostCard(this); game.PLAY_LAND_SURROGATE.setHostCard(this);

View File

@@ -4109,25 +4109,6 @@ public class CardFactoryUtil {
kws.addSpellAbility(sa); kws.addSpellAbility(sa);
} }
card.addSpellAbility(sa); card.addSpellAbility(sa);
} else if (keyword.equals("Retrace")) {
final SpellAbility sa = card.getFirstSpellAbility();
final SpellAbility newSA = sa.copy();
newSA.getRestrictions().setZone(ZoneType.Graveyard);
newSA.getMapParams().put("CostDesc", "Retrace");
newSA.getMapParams().put("Secondary", "True");
newSA.setBasicSpell(false);
final Cost cost = new Cost("Discard<1/Land>", false).add(sa.getPayCosts());
newSA.setPayCosts(cost);
newSA.setIntrinsic(intrinsic);
//newSA.setDescription(sa.getDescription() + " (Retrace)");
if (!intrinsic) {
newSA.setTemporary(true);
kws.addSpellAbility(newSA);
}
card.addSpellAbility(newSA);
} }
} }

View File

@@ -35,6 +35,7 @@ import forge.game.cost.CostPartMana;
import forge.game.mana.Mana; import forge.game.mana.Mana;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.AbilitySub; import forge.game.spellability.AbilitySub;
import forge.game.spellability.OptionalCostValue;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetChoices; import forge.game.spellability.TargetChoices;
@@ -254,4 +255,6 @@ public abstract class PlayerController {
public AnteResult getAnteResult() { public AnteResult getAnteResult() {
return game.getOutcome().anteResult.get(player); return game.getOutcome().anteResult.get(player);
} }
public abstract List<OptionalCostValue> chooseOptionalCosts(SpellAbility choosen, List<OptionalCostValue> optionalCostValues);
} }

View File

@@ -25,6 +25,7 @@ import com.google.common.collect.Maps;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.game.CardTraitBase; import forge.game.CardTraitBase;
import forge.game.Game; import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.IIdentifiable; import forge.game.IIdentifiable;
@@ -309,6 +310,27 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
// Spell, and Ability, and other Ability objects override this method // Spell, and Ability, and other Ability objects override this method
public abstract boolean canPlay(); public abstract boolean canPlay();
public boolean canPlay(boolean checkOptionalCosts) {
if (canPlay()) {
return true;
}
if (!checkOptionalCosts) {
return false;
}
for (OptionalCostValue val : GameActionUtil.getOptionalCostValues(this)) {
if (canPlayWithOptionalCost(val)) {
return true;
}
}
return false;
}
public boolean canPlayWithOptionalCost(OptionalCostValue opt) {
SpellAbility saCopy = this.copy();
saCopy = GameActionUtil.addOptionalCosts(saCopy, Lists.newArrayList(opt));
return saCopy.canPlay();
}
public boolean isPossible() { public boolean isPossible() {
return canPlay(); //by default, ability is only possible if it can be played return canPlay(); //by default, ability is only possible if it can be played
@@ -349,7 +371,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
} }
} }
} }
view.updateCanPlay(this); view.updateCanPlay(this, false);
} }
public Player getTargetingPlayer() { public Player getTargetingPlayer() {
@@ -1607,7 +1629,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public SpellAbilityView getView() { public SpellAbilityView getView() {
view.updateHostCard(this); view.updateHostCard(this);
view.updateDescription(this); view.updateDescription(this);
view.updateCanPlay(this); view.updateCanPlay(this, true);
view.updatePromptIfOnlyPossibleAbility(this); view.updatePromptIfOnlyPossibleAbility(this);
return view; return view;
} }

View File

@@ -53,8 +53,8 @@ public class SpellAbilityView extends TrackableObject implements IHasCardView {
public boolean canPlay() { public boolean canPlay() {
return get(TrackableProperty.CanPlay); return get(TrackableProperty.CanPlay);
} }
void updateCanPlay(SpellAbility sa) { void updateCanPlay(SpellAbility sa, boolean optionalCost) {
set(TrackableProperty.CanPlay, sa.canPlay()); set(TrackableProperty.CanPlay, sa.canPlay(optionalCost));
} }
public boolean promptIfOnlyPossibleAbility() { public boolean promptIfOnlyPossibleAbility() {

View File

@@ -53,6 +53,7 @@ import forge.game.player.PlayerController;
import forge.game.player.PlayerView; import forge.game.player.PlayerView;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.AbilitySub; import forge.game.spellability.AbilitySub;
import forge.game.spellability.OptionalCostValue;
import forge.game.spellability.Spell; import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.SpellAbilityStackInstance;
@@ -676,4 +677,11 @@ public class PlayerControllerForTests extends PlayerController {
return Lists.newArrayList(); return Lists.newArrayList();
} }
@Override
public List<OptionalCostValue> chooseOptionalCosts(SpellAbility choosen,
List<OptionalCostValue> optionalCostValues) {
// TODO Auto-generated method stub
return null;
}
} }

View File

@@ -6,6 +6,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import forge.game.cost.*; import forge.game.cost.*;
import forge.game.spellability.OptionalCostValue;
import forge.game.spellability.Spell; import forge.game.spellability.Spell;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -33,6 +34,7 @@ import forge.game.card.CardView;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.mana.ManaCostBeingPaid; import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerController;
import forge.game.player.PlayerView; import forge.game.player.PlayerView;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
@@ -80,6 +82,11 @@ public class HumanPlay {
return false; return false;
} }
// extra play check
if (!sa.canPlay()) {
return false;
}
if (flippedToCast && !castFaceDown) { if (flippedToCast && !castFaceDown) {
source.turnFaceUp(false, false); source.turnFaceUp(false, false);
} }
@@ -146,8 +153,19 @@ public class HumanPlay {
if (!original.isSpell()) { if (!original.isSpell()) {
return original; return original;
} }
final List<SpellAbility> abilities = GameActionUtil.getOptionalCosts(original);
return p.getController().getAbilityToPlay(original.getHostCard(), abilities); PlayerController c = p.getController();
// choose alternative additional cost
final List<SpellAbility> abilities = GameActionUtil.getAdditionalCostSpell(original);
final SpellAbility choosen = c.getAbilityToPlay(original.getHostCard(), abilities);
List<OptionalCostValue> list = GameActionUtil.getOptionalCostValues(choosen);
list = c.chooseOptionalCosts(choosen, list);
return GameActionUtil.addOptionalCosts(choosen, list);
//final List<SpellAbility> abilities = GameActionUtil.getOptionalCosts(original);
} }
private static boolean payManaCostIfNeeded(final PlayerControllerHuman controller, final Player p, final SpellAbility sa) { private static boolean payManaCostIfNeeded(final PlayerControllerHuman controller, final Player p, final SpellAbility sa) {

File diff suppressed because it is too large Load Diff