diff --git a/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java index 9a37000a753..7d81128d7bc 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java @@ -50,7 +50,7 @@ public class RestartGameEffect extends SpellAbilityEffect { game.resetPlayersAttackedOnNextTurn(); game.resetPlayersAttackedOnNextTurn(); GameAction action = game.getAction(); - + for (Player p: players) { p.setStartingLife(p.getStartingLife()); p.clearCounters(); @@ -58,6 +58,7 @@ public class RestartGameEffect extends SpellAbilityEffect { p.onCleanupPhase(); p.setLandsPlayedLastTurn(0); p.resetCommanderStats(); + p.resetCompletedDungeons(); CardCollection newLibrary = new CardCollection(p.getCardsIn(restartZones, false)); List filteredCards = null; @@ -74,7 +75,7 @@ public class RestartGameEffect extends SpellAbilityEffect { } } p.getZone(ZoneType.Command).removeAllCards(true); - + for (Card c : newLibrary) { action.moveToLibrary(c, 0, sa); } @@ -85,15 +86,15 @@ public class RestartGameEffect extends SpellAbilityEffect { trigHandler.clearSuppression(TriggerType.Shuffled); trigHandler.clearSuppression(TriggerType.ChangesZone); - + game.resetTurnOrder(); game.setAge(GameStage.RestartedByKarn); // Do not need this because ability will resolve only during that player's turn //game.getPhaseHandler().setPlayerTurn(sa.getActivatingPlayer()); - + // Set turn number? - - // The rest is handled by phaseHandler + + // The rest is handled by phaseHandler } /* (non-Javadoc) diff --git a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java index 6ed1eff907a..1d4c82e80be 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java @@ -1,5 +1,6 @@ package forge.game.ability.effects; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -62,18 +63,18 @@ public class SacrificeEffect extends SpellAbilityEffect { Cost cumCost = new Cost(sa.getParam("CumulativeUpkeep"), true); Cost payCost = new Cost(ManaCost.ZERO, true); int n = card.getCounters(CounterEnumType.AGE); - + // multiply cost for (int i = 0; i < n; ++i) { payCost.add(cumCost); } - + sa.setCumulativeupkeep(true); game.updateLastStateForCard(card); - + StringBuilder sb = new StringBuilder(); sb.append("Cumulative upkeep for ").append(card); - + boolean isPaid = activator.getController().payManaOptional(card, payCost, sa, sb.toString(), ManaPaymentPurpose.CumulativeUpkeep); final Map runParams = AbilityKey.mapFromCard(card); runParams.put(AbilityKey.CumulativeUpkeepPaid, isPaid); @@ -90,6 +91,7 @@ public class SacrificeEffect extends SpellAbilityEffect { final List tgts = getTargetPlayers(sa); final boolean devour = sa.hasParam("Devour"); final boolean exploit = sa.hasParam("Exploit"); + final boolean sacEachValid = sa.hasParam("SacEachValid"); String valid = sa.getParam("SacValid"); if (valid == null) { @@ -118,32 +120,63 @@ public class SacrificeEffect extends SpellAbilityEffect { } } } - } - else { + } else { CardCollectionView choosenToSacrifice = null; for (final Player p : tgts) { CardCollectionView battlefield = p.getCardsIn(ZoneType.Battlefield); - CardCollectionView validTargets = AbilityUtils.filterListByType(battlefield, valid, sa); - if (!destroy) { - validTargets = CardLists.filter(validTargets, CardPredicates.canBeSacrificedBy(sa)); - } - - if (sa.hasParam("Random")) { - choosenToSacrifice = Aggregates.random(validTargets, Math.min(amount, validTargets.size()), new CardCollection()); - } else if (sa.hasParam("OptionalSacrifice") && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantSacrifice"))) { - choosenToSacrifice = CardCollection.EMPTY; + if (sacEachValid) { // Sacrifice maximum permanents in any combination of types specified by SacValid + String [] validArray = valid.split(" & "); + String [] msgArray = msg.split(" & "); + List validTargetsList = new ArrayList<>(validArray.length); + for (String subValid : validArray) { + CardCollectionView validTargets = AbilityUtils.filterListByType(battlefield, subValid, sa); + validTargets = CardLists.filter(validTargets, CardPredicates.canBeSacrificedBy(sa)); + validTargetsList.add(new CardCollection(validTargets)); + } + CardCollection chosenCards = new CardCollection(); + for (int i = 0; i < validArray.length; ++i) { + CardCollection validTargets = validTargetsList.get(i); + if (validTargets.isEmpty()) continue; + if (validTargets.size() > 1 && i < validArray.length - 1) { + // remove candidates that must be chosen for later types + CardCollection union = new CardCollection(); + for (int j = i + 1; j < validArray.length; ++j) { + union.addAll(validTargetsList.get(j)); + if (union.size() == (j - i) * amount) { + validTargets.removeAll(union); + } + } + } + choosenToSacrifice = p.getController().choosePermanentsToSacrifice(sa, amount, amount, validTargets, msgArray[i]); + for (int j = i + 1; j < validArray.length; ++j) { + validTargetsList.get(j).removeAll(choosenToSacrifice); + } + chosenCards.addAll(choosenToSacrifice); + } + choosenToSacrifice = chosenCards; } else { - boolean isOptional = sa.hasParam("Optional"); - boolean isStrict = sa.hasParam("StrictAmount"); - int minTargets = isOptional ? 0 : amount; - boolean notEnoughTargets = isStrict && validTargets.size() < minTargets; - - if (!notEnoughTargets) { - choosenToSacrifice = destroy ? - p.getController().choosePermanentsToDestroy(sa, minTargets, amount, validTargets, msg) : - p.getController().choosePermanentsToSacrifice(sa, minTargets, amount, validTargets, msg); - } else { + CardCollectionView validTargets = AbilityUtils.filterListByType(battlefield, valid, sa); + if (!destroy) { + validTargets = CardLists.filter(validTargets, CardPredicates.canBeSacrificedBy(sa)); + } + + if (sa.hasParam("Random")) { + choosenToSacrifice = Aggregates.random(validTargets, Math.min(amount, validTargets.size()), new CardCollection()); + } else if (sa.hasParam("OptionalSacrifice") && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantSacrifice"))) { choosenToSacrifice = CardCollection.EMPTY; + } else { + boolean isOptional = sa.hasParam("Optional"); + boolean isStrict = sa.hasParam("StrictAmount"); + int minTargets = isOptional ? 0 : amount; + boolean notEnoughTargets = isStrict && validTargets.size() < minTargets; + + if (!notEnoughTargets) { + choosenToSacrifice = destroy ? + p.getController().choosePermanentsToDestroy(sa, minTargets, amount, validTargets, msg) : + p.getController().choosePermanentsToSacrifice(sa, minTargets, amount, validTargets, msg); + } else { + choosenToSacrifice = CardCollection.EMPTY; + } } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java b/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java index 96af917b2db..6a817fa0b58 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java @@ -49,7 +49,7 @@ public class VentureEffect extends SpellAbilityEffect { for (PaperCard pc : dungeonCards) { faces.add(pc.getRules().getMainPart()); } - String message = Localizer.getInstance().getMessage("lblChooseACardName"); + String message = Localizer.getInstance().getMessage("lblChooseDungeon"); String chosen = player.getController().chooseCardName(sa, faces, message); Card dungeon = Card.fromPaperCard(StaticData.instance().getVariantCards().getUniqueByName(chosen), player); @@ -81,7 +81,7 @@ public class VentureEffect extends SpellAbilityEffect { } } } - final String title = Localizer.getInstance().getMessage("lblChooseAbilityForObject", dungeon.toString()); + final String title = Localizer.getInstance().getMessage("lblChooseRoom"); SpellAbility chosen = player.getController().chooseSingleSpellForEffect(candidates, sa, title, null); return chosen.getParam("RoomName"); } else { 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 8a6c6618ace..f78e42c894e 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -1954,6 +1954,9 @@ public class Player extends GameEntity implements Comparable { public void addCompletedDungeon(Card dungeon) { completedDungeons.add(dungeon); } + public void resetCompletedDungeons() { + completedDungeons.clear(); + } public final void altWinBySpellEffect(final String sourceName) { if (cantWin()) { diff --git a/forge-gui-desktop/src/main/java/forge/view/arcane/CardPanel.java b/forge-gui-desktop/src/main/java/forge/view/arcane/CardPanel.java index 88d3494b36e..e1839f48113 100644 --- a/forge-gui-desktop/src/main/java/forge/view/arcane/CardPanel.java +++ b/forge-gui-desktop/src/main/java/forge/view/arcane/CardPanel.java @@ -510,6 +510,13 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl } + if (card.getCurrentRoom() != null && !card.getCurrentRoom().isEmpty()) { + List markers = new ArrayList<>(); + markers.add("In Room:"); + markers.add(card.getCurrentRoom()); + drawMarkersTabs(g, markers); + } + final int combatXSymbols = (cardXOffset + (cardWidth / 4)) - 16; final int stateXSymbols = (cardXOffset + (cardWidth / 2)) - 16; final int ySymbols = (cardYOffset + cardHeight) - (cardHeight / 8) - 16; @@ -840,6 +847,57 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl } + private void drawMarkersTabs(final Graphics g, List markers) { + + final Dimension imgSize = calculateImageSize(); + final int titleY = Math.round(imgSize.height * (54f / 640)) - 15; + + final int spaceFromTopOfCard = titleY + 60; + final int markerBoxHeight = 24; + final int markerBoxBaseWidth = 14; + final int markerBoxSpacing = 2; + + int currentMarker = 0; + + FontMetrics smallFontMetrics = g.getFontMetrics(smallCounterFont); + + for (String marker : markers) { + + final int markerBoxRealWidth = markerBoxBaseWidth + smallFontMetrics.stringWidth(marker); + final int markerYOffset; + + if (ForgeConstants.CounterDisplayLocation.from(FModel.getPreferences().getPref(FPref.UI_CARD_COUNTER_DISPLAY_LOCATION)) == ForgeConstants.CounterDisplayLocation.TOP) { + markerYOffset = cardYOffset + spaceFromTopOfCard - markerBoxHeight + currentMarker++ * (markerBoxHeight + markerBoxSpacing); + } else { + markerYOffset = cardYOffset + cardHeight - spaceFromTopOfCard / 2 - markerBoxHeight + currentMarker++ * (markerBoxHeight + markerBoxSpacing); + } + + if (isSelected) { + g.setColor(new Color(0, 0, 0, 255)); + } else { + g.setColor(new Color(0, 0, 0, 200)); + } + + RoundRectangle2D markerArea = new RoundRectangle2D.Float(cardXOffset, markerYOffset, markerBoxRealWidth, markerBoxHeight, 9, 9); + ((Graphics2D) g).fill(markerArea); + + g.fillRect(cardXOffset, markerYOffset, 9, markerBoxHeight); + + if (isSelected) { + g.setColor(new Color(200, 200, 200)); + } else { + g.setColor(new Color(200, 200, 200, 180)); + } + + Rectangle nameBounds = markerArea.getBounds(); + nameBounds.x += 8; + nameBounds.y -= 1; + nameBounds.width = 43; + drawVerticallyCenteredString(g, marker, nameBounds, smallCounterFont, smallFontMetrics); + } + + } + /** * Draws a String justified to the left of the rectangle, centered vertically. * diff --git a/forge-gui-mobile/src/forge/card/CardRenderer.java b/forge-gui-mobile/src/forge/card/CardRenderer.java index 47459ffea87..fcd57e85d93 100644 --- a/forge-gui-mobile/src/forge/card/CardRenderer.java +++ b/forge-gui-mobile/src/forge/card/CardRenderer.java @@ -113,7 +113,7 @@ public class CardRenderer { static { try { - for (int fontSize = 11; fontSize <= 22; fontSize++) { + for (int fontSize = 8; fontSize <= 22; fontSize++) { generateFontForCounters(fontSize); } } catch (Exception e) { @@ -603,6 +603,13 @@ public class CardRenderer { } + if (card.getCurrentRoom() != null && !card.getCurrentRoom().isEmpty()) { + List markers = new ArrayList<>(); + markers.add("In Room:"); + markers.add(card.getCurrentRoom()); + drawMarkersTabs(markers, g, x, y, w, h); + } + float otherSymbolsSize = w / 4f; final float combatXSymbols = (x + (w / 4)) - otherSymbolsSize / 2 - 10; final float stateXSymbols = (x + (w / 2)) - otherSymbolsSize / 2 - 10; @@ -1073,6 +1080,38 @@ public class CardRenderer { } + private static void drawMarkersTabs(final List markers, final Graphics g, final float x, final float y, final float w, final float h) { + + int fontSize = Math.max(8, Math.min(22, (int) (h * 0.05))); + BitmapFont font = counterFonts.get(fontSize); + + final float additionalXOffset = 3f * ((fontSize - 8) / 8f); + + float otherSymbolsSize = w / 3.5f; + final float ySymbols = (h / 12) - otherSymbolsSize / 2; + + final float markerBoxHeight = 9 + fontSize; + final float markerBoxBaseWidth = 8 + additionalXOffset * 2; + final float markerBoxSpacing = -4; + + final float spaceFromTopOfCard = y + h - markerBoxHeight - markerBoxSpacing - otherSymbolsSize + ySymbols; + + int markerCounter = markers.size() - 1; + + for (String marker : markers) { + layout.setText(font, marker); + final float markerBoxRealWidth = markerBoxBaseWidth + layout.width + 4; + + final float markerYOffset = spaceFromTopOfCard - (markerCounter-- * (markerBoxHeight + markerBoxSpacing)); + + g.fillRect(counterBackgroundColor, x - 3, markerYOffset, markerBoxRealWidth, markerBoxHeight); + + Color markerColor = new Color(200.0f / 255.0f, 200.0f / 255.0f, 200.0f / 255.0f, 1.0f); + + drawText(g, marker, font, markerColor, x + 2 + additionalXOffset, markerYOffset, markerBoxRealWidth, markerBoxHeight, Align.left); + } + } + private static void drawPtBox(Graphics g, CardView card, CardStateView details, Color color, float x, float y, float w, float h) { //use array of strings to render separately with a tiny amount of space in between //instead of using actual spaces which are too wide @@ -1202,7 +1241,7 @@ public class CardRenderer { int pageSize = 128; //only generate images for characters that could be used by Forge - String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890/-+"; + String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890/-+:'"; final PixmapPacker packer = new PixmapPacker(pageSize, pageSize, Pixmap.Format.RGBA8888, 2, false); final FreeTypeFontParameter parameter = new FreeTypeFontParameter(); diff --git a/forge-gui/res/cardsfolder/upcoming/tomb_of_annihilation.txt b/forge-gui/res/cardsfolder/upcoming/tomb_of_annihilation.txt index 3185bbc1abe..4e0b71d8b91 100644 --- a/forge-gui/res/cardsfolder/upcoming/tomb_of_annihilation.txt +++ b/forge-gui/res/cardsfolder/upcoming/tomb_of_annihilation.txt @@ -6,9 +6,7 @@ SVar:DBEntry:DB$ LoseLife | Defined$ Player | LifeAmount$ 1 | RoomName$ Trapped SVar:DBVeilsOfFear:DB$ RepeatEach | RepeatSubAbility$ DBLoseLife1 | RepeatPlayers$ Player | RoomName$ Veils of Fear | SpellDescription$ Each player loses 2 life unless they discard a card. | NextRoom$ DBSandfallCell SVar:DBLoseLife1:DB$ LoseLife | Defined$ Player.IsRemembered | LifeAmount$ 2 | UnlessCost$ Discard<1/Card> | UnlessPayer$ Remembered SVar:DBOubliette:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | SubAbility$ DBSacArtifact | RoomName$ Oubliette | SpellDescription$ Discard a card and sacrifice an artifact, a creature, and a land. | NextRoom$ DBCradleDeathGod -SVar:DBSacArtifact:DB$ Sacrifice | Defined$ You | SacValid$ Artifact | SubAbility$ DBSacCreature -SVar:DBSacCreature:DB$ Sacrifice | Defined$ You | SacValid$ Creature | SubAbility$ DBSacLand -SVar:DBSacLand:DB$ Sacrifice | Defined$ You | SacValid$ Land +SVar:DBSacArtifact:DB$ Sacrifice | Defined$ You | SacValid$ Artifact & Creature & Land | SacEachValid$ True SVar:DBSandfallCell:DB$ RepeatEach | RepeatSubAbility$ DBLoseLife2 | RepeatPlayers$ Player | RoomName$ Sandfall Cell | SpellDescription$ Each player loses 2 life unless they sacrifice an artifact, a creature, or a land. | NextRoom$ DBCradleDeathGod SVar:DBLoseLife2:DB$ LoseLife | Defined$ Player.IsRemembered | LifeAmount$ 2 | UnlessCost$ Sac<1/Artifact;Creature;Land/an artifact, a creature, or a land> | UnlessPayer$ Remembered SVar:DBCradleDeathGod:DB$ Token | TokenScript$ b_4_4_the_atropal_deathtouch | TokenOwner$ You | RoomName$ Cradle of the Death God | SpellDescription$ Create The Atropal, a legendary 4/4 black God Horror creature token with deathtouch. diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index c877f3190bf..017e62dd2b4 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -1960,6 +1960,9 @@ lblChoosesPile=Wähle Stapel lblEmptyPile=Leerer Stapel #UntapEffect.java lblSelectCardToUntap=Wähle Karten zum Enttappen +#VentureEffect.java +lblChooseDungeon=Which dungeon do you want to venture into? +lblChooseRoom=Which room do you want to venture into? #VoteEffect.java lblVote=Abstimmung lblCurrentVote=Aktuelle Stimmen diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index da97ecec73a..a473fd64eda 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -1960,6 +1960,9 @@ lblChoosesPile=chooses Pile lblEmptyPile=Empty pile #UntapEffect.java lblSelectCardToUntap=Select cards to untap +#VentureEffect.java +lblChooseDungeon=Which dungeon do you want to venture into? +lblChooseRoom=Which room do you want to venture into? #VoteEffect.java lblVote=Vote lblCurrentVote=Current Votes diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index f9c855a66bb..067f50de057 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -1960,6 +1960,9 @@ lblChoosesPile=elige Montón lblEmptyPile=Montón vacío #UntapEffect.java lblSelectCardToUntap=Selecciona las cartas que quieres enderezar +#VentureEffect.java +lblChooseDungeon=Which dungeon do you want to venture into? +lblChooseRoom=Which room do you want to venture into? #VoteEffect.java lblVote=Votar lblCurrentVote=Votos actuales diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index 73977e577d9..afe546fbe0d 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -1960,6 +1960,9 @@ lblChoosesPile=sceglie la Pila lblEmptyPile=Pila vuota #UntapEffect.java lblSelectCardToUntap=Scegli le carte da Stappare +#VentureEffect.java +lblChooseDungeon=Which dungeon do you want to venture into? +lblChooseRoom=Which room do you want to venture into? #VoteEffect.java lblVote=Voto lblCurrentVote=Voti attuali diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index 4105e791924..5164e03a71d 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -1961,6 +1961,9 @@ lblChoosesPile=束を選んだ lblEmptyPile=空っぽの束 #UntapEffect.java lblSelectCardToUntap=アンタップするカードを選ぶ +#VentureEffect.java +lblChooseDungeon=どのダンジョンを探索しますか? +lblChooseRoom=どの部屋を探索しますか? #VoteEffect.java lblVote=投票 lblCurrentVote=現在の投票 diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 83f6de3ac39..5d3e82e57e4 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -1961,6 +1961,9 @@ lblChoosesPile=选择堆 lblEmptyPile=空堆 #UntapEffect.java lblSelectCardToUntap=选择要重置的牌 +#VentureEffect.java +lblChooseDungeon=Which dungeon do you want to venture into? +lblChooseRoom=Which room do you want to venture into? #VoteEffect.java lblVote=投票 lblCurrentVote=当前投票