Enlist first part

This commit is contained in:
tool4EvEr
2023-01-21 18:24:39 +01:00
parent 62dcf9d025
commit fcc9c25725
17 changed files with 191 additions and 14 deletions

View File

@@ -238,6 +238,12 @@ public class AiCostDecision extends CostDecisionMakerBase {
return res.isEmpty() ? null : PaymentDecision.card(res); return res.isEmpty() ? null : PaymentDecision.card(res);
} }
@Override
public PaymentDecision visit(final CostEnlist cost) {
// currently unused
return null;
}
@Override @Override
public PaymentDecision visit(CostFlipCoin cost) { public PaymentDecision visit(CostFlipCoin cost) {
int c = cost.getAbilityAmount(ability); int c = cost.getAbilityAmount(ability);

View File

@@ -338,6 +338,12 @@ public class PlayerControllerAi extends PlayerController {
return AiAttackController.exertAttackers(attackers, brains.getAttackAggression()); return AiAttackController.exertAttackers(attackers, brains.getAttackAggression());
} }
@Override
public List<Card> enlistAttackers(List<Card> attackers) {
// not able yet
return Lists.newArrayList();
}
@Override @Override
public CardCollection orderBlocker(Card attacker, Card blocker, CardCollection oldBlockers) { public CardCollection orderBlocker(Card attacker, Card blocker, CardCollection oldBlockers) {
return AiBlockController.orderBlocker(attacker, blocker, oldBlockers); return AiBlockController.orderBlocker(attacker, blocker, oldBlockers);

View File

@@ -1211,7 +1211,6 @@ public class CardFactoryUtil {
trigger.setOverridingAbility(AbilityFactory.getAbility(effect, card)); trigger.setOverridingAbility(AbilityFactory.getAbility(effect, card));
inst.addTrigger(trigger); inst.addTrigger(trigger);
} else if (keyword.equals("For Mirrodin")) { } else if (keyword.equals("For Mirrodin")) {
final StringBuilder sbTrig = new StringBuilder(); final StringBuilder sbTrig = new StringBuilder();
sbTrig.append("Mode$ ChangesZone | Destination$ Battlefield | "); sbTrig.append("Mode$ ChangesZone | Destination$ Battlefield | ");
@@ -1235,7 +1234,6 @@ public class CardFactoryUtil {
saRebel.setIntrinsic(intrinsic); saRebel.setIntrinsic(intrinsic);
inst.addTrigger(etbTrigger); inst.addTrigger(etbTrigger);
} else if (keyword.startsWith("Graft")) { } else if (keyword.startsWith("Graft")) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
sb.append("DB$ MoveCounter | Source$ Self | Defined$ TriggeredCardLKICopy"); sb.append("DB$ MoveCounter | Source$ Self | Defined$ TriggeredCardLKICopy");
@@ -3708,12 +3706,19 @@ public class CardFactoryUtil {
+ " | Amount$ Escalate | Cost$ "+ manacost +" | EffectZone$ All" + " | Amount$ Escalate | Cost$ "+ manacost +" | EffectZone$ All"
+ " | Description$ " + sb.toString() + " (" + inst.getReminderText() + ")"; + " | Description$ " + sb.toString() + " (" + inst.getReminderText() + ")";
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic)); inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
} else if (keyword.equals("Enlist")) {
String effect = "Mode$ OptionalAttackCost | ValidCard$ Card.Self | Cost$ Enlist<1/CARDNAME> | Secondary$ True" +
"| Trigger$ TrigEnlist | Description$ Enlist ( " + inst.getReminderText() + ")";
StaticAbility st = StaticAbility.create(effect, state.getCard(), state, intrinsic);
st.setSVar("TrigEnlist", "DB$ Pump | NumAtt$ TriggerRemembered$CardPower" +
" | SpellDescription$ When you do, add its power to this creatures until end of turn.");
inst.addStaticAbility(st);
} else if (keyword.equals("Fear")) { } else if (keyword.equals("Fear")) {
String effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.nonArtifact+nonBlack | Secondary$ True " + String effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.nonArtifact+nonBlack | Secondary$ True" +
" | Description$ Fear ( " + inst.getReminderText() + ")"; " | Description$ Fear ( " + inst.getReminderText() + ")";
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic)); inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
} else if (keyword.equals("Flying")) { } else if (keyword.equals("Flying")) {
String effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.withoutFlying+withoutReach | Secondary$ True " + String effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.withoutFlying+withoutReach | Secondary$ True" +
" | Description$ Flying ( " + inst.getReminderText() + ")"; " | Description$ Flying ( " + inst.getReminderText() + ")";
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic)); inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
} else if (keyword.startsWith("Hexproof")) { } else if (keyword.startsWith("Hexproof")) {

View File

@@ -297,8 +297,8 @@ public class CombatUtil {
* @param attacker * @param attacker
* a {@link forge.game.card.Card} object. * a {@link forge.game.card.Card} object.
*/ */
public static boolean checkPropagandaEffects(final Game game, final Card attacker, final Combat combat, final List<Card> exerters) { public static boolean checkPropagandaEffects(final Game game, final Card attacker, final Combat combat, final List<Card> attackersWithOptionalCost) {
final Cost attackCost = getAttackCost(game, attacker, combat.getDefenderByAttacker(attacker), exerters); final Cost attackCost = getAttackCost(game, attacker, combat.getDefenderByAttacker(attacker), attackersWithOptionalCost);
if (attackCost == null) { if (attackCost == null) {
return true; return true;
} }
@@ -327,13 +327,13 @@ public class CombatUtil {
* @return the {@link Cost} of attacking, or {@code null} if there is no * @return the {@link Cost} of attacking, or {@code null} if there is no
* cost. * cost.
*/ */
public static Cost getAttackCost(final Game game, final Card attacker, final GameEntity defender, final List<Card> exerters) { public static Cost getAttackCost(final Game game, final Card attacker, final GameEntity defender, final List<Card> attackersWithOptionalCost) {
final Cost attackCost = new Cost(ManaCost.ZERO, true); final Cost attackCost = new Cost(ManaCost.ZERO, true);
boolean hasCost = false; boolean hasCost = false;
// Sort abilities to apply them in proper order // Sort abilities to apply them in proper order
for (final Card card : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { for (final Card card : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : card.getStaticAbilities()) { for (final StaticAbility stAb : card.getStaticAbilities()) {
final Cost additionalCost = stAb.getAttackCost(attacker, defender, exerters); final Cost additionalCost = stAb.getAttackCost(attacker, defender, attackersWithOptionalCost);
if (null != additionalCost) { if (null != additionalCost) {
attackCost.add(additionalCost); attackCost.add(additionalCost);
hasCost = true; hasCost = true;

View File

@@ -514,6 +514,12 @@ public class Cost implements Serializable {
return new CostExert(splitStr[0], splitStr[1], description); return new CostExert(splitStr[0], splitStr[1], description);
} }
if (parse.startsWith("Enlist<")) {
final String[] splitStr = abCostParse(parse, 3);
final String description = splitStr.length > 2 ? splitStr[2] : null;
return new CostEnlist(splitStr[0], splitStr[1], description);
}
if (parse.equals("RevealChosenPlayer")) { if (parse.equals("RevealChosenPlayer")) {
return new CostRevealChosenPlayer(); return new CostRevealChosenPlayer();
} }

View File

@@ -0,0 +1,96 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.game.cost;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* The Class CostExert.
*/
public class CostEnlist extends CostPartWithTrigger {
private static final long serialVersionUID = 1L;
/**
* Instantiates a new cost Exert.
*
* @param amount
* the amount
* @param type
* the type
* @param description
* the description
*/
public CostEnlist(final String amount, final String type, final String description) {
super(amount, type, description);
}
/*
* (non-Javadoc)
*
* @see forge.card.cost.CostPart#toString()
*/
@Override
public final String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("Enlist " + this.getType());
return sb.toString();
}
/*
* (non-Javadoc)
*
* @see
* forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility,
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
return true;
}
@Override
protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) {
targetCard.tap(true);
// need to transfer info
for (Card c : getLKIList()) {
payTrig.addRemembered(c);
}
return targetCard;
}
/* (non-Javadoc)
* @see forge.card.cost.CostPartWithList#getHashForList()
*/
@Override
public String getHashForLKIList() {
return "Enlisted";
}
@Override
public String getHashForCardList() {
return "EnlistedCards";
}
// Inputs
public <T> T accept(ICostVisitor<T> visitor) {
return visitor.visit(this);
}
}

View File

@@ -18,6 +18,8 @@ public abstract class CostPartWithTrigger extends CostPartWithList {
super(amount, type, description); super(amount, type, description);
} }
protected Trigger payTrig;
@Override @Override
protected final void handleBeforePayment(Player ai, SpellAbility ability, CardCollectionView targetCards) { protected final void handleBeforePayment(Player ai, SpellAbility ability, CardCollectionView targetCards) {
if (payingTrigSA != null) { if (payingTrigSA != null) {
@@ -30,13 +32,13 @@ public abstract class CostPartWithTrigger extends CostPartWithList {
SpellAbility sa = payingTrigSA.copy(source, ability.getActivatingPlayer(), false); SpellAbility sa = payingTrigSA.copy(source, ability.getActivatingPlayer(), false);
sa.changeText(); sa.changeText();
final Trigger immediateTrig = TriggerHandler.parseTrigger(mapParams, source, sa.isIntrinsic(), null); payTrig = TriggerHandler.parseTrigger(mapParams, source, sa.isIntrinsic(), null);
immediateTrig.setSpawningAbility(ability); // make the StaticAbility the Spawning one? payTrig.setSpawningAbility(ability); // make the StaticAbility the Spawning one?
immediateTrig.setOverridingAbility(sa); payTrig.setOverridingAbility(sa);
// Instead of registering this, add to the delayed triggers as an immediate trigger type? Which means it'll fire as soon as possible // Instead of registering this, add to the delayed triggers as an immediate trigger type? Which means it'll fire as soon as possible
ai.getGame().getTriggerHandler().registerDelayedTrigger(immediateTrig); ai.getGame().getTriggerHandler().registerDelayedTrigger(payTrig);
} }
} }

View File

@@ -12,6 +12,7 @@ public interface ICostVisitor<T> {
T visit(CostExileFromStack cost); T visit(CostExileFromStack cost);
T visit(CostExiledMoveToGrave cost); T visit(CostExiledMoveToGrave cost);
T visit(CostExert cost); T visit(CostExert cost);
T visit(CostEnlist cost);
T visit(CostFlipCoin cost); T visit(CostFlipCoin cost);
T visit(CostRollDice cost); T visit(CostRollDice cost);
T visit(CostMill cost); T visit(CostMill cost);
@@ -86,6 +87,11 @@ public interface ICostVisitor<T> {
return null; return null;
} }
@Override
public T visit(CostEnlist cost) {
return null;
}
@Override @Override
public T visit(CostFlipCoin cost) { public T visit(CostFlipCoin cost) {
return null; return null;

View File

@@ -67,6 +67,7 @@ public enum Keyword {
EMERGE("Emerge", KeywordWithCost.class, false, "You may cast this spell by sacrificing a creature and paying the emerge cost reduced by that creature's mana value."), EMERGE("Emerge", KeywordWithCost.class, false, "You may cast this spell by sacrificing a creature and paying the emerge cost reduced by that creature's mana value."),
ENCHANT("Enchant", KeywordWithType.class, false, "Target a %s as you cast this. This card enters the battlefield attached to that %s."), ENCHANT("Enchant", KeywordWithType.class, false, "Target a %s as you cast this. This card enters the battlefield attached to that %s."),
ENCORE("Encore", KeywordWithCost.class, false, "%s, Exile this card from your graveyard: For each opponent, create a token copy that attacks that opponent this turn if able. They gain haste. Sacrifice them at the beginning of the next end step. Activate only as a sorcery."), ENCORE("Encore", KeywordWithCost.class, false, "%s, Exile this card from your graveyard: For each opponent, create a token copy that attacks that opponent this turn if able. They gain haste. Sacrifice them at the beginning of the next end step. Activate only as a sorcery."),
ENLIST("Enlist", SimpleKeyword.class, false, "As this creature attacks, you may tap a nonattacking creature you control without summoning sickness. When you do, add its power to this creatures until end of turn."),
ENTWINE("Entwine", KeywordWithCost.class, true, "Choose both if you pay the entwine cost."), ENTWINE("Entwine", KeywordWithCost.class, true, "Choose both if you pay the entwine cost."),
EPIC("Epic", SimpleKeyword.class, true, "For the rest of the game, you can't cast spells. At the beginning of each of your upkeeps for the rest of the game, copy this spell except for its epic ability. If the spell has any targets, you may choose new targets for the copy."), EPIC("Epic", SimpleKeyword.class, true, "For the rest of the game, you can't cast spells. At the beginning of each of your upkeeps for the rest of the game, copy this spell except for its epic ability. If the spell has any targets, you may choose new targets for the copy."),
EQUIP("Equip", Equip.class, false, "%s: Attach to target %s you control. Equip only as a sorcery."), EQUIP("Equip", Equip.class, false, "%s: Attach to target %s you control. Equip only as a sorcery."),

View File

@@ -40,6 +40,7 @@ import forge.game.card.CardZoneTable;
import forge.game.card.CounterEnumType; import forge.game.card.CounterEnumType;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.combat.CombatUtil; import forge.game.combat.CombatUtil;
import forge.game.cost.CostEnlist;
import forge.game.cost.CostExert; import forge.game.cost.CostExert;
import forge.game.event.GameEventAttackersDeclared; import forge.game.event.GameEventAttackersDeclared;
import forge.game.event.GameEventBlockersDeclared; import forge.game.event.GameEventBlockersDeclared;
@@ -566,6 +567,13 @@ public class PhaseHandler implements java.io.Serializable {
possibleExerters = whoDeclares.getController().exertAttackers(possibleExerters); possibleExerters = whoDeclares.getController().exertAttackers(possibleExerters);
} }
List<Card> possibleEnlisters = CombatUtil.getOptionalAttackCostCreatures(combat.getAttackers(), CostEnlist.class);
if (!possibleEnlisters.isEmpty()) {
// TODO might want to skip if can't be paid
possibleEnlisters = whoDeclares.getController().enlistAttackers(possibleEnlisters);
possibleExerters.addAll(possibleEnlisters);
}
for (final Card attacker : combat.getAttackers()) { for (final Card attacker : combat.getAttackers()) {
// TODO currently doesn't refund previous attackers (can really only happen if you cancel paying for a creature with an attack requirement that could be satisfied without a tax) // TODO currently doesn't refund previous attackers (can really only happen if you cancel paying for a creature with an attack requirement that could be satisfied without a tax)
final boolean canAttack = CombatUtil.checkPropagandaEffects(game, attacker, combat, possibleExerters); final boolean canAttack = CombatUtil.checkPropagandaEffects(game, attacker, combat, possibleExerters);

View File

@@ -144,6 +144,8 @@ public abstract class PlayerController {
public abstract CardCollection orderBlockers(Card attacker, CardCollection blockers); public abstract CardCollection orderBlockers(Card attacker, CardCollection blockers);
public abstract List<Card> exertAttackers(List<Card> attackers); public abstract List<Card> exertAttackers(List<Card> attackers);
public abstract List<Card> enlistAttackers(List<Card> attackers);
/** /**
* Add a card to a pre-existing blocking order. * Add a card to a pre-existing blocking order.
* @param attacker the attacking creature. * @param attacker the attacking creature.

View File

@@ -325,8 +325,8 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
return false; return false;
} }
public final Cost getAttackCost(final Card attacker, final GameEntity target, final List<Card> exerters) { public final Cost getAttackCost(final Card attacker, final GameEntity target, final List<Card> attackersWithOptionalCost) {
if (!getParam("Mode").equals("CantAttackUnless") && (!getParam("Mode").equals("OptionalAttackCost") || !exerters.contains(attacker))) { if (!getParam("Mode").equals("CantAttackUnless") && (!getParam("Mode").equals("OptionalAttackCost") || !attackersWithOptionalCost.contains(attacker))) {
return null; return null;
} }
if (this.isSuppressed() || !this.checkConditions()) { if (this.isSuppressed() || !this.checkConditions()) {

View File

@@ -224,6 +224,11 @@ public class PlayerControllerForTests extends PlayerController {
return Lists.newArrayList(attackers); return Lists.newArrayList(attackers);
} }
@Override
public List<Card> enlistAttackers(List<Card> attackers) {
return Lists.newArrayList();
}
@Override @Override
public CardCollection orderBlocker(final Card attacker, final Card blocker, final CardCollection oldBlockers) { public CardCollection orderBlocker(final Card attacker, final Card blocker, final CardCollection oldBlockers) {
final CardCollection allBlockers = new CardCollection(oldBlockers); final CardCollection allBlockers = new CardCollection(oldBlockers);

View File

@@ -0,0 +1,6 @@
Name:Barkweave Crusher
ManaCost:1 G
Types:Creature Elemental Warrior
PT:2/5
K:Enlist
Oracle:Enlist (As this creature attacks, you may tap a nonattacking creature you control without summoning sickness. When you do, add its power to this creatures until end of turn.)

View File

@@ -466,6 +466,22 @@ public class HumanCostDecision extends CostDecisionMakerBase {
} }
@Override
public PaymentDecision visit(final CostEnlist cost) {
CardCollectionView list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), "Creature.notattacking+untapped+withHaste,Creature.notattacking+untapped+notFirstTurnControlled", player, source, ability);
if (list.isEmpty()) {
return null;
}
final InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, 1, 1, list, ability);
inp.setMessage(Localizer.getInstance().getMessage("lblSelectACostToEnlist", cost.getDescriptiveType(), "%d"));
inp.setCancelAllowed(true);
inp.showAndWait();
if (inp.hasCancelled()) {
return null;
}
return PaymentDecision.card(inp.getSelected());
}
@Override @Override
public PaymentDecision visit(final CostFlipCoin cost) { public PaymentDecision visit(final CostFlipCoin cost) {
Integer c = cost.getAbilityAmount(ability); Integer c = cost.getAbilityAmount(ability);

View File

@@ -283,6 +283,7 @@ public class HumanPlay {
|| part instanceof CostFlipCoin || part instanceof CostFlipCoin
|| part instanceof CostRollDice || part instanceof CostRollDice
|| part instanceof CostDamage || part instanceof CostDamage
|| part instanceof CostEnlist
|| part instanceof CostPutCounter || part instanceof CostPutCounter
|| part instanceof CostRemoveCounter || part instanceof CostRemoveCounter
|| part instanceof CostRemoveAnyCounter || part instanceof CostRemoveAnyCounter

View File

@@ -875,6 +875,17 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
return chosenCards; return chosenCards;
} }
@Override
public List<Card> enlistAttackers(List<Card> attackers) {
GameEntityViewMap<Card, CardView> gameCacheExert = GameEntityView.getMap(attackers);
List<CardView> chosen = getGui().order(localizer.getMessage("lblEnlistAttackersConfirm"), localizer.getMessage("lblEnlisted"),
0, gameCacheExert.size(), gameCacheExert.getTrackableKeys(), null, null, false);
List<Card> chosenCards = new CardCollection();
gameCacheExert.addToList(chosen, chosenCards);
return chosenCards;
}
@Override @Override
public CardCollection orderBlocker(final Card attacker, final Card blocker, final CardCollection oldBlockers) { public CardCollection orderBlocker(final Card attacker, final Card blocker, final CardCollection oldBlockers) {
GameEntityViewMap<Card, CardView> gameCacheBlockers = GameEntityView.getMap(oldBlockers); GameEntityViewMap<Card, CardView> gameCacheBlockers = GameEntityView.getMap(oldBlockers);