diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index eec62cff8f9..853571bac00 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -338,14 +338,14 @@ public class PlayerControllerAi extends PlayerController { } @Override - public void reveal(CardCollectionView cards, ZoneType zone, Player owner, String messagePrefix) { + public void reveal(CardCollectionView cards, ZoneType zone, Player owner, String messagePrefix, boolean addSuffix) { for (Card c : cards) { AiCardMemory.rememberCard(player, c, AiCardMemory.MemorySet.REVEALED_CARDS); } } @Override - public void reveal(List cards, ZoneType zone, PlayerView owner, String messagePrefix) { + public void reveal(List cards, ZoneType zone, PlayerView owner, String messagePrefix, boolean addSuffix) { for (CardView cv : cards) { AiCardMemory.rememberCard(player, player.getGame().findByView(cv), AiCardMemory.MemorySet.REVEALED_CARDS); } diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index b8527341a1a..e0991c8574b 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -139,6 +139,7 @@ public enum SpellApiToAi { .put(ApiType.PumpAll, PumpAllAi.class) .put(ApiType.PutCounter, CountersPutAi.class) .put(ApiType.PutCounterAll, CountersPutAllAi.class) + .put(ApiType.Radiation, AlwaysPlayAi.class) .put(ApiType.RearrangeTopOfLibrary, RearrangeTopOfLibraryAi.class) .put(ApiType.Regenerate, RegenerateAi.class) .put(ApiType.RegenerateAll, RegenerateAllAi.class) diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index f5497b9f36d..e5640bf366d 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -1926,15 +1926,15 @@ public class GameAction { revealTo(cards, to, null); } public void revealTo(final CardCollectionView cards, final Player to, String messagePrefix) { - revealTo(cards, Collections.singleton(to), messagePrefix); + revealTo(cards, Collections.singleton(to), messagePrefix, true); } public void revealTo(final Card card, final Iterable to) { revealTo(new CardCollection(card), to); } public void revealTo(final CardCollectionView cards, final Iterable to) { - revealTo(cards, to, null); + revealTo(cards, to, null, true); } - public void revealTo(final CardCollectionView cards, final Iterable to, String messagePrefix) { + public void revealTo(final CardCollectionView cards, final Iterable to, String messagePrefix, boolean addSuffix) { if (cards.isEmpty()) { return; } @@ -1942,7 +1942,7 @@ public class GameAction { final ZoneType zone = cards.getFirst().getZone().getZoneType(); final Player owner = cards.getFirst().getOwner(); for (final Player p : to) { - p.getController().reveal(cards, zone, owner, messagePrefix); + p.getController().reveal(cards, zone, owner, messagePrefix, addSuffix); } } @@ -1953,18 +1953,25 @@ public class GameAction { reveal(cards, cardOwner, dontRevealToOwner, null); } public void reveal(CardCollectionView cards, Player cardOwner, boolean dontRevealToOwner, String messagePrefix) { + reveal(cards, cardOwner, dontRevealToOwner, messagePrefix, true); + } + public void reveal(CardCollectionView cards, Player cardOwner, boolean dontRevealToOwner, String messagePrefix, boolean msgAddSuffix) { Card firstCard = Iterables.getFirst(cards, null); if (firstCard == null) { return; } - reveal(cards, game.getZoneOf(firstCard).getZoneType(), cardOwner, dontRevealToOwner, messagePrefix); + reveal(cards, game.getZoneOf(firstCard).getZoneType(), cardOwner, dontRevealToOwner, messagePrefix, msgAddSuffix); } + public void reveal(CardCollectionView cards, ZoneType zt, Player cardOwner, boolean dontRevealToOwner, String messagePrefix) { + reveal(cards, zt, cardOwner, dontRevealToOwner, messagePrefix, true); + } + public void reveal(CardCollectionView cards, ZoneType zt, Player cardOwner, boolean dontRevealToOwner, String messagePrefix, boolean msgAddSuffix) { for (Player p : game.getPlayers()) { if (dontRevealToOwner && cardOwner == p) { continue; } - p.getController().reveal(cards, zt, cardOwner, messagePrefix); + p.getController().reveal(cards, zt, cardOwner, messagePrefix, msgAddSuffix); } } diff --git a/forge-game/src/main/java/forge/game/GameLogFormatter.java b/forge-game/src/main/java/forge/game/GameLogFormatter.java index fba87ebc50a..45ddc33218f 100644 --- a/forge-game/src/main/java/forge/game/GameLogFormatter.java +++ b/forge-game/src/main/java/forge/game/GameLogFormatter.java @@ -10,6 +10,7 @@ import com.google.common.eventbus.Subscribe; import forge.LobbyPlayer; import forge.game.card.Card; +import forge.game.card.CounterEnumType; import forge.game.event.*; import forge.game.event.GameEventCardDamaged.DamageType; import forge.game.player.Player; @@ -224,6 +225,21 @@ public class GameLogFormatter extends IGameEventVisitor.Base { return new GameLogEntry(GameLogEntryType.DAMAGE, message); } + @Override + public GameLogEntry visit(GameEventPlayerRadiation ev) { + String message; + final int change = ev.change; + String radCtr = CounterEnumType.RAD.getName().toLowerCase() + " " + + Localizer.getInstance().getMessage("lblCounter").toLowerCase(); + if (change >= 0) message = localizer.getMessage("lblLogPlayerRadiation", + ev.receiver.toString(), Lang.nounWithNumeralExceptOne(String.valueOf(change), radCtr), + ev.source.toString()); + else message = localizer.getMessage("lblLogPlayerRadRemove", + ev.receiver.toString(), Lang.nounWithNumeralExceptOne(String.valueOf(Math.abs(change)), radCtr), + ev.source.toString()); + return new GameLogEntry(GameLogEntryType.DAMAGE, message); + } + @Override public GameLogEntry visit(final GameEventAttackersDeclared ev) { final StringBuilder sb = new StringBuilder(); diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java index 06b7ccecc0b..22c120c16df 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -139,6 +139,7 @@ public enum ApiType { PumpAll (PumpAllEffect.class), PutCounter (CountersPutEffect.class), PutCounterAll (CountersPutAllEffect.class), + Radiation (RadiationEffect.class), RearrangeTopOfLibrary (RearrangeTopOfLibraryEffect.class), Regenerate (RegenerateEffect.class), RegenerateAll (RegenerateAllEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java index 688e7741340..36c25a16e65 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java @@ -98,6 +98,7 @@ public class ChooseCardEffect extends SpellAbilityEffect { return; } + boolean revealTitle = (sa.hasParam("RevealTitle")); for (Player p : tgtPlayers) { if (!p.isInGame()) { p = getNewChooser(sa, activator, p); @@ -267,14 +268,15 @@ public class ChooseCardEffect extends SpellAbilityEffect { } } if (sa.hasParam("Reveal") && !sa.hasParam("SecretlyChoose")) { - game.getAction().reveal(chosen, p, dontRevealToOwner, sa.hasParam("RevealTitle") ? - sa.getParam("RevealTitle") : Localizer.getInstance().getMessage("lblChosenCards") + " "); + game.getAction().reveal(chosen, p, dontRevealToOwner, revealTitle ? sa.getParam("RevealTitle") : + Localizer.getInstance().getMessage("lblChosenCards") + " ", !revealTitle); } } if (sa.hasParam("Reveal") && sa.hasParam("SecretlyChoose")) { for (final Player p : tgtPlayers) { - game.getAction().reveal(chosen, p, true, sa.hasParam("RevealTitle") ? - sa.getParam("RevealTitle") : Localizer.getInstance().getMessage("lblChosenCards") + " "); + game.getAction().reveal(chosen, p, true, revealTitle ? + sa.getParam("RevealTitle") : Localizer.getInstance().getMessage("lblChosenCards") + " ", + !revealTitle); } } host.setChosenCards(chosen); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ClashEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ClashEffect.java index c790777399d..c4e920d7842 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ClashEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ClashEffect.java @@ -88,7 +88,6 @@ public class ClashEffect extends SpellAbilityEffect { } final StringBuilder reveal = new StringBuilder(); - reveal.append("OVERRIDE "); //will return substring with the original message parsed here.. Card pCard = null; Card oCard = null; final CardCollection toReveal = new CardCollection(); @@ -122,7 +121,7 @@ public class ClashEffect extends SpellAbilityEffect { reveal.append(winner + " " + Localizer.getInstance().getMessage("lblWinsClash") + "."); } - player.getGame().getAction().revealTo(toReveal, player.getGame().getPlayers(), reveal.toString()); + player.getGame().getAction().revealTo(toReveal, player.getGame().getPlayers(), reveal.toString(), false); clashMoveToTopOrBottom(player, pCard, sa); clashMoveToTopOrBottom(opponent, oCard, sa); diff --git a/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java index b6b8517a04f..e7283215c98 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java @@ -1,7 +1,5 @@ package forge.game.ability.effects; -import java.util.Map; - import forge.game.Game; import forge.game.GameLogEntryType; import forge.game.ability.AbilityKey; @@ -18,6 +16,8 @@ import forge.util.Lang; import forge.util.Localizer; import forge.util.TextUtil; +import java.util.Map; + public class MillEffect extends SpellAbilityEffect { @Override public void resolve(SpellAbility sa) { @@ -47,6 +47,8 @@ public class MillEffect extends SpellAbilityEffect { continue; } + String toZoneStr = destination.equals(ZoneType.Graveyard) ? "" : " (" + + Localizer.getInstance().getMessage("lblMilledToZone", destination.getTranslatedName()) + ")"; if (sa.hasParam("Optional")) { String d = destination.equals(ZoneType.Graveyard) ? "" : " (" + destination.getTranslatedName() + ")"; final String prompt = TextUtil.concatWithSpace(Localizer.getInstance(). @@ -61,11 +63,12 @@ public class MillEffect extends SpellAbilityEffect { // graveyard to figure out which ones were milled. if (!facedown && reveal) { // do not reveal when exiling face down if (showRevealDialog) { - game.getAction().reveal(milled, p, false); + final String message = Localizer.getInstance().getMessage("lblMilledCards"); + final boolean addSuffix = !toZoneStr.equals(""); + game.getAction().reveal(milled, destination, p, false, message, addSuffix); } - StringBuilder sb = new StringBuilder(); - sb.append(p).append(" milled ").append(milled).append(" to ").append(destination); - p.getGame().getGameLog().add(GameLogEntryType.ZONE_CHANGE, sb.toString()); + p.getGame().getGameLog().add(GameLogEntryType.ZONE_CHANGE, p + " milled " + + Lang.joinHomogenous(milled) + toZoneStr + "."); } if (destination.equals(ZoneType.Exile)) { for (final Card c : milled) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/RadiationEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RadiationEffect.java new file mode 100644 index 00000000000..e9aa8ad0e11 --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/RadiationEffect.java @@ -0,0 +1,43 @@ +package forge.game.ability.effects; + +import com.google.common.collect.Maps; +import forge.game.Game; +import forge.game.GameEntityCounterTable; +import forge.game.ability.AbilityUtils; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.card.CounterEnumType; +import forge.game.event.GameEventPlayerRadiation; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; + +import java.util.Map; + +public class RadiationEffect extends SpellAbilityEffect { + + @Override + public void resolve(SpellAbility sa) { + final Card host = sa.getHostCard(); + final Game game = host.getGame(); + final int toAdd = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Add", "0"), sa); + final int toRem = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Remove", "0"), sa); + final Map list = Maps.newHashMap(); + + GameEntityCounterTable table = new GameEntityCounterTable(); + + for (final Player p : getTargetPlayers(sa)) { + if (!p.isInGame()) continue; + + list.put(p, p.getCounters(CounterEnumType.RAD)); + if (toAdd >= 1) p.addRadCounters(toAdd, host, table); + else if (toRem >= 1) p.removeRadCounters(toRem, host); + } + table.replaceCounterEffect(game, sa, true); + for (final Player p : list.keySet()) { + int oldCount = list.get(p); + int newCount = p.getCounters(CounterEnumType.RAD); + if (newCount > 0 && !p.hasRadiationEffect()) p.createRadiationEffect(host.getSetCode()); + if (oldCount < newCount) game.fireEvent(new GameEventPlayerRadiation(p, host, newCount - oldCount)); + } + } +} diff --git a/forge-game/src/main/java/forge/game/ability/effects/RevealEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RevealEffect.java index e08175329d5..bdd8232741f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RevealEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RevealEffect.java @@ -76,7 +76,9 @@ public class RevealEffect extends SpellAbilityEffect { } if (sa.hasParam("RevealToAll") || sa.hasParam("Random")) { - game.getAction().reveal(revealed, p, false, sa.getParamOrDefault("RevealTitle", "")); + boolean revealTitle = sa.hasParam("RevealTitle"); + game.getAction().reveal(revealed, p, false, + revealTitle ? sa.getParam("RevealTitle") : "", !revealTitle); } else { game.getAction().reveal(revealed, p); } diff --git a/forge-game/src/main/java/forge/game/card/CounterEnumType.java b/forge-game/src/main/java/forge/game/card/CounterEnumType.java index 8c02778aa5e..f706482d3c3 100644 --- a/forge-game/src/main/java/forge/game/card/CounterEnumType.java +++ b/forge-game/src/main/java/forge/game/card/CounterEnumType.java @@ -427,6 +427,8 @@ public enum CounterEnumType { POISON("POISN"), + RAD("RAD"), + TICKET("TICKET"), // Keyword Counters diff --git a/forge-game/src/main/java/forge/game/event/GameEventPlayerRadiation.java b/forge-game/src/main/java/forge/game/event/GameEventPlayerRadiation.java new file mode 100644 index 00000000000..c7fa6dd0938 --- /dev/null +++ b/forge-game/src/main/java/forge/game/event/GameEventPlayerRadiation.java @@ -0,0 +1,21 @@ +package forge.game.event; + +import forge.game.card.Card; +import forge.game.player.Player; + +public class GameEventPlayerRadiation extends GameEvent { + public final Player receiver; + public final Card source; + public final int change; + + public GameEventPlayerRadiation(Player recv, Card src, int chng) { + receiver = recv; + source = src; + change = chng; + } + + @Override + public T visit(IGameEventVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java b/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java index 905fcb4b170..3eb4dfeac72 100644 --- a/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java +++ b/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java @@ -36,6 +36,7 @@ public interface IGameEventVisitor { T visit(GameEventPlayerDamaged gameEventPlayerDamaged); T visit(GameEventPlayerCounters event); T visit(GameEventPlayerPoisoned event); + T visit(GameEventPlayerRadiation event); T visit(GameEventPlayerPriority event); T visit(GameEventPlayerShardsChanged event); T visit(GameEventPlayerStatsChanged event); @@ -90,6 +91,7 @@ public interface IGameEventVisitor { public T visit(GameEventPlayerControl event) { return null; } public T visit(GameEventPlayerCounters event) { return null; } public T visit(GameEventPlayerPoisoned event) { return null; } + public T visit(GameEventPlayerRadiation event) { return null; } public T visit(GameEventPlayerPriority event) { return null; } public T visit(GameEventPlayerShardsChanged event) { return null; } public T visit(GameEventPlayerStatsChanged event) { return null; } diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java index 1740319256f..3c96a694e8f 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -17,43 +17,22 @@ */ package forge.game.phase; -import java.util.*; - -import com.google.common.collect.*; -import org.apache.commons.lang3.time.StopWatch; - -import forge.game.Game; -import forge.game.GameEntity; -import forge.game.GameEntityCounterTable; -import forge.game.GameStage; -import forge.game.GameType; -import forge.game.GlobalRuleChange; +import com.google.common.base.Predicates; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import forge.game.*; import forge.game.ability.AbilityKey; import forge.game.ability.effects.AddTurnEffect; import forge.game.ability.effects.SkipPhaseEffect; -import forge.game.card.Card; -import forge.game.card.CardCollection; -import forge.game.card.CardLists; -import forge.game.card.CardUtil; +import forge.game.card.*; import forge.game.card.CardPredicates.Presets; -import forge.game.card.CardZoneTable; -import forge.game.card.CounterEnumType; import forge.game.combat.Combat; import forge.game.combat.CombatUtil; import forge.game.cost.CostEnlist; import forge.game.cost.CostExert; -import forge.game.event.GameEventAttackersDeclared; -import forge.game.event.GameEventBlockersDeclared; -import forge.game.event.GameEventCardStatsChanged; -import forge.game.event.GameEventCombatChanged; -import forge.game.event.GameEventCombatEnded; -import forge.game.event.GameEventGameRestarted; -import forge.game.event.GameEventPlayerPriority; -import forge.game.event.GameEventPlayerStatsChanged; -import forge.game.event.GameEventTokenStateUpdate; -import forge.game.event.GameEventTurnBegan; -import forge.game.event.GameEventTurnEnded; -import forge.game.event.GameEventTurnPhase; +import forge.game.event.*; import forge.game.player.Player; import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementType; @@ -64,9 +43,14 @@ import forge.game.trigger.TriggerType; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.util.CollectionSuppliers; +import forge.util.Lang; +import forge.util.Localizer; import forge.util.TextUtil; import forge.util.maps.HashMapOfLists; import forge.util.maps.MapOfLists; +import org.apache.commons.lang3.time.StopWatch; + +import java.util.*; /** @@ -299,6 +283,9 @@ public class PhaseHandler implements java.io.Serializable { if (playerTurn.isArchenemy()) { playerTurn.setSchemeInMotion(); } + if (playerTurn.hasRadiationEffect()) { + handleRadiation(); + } GameEntityCounterTable table = new GameEntityCounterTable(); // all Saga get Lore counter at the begin of pre combat for (Card c : playerTurn.getCardsIn(ZoneType.Battlefield)) { @@ -541,6 +528,36 @@ public class PhaseHandler implements java.io.Serializable { } } + private void handleRadiation() { + int numRad = playerTurn.getCounters(CounterEnumType.RAD); + if (numRad == 0) playerTurn.removeRadiationEffect(); + else { + final CardZoneTable table = new CardZoneTable(); + Map moveParams = AbilityKey.newMap(); + moveParams.put(AbilityKey.LastStateBattlefield, game.getLastStateBattlefield()); + moveParams.put(AbilityKey.LastStateGraveyard, game.getLastStateGraveyard()); + final SpellAbility sa = new SpellAbility.EmptySa(playerTurn.getRadiationEffect(), playerTurn); + final CardCollectionView milled = playerTurn.mill(numRad, ZoneType.Graveyard, sa, + table, moveParams); + game.getAction().reveal(milled, playerTurn, false, + Localizer.getInstance().getMessage("lblMilledCards", playerTurn), false); + game.getGameLog().add(GameLogEntryType.ZONE_CHANGE, playerTurn + " milled " + + Lang.joinHomogenous(milled) + "."); + table.triggerChangesZoneAll(game, sa); + int n = CardLists.filter(milled, Predicates.not(CardPredicates.Presets.LANDS)).size(); + final Map lossMap = Maps.newHashMap(); + final int lost = playerTurn.loseLife(n, false, false); + if (lost > 0) { + lossMap.put(playerTurn, lost); + } + if (!lossMap.isEmpty()) { // Run triggers if any player actually lost life + final Map runParams = AbilityKey.mapFromPIMap(lossMap); + game.getTriggerHandler().runTrigger(TriggerType.LifeLostAll, runParams, false); + } + playerTurn.removeRadCounters(n, playerTurn.getRadiationEffect()); + } + } + private void declareAttackersTurnBasedAction() { final Player whoDeclares = playerDeclaresAttackers == null || playerDeclaresAttackers.hasLost() ? playerTurn diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index c72eba4d89f..86be271c33d 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -17,39 +17,9 @@ */ package forge.game.player; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NavigableMap; -import java.util.Set; -import java.util.SortedSet; - -import forge.game.event.*; -import forge.game.spellability.AbilitySub; -import forge.game.spellability.LandAbility; - -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; - import com.google.common.base.Function; import com.google.common.base.Predicates; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.google.common.collect.Table; -import com.google.common.collect.TreeBasedTable; - +import com.google.common.collect.*; import forge.ImageKeys; import forge.LobbyPlayer; import forge.card.CardStateName; @@ -58,36 +28,17 @@ import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostShard; -import forge.game.CardTraitBase; -import forge.game.Game; -import forge.game.GameActionUtil; -import forge.game.GameEntity; -import forge.game.GameEntityCounterTable; -import forge.game.GameLogEntryType; -import forge.game.GameStage; -import forge.game.GameType; +import forge.game.*; import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.ability.effects.DetachedCardEffect; -import forge.game.card.Card; -import forge.game.card.CardCollection; -import forge.game.card.CardCollectionView; -import forge.game.card.CardFactoryUtil; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates; +import forge.game.card.*; import forge.game.card.CardPredicates.Presets; -import forge.game.card.CardUtil; -import forge.game.card.CardZoneTable; -import forge.game.card.CounterEnumType; -import forge.game.card.CounterType; -import forge.game.keyword.Companion; -import forge.game.keyword.Keyword; -import forge.game.keyword.KeywordCollection; +import forge.game.event.*; +import forge.game.keyword.*; import forge.game.keyword.KeywordCollection.KeywordCollectionView; -import forge.game.keyword.KeywordInterface; -import forge.game.keyword.KeywordsChange; import forge.game.mana.ManaPool; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; @@ -95,16 +46,10 @@ import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementType; +import forge.game.spellability.AbilitySub; +import forge.game.spellability.LandAbility; import forge.game.spellability.SpellAbility; -import forge.game.staticability.StaticAbility; -import forge.game.staticability.StaticAbilityCantBeCast; -import forge.game.staticability.StaticAbilityCantDiscard; -import forge.game.staticability.StaticAbilityCantBecomeMonarch; -import forge.game.staticability.StaticAbilityCantDraw; -import forge.game.staticability.StaticAbilityCantGainLosePayLife; -import forge.game.staticability.StaticAbilityCantPutCounter; -import forge.game.staticability.StaticAbilityCantTarget; -import forge.game.staticability.StaticAbilityCantSetSchemesInMotion; +import forge.game.staticability.*; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerHandler; import forge.game.trigger.TriggerType; @@ -114,13 +59,14 @@ import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.item.IPaperCard; import forge.item.PaperCard; -import forge.util.Aggregates; -import forge.util.Lang; -import forge.util.Localizer; -import forge.util.MyRandom; -import forge.util.TextUtil; +import forge.util.*; import forge.util.collect.FCollection; import forge.util.collect.FCollectionView; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.*; +import java.util.Map.Entry; /** *

@@ -240,6 +186,8 @@ public class Player extends GameEntity implements Comparable { private Card monarchEffect; private Card initiativeEffect; private Card blessingEffect; + + private Card radiationEffect; private Card keywordEffect; private Map additionalVotes = Maps.newHashMap(); @@ -986,6 +934,18 @@ public class Player extends GameEntity implements Comparable { getGame().fireEvent(new GameEventPlayerCounters(this, null, 0, 0)); } + public final void addRadCounters(final int num, final Card source, GameEntityCounterTable table) { + addCounter(CounterEnumType.RAD, num, source.getController(), table); + } + public final void removeRadCounters(final int num, final Card source) { + int oldRad = getCounters(CounterEnumType.RAD); + if (oldRad != 0) subtractCounter(CounterEnumType.RAD, num); + + int newRad = getCounters(CounterEnumType.RAD); + if (newRad == 0) removeRadiationEffect(); + if (oldRad != newRad) game.fireEvent(new GameEventPlayerRadiation(this, source, newRad - oldRad)); + } + // TODO Merge These calls into the primary counter calls public final int getPoisonCounters() { return getCounters(CounterEnumType.POISON); @@ -3462,6 +3422,42 @@ public class Player extends GameEntity implements Comparable { } } + public final Card getRadiationEffect() { + return radiationEffect; + } + public void createRadiationEffect(String setCode) { + final PlayerZone com = getZone(ZoneType.Command); + if (radiationEffect == null) { + radiationEffect = new Card(game.nextCardId(), null, game); + radiationEffect.setOwner(this); + radiationEffect.setImmutable(true); + radiationEffect.setImageKey("t:radiation"); + radiationEffect.setName("Radiation"); + radiationEffect.setSetCode(setCode); + String desc = "Mode$ Continuous | Affected$ Card.Self | Description$ At the beginning of your precombat " + + "main phase, if you have any rad counters, mill that many cards. For each nonland card milled " + + "this way, you lose 1 life and a rad counter."; + StaticAbility st = StaticAbility.create(desc, radiationEffect, radiationEffect.getCurrentState(), true); + radiationEffect.addStaticAbility(st); + radiationEffect.updateStateForView(); + } + com.add(radiationEffect); + this.updateZoneForView(com); + } + + public void removeRadiationEffect() { + final PlayerZone com = getZone(ZoneType.Command); + if (radiationEffect != null) { + com.remove(radiationEffect); + radiationEffect = null; + this.updateZoneForView(com); + } + } + + public boolean hasRadiationEffect() { + return radiationEffect != null; + } + public void updateKeywordCardAbilityText() { if (getKeywordCard() == null) return; @@ -3735,7 +3731,7 @@ public class Player extends GameEntity implements Comparable { lki.setZone(c.getZone()); revealCards.add(lki); } - game.getAction().revealTo(revealCards, otherPlayers, Localizer.getInstance().getMessage("lblRevealFaceDownCards")); + game.getAction().revealTo(revealCards, otherPlayers, Localizer.getInstance().getMessage("lblRevealFaceDownCards"), true); } } diff --git a/forge-game/src/main/java/forge/game/player/PlayerController.java b/forge-game/src/main/java/forge/game/player/PlayerController.java index 2f301ca5ffe..e0f967c23d5 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerController.java +++ b/forge-game/src/main/java/forge/game/player/PlayerController.java @@ -163,8 +163,14 @@ public abstract class PlayerController { public final void reveal(CardCollectionView cards, ZoneType zone, Player owner) { reveal(cards, zone, owner, null); } - public abstract void reveal(CardCollectionView cards, ZoneType zone, Player owner, String messagePrefix); - public abstract void reveal(List cards, ZoneType zone, PlayerView owner, String messagePrefix); + public final void reveal(CardCollectionView cards, ZoneType zone, Player owner, String messagePrefix) { + reveal(cards, zone, owner, null, true); + } + public abstract void reveal(CardCollectionView cards, ZoneType zone, Player owner, String messagePrefix, boolean addMsgSuffix); + public final void reveal(List cards, ZoneType zone, PlayerView owner, String messagePrefix) { + reveal(cards, zone, owner, null, true); + } + public abstract void reveal(List cards, ZoneType zone, PlayerView owner, String messagePrefix, boolean addMsgSuffix); /** Shows message to player to reveal chosen cardName, creatureType, number etc. AI must analyze API to understand what that is */ public abstract void notifyOfValue(SpellAbility saSource, GameObject realtedTarget, String value); diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerMilledAll.java b/forge-game/src/main/java/forge/game/trigger/TriggerMilledAll.java index 17f57f1c280..8abbba6cadb 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerMilledAll.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerMilledAll.java @@ -57,6 +57,7 @@ public class TriggerMilledAll extends Trigger { } sa.setTriggeringObject(AbilityKey.Cards, cards); + sa.setTriggeringObject(AbilityKey.Amount, cards.size()); sa.setTriggeringObjectsFrom(runParams, AbilityKey.Player); } diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/views/VField.java b/forge-gui-desktop/src/main/java/forge/screens/match/views/VField.java index 8ad53a1993e..bc4ea268457 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/views/VField.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/views/VField.java @@ -80,6 +80,7 @@ public class VField implements IVDoc { private final FLabel lblEnergy = new FLabel.Builder().fontAlign(SwingConstants.CENTER).fontStyle(Font.BOLD).icon(FSkin.getImage(FSkinProp.IMG_ENERGY)).iconInBackground().build(); private final FLabel lblExperience = new FLabel.Builder().fontAlign(SwingConstants.CENTER).fontStyle(Font.BOLD).icon(FSkin.getImage(FSkinProp.IMG_EXPERIENCE)).iconInBackground().build(); private final FLabel lblTicket = new FLabel.Builder().fontAlign(SwingConstants.CENTER).fontStyle(Font.BOLD).icon(FSkin.getImage(FSkinProp.IMG_TICKET)).iconInBackground().build(); + private final FLabel lblRad = new FLabel.Builder().fontAlign(SwingConstants.CENTER).fontStyle(Font.BOLD).icon(FSkin.getImage(FSkinProp.IMG_RAD)).iconInBackground().build(); private final PhaseIndicator phaseIndicator = new PhaseIndicator(); @@ -115,6 +116,7 @@ public class VField implements IVDoc { lblEnergy.setFocusable(false); lblExperience.setFocusable(false); lblTicket.setFocusable(false); + lblRad.setFocusable(false); avatarArea.setOpaque(false); avatarArea.setBackground(FSkin.getColor(FSkin.Colors.CLR_HOVER)); @@ -236,6 +238,24 @@ public class VField implements IVDoc { avatarArea.add(lblLife, "w 100%!, h 20px!, wrap"); } + private void addLblRad() { + if (lblRad.isShowing() || lblExperience.isShowing() || lblEnergy.isShowing() || lblPoison.isShowing()) { + return; + } + avatarArea.remove(lblLife); + lblLife.setIcon(FSkin.getImage(FSkinProp.ICO_QUEST_LIFE)); + avatarArea.add(lblLife, "w 50%!, h 20px!, split 2"); + avatarArea.add(lblRad, "w 50%!, h 20px!, wrap"); + } + + private void removeLblRad() { + if (!lblRad.isShowing()) { + return; + } + avatarArea.remove(lblRad); + avatarArea.remove(lblLife); + avatarArea.add(lblLife, "w 100%!, h 20px!, wrap"); + } private void addLblExperience() { if (lblExperience.isShowing() || lblEnergy.isShowing() || lblPoison.isShowing()) { @@ -307,11 +327,13 @@ public class VField implements IVDoc { final int poison = player.getCounters(CounterEnumType.POISON); final int energy = player.getCounters(CounterEnumType.ENERGY); final int experience = player.getCounters(CounterEnumType.EXPERIENCE); + final int rad = player.getCounters(CounterEnumType.RAD); final int ticket = player.getCounters(CounterEnumType.TICKET); if (poison > 0) { removeLblEnergy(); removeLblExperience(); + removeLblRad(); removeLblTicket(); addLblPoison(); lblPoison.setText(String.valueOf(poison)); @@ -326,6 +348,7 @@ public class VField implements IVDoc { if (energy > 0) { removeLblExperience(); + removeLblRad(); removeLblTicket(); if (poison == 0) { addLblEnergy(); @@ -336,6 +359,7 @@ public class VField implements IVDoc { } if (experience > 0) { + removeLblRad(); removeLblTicket(); if (poison == 0 && energy == 0) { addLblExperience(); @@ -345,8 +369,18 @@ public class VField implements IVDoc { removeLblExperience(); } - if (ticket > 0) { + if (rad > 0) { + removeLblTicket(); if (poison == 0 && energy == 0 && experience == 0) { + addLblRad(); + lblRad.setText(String.valueOf(rad)); + } + } else { + removeLblRad(); + } + + if (ticket > 0) { + if (poison == 0 && energy == 0 && experience == 0 && rad == 0) { addLblTicket(); lblTicket.setText(String.valueOf(ticket)); } diff --git a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java index 7d4fa94ed6c..3675e447daa 100644 --- a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java +++ b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java @@ -239,12 +239,12 @@ public class PlayerControllerForTests extends PlayerController { } @Override - public void reveal(CardCollectionView cards, ZoneType zone, Player owner, String messagePrefix) { + public void reveal(CardCollectionView cards, ZoneType zone, Player owner, String messagePrefix, boolean addSuffix) { //nothing needs to be done here } @Override - public void reveal(List cards, ZoneType zone, PlayerView owner, String messagePrefix) { + public void reveal(List cards, ZoneType zone, PlayerView owner, String messagePrefix, boolean addSuffix) { //nothing needs to be done here } diff --git a/forge-gui-mobile/src/forge/assets/FSkinImage.java b/forge-gui-mobile/src/forge/assets/FSkinImage.java index d0adce5553d..11274fa3f15 100644 --- a/forge-gui-mobile/src/forge/assets/FSkinImage.java +++ b/forge-gui-mobile/src/forge/assets/FSkinImage.java @@ -146,6 +146,7 @@ public enum FSkinImage implements FImage { COUNTERS_MULTI (FSkinProp.IMG_COUNTERS_MULTI, SourceFile.ICONS), ENERGY (FSkinProp.IMG_ENERGY, SourceFile.ICONS), TICKET (FSkinProp.IMG_TICKET, SourceFile.ICONS), + RAD (FSkinProp.IMG_RAD, SourceFile.ICONS), //Dock Icons SHORTCUTS (FSkinProp.ICO_SHORTCUTS, SourceFile.ICONS), diff --git a/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java b/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java index 6880a13d26a..134bc87ecac 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java @@ -472,6 +472,7 @@ public class VPlayerPanel extends FContainer { private int energyCounters = player.getCounters(CounterEnumType.ENERGY); private int experienceCounters = player.getCounters(CounterEnumType.EXPERIENCE); private int ticketCounters = player.getCounters(CounterEnumType.TICKET); + private int radCounters = player.getCounters(CounterEnumType.RAD); private int manaShards = player.getNumManaShards(); private String lifeStr = String.valueOf(life); @@ -525,7 +526,7 @@ public class VPlayerPanel extends FContainer { adjustHeight = 1; float divider = Gdx.app.getGraphics().getHeight() > 900 ? 1.2f : 2f; if(Forge.altPlayerLayout && !Forge.altZoneTabs && Forge.isLandscapeMode()) { - if (poisonCounters == 0 && energyCounters == 0 && experienceCounters == 0 && ticketCounters ==0 && manaShards == 0) { + if (poisonCounters == 0 && energyCounters == 0 && experienceCounters == 0 && ticketCounters == 0 && radCounters == 0 && manaShards == 0) { g.fillRect(Color.DARK_GRAY, 0, 0, INFO2_FONT.getBounds(lifeStr).width+1, INFO2_FONT.getBounds(lifeStr).height+1); g.drawText(lifeStr, INFO2_FONT, getInfoForeColor().getColor(), 0, 0, getWidth(), getHeight(), false, Align.left, false); } else { @@ -554,6 +555,12 @@ public class VPlayerPanel extends FContainer { g.drawText(String.valueOf(experienceCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight*mod)+2, textWidth, halfHeight, false, Align.left, false); mod+=1; } + if (radCounters > 0) { + g.fillRect(Color.DARK_GRAY, 0, (halfHeight*mod)+2, INFO_FONT.getBounds(String.valueOf(radCounters)).width+halfHeight+1, INFO_FONT.getBounds(String.valueOf(radCounters)).height+1); + g.drawImage(FSkinImage.RAD, 0, (halfHeight*mod)+2, halfHeight, halfHeight); + g.drawText(String.valueOf(radCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight*mod)+2, textWidth, halfHeight, false, Align.left, false); + mod+=1; + } if (ticketCounters > 0) { g.fillRect(Color.DARK_GRAY, 0, (halfHeight*mod)+2, INFO_FONT.getBounds(String.valueOf(ticketCounters)).width+halfHeight+1, INFO_FONT.getBounds(String.valueOf(ticketCounters)).height+1); g.drawImage(FSkinImage.TICKET, 0, (halfHeight*mod)+2, halfHeight, halfHeight); diff --git a/forge-gui/res/adventure/common/custom_cards/death_ring.txt b/forge-gui/res/adventure/common/custom_cards/death_ring.txt index a1f18f40939..45567cc0113 100644 --- a/forge-gui/res/adventure/common/custom_cards/death_ring.txt +++ b/forge-gui/res/adventure/common/custom_cards/death_ring.txt @@ -1,7 +1,7 @@ Name:Death Ring Types:Artifact A:AB$ RepeatEach | Cost$ PayShards<2> | ActivationZone$ Command | SorcerySpeed$ True | IsPresent$ Creature.YouCtrl | RepeatPlayers$ Player | RepeatSubAbility$ DBChooseRandom | SubAbility$ DBPutCounter | SpellDescription$ For each player, put a -1/-1 counter on a random creature with the lowest toughness that player controls. Then if your creature has power less than one, sacrifice it. -SVar:DBChooseRandom:DB$ ChooseCard | AtRandom$ True | Choices$ Creature.leastToughnessControlledByRememberedPlayer | RevealTitle$ OVERRIDE Randomly chosen creature: | Reveal$ True | RememberChosen$ True +SVar:DBChooseRandom:DB$ ChooseCard | AtRandom$ True | Choices$ Creature.leastToughnessControlledByRememberedPlayer | RevealTitle$ Randomly chosen creature: | Reveal$ True | RememberChosen$ True SVar:DBPutCounter:DB$ PutCounter | Defined$ Remembered | CounterType$ M1M1 | SubAbility$ ConditionalSac | StackDescription$ None | SpellDescription$ Activate only if you control a creature and only as a sorcery. SVar:ConditionalSac:DB$ SacrificeAll | Defined$ Remembered.powerLT1+YouCtrl | SubAbility$ DBCleanup | SpellDescription$ If your creature has power less than one, sacrifice it. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearChosenCard$ True diff --git a/forge-gui/res/cardsfolder/c/call_to_the_void.txt b/forge-gui/res/cardsfolder/c/call_to_the_void.txt index 1d3cdcd9124..e055a213c45 100644 --- a/forge-gui/res/cardsfolder/c/call_to_the_void.txt +++ b/forge-gui/res/cardsfolder/c/call_to_the_void.txt @@ -1,6 +1,6 @@ Name:Call to the Void ManaCost:4 B Types:Sorcery -A:SP$ ChooseCard | Defined$ Player | Choices$ Creature | SecretlyChoose$ True | Amount$ 1 | ControlAndNot$ True | ChoiceTitle$ Secretly choose a creature | Reveal$ True | RevealTitle$ OVERRIDE Chosen creatures. They will be destroyed. | SubAbility$ DBDestroyChosen | Mandatory$ True +A:SP$ ChooseCard | Defined$ Player | Choices$ Creature | SecretlyChoose$ True | Amount$ 1 | ControlAndNot$ True | ChoiceTitle$ Secretly choose a creature | Reveal$ True | RevealTitle$ Chosen creatures. They will be destroyed. | SubAbility$ DBDestroyChosen | Mandatory$ True SVar:DBDestroyChosen:DB$ DestroyAll | ValidCards$ Creature.ChosenCard Oracle:Each player secretly chooses a creature they control and a creature they don't control. Then those choices are revealed. Destroy each creature chosen this way. diff --git a/forge-gui/res/cardsfolder/c/capricious_efreet.txt b/forge-gui/res/cardsfolder/c/capricious_efreet.txt index a2bc26d4fd2..502e8fe01f4 100644 --- a/forge-gui/res/cardsfolder/c/capricious_efreet.txt +++ b/forge-gui/res/cardsfolder/c/capricious_efreet.txt @@ -5,7 +5,7 @@ PT:6/4 T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ At the beginning of your upkeep, choose target nonland permanent you control and up to two target nonland permanents you don't control. Destroy one of them at random. SVar:TrigPump:DB$ Pump | ValidTgts$ Permanent.nonLand+YouCtrl | TgtPrompt$ Select target nonland permanent you control | Mandatory$ True | IsCurse$ True | SubAbility$ DBPump SVar:DBPump:DB$ Pump | ValidTgts$ Permanent.nonLand+YouDontCtrl | TgtPrompt$ Select up to two target nonland permanents you don't control | TargetMin$ 0 | TargetMax$ 2 | IsCurse$ True | SubAbility$ DBChooseRandom -SVar:DBChooseRandom:DB$ ChooseCard | AtRandom$ True | Reveal$ True | RevealTitle$ OVERRIDE Randomly chosen permanent: | DefinedCards$ TargetedCard | SubAbility$ DBDestroy +SVar:DBChooseRandom:DB$ ChooseCard | AtRandom$ True | Reveal$ True | RevealTitle$ Randomly chosen permanent: | DefinedCards$ TargetedCard | SubAbility$ DBDestroy SVar:DBDestroy:DB$ Destroy | Defined$ ChosenCard | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True Oracle:At the beginning of your upkeep, choose target nonland permanent you control and up to two target nonland permanents you don't control. Destroy one of them at random. diff --git a/forge-gui/res/cardsfolder/m/maelstrom_archangel_avatar.txt b/forge-gui/res/cardsfolder/m/maelstrom_archangel_avatar.txt index 94762de168a..21354435ce7 100644 --- a/forge-gui/res/cardsfolder/m/maelstrom_archangel_avatar.txt +++ b/forge-gui/res/cardsfolder/m/maelstrom_archangel_avatar.txt @@ -14,7 +14,7 @@ SVar:TrigRepeatEach:DB$ RepeatEach | UseImprinted$ True | RepeatCards$ Card.Note SVar:DBCopyRandom:DB$ NameCard | Defined$ You | AtRandom$ True | ValidCards$ Card.ManaCost=Imprinted | SubAbility$ DBMake SVar:DBMake:DB$ MakeCard | Name$ ChosenName | Zone$ None | RememberMade$ True | SubAbility$ DBClearNamed SVar:DBClearNamed:DB$ Cleanup | ClearNamedCard$ True -SVar:DBReveal:DB$ Reveal | Defined$ You | RevealDefined$ Remembered | RevealToAll$ True | RevealTitle$ OVERRIDE Cards created by Maelstrom Archangel Avatar | SubAbility$ DBChoose +SVar:DBReveal:DB$ Reveal | Defined$ You | RevealDefined$ Remembered | RevealToAll$ True | RevealTitle$ Cards created by Maelstrom Archangel Avatar | SubAbility$ DBChoose SVar:DBChoose:DB$ ChooseCard | UnlessCost$ 3 | UnlessPayer$ You | UnlessSwitched$ True | Choices$ Card.IsRemembered | ChoiceZone$ None | SubAbility$ DBCopyPerm | SpellDescription$ You may pay {3}. If you do, choose one of those copies. If a copy of a permanent card is chosen, you may create a token that's a copy of that card. If a copy of an instant or sorcery card is chosen, you may cast the copy without paying its mana cost. SVar:DBCopyPerm:DB$ CopyPermanent | Optional$ True | Defined$ ChosenCard | ConditionDefined$ ChosenCard | ConditionPresent$ Permanent | ConditionCompare$ GE1 | SubAbility$ DBCastSp SVar:DBCastSp:DB$ Play | Defined$ ChosenCard | ZoneRegardless$ True | Optional$ True | WithoutManaCost$ True | ValidSA$ Spell | ConditionDefined$ ChosenCard | ConditionPresent$ Permanent | ConditionCompare$ EQ0 | SubAbility$ DBCleanup diff --git a/forge-gui/res/cardsfolder/upcoming/feral_ghoul.txt b/forge-gui/res/cardsfolder/upcoming/feral_ghoul.txt new file mode 100644 index 00000000000..a717a90e89e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/feral_ghoul.txt @@ -0,0 +1,12 @@ +Name:Feral Ghoul +ManaCost:2 B +Types:Creature Zombie Mutant +PT:2/2 +K:Menace +T:Mode$ ChangesZone | ValidCard$ Creature.Other+YouCtrl | Origin$ Battlefield | Destination$ Graveyard | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever another creature you control dies, put a +1/+1 counter on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1 +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigRadiation | TriggerDescription$ When CARDNAME dies, each opponent gets a number of rad counters equal to its power. +SVar:TrigRadiation:DB$ Radiation | Defined$ Opponent | Add$ TriggeredCard$CardPower +DeckHas:Ability$Counters +DeckHints:Ability$Sacrifice +Oracle:Menace\nWhenever another creature you control dies, put a +1/+1 counter on Feral Ghoul.\nWhen Feral Ghoul dies, each opponent gets a number of rad counters equal to its power. diff --git a/forge-gui/res/cardsfolder/upcoming/the_wise_mothman.txt b/forge-gui/res/cardsfolder/upcoming/the_wise_mothman.txt new file mode 100644 index 00000000000..bc40d9c3720 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_wise_mothman.txt @@ -0,0 +1,13 @@ +Name:The Wise Mothman +ManaCost:1 B G U +Types:Legendary Creature Insect Mutant +PT:3/3 +K:Flying +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigRadiation | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, each player gets a rad counter. +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigRadiation | TriggerZones$ Battlefield | Secondary$ True | TriggerDescription$ Whenever CARDNAME enters the battlefield attacks, each player gets a rad counter. +SVar:TrigRadiation:DB$ Radiation | Defined$ Player | Add$ 1 +T:Mode$ MilledAll | ValidPlayer$ Player | ValidCard$ Card.nonLand | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever one or more nonland cards are milled, put a +1/+1 counter on each of up to X target creatures, where X is the number of nonland cards milled this way. +SVar:TrigPutCounter:DB$ PutCounter | CounterNum$ 1 | CounterType$ P1P1 | TargetMin$ 0 | TargetMax$ TriggerCount$Amount | ValidTgts$ Creature | TgtPrompt$ Select up to X target creatures +DeckHints:Ability$Mill +DeckHas:Ability$Counters|Mill +Oracle:Flying\nWhenever The Wise Mothman enters the battlefield or attacks, each player gets a rad counter.\nWhenever one or more nonland cards are milled, put a +1/+1 counter on each of up to X target creatures, where X is the number of nonland cards milled this way. diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 0d723941640..acfe7c194dc 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -1507,6 +1507,8 @@ lblCombat=Kampf lblNonCombat=Nicht-Kampf lblLogSourceDealsNDamageOfTypeToDest={0} verursacht {1} {2} Schaden bei {3}{4}. lblLogPlayerReceivesNPosionCounterFrom={0} hat {1} Giftmarken von {2} erhalten. +lblLogPlayerRadiation={0} erhält {1} von {2}. +lblLogPlayerRadRemove={0} entfernt {1} aufgrund von {2}. lblLogPlayerAssignedAttackerToAttackTarget={0} läßt {1} {2} angreifen. lblLogPlayerDidntBlockAttacker={0} hat {1} nicht geblockt. lblLogPlayerAssignedBlockerToBlockAttacker={0} läßt {1} {2} blocken. @@ -2035,6 +2037,8 @@ lblChooseCardToManifest=Wähle zu manifestierende Karte lblChooseCardToMeld=Wähle Karte zum Verschmelzen mit #MillEffect.java lblDoYouWantToMill=Willst du {0} millen?{1} +lblMilledToZone=(zu {0}) +lblMilledCards=Karten {0} gefräst #MultiplePilesEffect.java lblChooseCardsInTargetPile=Wähle Karten in Stapel {0}? #MutateEffect.java diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index c54af957e16..ffe937e0e8f 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -1512,6 +1512,8 @@ lblCombat=combat lblNonCombat=non-combat lblLogSourceDealsNDamageOfTypeToDest={0} deals {1} {2} damage to {3}{4}. lblLogPlayerReceivesNPosionCounterFrom={0} receives {1} poison counter from {2} +lblLogPlayerRadiation={0} gets {1} from {2}. +lblLogPlayerRadRemove={0} removes {1} due to {2}. lblLogPlayerAssignedAttackerToAttackTarget={0} assigned {1} to attack {2}. lblLogPlayerDidntBlockAttacker={0} didn''t block {1}. lblLogPlayerAssignedBlockerToBlockAttacker={0} assigned {1} to block {2}. @@ -2040,6 +2042,8 @@ lblChooseCardToManifest=Choose cards to manifest lblChooseCardToMeld=Choose card to meld with #MillEffect.java lblDoYouWantToMill=Do you want to mill {0}?{1} +lblMilledToZone=(to {0}) +lblMilledCards=Cards {0} milled #MultiplePilesEffect.java lblChooseCardsInTargetPile=Choose cards in Pile {0}? #MutateEffect.java diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index f7d5fbc426c..3a0b3a2dfa1 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -1508,6 +1508,8 @@ lblCombat=combate lblNonCombat=no combate lblLogSourceDealsNDamageOfTypeToDest={0} hace {1} punto(s) de daño de {2} a {3}{4}. lblLogPlayerReceivesNPosionCounterFrom={0} recibe {1} contador de veneno de {2} +lblLogPlayerRadiation={0} obtiene {1} de {2}. +lblLogPlayerRadRemove={0} elimina {1} debido a {2}. lblLogPlayerAssignedAttackerToAttackTarget={0} asigna a {1} para atacar a {2}. lblLogPlayerDidntBlockAttacker={0} no bloquea a {1}. lblLogPlayerAssignedBlockerToBlockAttacker={0} asigna {1} para bloquear a {2}. @@ -2036,6 +2038,8 @@ lblChooseCardToManifest=Elige las cartas para manifestar lblChooseCardToMeld=Elige una carta para fundirla con #MillEffect.java lblDoYouWantToMill=¿Quieres miller {0}?{1} +lblMilledToZone=(a {0}) +lblMilledCards=Tarjetas {0} fresadas #MultiplePilesEffect.java lblChooseCardsInTargetPile=¿Elegir las cartas en la Pila {0}? #MutateEffect.java diff --git a/forge-gui/res/languages/fr-FR.properties b/forge-gui/res/languages/fr-FR.properties index f92a366379b..d7e2164bf94 100644 --- a/forge-gui/res/languages/fr-FR.properties +++ b/forge-gui/res/languages/fr-FR.properties @@ -1511,6 +1511,8 @@ lblCombat=combat lblNonCombat=sans combat lblLogSourceDealsNDamageOfTypeToDest={0} inflige {1} {2} dégâts à {3}{4}. lblLogPlayerReceivesNPosionCounterFrom={0} reçoit {1} marqueur de poison de {2} +lblLogPlayerRadiation={0} obtient {1} de {2}. +lblLogPlayerRadRemove={0} supprime {1} en raison de {2}. lblLogPlayerAssignedAttackerToAttackTarget={0} a assigné {1} pour attaquer {2}. lblLogPlayerDidntBlockAttacker={0} n''a pas bloqué {1}. lblLogPlayerAssignedBlockerToBlockAttacker={0} a assigné {1} au bloc {2}. @@ -2040,6 +2042,8 @@ lblChooseCardToManifest=Choisir les cartes à manifester lblChooseCardToMeld=Choisissez la carte avec laquelle fusionner #MillEffect.java lblDoYouWantToMill=Voulez-vous meuler {0}?{1} +lblMilledToZone=(à {0}) +lblMilledCards=Cartes {0} fraisées #MultiplePilesEffect.java lblChooseCardsInTargetPile=Choisir les cartes dans la pile {0} ? #MutateEffect.java diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index c97c7f83fb7..3e44164bf2b 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -1508,6 +1508,8 @@ lblCombat=da combattimento lblNonCombat=non da combattimento lblLogSourceDealsNDamageOfTypeToDest={0} infligge {1} {2} danno/i a {3}{4}. lblLogPlayerReceivesNPosionCounterFrom={0} riceve {1} segnalino/i veleno da {2} +lblLogPlayerRadiation={0} ottiene {1} da {2}. +lblLogPlayerRadRemove={0} rimuove {1} a causa di {2}. lblLogPlayerAssignedAttackerToAttackTarget={0} ha dichiarato di attaccare {2} con {1}. lblLogPlayerDidntBlockAttacker={0} non blocca {1}. lblLogPlayerAssignedBlockerToBlockAttacker={0} ha dichiarato di bloccare {2} con {1}. @@ -2036,6 +2038,8 @@ lblChooseCardToManifest=Scegli le carte da manifestare lblChooseCardToMeld=Scegli le carte da combinare #MillEffect.java lblDoYouWantToMill=Vuoi macinare {0}?{1} +lblMilledToZone=(a {0}) +lblMilledCards=Carte {0} macinate #MultiplePilesEffect.java lblChooseCardsInTargetPile=Scegli le carte nella Pila {0}? #MutateEffect.java diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index 4afa9ad1447..9ddb9041e9c 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -1509,6 +1509,8 @@ lblCombat=戦闘 lblNonCombat=非戦闘 lblLogSourceDealsNDamageOfTypeToDest={0}は {3}{4}に対して{1} {2}点のダメージを与えました。 lblLogPlayerReceivesNPosionCounterFrom={0}は {2}から {1}個毒カウンターを貰いました。 +lblLogPlayerRadiation={0}{2}から{1}を得ます。 +lblLogPlayerRadRemove={0}は、{2}による{1}を削除します。 lblLogPlayerAssignedAttackerToAttackTarget={0}は {1}を {2}の攻撃に指定しました。 lblLogPlayerDidntBlockAttacker={0}が {1}をブロックしていません。 lblLogPlayerAssignedBlockerToBlockAttacker={0}は {1}を {2}のブロックに指定しました。 @@ -2035,6 +2037,8 @@ lblChooseCardToManifest=予示するカードを選ぶ lblChooseCardToMeld=合体するカードを選ぶ #MillEffect.java lblDoYouWantToMill={0}を製粉したいですか?{1} +lblMilledToZone=({0} まで) +lblMilledCards=カード{0}ミリング #MultiplePilesEffect.java lblChooseCardsInTargetPile={0}番目の束からカードを選びますか? #MutateEffect.java diff --git a/forge-gui/res/languages/pt-BR.properties b/forge-gui/res/languages/pt-BR.properties index 419931eaae9..a798cd2e484 100644 --- a/forge-gui/res/languages/pt-BR.properties +++ b/forge-gui/res/languages/pt-BR.properties @@ -1544,6 +1544,8 @@ lblCombat=combate lblNonCombat=não-combate lblLogSourceDealsNDamageOfTypeToDest={0} causa {1} {2} de dano a {3}{4}. lblLogPlayerReceivesNPosionCounterFrom={0} recebe {1} contador de veneno de {2} +lblLogPlayerRadiation={0} obtém {1} de {2}. +lblLogPlayerRadRemove={0} remove {1} devido a {2}. lblLogPlayerAssignedAttackerToAttackTarget={0} atribuiu {1} para atacar {2}. lblLogPlayerDidntBlockAttacker={0} não bloqueou {1}. lblLogPlayerAssignedBlockerToBlockAttacker={0} atribuiu {1} para bloquear {2}. @@ -2097,6 +2099,8 @@ lblChooseCardToManifest=Escolha cartas para manifestar lblChooseCardToMeld=Escolha a carta para fundir com #MillEffect.java lblDoYouWantToMill=Você quer moer {0}?{1} +lblMilledToZone=(para {0}) +lblMilledCards=Cartões {0} fresados #MultiplePilesEffect.java lblChooseCardsInTargetPile=Escolha cartas na pilha {0}? #MutateEffect.java diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index cc15f71ba30..4516d13c1a0 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -1512,6 +1512,8 @@ lblCombat=战斗 lblNonCombat=非战斗 lblLogSourceDealsNDamageOfTypeToDest={0}对{3}造成{1}点{2}伤害{4}。 lblLogPlayerReceivesNPosionCounterFrom={0}获得来自{2}的{1}个中毒指示物 +lblLogPlayerRadiation={0}从{2}那里得到{1}。 +lblLogPlayerRadRemove={0}消除了由于{2}而产生的{1}。 lblLogPlayerAssignedAttackerToAttackTarget={0}已分配{1}进攻{2}。 lblLogPlayerDidntBlockAttacker={0}没有阻挡{1}. lblLogPlayerAssignedBlockerToBlockAttacker={0}已分配{1}对{2}进行阻挡。 @@ -2040,6 +2042,8 @@ lblChooseCardToManifest=选择要显化的牌 lblChooseCardToMeld=选择要融合的牌 #MillEffect.java lblDoYouWantToMill=您想铣削{0}吗?{1} +lblMilledToZone=(到{0}) +lblMilledCards=卡{0}铣削 #MultiplePilesEffect.java lblChooseCardsInTargetPile=选择堆{0}中的牌? #MutateEffect.java diff --git a/forge-gui/res/skins/default/sprite_icons.png b/forge-gui/res/skins/default/sprite_icons.png index c4419564b5d..774067843b0 100644 Binary files a/forge-gui/res/skins/default/sprite_icons.png and b/forge-gui/res/skins/default/sprite_icons.png differ diff --git a/forge-gui/src/main/java/forge/gui/control/FControlGameEventHandler.java b/forge-gui/src/main/java/forge/gui/control/FControlGameEventHandler.java index 83610d04631..ae678d65575 100644 --- a/forge-gui/src/main/java/forge/gui/control/FControlGameEventHandler.java +++ b/forge-gui/src/main/java/forge/gui/control/FControlGameEventHandler.java @@ -505,6 +505,11 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { return processPlayer(event.receiver, livesUpdate); } + @Override + public Void visit(final GameEventPlayerRadiation event) { + return processPlayer(event.receiver, livesUpdate); + } + @Override public Void visit(final GameEventPlayerDamaged event) { return processEvent(); diff --git a/forge-gui/src/main/java/forge/localinstance/skin/FSkinProp.java b/forge-gui/src/main/java/forge/localinstance/skin/FSkinProp.java index 015d8212584..83487cc115e 100644 --- a/forge-gui/src/main/java/forge/localinstance/skin/FSkinProp.java +++ b/forge-gui/src/main/java/forge/localinstance/skin/FSkinProp.java @@ -90,6 +90,7 @@ public enum FSkinProp { IMG_ZONE_POISON (new int[] {320, 80, 40, 40}, PropType.IMAGE), IMG_TICKET (new int[] {360, 80, 40, 40}, PropType.IMAGE), + IMG_RAD (new int[] {360, 120, 40, 40}, PropType.IMAGE), //mana images IMG_MANA_B (new int[] {166, 2, 80, 80}, PropType.MANAICONS), diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 68c8ff53efd..ea25a4ba94b 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -939,22 +939,20 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont } @Override - public void reveal(final CardCollectionView cards, final ZoneType zone, final Player owner, String message) { - reveal(cards, zone, PlayerView.get(owner), message); + public void reveal(final CardCollectionView cards, final ZoneType zone, final Player owner, String message, boolean addSuffix) { + reveal(cards, zone, PlayerView.get(owner), message, addSuffix); } @Override - public void reveal(final List cards, final ZoneType zone, final PlayerView owner, String message) { - reveal(getCardList(cards), zone, owner, message); + public void reveal(final List cards, final ZoneType zone, final PlayerView owner, String message, boolean addSuffix) { + reveal(getCardList(cards), zone, owner, message, addSuffix); } - protected void reveal(final CardCollectionView cards, final ZoneType zone, final PlayerView owner, String message) { + protected void reveal(final CardCollectionView cards, final ZoneType zone, final PlayerView owner, String message, boolean addSuffix) { if (StringUtils.isBlank(message)) { message = localizer.getMessage("lblLookCardInPlayerZone", "{player's}", zone.getTranslatedName().toLowerCase()); - } else if (message.startsWith("OVERRIDE")) { - message = message.substring(9); } else { - message += " " + localizer.getMessage("lblPlayerZone", "{player's}", zone.getTranslatedName().toLowerCase()); + if (addSuffix) message += " " + localizer.getMessage("lblPlayerZone", "{player's}", zone.getTranslatedName().toLowerCase()); } final String fm = MessageUtil.formatMessage(message, getLocalPlayerView(), owner); if (!cards.isEmpty()) {