mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 20:58:03 +00:00
Improve applying attack requirements in multiplayer (#2582)
* Fix bad player position * Recover clean up --------- Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.59>
This commit is contained in:
@@ -18,6 +18,8 @@
|
||||
package forge.ai;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import forge.game.staticability.StaticAbility;
|
||||
@@ -815,14 +817,37 @@ public class AiAttackController {
|
||||
} else {
|
||||
if (combat.getAttackConstraints().getRequirements().get(attacker) == null) continue;
|
||||
// check defenders in order of maximum requirements
|
||||
for (Pair<GameEntity, Integer> e : combat.getAttackConstraints().getRequirements().get(attacker).getSortedRequirements()) {
|
||||
// TODO check if desired defender would also have the same amount
|
||||
List<Pair<GameEntity, Integer>> reqs = combat.getAttackConstraints().getRequirements().get(attacker).getSortedRequirements();
|
||||
final GameEntity def = defender;
|
||||
Collections.sort(reqs, new Comparator<Pair<GameEntity, Integer>>() {
|
||||
@Override
|
||||
public int compare(Pair<GameEntity, Integer> r1, Pair<GameEntity, Integer> r2) {
|
||||
if (r1.getValue() == r2.getValue()) {
|
||||
// try to attack the designated defender
|
||||
if (r1.getKey().equals(def) && !r2.getKey().equals(def)) {
|
||||
return -1;
|
||||
}
|
||||
if (r2.getKey().equals(def) && !r1.getKey().equals(def)) {
|
||||
return 1;
|
||||
}
|
||||
// otherwise PW
|
||||
if (r1.getKey() instanceof Card && r2.getKey() instanceof Player) {
|
||||
return -1;
|
||||
}
|
||||
if (r2.getKey() instanceof Card && r1.getKey() instanceof Player) {
|
||||
return 1;
|
||||
}
|
||||
// or weakest player
|
||||
if (r1.getKey() instanceof Player && r2.getKey() instanceof Player) {
|
||||
return ((Player) r1.getKey()).getLife() - ((Player) r2.getKey()).getLife();
|
||||
}
|
||||
}
|
||||
return r2.getValue() - r1.getValue();
|
||||
}
|
||||
});
|
||||
for (Pair<GameEntity, Integer> e : reqs) {
|
||||
if (e.getRight() == 0) continue;
|
||||
GameEntity mustAttackDefMaybe = e.getLeft();
|
||||
// Gideon Jura returns LKI
|
||||
if (mustAttackDefMaybe instanceof Card) {
|
||||
mustAttackDefMaybe = ai.getGame().getCardState((Card) mustAttackDefMaybe);
|
||||
}
|
||||
if (canAttackWrapper(attacker, mustAttackDefMaybe) && CombatUtil.getAttackCost(ai.getGame(), attacker, mustAttackDefMaybe) == null) {
|
||||
mustAttackDef = mustAttackDefMaybe;
|
||||
break;
|
||||
|
||||
@@ -1359,12 +1359,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
chance = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_CHANCE);
|
||||
}
|
||||
if (MyRandom.percentTrue(chance)) {
|
||||
Collections.sort(aiPlaneswalkers, new Comparator<Card>() {
|
||||
@Override
|
||||
public int compare(final Card a, final Card b) {
|
||||
return a.getCounters(CounterEnumType.LOYALTY) - b.getCounters(CounterEnumType.LOYALTY);
|
||||
}
|
||||
});
|
||||
Collections.sort(aiPlaneswalkers, CardPredicates.compareByCounterType(CounterEnumType.LOYALTY));
|
||||
for (Card pw : aiPlaneswalkers) {
|
||||
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
|
||||
int freshLoyalty = Integer.valueOf(pw.getCurrentState().getBaseLoyalty());
|
||||
|
||||
@@ -15,7 +15,6 @@ import com.google.common.collect.Table;
|
||||
import forge.GameCommand;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
@@ -220,11 +219,10 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
|
||||
GameObject aTo = Iterables.getFirst(
|
||||
AbilityUtils.getDefinedObjects(host, sa.getParam("AttachedTo"), sa), null);
|
||||
GameEntity aTo = Iterables.getFirst(
|
||||
AbilityUtils.getDefinedEntities(host, sa.getParam("AttachedTo"), sa), null);
|
||||
|
||||
if (aTo instanceof GameEntity) {
|
||||
GameEntity ge = (GameEntity)aTo;
|
||||
if (aTo != null) {
|
||||
// check what the token would be on the battlefield
|
||||
Card lki = CardUtil.getLKICopy(tok);
|
||||
|
||||
@@ -237,7 +235,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
|
||||
|
||||
boolean canAttach = lki.isAttachment();
|
||||
|
||||
if (canAttach && !ge.canBeAttached(lki, sa)) {
|
||||
if (canAttach && !aTo.canBeAttached(lki, sa)) {
|
||||
canAttach = false;
|
||||
}
|
||||
|
||||
@@ -253,7 +251,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
|
||||
return false;
|
||||
}
|
||||
|
||||
tok.attachToEntity(ge, sa);
|
||||
tok.attachToEntity(aTo, sa);
|
||||
return true;
|
||||
}
|
||||
// not a GameEntity, cant be attach
|
||||
|
||||
@@ -2064,7 +2064,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
sbLong.append(TextUtil.fastReplace(keyword, ":", " ")).append("\r\n");
|
||||
} else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph")
|
||||
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:")
|
||||
|| keyword.startsWith("Madness:")
|
||||
|| keyword.startsWith("Madness:")|| keyword.startsWith("Recover")
|
||||
|| keyword.startsWith("Reconfigure") || keyword.startsWith("Squad")
|
||||
|| keyword.startsWith("Miracle") || keyword.startsWith("More Than Meets the Eye")
|
||||
|| keyword.startsWith("Level up")) {
|
||||
@@ -2072,11 +2072,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
sbLong.append(k[0]);
|
||||
if (k.length > 1) {
|
||||
final Cost mCost = new Cost(k[1], true);
|
||||
if (!mCost.isOnlyManaCost()) {
|
||||
sbLong.append("—");
|
||||
}
|
||||
if (mCost.isOnlyManaCost()) {
|
||||
sbLong.append(" ");
|
||||
} else {
|
||||
sbLong.append("—");
|
||||
}
|
||||
sbLong.append(mCost.toString());
|
||||
if (!mCost.isOnlyManaCost()) {
|
||||
|
||||
@@ -1691,14 +1691,20 @@ public class CardFactoryUtil {
|
||||
AbilitySub exileSA = (AbilitySub) AbilityFactory.getAbility(exileStr, card);
|
||||
changeSA.setSubAbility(exileSA);
|
||||
|
||||
final Cost cost = new Cost(recoverCost, false);
|
||||
String costDesc = cost.toSimpleString();
|
||||
if (!cost.isOnlyManaCost()) {
|
||||
costDesc = "—" + costDesc;
|
||||
}
|
||||
|
||||
String trigObject = card.isCreature() ? "Creature.Other+YouOwn" : "Creature.YouOwn";
|
||||
String trigArticle = card.isCreature() ? "another" : "a";
|
||||
String trigStr = "Mode$ ChangesZone | ValidCard$ " + trigObject
|
||||
+ " | Origin$ Battlefield | Destination$ Graveyard | "
|
||||
+ "TriggerZones$ Graveyard | Secondary$ True | "
|
||||
+ "TriggerDescription$ Recover " + recoverCost + " (When " + trigArticle + " creature is "
|
||||
+ "TriggerDescription$ Recover " + costDesc + " (When " + trigArticle + " creature is "
|
||||
+ "put into your graveyard from the battlefield, you "
|
||||
+ "may pay " + recoverCost + ". If you do, return "
|
||||
+ "may pay " + costDesc + ". If you do, return "
|
||||
+ "CARDNAME from your graveyard to your hand. Otherwise,"
|
||||
+ " exile CARDNAME.)";
|
||||
final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic);
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:2 B B
|
||||
Types:Creature Zombie Dragon
|
||||
PT:4/3
|
||||
K:Flying
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Dragon.YouCtrl | IsPresent$ Card.StrictlySelf+inZoneGraveyard | PresentZone$ Graveyard | TriggerZones$ Graveyard | Execute$ TrigReturn | TriggerDescription$ Whenever a Dragon you control dies while Boneyard Scourge is in your graveyard, you may pay {1}{B}. If you do, return Boneyard Scourge from your graveyard to the battlefield.
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Dragon.YouCtrl | IsPresent$ Card.StrictlySelf+inZoneGraveyard | PresentZone$ Graveyard | TriggerZones$ Graveyard | Execute$ TrigReturn | TriggerDescription$ Whenever a Dragon you control dies while CARDNAME is in your graveyard, you may pay {1}{B}. If you do, return CARDNAME from your graveyard to the battlefield.
|
||||
SVar:TrigReturn:AB$ ChangeZone | Cost$ 1 B | Defined$ Self | Origin$ Graveyard | Destination$ Battlefield
|
||||
SVar:SacMe:1
|
||||
SVar:DiscardMe:1
|
||||
|
||||
@@ -4,7 +4,6 @@ Types:Creature Phoenix
|
||||
PT:2/2
|
||||
K:Flying
|
||||
K:Haste
|
||||
T:Mode$ DamageDone | ValidSource$ Spell.Instant+YouCtrl+Red,Spell.Sorcery+YouCtrl+Red | ValidTarget$ Opponent | TriggerZones$ Graveyard | Execute$ TrigReturn | TriggerDescription$ Whenever an opponent is dealt damage by a red instant or sorcery spell you control or by a red planeswalker you control, return Chandra's Phoenix from your graveyard to your hand.
|
||||
T:Mode$ DamageDone | ValidSource$ Planeswalker.YouCtrl+Red | ValidTarget$ Opponent | TriggerZones$ Graveyard | Execute$ TrigReturn | Secondary$ True | TriggerDescription$ Whenever an opponent is dealt damage by a red instant or sorcery spell you control or by a red planeswalker you control, return Chandra's Phoenix from your graveyard to your hand.
|
||||
T:Mode$ DamageDone | ValidSource$ Spell.Instant+YouCtrl+Red,Spell.Sorcery+YouCtrl+Red,Planeswalker.YouCtrl+Red | ValidTarget$ Opponent | TriggerZones$ Graveyard | Execute$ TrigReturn | TriggerDescription$ Whenever an opponent is dealt damage by a red instant or sorcery spell you control or by a red planeswalker you control, return CARDNAME from your graveyard to your hand.
|
||||
SVar:TrigReturn:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Hand
|
||||
Oracle:Flying\nHaste (This creature can attack and {T} as soon as it comes under your control.)\nWhenever an opponent is dealt damage by a red instant or sorcery spell you control or by a red planeswalker you control, return Chandra's Phoenix from your graveyard to your hand.
|
||||
|
||||
@@ -5,7 +5,7 @@ A:SP$ Sacrifice | Cost$ 2 U B R | SacValid$ Creature,Planeswalker | SacMessage$
|
||||
SVar:DBDiscard:DB$ Discard | NumCards$ 1 | Mode$ TgtChoose | Defined$ Player.Opponent | SubAbility$ DBRaiseDead
|
||||
SVar:DBRaiseDead:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | Hidden$ True | Mandatory$ True | ChangeType$ Creature.YouOwn,Planeswalker.YouOwn | SubAbility$ DBDraw | StackDescription$ You return a creature or planeswalker card from your graveyard to your hand
|
||||
SVar:DBDraw:DB$ Draw | NumCards$ 1 | Defined$ You
|
||||
T:Mode$ SpellCast | ValidCard$ Planeswalker.Bolas | TriggerZones$ Graveyard | Execute$ DBExileSelf | TriggerDescription$ When you cast a Bolas planeswalker spell, exile Dark Intimations from your graveyard. That planeswalker enters the battlefield with an additional loyalty counter on it.
|
||||
T:Mode$ SpellCast | ValidCard$ Planeswalker.Bolas | TriggerZones$ Graveyard | Execute$ DBExileSelf | TriggerDescription$ When you cast a Bolas planeswalker spell, exile CARDNAME from your graveyard. That planeswalker enters the battlefield with an additional loyalty counter on it.
|
||||
SVar:DBExileSelf:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | Defined$ Self | SubAbility$ DBExtraLoyaltyEffect
|
||||
SVar:DBExtraLoyaltyEffect:DB$ Effect | ReplacementEffects$ DBBoostLoyalty | RememberObjects$ TriggeredCard | ExileOnMoved$ Stack
|
||||
SVar:DBBoostLoyalty:Event$ Moved | ReplacementResult$ Updated | ActiveZones$ Command | Destination$ Battlefield | ValidCard$ Card.IsRemembered | ReplaceWith$ AddExtraCounter | Description$ That planeswalker enters the battlefield with an additional loyalty counter on it.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Garza's Assassin
|
||||
ManaCost:B B B
|
||||
Types:Creature Human Assassin
|
||||
PT:2/2
|
||||
K:Recover:PayLife<X>
|
||||
K:Recover:PayLife<X/half your life, rounded up>
|
||||
A:AB$ Destroy | Cost$ Sac<1/CARDNAME> | ValidTgts$ Creature.nonBlack | TgtPrompt$ Select target nonblack creature | SpellDescription$ Destroy target nonblack creature.
|
||||
SVar:X:Count$YourLifeTotal/HalfUp
|
||||
Oracle:Sacrifice Garza's Assassin: Destroy target nonblack creature.\nRecover—Pay half your life, rounded up. (When another creature is put into your graveyard from the battlefield, you may pay half your life, rounded up. If you do, return this card from your graveyard to your hand. Otherwise, exile this card.)
|
||||
|
||||
@@ -4,7 +4,7 @@ Types:Creature Phoenix
|
||||
PT:2/2
|
||||
K:Flying
|
||||
K:Haste
|
||||
T:Mode$ AttackersDeclared | CheckSVar$ X | SVarCompare$ GE3 | Execute$ TrigReturn | NoResolvingCheck$ True | TriggerZones$ Graveyard | AttackingPlayer$ You | TriggerDescription$ Whenever you attack with three or more creatures, you may pay {2}{R}. If you do, return Warcry Phoenix from your graveyard to the battlefield tapped and attacking.
|
||||
T:Mode$ AttackersDeclared | CheckSVar$ X | SVarCompare$ GE3 | Execute$ TrigReturn | NoResolvingCheck$ True | TriggerZones$ Graveyard | AttackingPlayer$ You | TriggerDescription$ Whenever you attack with three or more creatures, you may pay {2}{R}. If you do, return CARDNAME from your graveyard to the battlefield tapped and attacking.
|
||||
SVar:TrigReturn:AB$ ChangeZone | Cost$ 2 R | Defined$ Self | Origin$ Graveyard | Destination$ Battlefield | Tapped$ True | Attacking$ True
|
||||
SVar:X:Count$Valid Creature.attacking
|
||||
Oracle:Flying, haste\nWhenever you attack with three or more creatures, you may pay {2}{R}. If you do, return Warcry Phoenix from your graveyard to the battlefield tapped and attacking.
|
||||
|
||||
Reference in New Issue
Block a user