Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions forge-ai/src/main/java/forge/ai/AiAttackController.java
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,8 @@ private boolean doAssault() {
}

int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, defendingOpponent);
if (totalPoisonDamage >= 10 - defendingOpponent.getPoisonCounters()) {
final int poisonThreshold = defendingOpponent.getPoisonCounterThreshold();
if (totalPoisonDamage >= poisonThreshold - defendingOpponent.getPoisonCounters()) {
return true;
}
for (Card trampler : trampleDmg.keySet()) {
Expand All @@ -757,7 +758,7 @@ private boolean doAssault() {
totalPoisonDamage += dmg;
}
totalPoisonDamage += ComputerUtilCombat.predictExtraPoisonWithDamage(trampler, defendingOpponent, dmg);
if (totalPoisonDamage >= 10 - defendingOpponent.getPoisonCounters()) {
if (totalPoisonDamage >= poisonThreshold - defendingOpponent.getPoisonCounters()) {
return true;
}
}
Expand Down
4 changes: 2 additions & 2 deletions forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ public static boolean lifeInSeriousDanger(final Player ai, final Combat combat,
}
}

if (resultingPoison(ai, combat) >= ai.getGame().getRules().getPoisonCountersToLose()) {
if (resultingPoison(ai, combat) >= ai.getPoisonCounterThreshold()) {
return true;
}

Expand Down Expand Up @@ -2437,7 +2437,7 @@ public static Card mostDangerousAttacker(CardCollection list, Player ai, Combat
}

int life = ai.getLife();
int poisonLife = 10 - ai.getPoisonCounters();
int poisonLife = ai.getPoisonCounterThreshold() - ai.getPoisonCounters();
double percentLife = life * 1.0 / damageScore;
double percentPoison = poisonLife * 1.0 / poisonScore;

Expand Down
6 changes: 3 additions & 3 deletions forge-ai/src/main/java/forge/ai/SpecialAiLogic.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public static boolean doAristocratLogic(final Player ai, final SpellAbility sa)
final boolean defTappedOut = ComputerUtilMana.getAvailableManaEstimate(defPlayer) == 0;

final boolean isInfect = source.hasKeyword(Keyword.INFECT); // Flesh-Eater Imp
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
int lethalDmg = isInfect ? defPlayer.getPoisonCounterThreshold() - defPlayer.getPoisonCounters() : defPlayer.getLife();

if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterEnumType.POISON)) {
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
Expand Down Expand Up @@ -275,7 +275,7 @@ public static AiAbilityDecision doAristocratWithCountersLogic(final Player ai, f
final boolean defTappedOut = ComputerUtilMana.getAvailableManaEstimate(defPlayer) == 0;

final boolean isInfect = source.hasKeyword(Keyword.INFECT);
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
int lethalDmg = isInfect ? defPlayer.getPoisonCounterThreshold() - defPlayer.getPoisonCounters() : defPlayer.getLife();

if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterEnumType.POISON)) {
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
Expand Down Expand Up @@ -422,4 +422,4 @@ public static boolean preferHasteForRiot(SpellAbility sa, Player player) {
// haste might not be good enough?
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {

for (final Player o : ai.getOpponents()) {
// Lethal poison - proliferating would win the game
if (o.getPoisonCounters() >= 9 && o.canReceiveCounters(CounterEnumType.POISON)
if (o.getPoisonCounters() >= o.getPoisonCounterThreshold() - 1 && o.canReceiveCounters(CounterEnumType.POISON)
&& !o.cantLoseCheck(GameLossReason.Poisoned)) {
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
}
Expand Down
6 changes: 3 additions & 3 deletions forge-ai/src/main/java/forge/ai/ability/DrawAi.java
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ private boolean targetAI(final Player ai, final SpellAbility sa, final boolean m
// try to make opponent lose to poison
// currently only Caress of Phyrexia
if (getPoison != null && oppA.canReceiveCounters(CounterEnumType.POISON)) {
if (oppA.getPoisonCounters() + numCards > 9) {
if (oppA.getPoisonCounters() + numCards >= oppA.getPoisonCounterThreshold()) {
sa.getTargets().add(oppA);
return true;
}
Expand Down Expand Up @@ -442,7 +442,7 @@ private boolean targetAI(final Player ai, final SpellAbility sa, final boolean m
}

if (getPoison != null && ai.canReceiveCounters(CounterEnumType.POISON)) {
if (numCards + ai.getPoisonCounters() >= 8) {
if (numCards + ai.getPoisonCounters() >= ai.getPoisonCounterThreshold() - 2) {
aiTarget = false;
}
}
Expand Down Expand Up @@ -499,7 +499,7 @@ private boolean targetAI(final Player ai, final SpellAbility sa, final boolean m
}

// ally would lose because of poison
if (getPoison != null && ally.canReceiveCounters(CounterEnumType.POISON) && ally.getPoisonCounters() + numCards > 9) {
if (getPoison != null && ally.canReceiveCounters(CounterEnumType.POISON) && ally.getPoisonCounters() + numCards >= ally.getPoisonCounterThreshold()) {
continue;
}

Expand Down
2 changes: 1 addition & 1 deletion forge-game/src/main/java/forge/game/card/Card.java
Original file line number Diff line number Diff line change
Expand Up @@ -2583,7 +2583,7 @@ private String keywordsToText(final Collection<KeywordInterface> keywords) {
|| keyword.startsWith("Afterlife") || keyword.startsWith("Hideaway") || keyword.startsWith("Toxic")
|| keyword.startsWith("Afflict") || keyword.startsWith ("Poisonous") || keyword.startsWith("Rampage")
|| keyword.startsWith("Renown") || keyword.startsWith("Annihilator") || keyword.startsWith("Ripple")
|| keyword.startsWith("Ward")) {
|| keyword.startsWith("Ward") || keyword.startsWith("Poison Tolerance")) {
sbLong.append(inst.getTitle()).append(" (").append(inst.getReminderText()).append(")");
} else if (keyword.startsWith("Partner with:")) {
final String[] k = keyword.split(":");
Expand Down
1 change: 1 addition & 0 deletions forge-game/src/main/java/forge/game/keyword/Keyword.java
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ public enum Keyword {
PERSIST("Persist", SimpleKeyword.class, false, "When this creature dies, if it had no -1/-1 counters on it, return it to the battlefield under its owner's control with a -1/-1 counter on it."),
PHASING("Phasing", SimpleKeyword.class, true, "This phases in or out before you untap during each of your untap steps. While it's phased out, it's treated as though it doesn't exist."),
PLOT("Plot", KeywordWithCost.class, false, "You may pay %s and exile this card from your hand. Cast it as a sorcery on a later turn without paying its mana cost. Plot only as a sorcery."),
POISON_TOLERANCE("Poison Tolerance", PoisonTolerance.class, false, "It takes %d additional poison counters for you to lose the game to poison."),
POISONOUS("Poisonous", KeywordWithAmount.class, false, "Whenever this creature deals combat damage to a player, that player gets {%d:poison counter}."),
PROTECTION("Protection", Protection.class, true, "This creature can't be blocked, targeted, dealt damage, or equipped/enchanted by %s."),
PROTOTYPE("Prototype", KeywordWithCost.class, false, "You may cast this spell with different mana cost, color, and size. It keeps its abilities and types."),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package forge.game.keyword;

public class PoisonTolerance extends KeywordWithAmount {

@Override
public String getTitle() {
return getKeyword() + " +" + getAmountString();
}
}
13 changes: 11 additions & 2 deletions forge-game/src/main/java/forge/game/player/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,15 @@ public final void addPoisonCounters(final int num, final Player source, GameEnti
public final void removePoisonCounters(final int num, final Player source) {
subtractCounter(CounterEnumType.POISON, num, source);
}
// Rule 704.5c base threshold, plus this player's own Poison Tolerance
// bonus from permanents they control (e.g. Rosewater's Nemesis).
public final int getPoisonCounterThreshold() {
int threshold = game.getRules().getPoisonCountersToLose();
for (final Card c : getCardsIn(ZoneType.Battlefield)) {
threshold += c.getKeywordMagnitude(Keyword.POISON_TOLERANCE);
}
return threshold;
}
// ================ POISON Merged =================================
public final void addChangedKeywords(final List<String> addKeywords, final List<String> removeKeywords, final Long timestamp, final long staticId) {
List<KeywordInterface> kws = Lists.newArrayList();
Expand Down Expand Up @@ -2045,9 +2054,9 @@ public final boolean checkLoseCondition() {
return true;
}

// Rule 704.5c - If a player has ten or more poison counters, he or she loses the game.
// Rule 704.5c - If a player has ten or more poison counters, he or she loses the game. Handles Poison Tolerance increasing the threshold as well.
// 704.6b In a Two-Headed Giant game, if a team has fifteen or more poison counters, that team loses the game. See rule 810, “Two-Headed Giant Variant.”
if (getCounters(CounterEnumType.POISON) >= 10 && loseConditionMet(GameLossReason.Poisoned, null)) {
if (getCounters(CounterEnumType.POISON) >= getPoisonCounterThreshold() && loseConditionMet(GameLossReason.Poisoned, null)) {
return true;
}

Expand Down
8 changes: 8 additions & 0 deletions forge-gui/res/cardsfolder/r/rosewaters_nemesis.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Name:Rosewater's Nemesis
ManaCost:3 W W
Types:Creature Monkey Cleric
PT:4/6
K:Vigilance
K:Protection:Phyrexian
K:Poison Tolerance:3
Oracle:Vigilance, protection from Phyrexians\nPoison Tolerance +3 (It takes three additional poison counters for you to lose the game to poison.)