mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
Keyword: Saddle (#4807)
* Keyword: Saddle * AlterAttribute Logic for Suspected and Saddled
This commit is contained in:
@@ -1267,6 +1267,8 @@ public abstract class GameState {
|
|||||||
c.setRenowned(true);
|
c.setRenowned(true);
|
||||||
} else if (info.startsWith("Solved")) {
|
} else if (info.startsWith("Solved")) {
|
||||||
c.setSolved(true);
|
c.setSolved(true);
|
||||||
|
} else if (info.startsWith("Saddled")) {
|
||||||
|
c.setSaddled(true);
|
||||||
} else if (info.startsWith("Suspected")) {
|
} else if (info.startsWith("Suspected")) {
|
||||||
c.setSuspected(true);
|
c.setSuspected(true);
|
||||||
} else if (info.startsWith("Monstrous")) {
|
} else if (info.startsWith("Monstrous")) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class)
|
.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class)
|
||||||
.put(ApiType.AddPhase, AddPhaseAi.class)
|
.put(ApiType.AddPhase, AddPhaseAi.class)
|
||||||
.put(ApiType.AddTurn, AddTurnAi.class)
|
.put(ApiType.AddTurn, AddTurnAi.class)
|
||||||
.put(ApiType.AlterAttribute, AlwaysPlayAi.class)
|
.put(ApiType.AlterAttribute, AlterAttributeAi.class)
|
||||||
.put(ApiType.Amass, AmassAi.class)
|
.put(ApiType.Amass, AmassAi.class)
|
||||||
.put(ApiType.Animate, AnimateAi.class)
|
.put(ApiType.Animate, AnimateAi.class)
|
||||||
.put(ApiType.AnimateAll, AnimateAllAi.class)
|
.put(ApiType.AnimateAll, AnimateAllAi.class)
|
||||||
|
|||||||
107
forge-ai/src/main/java/forge/ai/ability/AlterAttributeAi.java
Normal file
107
forge-ai/src/main/java/forge/ai/ability/AlterAttributeAi.java
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.combat.CombatUtil;
|
||||||
|
import forge.game.phase.PhaseHandler;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
public class AlterAttributeAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
boolean activate = Boolean.valueOf(sa.getParamOrDefault("Activate", "true"));
|
||||||
|
String[] attributes = sa.getParam("Attributes").split(",");
|
||||||
|
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
|
// TODO add targeting logic
|
||||||
|
// needed for Suspected
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
|
for (Card c : defined) {
|
||||||
|
for (String attr : attributes) {
|
||||||
|
switch (attr.trim()) {
|
||||||
|
case "Solve":
|
||||||
|
case "Solved":
|
||||||
|
// there is currently no effect that would un-solve something
|
||||||
|
if (!c.isSolved() && activate) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Suspect":
|
||||||
|
case "Suspected":
|
||||||
|
// is Suspected good or bad?
|
||||||
|
// currently Suspected is better
|
||||||
|
if (!activate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "Saddle":
|
||||||
|
case "Saddled":
|
||||||
|
// AI should not try to Saddle again?
|
||||||
|
if (c.isSaddled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
String[] attributes = sa.getParam("Attributes").split(",");
|
||||||
|
|
||||||
|
// currently Phase is only checked for Saddled
|
||||||
|
|
||||||
|
for (String attr : attributes) {
|
||||||
|
switch (attr.trim()) {
|
||||||
|
case "Saddle":
|
||||||
|
case "Saddled":
|
||||||
|
if (!ph.isPlayerTurn(ai)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// it is too late for combat, Saddle is Sorcery Speed
|
||||||
|
if (!ph.getPhase().isBefore(PhaseType.COMBAT_BEGIN)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// would card attack?
|
||||||
|
if (!CombatUtil.canAttack(source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
|
||||||
|
boolean activate = Boolean.valueOf(sa.getParamOrDefault("Activate", "true"));
|
||||||
|
String[] attributes = sa.getParam("Attributes").split(",");
|
||||||
|
|
||||||
|
for (String attr : attributes) {
|
||||||
|
switch (attr.trim()) {
|
||||||
|
case "Suspect":
|
||||||
|
case "Suspected":
|
||||||
|
return activate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -399,6 +399,9 @@ public class GameCopier {
|
|||||||
if (c.isSolved()) {
|
if (c.isSolved()) {
|
||||||
newCard.setSolved(true);
|
newCard.setSolved(true);
|
||||||
}
|
}
|
||||||
|
if (c.isSaddled()) {
|
||||||
|
newCard.setSaddled(true);
|
||||||
|
}
|
||||||
if (c.isSuspected()) {
|
if (c.isSuspected()) {
|
||||||
newCard.setSuspected(true);
|
newCard.setSuspected(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ public class AlterAttributeEffect extends SpellAbilityEffect {
|
|||||||
case "Suspected":
|
case "Suspected":
|
||||||
altered = c.setSuspected(activate);
|
altered = c.setSuspected(activate);
|
||||||
break;
|
break;
|
||||||
|
case "Saddle":
|
||||||
|
case "Saddled":
|
||||||
|
// currently clean up in Card manually
|
||||||
|
altered = c.setSaddled(activate);
|
||||||
|
break;
|
||||||
|
|
||||||
// Other attributes: renown, monstrous, suspected, etc
|
// Other attributes: renown, monstrous, suspected, etc
|
||||||
|
|
||||||
|
|||||||
@@ -217,6 +217,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
|||||||
|
|
||||||
private boolean renowned;
|
private boolean renowned;
|
||||||
private boolean solved = false;
|
private boolean solved = false;
|
||||||
|
private boolean saddled = false;
|
||||||
private Long suspectedTimestamp = null;
|
private Long suspectedTimestamp = null;
|
||||||
private StaticAbility suspectedStatic = null;
|
private StaticAbility suspectedStatic = null;
|
||||||
|
|
||||||
@@ -2364,6 +2365,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
|||||||
|| keyword.startsWith("Graft") || keyword.startsWith("Fading") || keyword.startsWith("Vanishing:")
|
|| keyword.startsWith("Graft") || keyword.startsWith("Fading") || keyword.startsWith("Vanishing:")
|
||||||
|| keyword.startsWith("Afterlife") || keyword.startsWith("Hideaway") || keyword.startsWith("Toxic")
|
|| keyword.startsWith("Afterlife") || keyword.startsWith("Hideaway") || keyword.startsWith("Toxic")
|
||||||
|| keyword.startsWith("Afflict") || keyword.startsWith ("Poisonous") || keyword.startsWith("Rampage")
|
|| keyword.startsWith("Afflict") || keyword.startsWith ("Poisonous") || keyword.startsWith("Rampage")
|
||||||
|
|| keyword.startsWith("Saddle")
|
||||||
|| keyword.startsWith("Renown") || keyword.startsWith("Annihilator") || keyword.startsWith("Devour")) {
|
|| keyword.startsWith("Renown") || keyword.startsWith("Annihilator") || keyword.startsWith("Devour")) {
|
||||||
final String[] k = keyword.split(":");
|
final String[] k = keyword.split(":");
|
||||||
sbLong.append(k[0]).append(" ").append(k[1]).append(" (").append(inst.getReminderText()).append(")");
|
sbLong.append(k[0]).append(" ").append(k[1]).append(" (").append(inst.getReminderText()).append(")");
|
||||||
@@ -2671,6 +2673,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
|||||||
if (solved) {
|
if (solved) {
|
||||||
sb.append("Solved\r\n");
|
sb.append("Solved\r\n");
|
||||||
}
|
}
|
||||||
|
if (saddled) {
|
||||||
|
sb.append("Saddled\r\n");
|
||||||
|
}
|
||||||
if (isSuspected()) {
|
if (isSuspected()) {
|
||||||
sb.append("Suspected\r\n");
|
sb.append("Suspected\r\n");
|
||||||
}
|
}
|
||||||
@@ -6342,6 +6347,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final boolean isSaddled() {
|
||||||
|
return saddled;
|
||||||
|
}
|
||||||
|
public final boolean setSaddled(final boolean saddled) {
|
||||||
|
this.saddled = saddled;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public Long getSuspectedTimestamp() {
|
public Long getSuspectedTimestamp() {
|
||||||
return this.suspectedTimestamp;
|
return this.suspectedTimestamp;
|
||||||
}
|
}
|
||||||
@@ -6933,6 +6946,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
|||||||
if (!StaticAbilityNoCleanupDamage.damageNotRemoved(this)) {
|
if (!StaticAbilityNoCleanupDamage.damageNotRemoved(this)) {
|
||||||
setDamage(0);
|
setDamage(0);
|
||||||
}
|
}
|
||||||
|
setSaddled(false);
|
||||||
setHasBeenDealtDeathtouchDamage(false);
|
setHasBeenDealtDeathtouchDamage(false);
|
||||||
setHasBeenDealtExcessDamageThisTurn(false);
|
setHasBeenDealtExcessDamageThisTurn(false);
|
||||||
setRegeneratedThisTurn(0);
|
setRegeneratedThisTurn(0);
|
||||||
|
|||||||
@@ -297,6 +297,7 @@ public class CardCopyService {
|
|||||||
newCopy.setMonstrous(copyFrom.isMonstrous());
|
newCopy.setMonstrous(copyFrom.isMonstrous());
|
||||||
newCopy.setRenowned(copyFrom.isRenowned());
|
newCopy.setRenowned(copyFrom.isRenowned());
|
||||||
newCopy.setSolved(copyFrom.isSolved());
|
newCopy.setSolved(copyFrom.isSolved());
|
||||||
|
newCopy.setSaddled(copyFrom.isSaddled());
|
||||||
newCopy.setSuspectedTimestamp(copyFrom.getSuspectedTimestamp());
|
newCopy.setSuspectedTimestamp(copyFrom.getSuspectedTimestamp());
|
||||||
|
|
||||||
newCopy.setColor(copyFrom.getColor().getColor());
|
newCopy.setColor(copyFrom.getColor().getColor());
|
||||||
|
|||||||
@@ -3386,6 +3386,18 @@ public class CardFactoryUtil {
|
|||||||
sa.setSVar("X", "Count$xPaid");
|
sa.setSVar("X", "Count$xPaid");
|
||||||
}
|
}
|
||||||
inst.addSpellAbility(sa);
|
inst.addSpellAbility(sa);
|
||||||
|
} else if (keyword.startsWith("Saddle")) {
|
||||||
|
final String[] k = keyword.split(":");
|
||||||
|
final String power = k[1];
|
||||||
|
|
||||||
|
// tapXType has a special check for withTotalPower, and NEEDS it to be "+withTotalPowerGE"
|
||||||
|
String effect = "AB$ AlterAttribute | Cost$ tapXType<Any/Creature.Other+withTotalPowerGE" + power + ">" +
|
||||||
|
"| CostDesc$ Saddle " + power + " | Attributes$ Saddle | Secondary$ True | Defined$ Self | SorcerySpeed$ True " +
|
||||||
|
"| SpellDescription$ (" + inst.getReminderText() + ")";
|
||||||
|
|
||||||
|
final SpellAbility sa = AbilityFactory.getAbility(effect, card);
|
||||||
|
sa.setIntrinsic(intrinsic);
|
||||||
|
inst.addSpellAbility(sa);
|
||||||
} else if (keyword.startsWith("Scavenge")) {
|
} else if (keyword.startsWith("Scavenge")) {
|
||||||
final String[] k = keyword.split(":");
|
final String[] k = keyword.split(":");
|
||||||
final String manacost = k[1];
|
final String manacost = k[1];
|
||||||
|
|||||||
@@ -1905,6 +1905,10 @@ public class CardProperty {
|
|||||||
if (card.isSolved()) {
|
if (card.isSolved()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (property.equals("IsSaddled")) {
|
||||||
|
if (!card.isSaddled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (property.equals("IsSuspected")) {
|
} else if (property.equals("IsSuspected")) {
|
||||||
if (!card.isSuspected()) {
|
if (!card.isSuspected()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ public enum Keyword {
|
|||||||
RETRACE("Retrace", SimpleKeyword.class, false, "You may cast this card from your graveyard by discarding a land card in addition to paying its other costs."),
|
RETRACE("Retrace", SimpleKeyword.class, false, "You may cast this card from your graveyard by discarding a land card in addition to paying its other costs."),
|
||||||
RIOT("Riot", SimpleKeyword.class, false, "This creature enters the battlefield with your choice of a +1/+1 counter or haste."),
|
RIOT("Riot", SimpleKeyword.class, false, "This creature enters the battlefield with your choice of a +1/+1 counter or haste."),
|
||||||
RIPPLE("Ripple", KeywordWithAmount.class, false, "When you cast this spell, you may reveal the top {%d:card} of your library. You may cast any of those cards with the same name as this spell without paying their mana costs. Put the rest on the bottom of your library in any order."),
|
RIPPLE("Ripple", KeywordWithAmount.class, false, "When you cast this spell, you may reveal the top {%d:card} of your library. You may cast any of those cards with the same name as this spell without paying their mana costs. Put the rest on the bottom of your library in any order."),
|
||||||
|
SADDLE("Saddle", KeywordWithAmount.class, false, "Tap any number of other creatures you control with total power %1$d or more: This Mount becomes saddled until end of turn. Saddle only as a sorcery."),
|
||||||
SHADOW("Shadow", SimpleKeyword.class, true, "This creature can block or be blocked by only creatures with shadow."),
|
SHADOW("Shadow", SimpleKeyword.class, true, "This creature can block or be blocked by only creatures with shadow."),
|
||||||
SHROUD("Shroud", SimpleKeyword.class, true, "This can't be the target of spells or abilities."),
|
SHROUD("Shroud", SimpleKeyword.class, true, "This can't be the target of spells or abilities."),
|
||||||
SKULK("Skulk", SimpleKeyword.class, true, "This creature can't be blocked by creatures with greater power."),
|
SKULK("Skulk", SimpleKeyword.class, true, "This creature can't be blocked by creatures with greater power."),
|
||||||
|
|||||||
@@ -491,9 +491,11 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 702.36e
|
// 702.37e
|
||||||
// If the permanent wouldn't have a morph cost if it were face up, it can't be turned face up this way.
|
// If the permanent wouldn't have a morph cost if it were face up, it can't be turned face up this way.
|
||||||
if (sa.isMorphUp() && c.isInPlay()) {
|
// 702.168b
|
||||||
|
// If the permanent wouldn't have a disguise cost if it were face up, it can't be turned face up this way.
|
||||||
|
if ((sa.isMorphUp() || sa.isDisguiseUp()) && c.isInPlay()) {
|
||||||
Card cp = c;
|
Card cp = c;
|
||||||
if (!c.isLKI()) {
|
if (!c.isLKI()) {
|
||||||
cp = CardCopyService.getLKICopy(c);
|
cp = CardCopyService.getLKICopy(c);
|
||||||
|
|||||||
8
forge-gui/res/cardsfolder/upcoming/drover_grizzly.txt
Normal file
8
forge-gui/res/cardsfolder/upcoming/drover_grizzly.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Name:Drover Grizzly
|
||||||
|
ManaCost:2 G
|
||||||
|
Types:Creature Bear Mount
|
||||||
|
PT:4/2
|
||||||
|
T:Mode$ Attacks | ValidCard$ Card.Self+IsSaddled | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever CARDNAME attacks while saddled, creatures you control gain trample until end of turn.
|
||||||
|
SVar:TrigPump:DB$ PumpAll | ValidCards$ Creature.YouCtrl | KW$ Trample
|
||||||
|
K:Saddle:1
|
||||||
|
Oracle:Whenever Drover Grizzly attacks while saddled, creatures you control gain trample until end of turn.\nSaddle 1 (Tap any number of other creatures you control with total power 1 or more: This Mount becomes saddled until end of turn. Saddle only as a sorcery.)
|
||||||
@@ -181,6 +181,7 @@ Mongoose:Mongooses
|
|||||||
Monk:Monks
|
Monk:Monks
|
||||||
Monkey:Monkeys
|
Monkey:Monkeys
|
||||||
Moonfolk:Moonfolks
|
Moonfolk:Moonfolks
|
||||||
|
Mount:Mounts
|
||||||
Mouse:Mice
|
Mouse:Mice
|
||||||
Mutant:Mutants
|
Mutant:Mutants
|
||||||
Myr:Myrs
|
Myr:Myrs
|
||||||
|
|||||||
Reference in New Issue
Block a user