diff --git a/.gitattributes b/.gitattributes index 9c6da4a8af6..9ef04c3f889 100644 --- a/.gitattributes +++ b/.gitattributes @@ -14240,7 +14240,8 @@ src/main/java/forge/game/event/GameEventLifeLoss.java -text src/main/java/forge/game/event/GameEventManaBurn.java -text src/main/java/forge/game/event/GameEventMulligan.java -text src/main/java/forge/game/event/GameEventPlayerControl.java -text -src/main/java/forge/game/event/GameEventPoisonCounter.java -text +src/main/java/forge/game/event/GameEventPlayerDamaged.java -text +src/main/java/forge/game/event/GameEventPlayerPoisoned.java -text src/main/java/forge/game/event/GameEventShuffle.java -text src/main/java/forge/game/event/GameEventSpellResolved.java -text src/main/java/forge/game/event/GameEventTokenCreated.java -text diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index 2d2878a716b..0cd29d4832d 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -65,6 +65,7 @@ import forge.card.trigger.ZCTrigger; import forge.game.Game; import forge.game.GlobalRuleChange; import forge.game.event.GameEventCardDamaged; +import forge.game.event.GameEventCardDamaged.DamageType; import forge.game.event.GameEventCardEquipped; import forge.game.event.GameEventCounterAdded; import forge.game.event.GameEventCounterRemoved; @@ -7354,62 +7355,63 @@ public class Card extends GameEntity implements Comparable { runParams.put("IsCombatDamage", isCombat); getGame().getTriggerHandler().runTrigger(TriggerType.DamageDone, runParams, false); - String additionalLog = ""; + GameEventCardDamaged.DamageType damageType = DamageType.Normal; if (this.isPlaneswalker()) { this.subtractCounter(CounterType.LOYALTY, damageToAdd); - additionalLog = String.format("(Removing %d Loyalty Counters)", damageToAdd); + damageType = DamageType.LoyaltyLoss; } else { final Game game = source.getGame(); - final String s = this + " - destroy"; + final String s = this + " - destroy"; - final int amount = this.getAmountOfKeyword("When CARDNAME is dealt damage, destroy it."); - if(amount > 0) { - final Ability abDestroy = new Ability(source, ManaCost.ZERO){ - @Override public void resolve() { game.getAction().destroy(Card.this, this); } - }; - abDestroy.setStackDescription(s + ", it cannot be regenerated."); - - for (int i = 0; i < amount; i++) { - game.getStack().addSimultaneousStackEntry(abDestroy); - } + final int amount = this.getAmountOfKeyword("When CARDNAME is dealt damage, destroy it."); + if(amount > 0) { + final Ability abDestroy = new Ability(source, ManaCost.ZERO){ + @Override public void resolve() { game.getAction().destroy(Card.this, this); } + }; + abDestroy.setStackDescription(s + ", it cannot be regenerated."); + + for (int i = 0; i < amount; i++) { + game.getStack().addSimultaneousStackEntry(abDestroy); } + } + + final int amount2 = this.getAmountOfKeyword("When CARDNAME is dealt damage, destroy it. It can't be regenerated."); + if( amount2 > 0 ) { + final Ability abDestoryNoRegen = new Ability(source, ManaCost.ZERO){ + @Override public void resolve() { game.getAction().destroyNoRegeneration(Card.this, this); } + }; + abDestoryNoRegen.setStackDescription(s); + + for (int i = 0; i < amount2; i++) { + game.getStack().addSimultaneousStackEntry(abDestoryNoRegen); + } + } + + - final int amount2 = this.getAmountOfKeyword("When CARDNAME is dealt damage, destroy it. It can't be regenerated."); - if( amount2 > 0 ) { - final Ability abDestoryNoRegen = new Ability(source, ManaCost.ZERO){ - @Override public void resolve() { game.getAction().destroyNoRegeneration(Card.this, this); } - }; - abDestoryNoRegen.setStackDescription(s); - for (int i = 0; i < amount2; i++) { - game.getStack().addSimultaneousStackEntry(abDestoryNoRegen); - } - } - - boolean wither = (getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.alwaysWither) || source.hasKeyword("Wither") || source.hasKeyword("Infect")); if (this.isInPlay()) { if (wither) { this.addCounter(CounterType.M1M1, damageToAdd, true); - additionalLog = "(As -1/-1 Counters)"; + damageType = DamageType.M1M1Counters; } else this.damage += damageToAdd; } if (source.hasKeyword("Deathtouch") && this.isCreature()) { getGame().getAction().destroy(this, null); - additionalLog = "(Deathtouch)"; + damageType = DamageType.Deathtouch; } // Play the Damage sound - game.fireEvent(new GameEventCardDamaged()); + game.fireEvent(new GameEventCardDamaged(this, source, damageToAdd, damageType)); } - getGame().getGameLog().add(GameLogEntryType.DAMAGE, String.format("Dealing %d damage to %s. %s", damageToAdd, this.getName(), additionalLog)); return true; } diff --git a/src/main/java/forge/GameLogEntryType.java b/src/main/java/forge/GameLogEntryType.java index 1882eff7ffd..ffd289413d4 100644 --- a/src/main/java/forge/GameLogEntryType.java +++ b/src/main/java/forge/GameLogEntryType.java @@ -13,7 +13,6 @@ public enum GameLogEntryType { STACK_RESOLVE("Resolve stack"), STACK_ADD("Add to stack"), DAMAGE("Damage"), - DAMAGE_POISON("Poison"), MANA("Mana"), PHASE("Phase"); diff --git a/src/main/java/forge/GameLogFormatter.java b/src/main/java/forge/GameLogFormatter.java index 12e37486444..e4ea2c60431 100644 --- a/src/main/java/forge/GameLogFormatter.java +++ b/src/main/java/forge/GameLogFormatter.java @@ -7,6 +7,10 @@ import java.util.Map.Entry; import com.google.common.eventbus.Subscribe; import forge.game.GameOutcome; +import forge.game.event.GameEventCardDamaged; +import forge.game.event.GameEventCardDamaged.DamageType; +import forge.game.event.GameEventPlayerDamaged; +import forge.game.event.GameEventPlayerPoisoned; import forge.game.event.IGameEventVisitor; import forge.game.event.GameEventDuelOutcome; import forge.game.event.GameEvent; @@ -86,10 +90,34 @@ public class GameLogFormatter extends IGameEventVisitor.Base { Player p = ev.playerTurn; return new GameLogEntry(GameLogEntryType.PHASE, ev.phaseDesc + Lang.getPossesive(p.getName()) + " " + ev.phase.nameForUi); } - - + @Override + public GameLogEntry visit(GameEventCardDamaged event) { + String additionalLog = ""; + if( event.type == DamageType.Deathtouch ) additionalLog = " (Deathtouch)"; + if( event.type == DamageType.M1M1Counters ) additionalLog = " (As -1/-1 Counters)"; + if( event.type == DamageType.LoyaltyLoss ) additionalLog = " (Removing " + Lang.nounWithAmount(event.amount, "loyalty counter") + ")"; + + String message = String.format("%s deals %d damage%s to %s.", event.source, event.amount, event.damaged, additionalLog); + return new GameLogEntry(GameLogEntryType.DAMAGE, message); + } + + + @Override + public GameLogEntry visit(GameEventPlayerDamaged ev) { + String extra = ev.infect ? " (as poison counters)" : ""; + String message = String.format("%s deals %d %s damage to %s%s.", ev.source, ev.amount, ev.combat ? "combat" : "non-combat", ev.target, extra ); + return new GameLogEntry(GameLogEntryType.DAMAGE, message); + } + + @Override + public GameLogEntry visit(GameEventPlayerPoisoned ev) { + String message = String.format("%s receives %s from %s", ev.receiver, Lang.nounWithAmount(ev.amount, "posion counter"), ev.source); + return new GameLogEntry(GameLogEntryType.DAMAGE, message); + } + + static GameLogEntry describeAttack(final Combat combat) { final StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/forge/deck/DeckgenUtil.java b/src/main/java/forge/deck/DeckgenUtil.java index e78d1b7886b..76ff3538cec 100644 --- a/src/main/java/forge/deck/DeckgenUtil.java +++ b/src/main/java/forge/deck/DeckgenUtil.java @@ -24,6 +24,7 @@ import forge.deck.generate.GenerateThemeDeck; import forge.item.CardDb; import forge.item.CardPrinted; import forge.item.ItemPoolView; +import forge.item.PreconDeck; import forge.quest.QuestController; import forge.quest.QuestEvent; import forge.quest.QuestEventChallenge; @@ -32,6 +33,7 @@ import forge.util.Aggregates; import forge.util.Lang; import forge.util.MyRandom; import forge.util.storage.IStorage; +import forge.util.storage.IStorageView; /** * Utility collection for various types of decks. @@ -47,7 +49,8 @@ public class DeckgenUtil { COLORS, THEMES, CUSTOM, - QUESTEVENTS + QUESTEVENTS, + PRECON } /** @@ -105,6 +108,10 @@ public class DeckgenUtil { public static Deck getConstructedDeck(final String[] selection) { return Singletons.getModel().getDecks().getConstructed().get(selection[0]); } + + public static Deck getPreconDeck(String[] selection) { + return QuestController.getPrecons().get(selection[0]).getDeck(); + } public static QuestEvent getQuestEvent(final String name) { QuestController qCtrl = Singletons.getModel().getQuest(); @@ -147,6 +154,13 @@ public class DeckgenUtil { return allDecks.get(name); } + public static Deck getRandomPreconDeck() { + final IStorageView allDecks = QuestController.getPrecons(); + final int rand = (int) (Math.floor(Math.random() * allDecks.size())); + final String name = allDecks.getNames().toArray(new String[0])[rand]; + return allDecks.get(name).getDeck(); + } + /** @return {@link forge.deck.Deck} */ public static Deck getRandomQuestDeck() { final List allQuestDecks = new ArrayList(); diff --git a/src/main/java/forge/game/event/GameEventCardDamaged.java b/src/main/java/forge/game/event/GameEventCardDamaged.java index 8a88a28e305..f3080005819 100644 --- a/src/main/java/forge/game/event/GameEventCardDamaged.java +++ b/src/main/java/forge/game/event/GameEventCardDamaged.java @@ -1,7 +1,28 @@ package forge.game.event; +import forge.Card; + public class GameEventCardDamaged extends GameEvent { + public enum DamageType { + Normal, + M1M1Counters, + Deathtouch, + LoyaltyLoss; + } + + public final Card damaged; + public final Card source; + public final int amount; + public final DamageType type; + + public GameEventCardDamaged(Card card, Card src, int damageToAdd, DamageType damageType) { + damaged = card; + source = src; + amount = damageToAdd; + type = damageType; + } + @Override public T visit(IGameEventVisitor visitor) { return visitor.visit(this); diff --git a/src/main/java/forge/game/event/GameEventPlayerDamaged.java b/src/main/java/forge/game/event/GameEventPlayerDamaged.java new file mode 100644 index 00000000000..47e94cedc2e --- /dev/null +++ b/src/main/java/forge/game/event/GameEventPlayerDamaged.java @@ -0,0 +1,42 @@ +package forge.game.event; + +import forge.Card; +import forge.game.player.Player; + + +/** + * TODO: Write javadoc for this type. + * + */ +public class GameEventPlayerDamaged extends GameEvent { + public final Player target; + public final Card source; + public final int amount; + final public boolean infect; + public final boolean combat; + + /** + * TODO: Write javadoc for Constructor. + * @param player + * @param source + * @param amount + * @param isCombat + * @param infect + */ + public GameEventPlayerDamaged(Player player, Card source, int amount, boolean isCombat, boolean infect) { + target = player; + this.source = source; + this.amount = amount; + combat = isCombat; + this.infect = infect; + } + + /* (non-Javadoc) + * @see forge.game.event.GameEvent#visit(forge.game.event.IGameEventVisitor) + */ + @Override + public T visit(IGameEventVisitor visitor) { + return visitor.visit(this); + } + +} diff --git a/src/main/java/forge/game/event/GameEventPlayerPoisoned.java b/src/main/java/forge/game/event/GameEventPlayerPoisoned.java new file mode 100644 index 00000000000..97ec402c6d0 --- /dev/null +++ b/src/main/java/forge/game/event/GameEventPlayerPoisoned.java @@ -0,0 +1,30 @@ +package forge.game.event; + +import forge.Card; +import forge.game.player.Player; + +/** + * + * + */ +public class GameEventPlayerPoisoned extends GameEvent { + public final Player receiver; + public final Card source; + public final int amount; + + public GameEventPlayerPoisoned(Player recv, Card src, int n) { + receiver = recv; + source = src; + amount = n; + } + + public GameEventPlayerPoisoned(Player recv, Card src) { + this(recv, src, 1); + } + + + @Override + public T visit(IGameEventVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/forge/game/event/GameEventPoisonCounter.java b/src/main/java/forge/game/event/GameEventPoisonCounter.java deleted file mode 100644 index 833f5a68438..00000000000 --- a/src/main/java/forge/game/event/GameEventPoisonCounter.java +++ /dev/null @@ -1,30 +0,0 @@ -package forge.game.event; - -import forge.Card; -import forge.game.player.Player; - -/** - * - * - */ -public class GameEventPoisonCounter extends GameEvent { - public final Player Receiver; - public final Card Source; - public final int Amount; - - public GameEventPoisonCounter(Player recv, Card src, int n) { - Receiver = recv; - Source = src; - Amount = n; - } - - public GameEventPoisonCounter(Player recv, Card src) { - this(recv, src, 1); - } - - - @Override - public T visit(IGameEventVisitor visitor) { - return visitor.visit(this); - } -} diff --git a/src/main/java/forge/game/event/IGameEventVisitor.java b/src/main/java/forge/game/event/IGameEventVisitor.java index 02e37d85e31..0e2da2ad7d9 100644 --- a/src/main/java/forge/game/event/IGameEventVisitor.java +++ b/src/main/java/forge/game/event/IGameEventVisitor.java @@ -27,7 +27,8 @@ public interface IGameEventVisitor { T visit(GameEventManaBurn event); T visit(GameEventMulligan event); T visit(GameEventPlayerControl event); - T visit(GameEventPoisonCounter event); + T visit(GameEventPlayerDamaged gameEventPlayerDamaged); + T visit(GameEventPlayerPoisoned event); T visit(GameEventShuffle event); T visit(GameEventSpellResolved event); T visit(GameEventTokenCreated event); @@ -58,11 +59,12 @@ public interface IGameEventVisitor { public T visit(GameEventManaBurn event) { return null; } public T visit(GameEventMulligan event) { return null; } public T visit(GameEventPlayerControl event) { return null; } - public T visit(GameEventPoisonCounter event) { return null; } + public T visit(GameEventPlayerPoisoned event) { return null; } public T visit(GameEventShuffle event) { return null; } public T visit(GameEventSpellResolved event) { return null; } public T visit(GameEventTokenCreated event) { return null; } public T visit(GameEventTurnPhase event) { return null; } + public T visit(GameEventPlayerDamaged event) { return null; } } } diff --git a/src/main/java/forge/game/player/Player.java b/src/main/java/forge/game/player/Player.java index 4204793c0fc..94da30ffc99 100644 --- a/src/main/java/forge/game/player/Player.java +++ b/src/main/java/forge/game/player/Player.java @@ -63,7 +63,8 @@ import forge.game.event.GameEventLandPlayed; import forge.game.event.GameEventLifeLoss; import forge.game.event.GameEventMulligan; import forge.game.event.GameEventPlayerControl; -import forge.game.event.GameEventPoisonCounter; +import forge.game.event.GameEventPlayerDamaged; +import forge.game.event.GameEventPlayerPoisoned; import forge.game.event.GameEventShuffle; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; @@ -622,38 +623,34 @@ public class Player extends GameEntity implements Comparable { * @return whether or not damage was dealt */ @Override - public final boolean addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat) { - final int damageToDo = damage; - - if (damageToDo <= 0) { + public final boolean addDamageAfterPrevention(final int amount, final Card source, final boolean isCombat) { + if (amount <= 0) { return false; } - String additionalLog = ""; - source.addDealtDamageToPlayerThisTurn(this.getName(), damageToDo); + //String additionalLog = ""; + source.addDealtDamageToPlayerThisTurn(this.getName(), amount); boolean infect = source.hasKeyword("Infect") || this.hasKeyword("All damage is dealt to you as though its source had infect."); if (infect) { - this.addPoisonCounters(damageToDo, source); - additionalLog = "(as Poison Counters)"; + this.addPoisonCounters(amount, source); } else { // Worship does not reduce the damage dealt but changes the effect // of the damage if (this.hasKeyword("Damage that would reduce your life total to less than 1 reduces it to 1 instead.") - && this.life <= damageToDo) { - this.loseLife(Math.min(damageToDo, this.life - 1)); - additionalLog = "(would reduce life total to less than 1, reduced to 1 instead.)"; + && this.life <= amount) { + this.loseLife(Math.min(amount, this.life - 1)); } else { // rule 118.2. Damage dealt to a player normally causes that // player to lose that much life. - this.loseLife(damageToDo); + this.loseLife(amount); } } - this.assignedDamage.put(source, damageToDo); + this.assignedDamage.put(source, amount); if (source.hasKeyword("Lifelink")) { - source.getController().gainLife(damageToDo, source); + source.getController().gainLife(amount, source); } source.getDamageHistory().registerDamage(this); this.getGame().fireEvent(new GameEventLifeLoss()); @@ -669,11 +666,11 @@ public class Player extends GameEntity implements Comparable { final HashMap runParams = new HashMap(); runParams.put("DamageSource", source); runParams.put("DamageTarget", this); - runParams.put("DamageAmount", damageToDo); + runParams.put("DamageAmount", amount); runParams.put("IsCombatDamage", isCombat); game.getTriggerHandler().runTrigger(TriggerType.DamageDone, runParams, false); - game.getGameLog().add(GameLogEntryType.DAMAGE, String.format("Dealing %d damage to %s. %s", damageToDo, this.getName(), additionalLog)); + game.fireEvent(new GameEventPlayerDamaged(this, source, amount, isCombat, infect)); return true; } @@ -1020,9 +1017,7 @@ public class Player extends GameEntity implements Comparable { if (!this.hasKeyword("You can't get poison counters")) { this.poisonCounters += num; - game.fireEvent(new GameEventPoisonCounter(this, source, num)); - game.getGameLog().add(GameLogEntryType.DAMAGE_POISON, this + " receives a poison counter from " + source); - + game.fireEvent(new GameEventPlayerPoisoned(this, source, num)); this.updateObservers(); } } diff --git a/src/main/java/forge/gui/toolbox/FDeckChooser.java b/src/main/java/forge/gui/toolbox/FDeckChooser.java index 76a19ffa639..128d81f0b64 100644 --- a/src/main/java/forge/gui/toolbox/FDeckChooser.java +++ b/src/main/java/forge/gui/toolbox/FDeckChooser.java @@ -26,11 +26,13 @@ import forge.deck.Deck; import forge.deck.DeckgenUtil; import forge.deck.generate.GenerateThemeDeck; import forge.game.RegisteredPlayer; +import forge.item.PreconDeck; import forge.quest.QuestController; import forge.quest.QuestEvent; import forge.quest.QuestEventChallenge; import forge.quest.QuestUtil; import forge.util.storage.IStorage; +import forge.util.storage.IStorageView; @SuppressWarnings("serial") public class FDeckChooser extends JPanel { @@ -38,6 +40,7 @@ public class FDeckChooser extends JPanel { private final JRadioButton radThemes = new FRadioButton("Semi-random theme deck"); private final JRadioButton radCustom = new FRadioButton("Custom user deck"); private final JRadioButton radQuests = new FRadioButton("Quest opponent deck"); + private final JRadioButton radPrecons = new FRadioButton("Decks from quest shop"); private final JList lstDecks = new FList(); private final FLabel btnRandom = new FLabel.ButtonBuilder().text("Random").fontSize(16).build(); @@ -101,6 +104,7 @@ public class FDeckChooser extends JPanel { JXButtonPanel grpRadios = new JXButtonPanel(); grpRadios.add(radCustom, strRadioConstraints); grpRadios.add(radQuests, strRadioConstraints); + grpRadios.add(radPrecons, strRadioConstraints); grpRadios.add(radColors, strRadioConstraints); grpRadios.add(radThemes, strRadioConstraints); @@ -122,6 +126,7 @@ public class FDeckChooser extends JPanel { _listen(getRadThemes(), new Runnable() { @Override public void run() { updateThemes(); } }); _listen(getRadCustom(), new Runnable() { @Override public void run() { updateCustom(); } }); _listen(getRadQuests(), new Runnable() { @Override public void run() { updateQuestEvents(); } }); + _listen(getRadPrecons(), new Runnable() { @Override public void run() { updatePrecons(); } }); // First run: colors getRadColors().setSelected(true); @@ -138,6 +143,7 @@ public class FDeckChooser extends JPanel { private JRadioButton getRadThemes() { return radThemes; } private JRadioButton getRadCustom() { return radCustom; } private JRadioButton getRadQuests() { return radQuests; } + private JRadioButton getRadPrecons() { return radPrecons; } /** Handles all control for "colors" radio button click. */ private void updateColors() { @@ -202,6 +208,28 @@ public class FDeckChooser extends JPanel { lst.setSelectedIndex(0); } + /** Handles all control for "custom" radio button click. */ + private void updatePrecons() { + final JList lst = getLstDecks(); + lst.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + final List customNames = new ArrayList(); + final IStorageView allDecks = QuestController.getPrecons(); + for (final PreconDeck d : allDecks) { customNames.add(d.getName()); } + + lst.setListData(customNames.toArray(ArrayUtils.EMPTY_STRING_ARRAY)); + lst.setName(DeckgenUtil.DeckTypes.PRECON.toString()); + lst.removeMouseListener(madDecklist); + lst.addMouseListener(madDecklist); + + getBtnRandom().setText("Random deck"); + getBtnRandom().setCommand(new Command() { + @Override public void run() { DeckgenUtil.randomSelect(lst); } }); + + // Init first in list + lst.setSelectedIndex(0); + } + /** Handles all control for "quest event" radio button click. */ private void updateQuestEvents() { final JList lst = getLstDecks(); @@ -258,6 +286,8 @@ public class FDeckChooser extends JPanel { deck = DeckgenUtil.buildThemeDeck(selection); } else if (lst0.getName().equals(DeckgenUtil.DeckTypes.CUSTOM.toString())) { deck = DeckgenUtil.getConstructedDeck(selection); + } else if (lst0.getName().equals(DeckgenUtil.DeckTypes.PRECON.toString())) { + deck = DeckgenUtil.getPreconDeck(selection); } return RegisteredPlayer.fromDeck(deck); diff --git a/src/main/java/forge/sound/EventVisualizer.java b/src/main/java/forge/sound/EventVisualizer.java index 8a8cf787909..a2dde73e2d1 100644 --- a/src/main/java/forge/sound/EventVisualizer.java +++ b/src/main/java/forge/sound/EventVisualizer.java @@ -19,7 +19,7 @@ import forge.game.event.GameEvent; import forge.game.event.GameEventFlipCoin; import forge.game.event.GameEventLandPlayed; import forge.game.event.GameEventLifeLoss; -import forge.game.event.GameEventPoisonCounter; +import forge.game.event.GameEventPlayerPoisoned; import forge.game.event.GameEventCardTapped; import forge.game.event.GameEventShuffle; import forge.game.event.GameEventSpellResolved; @@ -46,7 +46,7 @@ public class EventVisualizer extends IGameEventVisitor.Base { public SoundEffectType visit(GameEventEndOfTurn event) { return SoundEffectType.EndOfTurn; } public SoundEffectType visit(GameEventFlipCoin event) { return SoundEffectType.FlipCoin; } public SoundEffectType visit(GameEventLifeLoss event) { return SoundEffectType.LifeLoss; } - public SoundEffectType visit(GameEventPoisonCounter event) { return SoundEffectType.Poison; } + public SoundEffectType visit(GameEventPlayerPoisoned event) { return SoundEffectType.Poison; } public SoundEffectType visit(GameEventShuffle event) { return SoundEffectType.Shuffle; } public SoundEffectType visit(GameEventTokenCreated event) { return SoundEffectType.Token; }