mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 19:58:00 +00:00
Merge branch 'miracle' into 'master'
Miracle is optional Closes #2083 See merge request core-developers/forge!6201
This commit is contained in:
@@ -153,10 +153,15 @@ public class AiAttackController {
|
||||
}
|
||||
}
|
||||
|
||||
/** Choose opponent for AI to attack here. Expand as necessary. */
|
||||
/**
|
||||
* Choose opponent for AI to attack here. Expand as necessary.
|
||||
* No strategy to secure a second place instead, since Forge has no variant for that
|
||||
*/
|
||||
public static Player choosePreferredDefenderPlayer(Player ai) {
|
||||
Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range
|
||||
|
||||
// TODO connect with evaluateBoardPosition and only fall back to random when no player is the biggest threat by a fair margin
|
||||
|
||||
if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players
|
||||
// TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked
|
||||
return ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
|
||||
@@ -720,7 +725,7 @@ public class AiAttackController {
|
||||
continue;
|
||||
}
|
||||
boolean mustAttack = false;
|
||||
// TODO for nextTurn check if it was temporary
|
||||
// TODO this might result into attacking the wrong player
|
||||
if (attacker.isGoaded()) {
|
||||
mustAttack = true;
|
||||
} else if (attacker.getSVar("MustAttack").equals("True")) {
|
||||
@@ -737,7 +742,7 @@ public class AiAttackController {
|
||||
mustAttack = true;
|
||||
}
|
||||
}
|
||||
if (mustAttack || (attacker.getController().getMustAttackEntity() != null && nextTurn) || (attacker.getController().getMustAttackEntityThisTurn() != null && !nextTurn)) {
|
||||
if (mustAttack ||attacker.getController().getMustAttackEntityThisTurn() != null) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
attackersLeft.remove(attacker);
|
||||
numForcedAttackers++;
|
||||
|
||||
@@ -426,7 +426,6 @@ public class AiBlockController {
|
||||
}
|
||||
|
||||
attackersLeft = new ArrayList<>(currentAttackers);
|
||||
currentAttackers = new ArrayList<>(attackersLeft);
|
||||
|
||||
boolean considerTripleBlock = true;
|
||||
|
||||
@@ -437,6 +436,11 @@ public class AiBlockController {
|
||||
continue;
|
||||
}
|
||||
|
||||
// AI can't handle good blocks with more than three creatures yet
|
||||
if (CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > (considerTripleBlock ? 3 : 2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int evalAttackerValue = ComputerUtilCard.evaluateCreature(attacker);
|
||||
|
||||
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
|
||||
@@ -446,11 +450,6 @@ public class AiBlockController {
|
||||
int currentValue; // The value of the creatures in the blockgang
|
||||
boolean foundDoubleBlock = false; // if true, a good double block is found
|
||||
|
||||
// AI can't handle good blocks with more than three creatures yet
|
||||
if (CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > (considerTripleBlock ? 3 : 2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to add blockers that could be destroyed, but are worth less than the attacker
|
||||
// Don't use blockers without First Strike or Double Strike if attacker has it
|
||||
usableBlockers = CardLists.filter(blockers, new Predicate<Card>() {
|
||||
@@ -460,8 +459,7 @@ public class AiBlockController {
|
||||
&& !ComputerUtilCombat.dealsFirstStrikeDamage(c, false, combat)) {
|
||||
return false;
|
||||
}
|
||||
final boolean randomTrade = wouldLikeToRandomlyTrade(attacker, c, combat);
|
||||
return lifeInDanger || randomTrade || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker);
|
||||
return lifeInDanger || wouldLikeToRandomlyTrade(attacker, c, combat) || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker);
|
||||
}
|
||||
});
|
||||
if (usableBlockers.size() < 2) {
|
||||
|
||||
@@ -2822,8 +2822,6 @@ public class ComputerUtil {
|
||||
pRating /= 5;
|
||||
}
|
||||
|
||||
System.out.println("Board position evaluation for " + p + ": " + pRating);
|
||||
|
||||
if (pRating > bestBoardRating) {
|
||||
bestBoardRating = pRating;
|
||||
bestBoardPosition = p;
|
||||
|
||||
@@ -830,7 +830,7 @@ public class ComputerUtilCombat {
|
||||
}
|
||||
|
||||
// defender == null means unblocked
|
||||
if ((defender == null) && mode == TriggerType.AttackerUnblocked) {
|
||||
if (defender == null && mode == TriggerType.AttackerUnblocked) {
|
||||
willTrigger = true;
|
||||
if (!trigger.matchesValidParam("ValidCard", attacker)) {
|
||||
return false;
|
||||
|
||||
@@ -25,6 +25,9 @@ import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostDiscard;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostReveal;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
@@ -70,7 +73,7 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
Player controlledByPlayer = null;
|
||||
long controlledByTimeStamp = -1;
|
||||
final Game game = activator.getGame();
|
||||
final boolean optional = sa.hasParam("Optional");
|
||||
boolean optional = sa.hasParam("Optional");
|
||||
boolean remember = sa.hasParam("RememberPlayed");
|
||||
int amount = 1;
|
||||
boolean hasTotalCMCLimit = sa.hasParam("WithTotalCMC");
|
||||
@@ -330,7 +333,17 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (!optional) {
|
||||
tgtSA.getPayCosts().setMandatory(true);
|
||||
// 118.8c
|
||||
for (CostPart cost : tgtSA.getPayCosts().getCostParts()) {
|
||||
if ((cost instanceof CostDiscard || cost instanceof CostReveal)
|
||||
&& !cost.getType().equals("Card") && !cost.getType().equals("Random")) {
|
||||
optional = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!optional) {
|
||||
tgtSA.getPayCosts().setMandatory(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("PlayReduceCost")) {
|
||||
|
||||
@@ -1452,7 +1452,7 @@ public class CardFactoryUtil {
|
||||
final String manacost = k[1];
|
||||
final String abStrReveal = "DB$ Reveal | Defined$ You | RevealDefined$ Self"
|
||||
+ " | MiracleCost$ " + manacost;
|
||||
final String abStrPlay = "DB$ Play | Defined$ Self | PlayCost$ " + manacost;
|
||||
final String abStrPlay = "DB$ Play | Defined$ Self | Optional$ True | PlayCost$ " + manacost;
|
||||
|
||||
String revealed = "DB$ ImmediateTrigger | TriggerDescription$ CARDNAME - Miracle";
|
||||
|
||||
|
||||
@@ -6,6 +6,6 @@ K:Flying
|
||||
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDigUntil | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order.
|
||||
SVar:TrigDigUntil:DB$ DigUntil | Defined$ TriggeredTarget | Valid$ Instant,Sorcery | ValidDescription$ instant or sorcery | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | RememberRevealed$ True | IsCurse$ True | SubAbility$ DBPlay | SpellDescription$ Whenever CARDNAME deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order.
|
||||
SVar:DBPlay:DB$ Play | Defined$ Remembered | ValidZone$ Exile | Valid$ Instant.IsRemembered,Sorcery.IsRemembered | ValidSA$ Spell | WithoutManaCost$ True | RememberObjects$ Remembered | Optional$ True | ForgetTargetRemembered$ True | SubAbility$ DBRestRandomOrder
|
||||
SVar:DBRestRandomOrder:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | SubAbility$ DBCleanup
|
||||
SVar:DBRestRandomOrder:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Exile | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
Oracle:Flying\nWhenever Dazzling Sphinx deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order.
|
||||
|
||||
@@ -760,7 +760,8 @@ public class HumanCostDecision extends CostDecisionMakerBase {
|
||||
if (num == 0) {
|
||||
return PaymentDecision.number(0);
|
||||
}
|
||||
if (hand.size() == num) {
|
||||
// player might not want to pay if from a trigger
|
||||
if (!ability.hasSVar("IsCastFromPlayEffect") && hand.size() == num) {
|
||||
return PaymentDecision.card(hand);
|
||||
}
|
||||
|
||||
|
||||
@@ -472,7 +472,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
||||
return null;
|
||||
}
|
||||
|
||||
String announceTitle = ("X".equals(announce)) ? announce : ability.getParamOrDefault("AnnounceTitle", announce);
|
||||
String announceTitle = "X".equals(announce) ? announce : ability.getParamOrDefault("AnnounceTitle", announce);
|
||||
if (cost.isMandatory()) {
|
||||
return chooseNumber(ability, localizer.getMessage("lblChooseAnnounceForCard", announceTitle,
|
||||
CardTranslation.getTranslatedName(ability.getHostCard().getName())) , min, max);
|
||||
|
||||
Reference in New Issue
Block a user