Keyword: Saddle (#4807)

* Keyword: Saddle

* AlterAttribute Logic for Suspected and Saddled
This commit is contained in:
Hans Mackowiak
2024-03-17 09:56:16 +01:00
committed by GitHub
parent 4351eff67e
commit 9b58a3cfac
13 changed files with 163 additions and 3 deletions

View File

@@ -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")) {

View File

@@ -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)

View 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;
}
}

View File

@@ -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);
} }

View File

@@ -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

View File

@@ -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);

View File

@@ -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());

View File

@@ -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];

View File

@@ -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;

View File

@@ -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."),

View File

@@ -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);

View 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.)

View File

@@ -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