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;
}
@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;
}
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.
*
@@ -229,44 +336,17 @@ public final class GameActionUtil {
* @return an ArrayList<SpellAbility>.
*/
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();
abilities.add(original);
if (!original.isSpell()) {
return abilities;
}
// Buyback, Kicker
for (String keyword : source.getKeywords()) {
if (keyword.startsWith("AlternateAdditionalCost")) {
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")) {
if (keyword.startsWith("Buyback")) {
for (int i = 0; i < abilities.size(); i++) {
final SpellAbility newSA = abilities.get(i).copy();
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());
for (final SpellAbility sa : abilities) {
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()) {
toRemove.add(sa);
}
}
for (final SpellAbility sa : toRemove) {
abilities.remove(sa);
}
abilities.removeAll(toRemove);
if (getState(CardStateName.Original).getType().isLand() && player.canPlayLand(this)) {
game.PLAY_LAND_SURROGATE.setHostCard(this);

View File

@@ -4109,25 +4109,6 @@ public class CardFactoryUtil {
kws.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.replacement.ReplacementEffect;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.OptionalCostValue;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetChoices;
@@ -254,4 +255,6 @@ public abstract class PlayerController {
public AnteResult getAnteResult() {
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.game.CardTraitBase;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.IIdentifiable;
@@ -310,6 +311,27 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
// Spell, and Ability, and other Ability objects override this method
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() {
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() {
@@ -1607,7 +1629,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public SpellAbilityView getView() {
view.updateHostCard(this);
view.updateDescription(this);
view.updateCanPlay(this);
view.updateCanPlay(this, true);
view.updatePromptIfOnlyPossibleAbility(this);
return view;
}

View File

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

View File

@@ -53,6 +53,7 @@ import forge.game.player.PlayerController;
import forge.game.player.PlayerView;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.OptionalCostValue;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
@@ -676,4 +677,11 @@ public class PlayerControllerForTests extends PlayerController {
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 forge.game.cost.*;
import forge.game.spellability.OptionalCostValue;
import forge.game.spellability.Spell;
import org.apache.commons.lang3.StringUtils;
@@ -33,6 +34,7 @@ import forge.game.card.CardView;
import forge.game.card.CounterType;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
import forge.game.player.PlayerController;
import forge.game.player.PlayerView;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
@@ -80,6 +82,11 @@ public class HumanPlay {
return false;
}
// extra play check
if (!sa.canPlay()) {
return false;
}
if (flippedToCast && !castFaceDown) {
source.turnFaceUp(false, false);
}
@@ -146,8 +153,19 @@ public class HumanPlay {
if (!original.isSpell()) {
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) {

File diff suppressed because it is too large Load Diff