From ab31c8fa9de45f40a962093491192a94d1e01c2f Mon Sep 17 00:00:00 2001 From: Lyu Zong-Hong Date: Sat, 5 Jun 2021 18:18:34 +0900 Subject: [PATCH] Implement missing cards with dividable shields for damage replacement effects --- .../java/forge/ai/PlayerControllerAi.java | 36 +- .../java/forge/game/ability/AbilityKey.java | 1 + .../ability/effects/ReplaceDamageEffect.java | 4 + .../effects/ReplaceSplitDamageEffect.java | 4 + .../forge/game/player/PlayerController.java | 1 + .../game/replacement/ReplacementHandler.java | 84 ++++- .../java/forge/screens/match/CMatchUI.java | 140 ++++--- .../screens/match/VAssignGenericAmount.java | 314 ++++++++++++++++ .../util/PlayerControllerForTests.java | 6 + .../forge/screens/match/MatchController.java | 13 + .../match/views/VAssignGenericAmount.java | 348 ++++++++++++++++++ .../res/cardsfolder/d/divine_deflection.txt | 13 + forge-gui/res/cardsfolder/h/harms_way.txt | 11 + .../res/cardsfolder/r/refraction_trap.txt | 15 + forge-gui/res/cardsfolder/s/shining_shoal.txt | 16 + forge-gui/res/languages/de-DE.properties | 17 +- forge-gui/res/languages/en-US.properties | 57 +-- forge-gui/res/languages/es-ES.properties | 57 +-- forge-gui/res/languages/it-IT.properties | 31 +- forge-gui/res/languages/ja-JP.properties | 29 +- forge-gui/res/languages/zh-CN.properties | 25 +- .../forge/gamemodes/net/ProtocolMethod.java | 1 + .../gamemodes/net/server/NetGuiGame.java | 7 +- .../java/forge/gui/interfaces/IGuiGame.java | 1 + .../forge/player/PlayerControllerHuman.java | 19 + 25 files changed, 1077 insertions(+), 173 deletions(-) create mode 100644 forge-gui-desktop/src/main/java/forge/screens/match/VAssignGenericAmount.java create mode 100644 forge-gui-mobile/src/forge/screens/match/views/VAssignGenericAmount.java create mode 100644 forge-gui/res/cardsfolder/d/divine_deflection.txt create mode 100644 forge-gui/res/cardsfolder/h/harms_way.txt create mode 100644 forge-gui/res/cardsfolder/r/refraction_trap.txt create mode 100644 forge-gui/res/cardsfolder/s/shining_shoal.txt diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index aca2611dc01..e1cd8b7da92 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -81,9 +81,9 @@ import forge.util.collect.FCollection; import forge.util.collect.FCollectionView; -/** +/** * A prototype for player controller class - * + * * Handles phase skips for now. */ public class PlayerControllerAi extends PlayerController { @@ -94,11 +94,11 @@ public class PlayerControllerAi extends PlayerController { brains = new AiController(p, game); } - + public void allowCheatShuffle(boolean value){ brains.allowCheatShuffle(value); } - + public void setUseSimulation(boolean value) { brains.setUseSimulation(value); } @@ -131,6 +131,12 @@ public class PlayerControllerAi extends PlayerController { return ComputerUtilCombat.distributeAIDamage(attacker, blockers, damageDealt, defender, overrideOrder); } + @Override + public Map divideShield(Card effectSource, Map affected, int shieldAmount) { + // TODO: AI current can't use this so this is not implemented. + return new HashMap<>(); + } + @Override public Integer announceRequirements(SpellAbility ability, String announce) { // For now, these "announcements" are made within the AI classes of the appropriate SA effects @@ -157,7 +163,7 @@ public class PlayerControllerAi extends PlayerController { case BidLife: return 0; default: - return null; + return null; } } return null; // return incorrect value to indicate that @@ -240,7 +246,7 @@ public class PlayerControllerAi extends PlayerController { public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) { return getAi().confirmAction(sa, mode, message); } - + @Override public boolean confirmBidAction(SpellAbility sa, PlayerActionConfirmMode mode, String string, int bid, Player winner) { @@ -646,7 +652,7 @@ public class PlayerControllerAi extends PlayerController { public List chooseSpellAbilityToPlay() { return brains.chooseSpellAbilityToPlay(); } - + @Override public boolean playChosenSpellAbility(SpellAbility sa) { if (sa instanceof LandAbility) { @@ -658,7 +664,7 @@ public class PlayerControllerAi extends PlayerController { ComputerUtil.handlePlayingSpellAbility(player, sa, getGame()); } return true; - } + } @Override public CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard) { @@ -711,7 +717,7 @@ public class PlayerControllerAi extends PlayerController { public int chooseNumber(SpellAbility sa, String title, int min, int max) { return brains.chooseNumber(sa, title, min, max); } - + @Override public int chooseNumber(SpellAbility sa, String string, int min, int max, Map params) { ApiType api = sa.getApi(); @@ -814,7 +820,7 @@ public class PlayerControllerAi extends PlayerController { /* * (non-Javadoc) - * + * * @see * forge.game.player.PlayerController#chooseBinary(forge.game.spellability. * SpellAbility, java.lang.String, @@ -855,7 +861,7 @@ public class PlayerControllerAi extends PlayerController { } return sa.getChosenList(); } - + @Override public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) { final String c = ComputerUtilCard.getMostProminentColor(player.getCardsIn(ZoneType.Hand)); @@ -901,7 +907,7 @@ public class PlayerControllerAi extends PlayerController { /* * (non-Javadoc) - * + * * @see forge.game.player.PlayerController#chooseCounterType(java.util.List, * forge.game.spellability.SpellAbility, java.lang.String, java.util.Map) */ @@ -917,7 +923,7 @@ public class PlayerControllerAi extends PlayerController { @Override public boolean confirmPayment(CostPart costPart, String prompt, SpellAbility sa) { - return brains.confirmPayment(costPart); // AI is expected to know what it is paying for at the moment (otherwise add another parameter to this method) + return brains.confirmPayment(costPart); // AI is expected to know what it is paying for at the moment (otherwise add another parameter to this method) } @Override @@ -1105,7 +1111,7 @@ public class PlayerControllerAi extends PlayerController { public void revealAnte(String message, Multimap removedAnteCards) { // Ai won't understand that anyway } - + @Override public Collection complainCardsCantPlayWell(Deck myDeck) { return brains.complainCardsCantPlayWell(myDeck); @@ -1153,7 +1159,7 @@ public class PlayerControllerAi extends PlayerController { return new HashMap<>(); } } - + //Do not convoke potential blockers until after opponent's attack final CardCollectionView blockers = ComputerUtilCard.getLikelyBlockers(ai, null); if ((ph.isPlayerTurn(ai) && ph.getPhase().isAfter(PhaseType.COMBAT_BEGIN)) || diff --git a/forge-game/src/main/java/forge/game/ability/AbilityKey.java b/forge-game/src/main/java/forge/game/ability/AbilityKey.java index 7c002b52839..526173cdd87 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityKey.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityKey.java @@ -57,6 +57,7 @@ public enum AbilityKey { DefendingPlayer("DefendingPlayer"), Destination("Destination"), Devoured("Devoured"), + DividedShieldAmount("DividedShieldAmount"), EchoPaid("EchoPaid"), EffectOnly("EffectOnly"), Exploited("Exploited"), diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java index 4bdd454fb15..cb59a12eb10 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java @@ -39,6 +39,10 @@ public class ReplaceDamageEffect extends SpellAbilityEffect { if (prevent > 0) { int n = Math.min(dmg, prevent); + // if the effect has divided shield, use that + if (originalParams.get(AbilityKey.DividedShieldAmount) != null) { + n = Math.min(n, (Integer)originalParams.get(AbilityKey.DividedShieldAmount)); + } dmg -= n; prevent -= n; diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java index 8a6bc244cdb..1f462099afe 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java @@ -38,6 +38,10 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect { if (prevent > 0 && list.size() > 0 && list.get(0) instanceof GameEntity) { int n = Math.min(dmg, prevent); + // if the effect has divided shield, use that + if (originalParams.get(AbilityKey.DividedShieldAmount) != null) { + n = Math.min(n, (Integer)originalParams.get(AbilityKey.DividedShieldAmount)); + } dmg -= n; prevent -= n; 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 5bd6a56eb29..6a94b3e4ccb 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerController.java +++ b/forge-game/src/main/java/forge/game/player/PlayerController.java @@ -108,6 +108,7 @@ public abstract class PlayerController { public abstract List chooseCardsYouWonToAddToDeck(List losses); public abstract Map assignCombatDamage(Card attacker, CardCollectionView blockers, int damageDealt, GameEntity defender, boolean overrideOrder); + public abstract Map divideShield(Card effectSource, Map affected, int shieldAmount); public abstract Integer announceRequirements(SpellAbility ability, String announce); public abstract CardCollectionView choosePermanentsToSacrifice(SpellAbility sa, int min, int max, CardCollectionView validTargets, String message); diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java index a982663eb0e..dc963073153 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java @@ -688,6 +688,7 @@ public class ReplacementHandler { ApiType apiType = null; SpellAbility bufferedSA = effectSA; boolean needRestoreSubSA = false; + boolean needDivideShield = false; boolean needChooseSource = false; int shieldAmount = 0; if (effectSA != null) { @@ -701,7 +702,8 @@ public class ReplacementHandler { } } - // Determine if the prevent next N damage shield is large enough to replace all damage + // Determine if need to divide shield among affected entity and + // determine if the prevent next N damage shield is large enough to replace all damage Map mapParams = chosenRE.getMapParams(); if ((mapParams.containsKey("PreventionEffect") && mapParams.get("PreventionEffect").equals("NextN")) || apiType == ApiType.ReplaceSplitDamage) { @@ -711,17 +713,50 @@ public class ReplacementHandler { shieldAmount = AbilityUtils.calculateAmount(effectSA.getHostCard(), effectSA.getParamOrDefault("VarName", "1"), effectSA); } int damageAmount = 0; + boolean hasMultipleSource = false; + boolean hasMultipleTarget = false; + Card firstSource = null; + GameEntity firstTarget = null; for (Map runParams : runParamList) { // Only count damage that can be prevented if (apiType == ApiType.ReplaceDamage && Boolean.TRUE.equals(runParams.get(AbilityKey.NoPreventDamage))) continue; damageAmount += (int) runParams.get(AbilityKey.DamageAmount); + if (firstSource == null) { + firstSource = (Card) runParams.get(AbilityKey.DamageSource); + } else if (!firstSource.equals(runParams.get(AbilityKey.DamageSource))) { + hasMultipleSource = true; + } + if (firstTarget == null) { + firstTarget = (GameEntity) runParams.get(AbilityKey.Affected); + } else if (!firstTarget.equals(runParams.get(AbilityKey.Affected))) { + hasMultipleTarget = true; + } } - if (damageAmount > shieldAmount) { - needChooseSource = true; + if (damageAmount > shieldAmount && runParamList.size() > 1) { + if (hasMultipleSource) + needChooseSource = true; + if (effectSA.hasParam("DivideShield") && hasMultipleTarget) + needDivideShield = true; } } } + // Ask the decider to divide shield among affected damage target + Map shieldMap = null; + if (needDivideShield) { + Map affected = new HashMap<>(); + for (Map runParams : runParamList) { + GameEntity target = (GameEntity) runParams.get(AbilityKey.Affected); + Integer damage = (Integer) runParams.get(AbilityKey.DamageAmount); + if (!affected.containsKey(target)) { + affected.put(target, damage); + } else { + affected.put(target, damage + affected.get(target)); + } + } + shieldMap = decider.getController().divideShield(chosenRE.getHostCard(), affected, shieldAmount); + } + // CR 615.7 // If damage would be dealt to the shielded permanent or player by two or more applicable sources at the same time, // the player or the controller of the permanent chooses which damage the shield prevents. @@ -735,23 +770,42 @@ public class ReplacementHandler { while (shieldAmount > 0 && !sourcesToChooseFrom.isEmpty()) { Card source = decider.getController().chooseSingleEntityForEffect(sourcesToChooseFrom, effectSA, choiceTitle, null); sourcesToChooseFrom.remove(source); - Map runParams = null; - for (Map rp : runParamList) { - if (source.equals(rp.get(AbilityKey.DamageSource))) { - runParams = rp; - break; + Iterator> itr = runParamList.iterator(); + while (itr.hasNext()) { + Map runParams = itr.next(); + if (source.equals(runParams.get(AbilityKey.DamageSource))) { + itr.remove(); + if (shieldMap != null) { + GameEntity target = (GameEntity) runParams.get(AbilityKey.Affected); + if (shieldMap.containsKey(target) && shieldMap.get(target) > 0) { + Integer dividedShieldAmount = shieldMap.get(target); + runParams.put(AbilityKey.DividedShieldAmount, dividedShieldAmount); + shieldAmount -= (int) dividedShieldAmount; + } else { + continue; + } + } else { + shieldAmount -= (int) runParams.get(AbilityKey.DamageAmount); + } + if (!runParams.containsKey(AbilityKey.ReplacementResultMap)) { + Map resultMap = new HashMap<>(); + runParams.put(AbilityKey.ReplacementResultMap, resultMap); + } + runSingleReplaceDamageEffect(chosenRE, runParams, replaceCandidateMap, executedDamageMap, decider, damageMap, preventMap); } } - runParamList.remove(runParams); - shieldAmount -= (int) runParams.get(AbilityKey.DamageAmount); - if (!runParams.containsKey(AbilityKey.ReplacementResultMap)) { - Map resultMap = new HashMap<>(); - runParams.put(AbilityKey.ReplacementResultMap, resultMap); - } - runSingleReplaceDamageEffect(chosenRE, runParams, replaceCandidateMap, executedDamageMap, decider, damageMap, preventMap); } } else { for (Map runParams : runParamList) { + if (shieldMap != null) { + GameEntity target = (GameEntity) runParams.get(AbilityKey.Affected); + if (shieldMap.containsKey(target) && shieldMap.get(target) > 0) { + Integer dividedShieldAmount = shieldMap.get(target); + runParams.put(AbilityKey.DividedShieldAmount, dividedShieldAmount); + } else { + continue; + } + } if (!runParams.containsKey(AbilityKey.ReplacementResultMap)) { Map resultMap = new HashMap<>(); runParams.put(AbilityKey.ReplacementResultMap, resultMap); diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java index 2e9e256baa1..ea712979fd9 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java @@ -760,7 +760,7 @@ public final class CMatchUI } } }; - + if (FThreads.isGuiThread()) { // run this now whether in EDT or not so that it doesn't clobber later stuff FThreads.invokeInEdtNowOrLater(focusRoutine); } else { @@ -1028,6 +1028,24 @@ public final class CMatchUI return result.get(); } + @Override + public Map assignGenericAmount(final CardView effectSource, final Map target, + final int amount, final boolean atLeastOne, final String amountLabel) { + if (amount <= 0) { + return Collections.emptyMap(); + } + + final AtomicReference> result = new AtomicReference<>(); + FThreads.invokeInEdtAndWait(new Runnable() { + @Override + public void run() { + final VAssignGenericAmount v = new VAssignGenericAmount(CMatchUI.this, effectSource, target, amount, atLeastOne, amountLabel); + result.set(v.getAssignedMap()); + }}); + return result.get(); + } + + @Override public void openView(final TrackableCollection myPlayers) { final GameView gameView = getGameView(); @@ -1273,28 +1291,28 @@ public final class CMatchUI String stackNotificationPolicy = FModel.getPreferences().getPref(FPref.UI_STACK_EFFECT_NOTIFICATION_POLICY); boolean isAi = sa.getActivatingPlayer().isAI(); boolean isTrigger = sa.isTrigger(); - int stackIndex = event.stackIndex; - if(stackIndex == nextNotifiableStackIndex) { + int stackIndex = event.stackIndex; + if(stackIndex == nextNotifiableStackIndex) { if(ForgeConstants.STACK_EFFECT_NOTIFICATION_ALWAYS.equals(stackNotificationPolicy) || (ForgeConstants.STACK_EFFECT_NOTIFICATION_AI_AND_TRIGGERED.equals(stackNotificationPolicy) && (isAi || isTrigger))) { // We can go and show the modal SpellAbilityStackInstance si = event.si; - + MigLayout migLayout = new MigLayout("insets 15, left, gap 30, fill"); JPanel mainPanel = new JPanel(migLayout); final Dimension parentSize = JOptionPane.getRootFrame().getSize(); Dimension maxSize = new Dimension(1400, parentSize.height - 100); mainPanel.setMaximumSize(maxSize); - mainPanel.setOpaque(false); + mainPanel.setOpaque(false); // Big Image addBigImageToStackModalPanel(mainPanel, si); - + // Text addTextToStackModalPanel(mainPanel,sa,si); - + // Small images int numSmallImages = 0; - + // If current effect is a triggered/activated ability of an enchantment card, I want to show the enchanted card GameEntityView enchantedEntityView = null; Card hostCard = sa.getHostCard(); @@ -1308,45 +1326,45 @@ public final class CMatchUI && !sa.getRootAbility().getPaidList("Sacrificed").isEmpty()) { // If the player activated its ability by sacrificing the enchantment, the enchantment has not anything attached anymore and the ex-enchanted card has to be searched in other ways.. for example, the green enchantment "Carapace" enchantedEntity = sa.getRootAbility().getPaidList("Sacrificed").get(0).getEnchantingCard(); - if(enchantedEntity != null) { - enchantedEntityView = enchantedEntity.getView(); + if(enchantedEntity != null) { + enchantedEntityView = enchantedEntity.getView(); numSmallImages++; } } } - + // If current effect is a triggered ability, I want to show the triggering card if present SpellAbility sourceSA = (SpellAbility) si.getTriggeringObject(AbilityKey.SourceSA); CardView sourceCardView = null; if(sourceSA != null) { sourceCardView = sourceSA.getHostCard().getView(); numSmallImages++; - } + } // I also want to show each type of targets (both cards and players) List targets = getTargets(si,new ArrayList()); numSmallImages = numSmallImages + targets.size(); - + // Now I know how many small images - on to render them if(enchantedEntityView != null) { - addSmallImageToStackModalPanel(enchantedEntityView,mainPanel,numSmallImages); + addSmallImageToStackModalPanel(enchantedEntityView,mainPanel,numSmallImages); } if(sourceCardView != null) { - addSmallImageToStackModalPanel(sourceCardView,mainPanel,numSmallImages); + addSmallImageToStackModalPanel(sourceCardView,mainPanel,numSmallImages); } for(GameEntityView gev : targets) { addSmallImageToStackModalPanel(gev, mainPanel, numSmallImages); - } - - FOptionPane.showOptionDialog(null, "Forge", null, mainPanel, ImmutableList.of(Localizer.getInstance().getMessage("lblOK"))); + } + + FOptionPane.showOptionDialog(null, "Forge", null, mainPanel, ImmutableList.of(Localizer.getInstance().getMessage("lblOK"))); // here the user closed the modal - time to update the next notifiable stack index - + } // In any case, I have to increase the counter nextNotifiableStackIndex++; - + } else { - + // Not yet time to show the modal - schedule the method again, and try again later Runnable tryAgainThread = new Runnable() { @Override @@ -1355,8 +1373,8 @@ public final class CMatchUI } }; GuiBase.getInterface().invokeInEdtLater(tryAgainThread); - - } + + } } private List getTargets(SpellAbilityStackInstance si, List result){ @@ -1380,22 +1398,22 @@ public final class CMatchUI return getTargets(si.getSubInstance(),result); } - + private void addBigImageToStackModalPanel(JPanel mainPanel, SpellAbilityStackInstance si) { StackItemView siv = si.getView(); int rotation = getRotation(si.getCardView()); - FImagePanel imagePanel = new FImagePanel(); - BufferedImage bufferedImage = FImageUtil.getImage(siv.getSourceCard().getCurrentState()); + FImagePanel imagePanel = new FImagePanel(); + BufferedImage bufferedImage = FImageUtil.getImage(siv.getSourceCard().getCurrentState()); imagePanel.setImage(bufferedImage, rotation, AutoSizeImageMode.SOURCE); int imageWidth = 433; int imageHeight = 600; Dimension imagePanelDimension = new Dimension(imageWidth,imageHeight); imagePanel.setMinimumSize(imagePanelDimension); - - mainPanel.add(imagePanel, "cell 0 0, spany 3"); + + mainPanel.add(imagePanel, "cell 0 0, spany 3"); } - + private void addTextToStackModalPanel(JPanel mainPanel, SpellAbility sa, SpellAbilityStackInstance si) { String who = sa.getActivatingPlayer().getName(); String action = sa.isSpell() ? " cast " : sa.isTrigger() ? " triggered " : " activated "; @@ -1409,45 +1427,45 @@ public final class CMatchUI TargetChoices targets = si.getTargetChoices(); sb.append(targets); } - sb.append("."); + sb.append("."); String message1 = sb.toString(); - String message2 = si.getStackDescription(); + String message2 = si.getStackDescription(); String messageTotal = message1 + "\n\n" + message2; - + final FTextArea prompt1 = new FTextArea(messageTotal); prompt1.setFont(FSkin.getFont(21)); prompt1.setAutoSize(true); prompt1.setMinimumSize(new Dimension(475,200)); - mainPanel.add(prompt1, "cell 1 0, aligny top"); + mainPanel.add(prompt1, "cell 1 0, aligny top"); } - + private void addSmallImageToStackModalPanel(GameEntityView gameEntityView, JPanel mainPanel, int numTarget) { if(gameEntityView instanceof CardView) { CardView cardView = (CardView) gameEntityView; - int currRotation = getRotation(cardView); + int currRotation = getRotation(cardView); FImagePanel targetPanel = new FImagePanel(); - BufferedImage bufferedImage = FImageUtil.getImage(cardView.getCurrentState()); + BufferedImage bufferedImage = FImageUtil.getImage(cardView.getCurrentState()); targetPanel.setImage(bufferedImage, currRotation, AutoSizeImageMode.SOURCE); int imageWidth = 217; int imageHeight = 300; Dimension targetPanelDimension = new Dimension(imageWidth,imageHeight); targetPanel.setMinimumSize(targetPanelDimension); - mainPanel.add(targetPanel, "cell 1 1, split " + numTarget+ ", aligny bottom"); + mainPanel.add(targetPanel, "cell 1 1, split " + numTarget+ ", aligny bottom"); } else if(gameEntityView instanceof PlayerView) { PlayerView playerView = (PlayerView) gameEntityView; SkinImage playerAvatar = getPlayerAvatar(playerView, 0); final FLabel lblIcon = new FLabel.Builder().icon(playerAvatar).build(); Dimension dimension = playerAvatar.getSizeForPaint(JOptionPane.getRootFrame().getGraphics()); - mainPanel.add(lblIcon, "cell 1 1, split " + numTarget+ ", w " + dimension.getWidth() + ", h " + dimension.getHeight() + ", aligny bottom"); + mainPanel.add(lblIcon, "cell 1 1, split " + numTarget+ ", w " + dimension.getWidth() + ", h " + dimension.getHeight() + ", aligny bottom"); } - } - + } + private int getRotation(CardView cardView) { final int rotation; if (cardView.isSplitCard()) { String cardName = cardView.getName(); if (cardName.isEmpty()) { cardName = cardView.getAlternateState().getName(); } - + PaperCard pc = StaticData.instance().getCommonCards().getCard(cardName); boolean hasKeywordAftermath = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH); @@ -1459,7 +1477,7 @@ public final class CMatchUI return rotation; } - + @Override public void notifyStackRemoval(GameEventSpellRemovedFromStack event) { // I always decrease the counter @@ -1474,49 +1492,49 @@ public final class CMatchUI createLandPopupPanel(land); } }; - GuiBase.getInterface().invokeInEdtAndWait(createPopupThread); + GuiBase.getInterface().invokeInEdtAndWait(createPopupThread); } private void createLandPopupPanel(Card land) { - + String landPlayedNotificationPolicy = FModel.getPreferences().getPref(FPref.UI_LAND_PLAYED_NOTIFICATION_POLICY); - Player cardController = land.getController(); - boolean isAi = cardController.isAI(); - if(ForgeConstants.LAND_PLAYED_NOTIFICATION_ALWAYS.equals(landPlayedNotificationPolicy) + Player cardController = land.getController(); + boolean isAi = cardController.isAI(); + if(ForgeConstants.LAND_PLAYED_NOTIFICATION_ALWAYS.equals(landPlayedNotificationPolicy) || (ForgeConstants.LAND_PLAYED_NOTIFICATION_AI.equals(landPlayedNotificationPolicy) && (isAi)) || (ForgeConstants.LAND_PLAYED_NOTIFICATION_ALWAYS_FOR_NONBASIC_LANDS.equals(landPlayedNotificationPolicy) && !land.isBasicLand()) || (ForgeConstants.LAND_PLAYED_NOTIFICATION_AI_FOR_NONBASIC_LANDS.equals(landPlayedNotificationPolicy) && !land.isBasicLand()) && (isAi)) { - String title = "Forge"; + String title = "Forge"; List options = ImmutableList.of(Localizer.getInstance().getMessage("lblOK")); - + MigLayout migLayout = new MigLayout("insets 15, left, gap 30, fill"); JPanel mainPanel = new JPanel(migLayout); final Dimension parentSize = JOptionPane.getRootFrame().getSize(); Dimension maxSize = new Dimension(1400, parentSize.height - 100); mainPanel.setMaximumSize(maxSize); - mainPanel.setOpaque(false); - + mainPanel.setOpaque(false); + int rotation = getRotation(land.getView()); - FImagePanel imagePanel = new FImagePanel(); - BufferedImage bufferedImage = FImageUtil.getImage(land.getCurrentState().getView()); + FImagePanel imagePanel = new FImagePanel(); + BufferedImage bufferedImage = FImageUtil.getImage(land.getCurrentState().getView()); imagePanel.setImage(bufferedImage, rotation, AutoSizeImageMode.SOURCE); int imageWidth = 433; int imageHeight = 600; Dimension imagePanelDimension = new Dimension(imageWidth,imageHeight); imagePanel.setMinimumSize(imagePanelDimension); - + mainPanel.add(imagePanel, "cell 0 0, spany 3"); - - String msg = cardController.toString() + " puts " + land.toString() + " into play into " + ZoneType.Battlefield.toString() + "."; - + + String msg = cardController.toString() + " puts " + land.toString() + " into play into " + ZoneType.Battlefield.toString() + "."; + final FTextArea prompt1 = new FTextArea(msg); prompt1.setFont(FSkin.getFont(21)); prompt1.setAutoSize(true); prompt1.setMinimumSize(new Dimension(475,200)); - mainPanel.add(prompt1, "cell 1 0, aligny top"); - - FOptionPane.showOptionDialog(null, title, null, mainPanel, options); - } + mainPanel.add(prompt1, "cell 1 0, aligny top"); + + FOptionPane.showOptionDialog(null, title, null, mainPanel, options); + } } } diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/VAssignGenericAmount.java b/forge-gui-desktop/src/main/java/forge/screens/match/VAssignGenericAmount.java new file mode 100644 index 00000000000..b2ee1fd8784 --- /dev/null +++ b/forge-gui-desktop/src/main/java/forge/screens/match/VAssignGenericAmount.java @@ -0,0 +1,314 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2021 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.screens.match; + +import java.awt.Dialog.ModalityType; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.border.Border; + +import forge.game.GameEntityView; +import forge.game.card.CardView; +import forge.game.player.PlayerView; +import forge.gui.SOverlayUtils; +import forge.toolbox.FButton; +import forge.toolbox.FLabel; +import forge.toolbox.FScrollPane; +import forge.toolbox.FSkin; +import forge.toolbox.FSkin.SkinnedPanel; +import forge.util.Localizer; +import forge.util.TextUtil; +import forge.view.FDialog; +import forge.view.arcane.CardPanel; +import net.miginfocom.swing.MigLayout; + +/** + * Assembles Swing components of assign damage dialog. + * + * This needs a JDialog to maintain a modal state. + * Without the modal state, the PhaseHandler automatically + * moves forward to phase Main2 without assigning damage. + * + *

(V at beginning of class name denotes a view class.) + */ +public class VAssignGenericAmount { + final Localizer localizer = Localizer.getInstance(); + private final CMatchUI matchUI; + + // Width and height of assign dialog + private final int wDlg = 700; + private final int hDlg = 500; + private final FDialog dlg = new FDialog(); + + // Amount storage + private final int totalAmountToAssign; + + private final String lblAmount; + private final JLabel lblTotalAmount; + // Label Buttons + private final FButton btnOK = new FButton(localizer.getMessage("lblOk")); + private final FButton btnReset = new FButton(localizer.getMessage("lblReset")); + + private static class AssignTarget { + public final GameEntityView entity; + public final JLabel label; + public final int max; + public int amount; + + public AssignTarget(final GameEntityView e, final JLabel lbl, int max0) { + entity = e; + label = lbl; + max = max0; + amount = 0; + } + } + + private final List targetsList = new ArrayList<>(); + private final Map targetsMap = new HashMap<>(); + + // Mouse actions + private final MouseAdapter mad = new MouseAdapter() { + @Override + public void mouseEntered(final MouseEvent evt) { + ((CardPanel) evt.getSource()).setBorder(new FSkin.LineSkinBorder(FSkin.getColor(FSkin.Colors.CLR_ACTIVE), 2)); + } + + @Override + public void mouseExited(final MouseEvent evt) { + ((CardPanel) evt.getSource()).setBorder((Border)null); + } + + @Override + public void mousePressed(final MouseEvent evt) { + CardView source = ((CardPanel) evt.getSource()).getCard(); // will be NULL for player + + boolean meta = evt.isControlDown(); + boolean isLMB = SwingUtilities.isLeftMouseButton(evt); + boolean isRMB = SwingUtilities.isRightMouseButton(evt); + + if ( isLMB || isRMB) + assignAmountTo(source, meta, isLMB); + } + }; + + public VAssignGenericAmount(final CMatchUI matchUI, final CardView effectSource, final Map targets, final int amount, final boolean atLeastOne, final String amountLabel) { + this.matchUI = matchUI; + dlg.setTitle(localizer.getMessage("lbLAssignAmountForEffect", amountLabel, effectSource.toString())); + + totalAmountToAssign = amount; + + lblAmount = amountLabel; + lblTotalAmount = new FLabel.Builder().text(localizer.getMessage("lblTotalAmountText", lblAmount)).build(); + + // Top-level UI stuff + final JPanel overlay = SOverlayUtils.genericOverlay(); + final SkinnedPanel pnlMain = new SkinnedPanel(); + pnlMain.setBackground(FSkin.getColor(FSkin.Colors.CLR_THEME2)); + + // Effect Source area + final CardPanel pnlSource = new CardPanel(matchUI, effectSource); + pnlSource.setOpaque(false); + pnlSource.setCardBounds(0, 0, 105, 150); + + final JPanel pnlInfo = new JPanel(new MigLayout("insets 0, gap 0, wrap")); + pnlInfo.setOpaque(false); + pnlInfo.add(lblTotalAmount, "gap 0 0 20px 5px"); + pnlInfo.add(new FLabel.Builder().text(localizer.getMessage("lblLClickAmountMessage", lblAmount)).build(), "gap 0 0 0 5px"); + pnlInfo.add(new FLabel.Builder().text(localizer.getMessage("lblRClickAmountMessage", lblAmount)).build(), "gap 0 0 0 5px"); + + // Targets area + final JPanel pnlTargets = new JPanel(); + pnlTargets.setOpaque(false); + int cols = targets.size(); + final String wrap = "wrap " + cols; + pnlTargets.setLayout(new MigLayout("insets 0, gap 0, ax center, " + wrap)); + + final FScrollPane scrTargets = new FScrollPane(pnlTargets, false); + + // Top row of cards... + for (final Map.Entry e : targets.entrySet()) { + int maxAmount = e.getValue() != null ? e.getValue() : amount; + final AssignTarget at = new AssignTarget(e.getKey(), new FLabel.Builder().text("0").fontSize(18).fontAlign(SwingConstants.CENTER).build(), maxAmount); + addPanelForTarget(pnlTargets, at); + } + + // ... bottom row of labels. + for (final AssignTarget l : targetsList) { + pnlTargets.add(l.label, "w 145px!, h 30px!, gap 5px 5px 0 5px"); + } + + btnOK.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent arg0) { finish(); } }); + btnReset.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent arg0) { resetAssignedAmount(); initialAssignAmount(atLeastOne); } }); + + // Final UI layout + pnlMain.setLayout(new MigLayout("insets 0, gap 0, wrap 2, ax center")); + pnlMain.add(pnlSource, "w 125px!, h 160px!, gap 50px 0 0 15px"); + pnlMain.add(pnlInfo, "gap 20px 0 0 15px"); + pnlMain.add(scrTargets, "w 96%!, gap 2% 0 0 0, pushy, growy, ax center, span 2"); + + final JPanel pnlButtons = new JPanel(new MigLayout("insets 0, gap 0, ax center")); + pnlButtons.setOpaque(false); + pnlButtons.add(btnOK, "w 110px!, h 30px!, gap 0 10px 0 0"); + pnlButtons.add(btnReset, "w 110px!, h 30px!"); + + pnlMain.add(pnlButtons, "ax center, w 350px!, gap 10px 10px 10px 10px, span 2"); + overlay.add(pnlMain); + + pnlMain.getRootPane().setDefaultButton(btnOK); + + initialAssignAmount(atLeastOne); + SOverlayUtils.showOverlay(); + + dlg.setUndecorated(true); + dlg.setContentPane(pnlMain); + dlg.setSize(new Dimension(wDlg, hDlg)); + dlg.setLocation((overlay.getWidth() - wDlg) / 2, (overlay.getHeight() - hDlg) / 2); + dlg.setModalityType(ModalityType.APPLICATION_MODAL); + dlg.setVisible(true); + } + + private void addPanelForTarget(final JPanel pnlTargets, final AssignTarget at) { + CardView cv = null; + if (at.entity instanceof CardView) { + cv = (CardView)at.entity; + } else if (at.entity instanceof PlayerView) { + final PlayerView p = (PlayerView)at.entity; + cv = new CardView(-1, null, at.entity.toString(), p, matchUI.getAvatarImage(p.getLobbyPlayerName())); + } else { + return; + } + final CardPanel cp = new CardPanel(matchUI, cv); + cp.setCardBounds(0, 0, 105, 150); + cp.setOpaque(true); + pnlTargets.add(cp, "w 145px!, h 170px!, gap 5px 5px 3px 3px, ax center"); + cp.addMouseListener(mad); + targetsMap.put(cv, at); + targetsList.add(at); + } + + private void assignAmountTo(CardView source, final boolean meta, final boolean isAdding) { + AssignTarget at = targetsMap.get(source); + int assigned = at.amount; + int leftToAssign = Math.max(0, at.max - assigned); + int amountToAdd = isAdding ? 1 : -1; + int remainingAmount = Math.min(getRemainingAmount(), leftToAssign); + // Left click adds, right click substracts. + // Hold Ctrl to assign to maximum amount + if (meta) { + if (isAdding) { + amountToAdd = leftToAssign > 0 ? leftToAssign : 0; + } else { + amountToAdd = -assigned; + } + } + + if (amountToAdd > remainingAmount) { + amountToAdd = remainingAmount; + } + + if (0 == amountToAdd || amountToAdd + assigned < 0) { + return; + } + + addAssignedAmount(at, amountToAdd); + updateLabels(); + } + + private void initialAssignAmount(boolean atLeastOne) { + if (!atLeastOne) { + updateLabels(); + return; + } + + for(AssignTarget at : targetsList) { + addAssignedAmount(at, 1); + } + updateLabels(); + } + + private void resetAssignedAmount() { + for(AssignTarget at : targetsList) + at.amount = 0; + } + + private void addAssignedAmount(final AssignTarget at, int addedAmount) { + // If we don't have enough left or we're trying to unassign too much return + final int canAssign = getRemainingAmount(); + if (canAssign < addedAmount) { + addedAmount = canAssign; + } + + at.amount = Math.max(0, addedAmount + at.amount); + } + + private int getRemainingAmount() { + int spent = 0; + for(AssignTarget at : targetsList) { + spent += at.amount; + } + return totalAmountToAssign - spent; + } + + /** Updates labels and other UI elements.*/ + private void updateLabels() { + int amountLeft = totalAmountToAssign; + + for ( AssignTarget at : targetsList ) + { + amountLeft -= at.amount; + StringBuilder sb = new StringBuilder(); + sb.append(at.amount); + if (at.max - at.amount == 0) { + sb.append(" (").append(localizer.getMessage("lblMax")).append(")"); + } + at.label.setText(sb.toString()); + } + + lblTotalAmount.setText(TextUtil.concatNoSpace(localizer.getMessage("lblAvailableAmount", lblAmount), ": " , String.valueOf(amountLeft), " (of ", String.valueOf(totalAmountToAssign), ")")); + btnOK.setEnabled(amountLeft == 0); + } + + private void finish() { + if ( getRemainingAmount() > 0 ) + return; + + dlg.dispose(); + SOverlayUtils.hideOverlay(); + } + + public Map getAssignedMap() { + Map result = new HashMap<>(targetsList.size()); + for (AssignTarget at : targetsList) + result.put(at.entity, at.amount); + return result; + } +} 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 ab95d3e297f..f13bbf7c2ea 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 @@ -146,6 +146,12 @@ public class PlayerControllerForTests extends PlayerController { throw new IllegalStateException("Erring on the side of caution here..."); } + @Override + public Map divideShield(Card effectSource, Map affected, int shieldAmount) { + throw new IllegalStateException("Erring on the side of caution here..."); + } + + @Override public Integer announceRequirements(SpellAbility ability, String announce) { throw new IllegalStateException("Erring on the side of caution here..."); diff --git a/forge-gui-mobile/src/forge/screens/match/MatchController.java b/forge-gui-mobile/src/forge/screens/match/MatchController.java index 98f2d64cf0b..0ea00c3c30e 100644 --- a/forge-gui-mobile/src/forge/screens/match/MatchController.java +++ b/forge-gui-mobile/src/forge/screens/match/MatchController.java @@ -49,6 +49,7 @@ import forge.model.FModel; import forge.player.PlayerZoneUpdate; import forge.player.PlayerZoneUpdates; import forge.screens.match.views.VAssignCombatDamage; +import forge.screens.match.views.VAssignGenericAmount; import forge.screens.match.views.VPhaseIndicator; import forge.screens.match.views.VPhaseIndicator.PhaseLabel; import forge.screens.match.views.VPlayerPanel; @@ -395,6 +396,18 @@ public class MatchController extends AbstractGuiGame { }.invokeAndWait(); } + @Override + public Map assignGenericAmount(final CardView effectSource, final Map targets, + final int amount, final boolean atLeastOne, final String amountLabel) { + return new WaitCallback>() { + @Override + public void run() { + final VAssignGenericAmount v = new VAssignGenericAmount(effectSource, targets, amount, atLeastOne, amountLabel, this); + v.show(); + } + }.invokeAndWait(); + } + @Override public void updateManaPool(final Iterable manaPoolUpdate) { for (final PlayerView p : manaPoolUpdate) { diff --git a/forge-gui-mobile/src/forge/screens/match/views/VAssignGenericAmount.java b/forge-gui-mobile/src/forge/screens/match/views/VAssignGenericAmount.java new file mode 100644 index 00000000000..1a17df91966 --- /dev/null +++ b/forge-gui-mobile/src/forge/screens/match/views/VAssignGenericAmount.java @@ -0,0 +1,348 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.screens.match.views; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.badlogic.gdx.utils.Align; + +import forge.Forge; +import forge.Graphics; +import forge.assets.FImage; +import forge.assets.FSkinColor; +import forge.assets.FSkinColor.Colors; +import forge.assets.FSkinFont; +import forge.assets.FSkinImage; +import forge.card.CardZoom; +import forge.game.GameEntityView; +import forge.game.card.CardView; +import forge.game.player.PlayerView; +import forge.screens.match.MatchController; +import forge.toolbox.FCardPanel; +import forge.toolbox.FContainer; +import forge.toolbox.FDialog; +import forge.toolbox.FDisplayObject; +import forge.toolbox.FEvent; +import forge.toolbox.FEvent.FEventHandler; +import forge.toolbox.FLabel; +import forge.toolbox.FOptionPane; +import forge.toolbox.FScrollPane; +import forge.util.Callback; +import forge.util.CardTranslation; +import forge.util.Localizer; +import forge.util.TextUtil; +import forge.util.Utils; +import forge.util.WaitCallback; + +public class VAssignGenericAmount extends FDialog { + private static final float CARD_GAP_X = Utils.scale(10); + private static final float ADD_BTN_HEIGHT = Utils.AVG_FINGER_HEIGHT * 0.75f; + + private final Callback> callback; + private final int totalAmountToAssign; + + private final String lblAmount; + private final FLabel lblTotalAmount; + + private final EffectSourcePanel pnlSource; + private final TargetsPanel pnlTargets; + + private final List targetsList = new ArrayList<>(); + private final Map targetsMap = new HashMap<>(); + + /** Constructor. + * + * @param attacker0 {@link forge.game.card.Card} + * @param targets Map, map of GameEntity and its maximum assignable amount + * @param amount Total amount to be assigned + * @param atLeastOne Must assign at least one amount to each target + */ + public VAssignGenericAmount(final CardView effectSource, final Map targets, final int amount, final boolean atLeastOne, final String amountLabel, final WaitCallback> waitCallback) { + super(Localizer.getInstance().getMessage("lbLAssignAmountForEffect", amountLabel, CardTranslation.getTranslatedName(effectSource.getName())) , 2); + + callback = waitCallback; + totalAmountToAssign = amount; + + lblAmount = amountLabel; + lblTotalAmount = add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblTotalAmountText", lblAmount)).align(Align.center).build()); + + pnlSource = add(new EffectSourcePanel(effectSource)); + pnlTargets = add(new TargetsPanel(targets)); + + initButton(0, Localizer.getInstance().getMessage("lblOK"), new FEventHandler() { + @Override + public void handleEvent(FEvent e) { + finish(); + } + }); + initButton(1, Localizer.getInstance().getMessage("lblReset"), new FEventHandler() { + @Override + public void handleEvent(FEvent e) { + resetAssignedDamage(); + initialAssignAmount(atLeastOne); + } + }); + + initialAssignAmount(atLeastOne); + } + + @Override + protected float layoutAndGetHeight(float width, float maxHeight) { + float padding = FOptionPane.PADDING; + float w = width - 2 * padding; + + float x = padding; + float labelHeight = lblTotalAmount.getAutoSizeBounds().height; + float y = maxHeight - labelHeight + padding; + + float dtOffset = ADD_BTN_HEIGHT + targetsList.get(0).label.getAutoSizeBounds().height; + float cardPanelHeight = (y - dtOffset - labelHeight - 3 * padding) / 2; + float cardPanelWidth = cardPanelHeight / FCardPanel.ASPECT_RATIO; + + y = padding; + pnlSource.setBounds(x + (w - cardPanelWidth) / 2, y, cardPanelWidth, cardPanelHeight); + + y += cardPanelHeight + padding; + lblTotalAmount.setBounds(x, y, w, labelHeight); + + y += labelHeight + padding; + pnlTargets.setBounds(0, y, width, cardPanelHeight + dtOffset); + + return maxHeight; + } + + private class TargetsPanel extends FScrollPane { + private TargetsPanel(final Map targets) { + for (final Map.Entry e : targets.entrySet()) { + addDamageTarget(e.getKey(), e.getValue()); + } + } + + private void addDamageTarget(GameEntityView entity, int max) { + AssignTarget at = add(new AssignTarget(entity, max)); + targetsMap.put(entity, at); + targetsList.add(at); + } + + @Override + protected ScrollBounds layoutAndGetScrollBounds(float visibleWidth, float visibleHeight) { + float cardPanelHeight = visibleHeight - ADD_BTN_HEIGHT - targetsList.get(0).label.getAutoSizeBounds().height; + float width = cardPanelHeight / FCardPanel.ASPECT_RATIO; + float dx = width + CARD_GAP_X; + + float x = (visibleWidth - targetsList.size() * dx + CARD_GAP_X) / 2; + if (x < FOptionPane.PADDING) { + x = FOptionPane.PADDING; + } + + for (AssignTarget at : targetsList) { + at.setBounds(x, 0, width, visibleHeight); + x += dx; + } + return new ScrollBounds(x - CARD_GAP_X + FOptionPane.PADDING, visibleHeight); + } + } + + private class AssignTarget extends FContainer { + private final GameEntityView entity; + private final FDisplayObject obj; + private final FLabel label, btnSubtract, btnAdd; + private final int max; + private int amount; + + public AssignTarget(GameEntityView entity0, int max0) { + entity = entity0; + max = max0; + if (entity instanceof CardView) { + obj = add(new EffectSourcePanel((CardView)entity)); + } + else if (entity instanceof PlayerView) { + PlayerView player = (PlayerView)entity; + obj = add(new MiscTargetPanel(player.getName(), MatchController.getPlayerAvatar(player))); + } + else { + obj = add(new MiscTargetPanel(entity.toString(), FSkinImage.UNKNOWN)); + } + label = add(new FLabel.Builder().text("0").font(FSkinFont.get(18)).align(Align.center).build()); + btnSubtract = add(new FLabel.ButtonBuilder().icon(FSkinImage.MINUS).command(new FEventHandler() { + @Override + public void handleEvent(FEvent e) { + assignAmountTo(entity, false); + } + }).build()); + btnAdd = add(new FLabel.ButtonBuilder().icon(Forge.hdbuttons ? FSkinImage.HDPLUS : FSkinImage.PLUS).command(new FEventHandler() { + @Override + public void handleEvent(FEvent e) { + assignAmountTo(entity, true); + } + }).build()); + } + + @Override + protected void doLayout(float width, float height) { + float y = 0; + obj.setBounds(0, y, width, FCardPanel.ASPECT_RATIO * width); + y += obj.getHeight(); + + label.setBounds(0, y, width, label.getAutoSizeBounds().height); + y += label.getHeight(); + + float buttonSize = (width - FOptionPane.PADDING) / 2; + btnSubtract.setBounds(0, y, buttonSize, ADD_BTN_HEIGHT); + btnAdd.setBounds(width - buttonSize, y, buttonSize, ADD_BTN_HEIGHT); + } + } + + private static class EffectSourcePanel extends FCardPanel { + private EffectSourcePanel(CardView card) { + super(card); + } + + @Override + public boolean tap(float x, float y, int count) { + CardZoom.show(getCard()); + return true; + } + + @Override + public boolean longPress(float x, float y) { + CardZoom.show(getCard()); + return true; + } + + @Override + protected float getPadding() { + return 0; + } + } + + private static class MiscTargetPanel extends FDisplayObject { + private static final FSkinFont FONT = FSkinFont.get(18); + private static final FSkinColor FORE_COLOR = FSkinColor.get(Colors.CLR_TEXT); + private final String name; + private final FImage image; + + private MiscTargetPanel(String name0, FImage image0) { + name = name0; + image = image0; + } + + @Override + public void draw(Graphics g) { + float w = getWidth(); + float h = getHeight(); + g.drawImage(image, 0, 0, w, w); + g.drawText(name, FONT, FORE_COLOR, 0, w, w, h - w, false, Align.center, true); + } + } + + private void assignAmountTo(GameEntityView source, boolean isAdding) { + AssignTarget at = targetsMap.get(source); + int assigned = at.amount; + int leftToAssign = Math.max(0, at.max - assigned); + int amountToAdd = isAdding ? 1 : -1; + int remainingAmount = Math.min(getRemainingAmount(), leftToAssign); + + if (amountToAdd > remainingAmount) { + amountToAdd = remainingAmount; + } + + if (0 == amountToAdd || amountToAdd + assigned < 0) { + return; + } + + addAssignedAmount(at, amountToAdd); + updateLabels(); + } + + private void initialAssignAmount(boolean atLeastOne) { + if (!atLeastOne) { + updateLabels(); + return; + } + + for(AssignTarget at : targetsList) { + addAssignedAmount(at, 1); + } + updateLabels(); + } + + private void resetAssignedDamage() { + for (AssignTarget at : targetsList) { + at.amount = 0; + } + } + + private void addAssignedAmount(final AssignTarget at, int addedAmount) { + // If we don't have enough left or we're trying to unassign too much return + int canAssign = getRemainingAmount(); + if (canAssign < addedAmount) { + addedAmount = canAssign; + } + + at.amount = Math.max(0, addedAmount + at.amount); + } + + private int getRemainingAmount() { + int spent = 0; + for (AssignTarget at : targetsList) { + spent += at.amount; + } + return totalAmountToAssign - spent; + } + + /** Updates labels and other UI elements.*/ + private void updateLabels() { + int amountLeft = totalAmountToAssign; + + for (AssignTarget at : targetsList) { + amountLeft -= at.amount; + StringBuilder sb = new StringBuilder(); + sb.append(at.amount); + if (at.max - at.amount == 0) { + sb.append(" (").append(Localizer.getInstance().getMessage("lblMax")).append(")"); + } + at.label.setText(sb.toString()); + } + + lblTotalAmount.setText(TextUtil.concatNoSpace(Localizer.getInstance().getMessage("lblAvailableAmount", lblAmount) + ": ", + String.valueOf(amountLeft), " (of ", String.valueOf(totalAmountToAssign), ")")); + setButtonEnabled(0, amountLeft == 0); + } + + // Dumps damage onto cards. Damage must be stored first, because if it is + // assigned dynamically, the cards die off and further damage to them can't + // be modified. + private void finish() { + if (getRemainingAmount() > 0) { + return; + } + hide(); + callback.run(getAssignedMap()); + } + + public Map getAssignedMap() { + Map result = new HashMap<>(targetsList.size()); + for (AssignTarget at : targetsList) + result.put(at.entity, at.amount); + return result; + } +} diff --git a/forge-gui/res/cardsfolder/d/divine_deflection.txt b/forge-gui/res/cardsfolder/d/divine_deflection.txt new file mode 100644 index 00000000000..03207af8e6a --- /dev/null +++ b/forge-gui/res/cardsfolder/d/divine_deflection.txt @@ -0,0 +1,13 @@ +Name:Divine Deflection +ManaCost:X W +Types:Instant +A:SP$ StoreSVar | Cost$ X W | SVar$ ShieldAmount | Type$ Calculate | Expression$ X | SubAbility$ DBEffect | StackDescription$ SpellDescription | SpellDescription$ Prevent the next X damage that would be dealt to you and/or permanents you control this turn. If damage is prevented this way, CARDNAME deals that much damage to any target. +SVar:DBEffect:DB$ Effect | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target to deal prevented damage to | ReplacementEffects$ ReDamage | RememberObjects$ Targeted +SVar:ReDamage:Event$ DamageDone | ActiveZones$ Command | ValidTarget$ You,Permanent.YouCtrl | ReplaceWith$ PreventDamage | PreventionEffect$ NextN | Description$ Prevent the next X damage that would be dealt to you and/or permanents you control this turn. If damage is prevented this way, EFFECTSOURCE deals that much damage to any target. +SVar:PreventDamage:DB$ ReplaceDamage | Amount$ ShieldAmount | DivideShield$ True | SubAbility$ DBDealDamage +SVar:DBDealDamage:DB$ DealDamage | NumDmg$ Y | Defined$ Remembered | DamageSource$ EffectSource +SVar:X:Count$xPaid +SVar:ShieldAmount:Number$0 +SVar:Y:ReplaceCount$DamageAmount +AI:RemoveDeck:All +Oracle:Prevent the next X damage that would be dealt to you and/or permanents you control this turn. If damage is prevented this way, Divine Deflection deals that much damage to any target. diff --git a/forge-gui/res/cardsfolder/h/harms_way.txt b/forge-gui/res/cardsfolder/h/harms_way.txt new file mode 100644 index 00000000000..33fd78c0f01 --- /dev/null +++ b/forge-gui/res/cardsfolder/h/harms_way.txt @@ -0,0 +1,11 @@ +Name:Harm's Way +ManaCost:W +Types:Instant +A:SP$ ChooseSource | Cost$ W | Choices$ Card,Emblem | AILogic$ NeedsPrevention | SubAbility$ DBEffect | StackDescription$ SpellDescription | SpellDescription$ The next 2 damage that a source of your choice would deal to you and/or permanents you control this turn is dealt to any target instead. +SVar:DBEffect:DB$ Effect | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target to redirect damage to | ReplacementEffects$ ReDamage | RememberObjects$ Targeted | SubAbility$ DBCleanup +SVar:ReDamage:Event$ DamageDone | ActiveZones$ Command | ValidTarget$ You,Permanent.YouCtrl | ValidSource$ Card.ChosenCard,Emblem.ChosenCard | ReplaceWith$ SplitDamage | DamageTarget$ Remembered | Description$ The next 2 damage that a source of your choice would deal to you and/or permanents you control this turn is dealt to any target instead. +SVar:SplitDamage:DB$ ReplaceSplitDamage | DamageTarget$ Remembered | VarName$ ShieldAmount | DivideShield$ True +SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True +SVar:ShieldAmount:Number$2 +AI:RemoveDeck:All +Oracle:The next 2 damage that a source of your choice would deal to you and/or permanents you control this turn is dealt to any target instead. diff --git a/forge-gui/res/cardsfolder/r/refraction_trap.txt b/forge-gui/res/cardsfolder/r/refraction_trap.txt new file mode 100644 index 00000000000..4a5a76fe006 --- /dev/null +++ b/forge-gui/res/cardsfolder/r/refraction_trap.txt @@ -0,0 +1,15 @@ +Name:Refraction Trap +ManaCost:3 W +Types:Instant Trap +SVar:AltCost:Cost$ W | CheckSVar$ X | SVarCompare$ GE1 | Description$ If an opponent cast a red instant or sorcery spell this turn, you may pay {W} rather than pay this spell's mana cost. +A:SP$ ChooseSource | Cost$ 3 W | Choices$ Card,Emblem | AILogic$ NeedsPrevention | SubAbility$ DBEffect | StackDescription$ SpellDescription | SpellDescription$ Prevent the next 3 damage that a source of your choice would deal to you and/or permanents you control this turn. If damage is prevented this way, CARDNAME deals that much damage to any target. +SVar:DBEffect:DB$ Effect | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target to deal prevented damage to | ReplacementEffects$ ReDamage | RememberObjects$ Targeted | SubAbility$ DBCleanup +SVar:ReDamage:Event$ DamageDone | ActiveZones$ Command | ValidTarget$ You,Permanent.YouCtrl | ValidSource$ Card.ChosenCard,Emblem.ChosenCard | ReplaceWith$ PreventDamage | PreventionEffect$ NextN | Description$ Prevent the next 3 damage that a source of your choice would deal to you and/or permanents you control this turn. If damage is prevented this way, EFFECTSOURCE deals that much damage to any target. +SVar:PreventDamage:DB$ ReplaceDamage | Amount$ ShieldAmount | DivideShield$ True | SubAbility$ DBDealDamage +SVar:DBDealDamage:DB$ DealDamage | NumDmg$ Y | Defined$ Remembered | DamageSource$ EffectSource +SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True +SVar:X:Count$ThisTurnCast_Instant.Red+OppCtrl,Sorcery.Red+OppCtrl +SVar:ShieldAmount:Number$3 +SVar:Y:ReplaceCount$DamageAmount +AI:RemoveDeck:All +Oracle:If an opponent cast a red instant or sorcery spell this turn, you may pay {W} rather than pay this spell's mana cost.\nPrevent the next 3 damage that a source of your choice would deal to you and/or permanents you control this turn. If damage is prevented this way, Refraction Trap deals that much damage to any target. diff --git a/forge-gui/res/cardsfolder/s/shining_shoal.txt b/forge-gui/res/cardsfolder/s/shining_shoal.txt new file mode 100644 index 00000000000..ad146dbae5b --- /dev/null +++ b/forge-gui/res/cardsfolder/s/shining_shoal.txt @@ -0,0 +1,16 @@ +Name:Shining Shoal +ManaCost:X W W +Types:Instant Arcane +SVar:AltCost:Cost$ ExileFromHand<1/Card.White+Other/white card> | Description$ You may exile a white card with mana value X from your hand rather than pay this spell's mana cost. +A:SP$ ChooseSource | Cost$ X W W | Choices$ Card,Emblem | AILogic$ NeedsPrevention | SubAbility$ DBStoreSVar | StackDescription$ SpellDescription | SpellDescription$ The next X damage that a source of your choice would deal to you and/or creatures you control this turn is dealt to any target instead. +SVar:DBStoreSVar:DB$ StoreSVar | SVar$ ShieldAmount | Type$ Calculate | Expression$ Z | SubAbility$ DBEffect +SVar:DBEffect:DB$ Effect | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target to redirect damage to | ReplacementEffects$ ReDamage | RememberObjects$ Targeted | SubAbility$ DBCleanup +SVar:ReDamage:Event$ DamageDone | ActiveZones$ Command | ValidTarget$ You,Creature.YouCtrl | ValidSource$ Card.ChosenCard,Emblem.ChosenCard | ReplaceWith$ SplitDamage | DamageTarget$ Remembered | Description$ The next X damage that a source of your choice would deal to you and/or creatures you control this turn is dealt to any target instead. +SVar:SplitDamage:DB$ ReplaceSplitDamage | DamageTarget$ Remembered | VarName$ ShieldAmount | DivideShield$ True +SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True +SVar:X:Count$xPaid +SVar:Y:Exiled$CardManaCost +SVar:Z:SVar$Y/Plus.X +SVar:ShieldAmount:Number$0 +AI:RemoveDeck:All +Oracle:You may exile a white card with mana value X from your hand rather than pay this spell's mana cost.\nThe next X damage that a source of your choice would deal to you and/or creatures you control this turn is dealt to any target instead. diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index a40d4ecc4ef..d15e1ff73c8 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -361,6 +361,15 @@ lblTotalDamageText=Gesamtschaden: Unbekannt lblAssignRemainingText=Verteile verbleibenden Schaden unter tödlich Verwundeten lblLethal=Tödlich lblAvailableDamagePoints=Verfügbare Schadenspunkte +#VAssignGenericAmount.java +# The {0} below should be amount label (like "shield" or "damage"), and {1} will be the name of the effect source +lbLAssignAmountForEffect=Assign {0} by {1} +lblLClickAmountMessage=Left click: Assign 1 {0}. (Left Click + Control): Assign maximum {0} points +lblRClickAmountMessage=Right click: Unassign 1 {0}. (Right Click + Control): Unassign all {0} points. +lblTotalAmountText=Available {0} points: Unknown +lblAvailableAmount=Available {0} points +lblMax=Max +lblShield=shield #KeyboardShortcuts.java lblSHORTCUT_SHOWSTACK=Duell: Zeige Stapelfenster lblSHORTCUT_SHOWCOMBAT=Duell: Zeige Kampffenster @@ -416,7 +425,7 @@ lblSelectOpponentDeck=Wähle das Deck des Gegners lblGenerateNewDeck=Erzeuge ein neues Deck lblRandomTheme=Zufälliges Deck lblTestDeck=Teste Deck -lblLoading=Lade +lblLoading=Lade #GameType.java lblSealed=Sealed lblDraft=Draft @@ -1131,7 +1140,7 @@ lblSwipeUpTo=Wische hoch für %s lblSwipeDownDetailView=Wische runter für Detailansicht lblSwipeDownPictureView=Wische runter für Bildansicht #VGameMenu.java -lblShowWinLoseOverlay=Zeige Sieg/Verlust-Anzeige +lblShowWinLoseOverlay=Zeige Sieg/Verlust-Anzeige lblNoPlayerPriorityNoDeckListViewed=Kein Spieler hat Priorität, daher keine Decklistenanzeige möglich. #FilesPage.java lblFiles=Dateien @@ -2276,7 +2285,7 @@ lblConquestName=Eroberung-Name #HumanCostDecision.java lblChooseXValueForCard={0} - Wähle Wert für X lblSelectOneSameNameCardToDiscardAlreadyChosen=Wähle eine Karte mit dem gleichen Namen zum Abwerfen. Bereits gewählt: -lblSelectOneDifferentNameCardToDiscardAlreadyChosen=Wähle eine Karte mit einem anderen Namen zum Abwerfen. Bereits gewählt: +lblSelectOneDifferentNameCardToDiscardAlreadyChosen=Wähle eine Karte mit einem anderen Namen zum Abwerfen. Bereits gewählt: lblSelectNMoreTargetTypeCardToDiscard=Wähle {0} weitere {1} zum Abwerfen. lblDoYouWantCardDealNDamageToYou=Möchtest du, daß {0} dir {1} Schaden zufügt? lblDrawNCardsConfirm=Ziehe {0} Karte(n)? @@ -2672,7 +2681,7 @@ lblDetectedInvalidHostAddress=Ungültige Host-Adresse ({0}) wurde festgestellt. #Player.java lblChooseACompanion=Wähle einen Gefährten lblChooseAColorFor=Wähle eine Farbe für {0} -lblRevealFaceDownCards=Enthülle verdeckte Karten von +lblRevealFaceDownCards=Enthülle verdeckte Karten von lblLearnALesson=Lerne eine Lektion #QuestPreferences.java lblWildOpponentNumberError=Anzahl der Wild-Gegner kann nur 0 bis 3 sein diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index e1a110cdbe6..80052a3e399 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -18,8 +18,8 @@ lblerrLoadingLayoutFile=Your %s layout file could not be read. It will be delete lblLoadingQuest=Loading quest... #FScreen.java #translate lblHomeWithSpaces,lblDeckEditorWithSpaces need keep spaces in text -lblHomeWithSpaces=Home -lblDeckEditorWithSpaces=Deck Editor +lblHomeWithSpaces=Home +lblDeckEditorWithSpaces=Deck Editor lblWorkshop=Workshop lblBacktoHome=Back to Home lblCloseEditor=Close Editor @@ -52,7 +52,7 @@ btnResetJavaFutureCompatibilityWarnings=Reset Java Compatibility Warnings btnClearImageCache=Clear Image Cache btnTokenPreviewer=Token Previewer btnCopyToClipboard=Copy to Clipboard -cbpAutoUpdater=Auto updater +cbpAutoUpdater=Auto updater nlAutoUpdater=Select the release channel to use for updating Forge cbpSelectLanguage=Language nlSelectLanguage=Select Language (Excluded Game part. Still a work in progress) (RESTART REQUIRED) @@ -361,6 +361,15 @@ lblTotalDamageText=Available damage points: Unknown lblAssignRemainingText=Distribute the remaining damage points among lethally wounded entities lblLethal=Lethal lblAvailableDamagePoints=Available damage points +#VAssignGenericAmount.java +# The {0} below should be amount label (like "shield" or "damage"), and {1} will be the name of the effect source +lbLAssignAmountForEffect=Assign {0} by {1} +lblLClickAmountMessage=Left click: Assign 1 {0}. (Left Click + Control): Assign maximum {0} points +lblRClickAmountMessage=Right click: Unassign 1 {0}. (Right Click + Control): Unassign all {0} points. +lblTotalAmountText=Available {0} points: Unknown +lblAvailableAmount=Available {0} points +lblMax=Max +lblShield=shield #KeyboardShortcuts.java lblSHORTCUT_SHOWSTACK=Match: show stack panel lblSHORTCUT_SHOWCOMBAT=Match: show combat panel @@ -416,7 +425,7 @@ lblSelectOpponentDeck=Select Opponent''s Deck lblGenerateNewDeck=Generate New Deck lblRandomTheme=Random Theme lblTestDeck=Test Deck -lblLoading=Loading +lblLoading=Loading #GameType.java lblSealed=Sealed lblDraft=Draft @@ -763,8 +772,8 @@ lblWinsperDraftRotation=Wins per Draft Rotation ttWinsperDraftRotation=If a Draft is not played for this many match wins, it will be removed or replaced. lblRotationType=Rotation Type ttRotationType=If set to 0, old drafts disappear, if set to 1, they are replaced with another one using different sets. -lblWildOpponentNumber=Number of Wild Opponents -lblWildOpponentMultiplier=Wild Multiplier +lblWildOpponentNumber=Number of Wild Opponents +lblWildOpponentMultiplier=Wild Multiplier #StatTypeFilter.java lblclicktotoogle=click to toggle the filter, right-click to show only #SItemManagerUtil.java @@ -1193,7 +1202,7 @@ lblSelectAttackTarget= or select player/planeswalker you wish to attack. lblSelectBandingTarget= To attack as a band, select an attacking creature to activate its ''band'' then select another to join it. #InputBlock.java lblSelectBlockTarget=Select another attacker to declare blockers for. -lblSelectBlocker=Select creatures to block +lblSelectBlocker=Select creatures to block lblOrSelectBlockTarget= or select another attacker to declare blockers for. lblMorph=Morph #PlayerControllerHuman.java @@ -1569,9 +1578,9 @@ lblCreature=Creature btnRestartRound=Restart Round btnTournamentInfo=Tournament Info btnNextRound=Next Round -btnWonRound=YOU HAVE WON ROUND +btnWonRound=YOU HAVE WON ROUND btnWonTournament=***CONGRATULATIONS! YOU HAVE WON THE TOURNAMENT!*** -btnLoseRound=YOU HAVE LOST ON ROUND +btnLoseRound=YOU HAVE LOST ON ROUND btnQuit=Quit btnContinue=Continue btnRestart=Restart @@ -1588,8 +1597,8 @@ lblFailedGauntlet=You have failed to pass the gauntlet. lblLeaveTournamentDraftWarning1=If you leave now, this tournament will be forever gone.\nYou will keep the cards you drafted, but will receive no other prizes.\n\nWould you still like to quit the tournament? lblLeaveTournamentDraftWarning2=You have matches left to play!\nLeaving the tournament early will forfeit your potential future winnings.\nYou will still receive winnings as if you conceded your next match and you will keep the cards you drafted.\n\nWould you still like to quit the tournament? lblReallyQuit=Really Quit? -lblForPlacing=For placing -lblHaveBeAward=, you have been awarded +lblForPlacing=For placing +lblHaveBeAward=, you have been awarded lblTournamentReward=Tournament Reward lblParticipateingTournamentReward=For participating in the tournament, you have been awarded the following promotional card: lblCreditsAwarded=Credits Awarded @@ -1608,22 +1617,22 @@ lblWouldLikeSaveDraft=Would you like to save this draft to the regular draft mod lblSaveDraft=Save Draft lblNoAvailableDraftsMessage=You do not have any draft-able sets unlocked!\nCome back later when you''ve unlocked more sets. lblNoAvailableDrafts=No Available Drafts -lblEntryFeeOfDraftTournament=The entry fee for this booster draft tournament is +lblEntryFeeOfDraftTournament=The entry fee for this booster draft tournament is lblWouldLikeCreateTournament= credits.\nWould you like to spend a token and create this tournament? lblCreatingDraftTournament=Creating a Booster Draft Tournament -lblUnexpectedCreatingDraftTournament=Unexpected error when creating a draft tournament +lblUnexpectedCreatingDraftTournament=Unexpected error when creating a draft tournament lblPleaseReportBug=. Please report this as a bug. -lbl1stPlace=1st Place: -lbl2ndPlace=2nd Place: -lbl3rdPlace=3rd Place: -lbl4thPlace=4th Place: +lbl1stPlace=1st Place: +lbl2ndPlace=2nd Place: +lbl3rdPlace=3rd Place: +lbl4thPlace=4th Place: lblTime= time lblCollectPrizes=Collect Prizes lblCurrentlyInDraft=You are currently in a draft.\nYou should leave or finish that draft before starting another. -lblYouNeed=You need +lblYouNeed=You need lblMoreCredits=more credits to enter this tournament. lblNotEnoughCredits=Not Enough Credits -lblTournamentCosts=This tournament costs +lblTournamentCosts=This tournament costs lblSureEnterTournament= credits to enter.\nAre you sure you wish to enter? lblEnterDraftTournament=Enter Draft Tournament? lblLeaveDraftConfirm=This will end the current draft and you will not be able to join this tournament again.\nYour credits will be refunded and the draft will be removed.\n\nLeave anyway? @@ -1683,7 +1692,7 @@ lblWinsForRankIncrease=Wins For Rank Increase lblWinsForMediumAI=Wins For Medium AI lblWinsForHardAI=Wins For Hard AI lblWinsForExpertAI=Wins For Expert AI -lblSaveFailed=Save Failed - +lblSaveFailed=Save Failed - #QuestSpellShopScreen.java lblMaximumSellingCredits=Maximum selling price is %d credits. lblSellCardsAt=Selling cards at @@ -2274,8 +2283,8 @@ lblHistoriiansWillRecallYourConquestAs=Historians will recall your conquest as: lblConquestName=Conquest Name #HumanCostDecision.java lblChooseXValueForCard={0} - Choose a Value for X -lblSelectOneSameNameCardToDiscardAlreadyChosen=Select one of the cards with the same name to discard. Already chosen: -lblSelectOneDifferentNameCardToDiscardAlreadyChosen=Select one of the cards with a different name to discard. Already chosen: +lblSelectOneSameNameCardToDiscardAlreadyChosen=Select one of the cards with the same name to discard. Already chosen: +lblSelectOneDifferentNameCardToDiscardAlreadyChosen=Select one of the cards with a different name to discard. Already chosen: lblSelectNMoreTargetTypeCardToDiscard=Select {0} more {1} to discard. lblDoYouWantCardDealNDamageToYou=Do you want {0} to deal {1} damage to you? lblDrawNCardsConfirm=Draw {0} Card(s)? @@ -2312,7 +2321,7 @@ lblRemoveNTargetCounterFromCardPayCostConfirm=Pay Cost: Remove {0} {1} counter f lblRemoveCountersFromAInZoneCard=Remove counter(s) from a card in {0} lblSacrificeCardConfirm=Sacrifice {0}? lblSelectATargetToSacrifice=Select a {0} to sacrifice ({1} left) -lblSelectOneOfCardsToTapAlreadyChosen=Select one of the cards to tap. Already chosen: +lblSelectOneOfCardsToTapAlreadyChosen=Select one of the cards to tap. Already chosen: lblSelectACreatureToTap=Select a creature to tap. lblEnoughValidCardNotToPayTheCost=Not enough valid cards left to tap to pay the cost. lblCostPaymentInvalid=Cost payment invalid @@ -2671,7 +2680,7 @@ lblDetectedInvalidHostAddress=Invalid host address ({0}) was detected. #Player.java lblChooseACompanion=Choose a companion lblChooseAColorFor=Choose a color for {0} -lblRevealFaceDownCards=Revealing face-down cards from +lblRevealFaceDownCards=Revealing face-down cards from lblLearnALesson=Learn a Lesson #QuestPreferences.java lblWildOpponentNumberError=Wild Opponents can only be 0 to 3 diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index 6554f738261..fa7f9155b55 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -18,8 +18,8 @@ lblerrLoadingLayoutFile=No se pudo leer el archivo de diseño %s. Se eliminará lblLoadingQuest=Cargando datos de aventura.... #FScreen.java #translate lblHomeWithSpaces,lblDeckEditorWithSpaces need keep spaces in text -lblHomeWithSpaces=Inicio -lblDeckEditorWithSpaces=Editor de mazos +lblHomeWithSpaces=Inicio +lblDeckEditorWithSpaces=Editor de mazos lblWorkshop=Taller lblBacktoHome=Volver a Inicio lblCloseEditor=Cerrar editor @@ -361,6 +361,15 @@ lblTotalDamageText=Puntos de daño disponibles: Desconocido lblAssignRemainingText=Distribuye los puntos de daño restantes entre las entidades letalmente heridas. lblLethal=Letal lblAvailableDamagePoints=Puntos de daño disponibles +#VAssignGenericAmount.java +# The {0} below should be amount label (like "shield" or "damage"), and {1} will be the name of the effect source +lbLAssignAmountForEffect=Assign {0} by {1} +lblLClickAmountMessage=Left click: Assign 1 {0}. (Left Click + Control): Assign maximum {0} points +lblRClickAmountMessage=Right click: Unassign 1 {0}. (Right Click + Control): Unassign all {0} points. +lblTotalAmountText=Available {0} points: Unknown +lblAvailableAmount=Available {0} points +lblMax=Max +lblShield=shield #KeyboardShortcuts.java lblSHORTCUT_SHOWSTACK=Partida: mostrar panel de pila lblSHORTCUT_SHOWCOMBAT=Partida: mostrar panel de combate @@ -416,7 +425,7 @@ lblSelectOpponentDeck=Seleccionar mazo del oponente lblGenerateNewDeck=Generar nuevo mazo lblRandomTheme=Tema aleatorio lblTestDeck=Probar mazo -lblLoading=Cargando +lblLoading=Cargando #GameType.java lblSealed=Sellado lblDraft=Draft @@ -763,8 +772,8 @@ lblWinsperDraftRotation=Victorias rotación de Draft ttWinsperDraftRotation=Si no se juega un Draft para esta cantidad de victorias, se eliminará o reemplazará. lblRotationType=Tipo de rotación ttRotationType=Si se establece en 0, los anteriores Draft desaparecen, si se establece en 1, se reemplazan por otros que utilizan sets diferentes. -lblWildOpponentNumber=Número de oponentes salvajes -lblWildOpponentMultiplier=Multiplicador salvaje +lblWildOpponentNumber=Número de oponentes salvajes +lblWildOpponentMultiplier=Multiplicador salvaje #StatTypeFilter.java lblclicktotoogle=haz clic para alternar el filtro, haz clic con el botón derecho para solo mostrar #SItemManagerUtil.java @@ -1569,9 +1578,9 @@ lblCreature=Criatura btnRestartRound=Reiniciar ronda btnTournamentInfo=Info del torneo btnNextRound=Siguiente ronda -btnWonRound=HAS GANADO LA RONDA +btnWonRound=HAS GANADO LA RONDA btnWonTournament=***¡FELICIDADES! ¡HAS GANADO EL TORNEO!*** -btnLoseRound=HAS PERDIDO EN LA RONDA +btnLoseRound=HAS PERDIDO EN LA RONDA btnQuit=Salir btnContinue=Continuar btnRestart=Reiniciar @@ -1588,8 +1597,8 @@ lblFailedGauntlet=No has podido superar el desafío. lblLeaveTournamentDraftWarning1=Si te retiras ahora, este torneo se acabará para siempre.\nConservarás las cartas que has sacado, pero no recibirás ningún otro premio.\n\n¿Sigues queriendo dejar el torneo? lblLeaveTournamentDraftWarning2=¡Te quedan partidas por jugar!\nSi dejas el torneo antes de tiempo, perderás tus potenciales ganancias futuras.\nSeguirás recibiendo ganancias como si te hubieras rendido en tu próxima partida y te quedarás con las cartas que has sacado.\n\n¿Sigues queriendo dejar el torneo? lblReallyQuit=¿Realmente quieres abandonar? -lblForPlacing=Para colocar -lblHaveBeAward=, has sido premiado +lblForPlacing=Para colocar +lblHaveBeAward=, has sido premiado lblTournamentReward=Recompensa del Torneo lblParticipateingTournamentReward=Por participar en el torneo, se le ha premiado con la siguiente carta promocional: lblCreditsAwarded=Créditos ganados @@ -1608,22 +1617,22 @@ lblWouldLikeSaveDraft=¿Te gustaría guardar este Draft en el modo de draft norm lblSaveDraft=Guardar Draft lblNoAvailableDraftsMessage=¡No tienes ningún set de draft desbloqueado!\nVuelve más tarde cuando hayas desbloqueado más ediciones. lblNoAvailableDrafts=No hay Drafts disponibles -lblEntryFeeOfDraftTournament=La cuota de inscripción para este torneo de booster draft es +lblEntryFeeOfDraftTournament=La cuota de inscripción para este torneo de booster draft es lblWouldLikeCreateTournament= créditos.\n¿Te gustaría gastar una ficha y crear este torneo? lblCreatingDraftTournament=Creación de un Torneo de Booster Draft -lblUnexpectedCreatingDraftTournament=Error inesperado al crear un torneo de draft +lblUnexpectedCreatingDraftTournament=Error inesperado al crear un torneo de draft lblPleaseReportBug=. Por favor, informe de esto como un error. -lbl1stPlace=1er Puesto: -lbl2ndPlace=2o Puesto: -lbl3rdPlace=3er Puesto: -lbl4thPlace=4o Puesto: +lbl1stPlace=1er Puesto: +lbl2ndPlace=2o Puesto: +lbl3rdPlace=3er Puesto: +lbl4thPlace=4o Puesto: lblTime= veces lblCollectPrizes=Recoger Premios lblCurrentlyInDraft=Actualmente estás en un draft.\nDeberías dejar o terminar ese draft antes de empezar otro. -lblYouNeed=Necesitas +lblYouNeed=Necesitas lblMoreCredits=créditos más para entrar en este torneo. lblNotEnoughCredits=No hay suficientes créditos -lblTournamentCosts=Este torneo cuesta +lblTournamentCosts=Este torneo cuesta lblSureEnterTournament= créditos para entrar.\n¿Está seguro de que desea entrar? lblEnterDraftTournament=¿Entrar en el Torneo de Draft? lblLeaveDraftConfirm=Esto terminará con el actual Draft y no podrás volver a unirte a este torneo.\nTus créditos serán reembolsados y el Draft será eliminado.\n\n¿Abandonar de todos modos? @@ -1683,7 +1692,7 @@ lblWinsForRankIncrease=Ganancias por aumento de rango lblWinsForMediumAI=Ganancias para la IA media lblWinsForHardAI=Ganancias para la IA difícil lblWinsForExpertAI=Ganancias para la IA experta -lblSaveFailed=Error al Guardar - +lblSaveFailed=Error al Guardar - #QuestSpellShopScreen.java lblMaximumSellingCredits=El precio máximo de venta es de %d créditos. lblSellCardsAt=Vender cartas a @@ -1769,7 +1778,7 @@ lblDoYouWantMoveTargetFromOriToDest=¿Quieres mover {0} de {1} a {2}? lblPutThatCardFromPlayerOriginToDestination=¿Poner esa carta desde {0} {1} hasta {2}? lblSearchPlayerZoneConfirm=¿Buscar {0} {1}? lblCardMatchSearchingTypeInAlternateZones=las cartas coinciden con tu tipo de búsqueda en las zonas alternativas. -lblLookingCardIn=Mirando las cartas en +lblLookingCardIn=Mirando las cartas en lblDoYouWantPlayCard=¿Quieres jugar {0}? lblSelectCardFromPlayerZone=Selecciona una carta de {0} {1} lblSelectUpToNumCardFromPlayerZone=Selecciona hasta {0} cartas de {1} {2} @@ -1861,7 +1870,7 @@ lblChooseACardLeaveTarget=Elige una carta para dejar en {0} {1} lblChooseCardsPutIntoZone=Elige la(s) carta(s) a poner en {0} lblChooseCardPutOnTargetLibraryBottom=Elige la(s) carta(s) a poner en la parte inferior de la biblioteca {0} lblChooseCardPutOnTargetLibraryTop=Elige la(s) carta(s) a poner en la parte superior de la biblioteca {0} -lblPlayerPickedCardFrom={0} eligió carta(s) de +lblPlayerPickedCardFrom={0} eligió carta(s) de lblNoValidCards=No hay cartas válidas #DigUntilEffect.java lblDoYouWantDigYourLibrary=¿Quieres escarbar en tu biblioteca? @@ -2281,7 +2290,7 @@ lblDoYouWantCardDealNDamageToYou=¿Quieres que {0} te haga {1} de daño? lblDrawNCardsConfirm=¿Robar {0} carta(s)? lblExileConfirm=¿Exiliar {0}? lblExileNCardsFromYourZone=Exilia {0} carta(s) de tu {1} -lblExileFromWhoseZone=Exilia {0} de quién +lblExileFromWhoseZone=Exilia {0} de quién lblExileProgressFromZone=Exilia {0}/{1} de {2} lblToBeExiled=Para ser exiliado lblExileFromStack=Exiliar de la pila @@ -2312,7 +2321,7 @@ lblRemoveNTargetCounterFromCardPayCostConfirm=Paga el coste: ¿Quitar el contado lblRemoveCountersFromAInZoneCard=Quitar contador(es) de una carta en {0} lblSacrificeCardConfirm=¿Sacrificar {0}? lblSelectATargetToSacrifice=Selecciona un {0} para sacrificar ({1} pendiente) -lblSelectOneOfCardsToTapAlreadyChosen=Selecciona una de las cartas para girar. Ya elegido: +lblSelectOneOfCardsToTapAlreadyChosen=Selecciona una de las cartas para girar. Ya elegido: lblSelectACreatureToTap=Selecciona una criatura para girar. lblEnoughValidCardNotToPayTheCost=No quedan suficientes cartas válidas para girar para pagar el coste. lblCostPaymentInvalid=Pago del coste no válido @@ -2570,7 +2579,7 @@ lblRecordAndAssets=Registro | Bienes lblXWinOfYLost={0} G/{1} P lblDeleteThisQuest=Eliminar esta aventura lblRenameThisQuest=Renombrar esta aventura -lblRenameQuestTo=Renombrar aventura como +lblRenameQuestTo=Renombrar aventura como lblQuestRename=Renombrar aventura #StartingPoolType.java lblUnrestricted=Sin restriciones @@ -2671,7 +2680,7 @@ lblDetectedInvalidHostAddress=Dirección de host inválida ({0}) detectada. #Player.java lblChooseACompanion=Elige un compañero lblChooseAColorFor=Elige un color para {0} -lblRevealFaceDownCards=Mostrar cartas boca abajo de +lblRevealFaceDownCards=Mostrar cartas boca abajo de lblLearnALesson=Learn a Lesson #QuestPreferences.java lblWildOpponentNumberError=Los oponentes salvajes sólo pueden ser de 0 a 3 diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index cf1e4e8da63..a654421a931 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -18,8 +18,8 @@ lblerrLoadingLayoutFile=Impossibile leggere il file di layout %s. Verrà elimina lblLoadingQuest=Caricamento avevntura ... #FScreen.java #translate lblHomeWithSpaces,lblDeckEditorWithSpaces need keep spaces in text -lblHomeWithSpaces=Home -lblDeckEditorWithSpaces=Gestione dei mazzi +lblHomeWithSpaces=Home +lblDeckEditorWithSpaces=Gestione dei mazzi lblWorkshop=Officina lblBacktoHome=Torna a Home lblCloseEditor=Esci dalla gestione @@ -361,6 +361,15 @@ lblTotalDamageText=Punti di danno disponibili: Sconosciuto lblAssignRemainingText=Distribuire i punti di danno rimanenti tra le entità ferite letalmente lblLethal=Letale lblAvailableDamagePoints=Punti danno disponibili +#VAssignGenericAmount.java +# The {0} below should be amount label (like "shield" or "damage"), and {1} will be the name of the effect source +lbLAssignAmountForEffect=Assign {0} by {1} +lblLClickAmountMessage=Left click: Assign 1 {0}. (Left Click + Control): Assign maximum {0} points +lblRClickAmountMessage=Right click: Unassign 1 {0}. (Right Click + Control): Unassign all {0} points. +lblTotalAmountText=Available {0} points: Unknown +lblAvailableAmount=Available {0} points +lblMax=Max +lblShield=shield #KeyboardShortcuts.java lblSHORTCUT_SHOWSTACK=Incontro: mostra il pannello della pila lblSHORTCUT_SHOWCOMBAT=Incontro: mostra il pannello di combattimento @@ -1613,10 +1622,10 @@ lblWouldLikeCreateTournament= monete.\nDesideri usare un token e creare questo t lblCreatingDraftTournament=Creazione del torneo Booster Draft lblUnexpectedCreatingDraftTournament=Errore inaspettato durante la creazione del torneo lblPleaseReportBug=. Per favore segnala il bug. -lbl1stPlace=Primo posto: -lbl2ndPlace=Secondo posto: -lbl3rdPlace=Terzo posto: -lbl4thPlace=Quarto posto: +lbl1stPlace=Primo posto: +lbl2ndPlace=Secondo posto: +lbl3rdPlace=Terzo posto: +lbl4thPlace=Quarto posto: lblTime= tempo lblCollectPrizes=Ricevi i premi lblCurrentlyInDraft=Stai partecipando a un draft.\nDevi terminare o abbandonare quel draft prima di iniziarne un altro. @@ -1683,10 +1692,10 @@ lblWinsForRankIncrease=Vittorie per aumento di rango lblWinsForMediumAI=Numero di vittorie per IA livello Apprendista lblWinsForHardAI=Numero di vittorie per IA livello Esperto lblWinsForExpertAI=Numero di vittorie per IA livello Maestro -lblSaveFailed=Salvataggio fallito - +lblSaveFailed=Salvataggio fallito - #QuestSpellShopScreen.java lblMaximumSellingCredits=Il prezzo massimo di vendita è di %d monete. -lblSellCardsAt=Carte in vendita al +lblSellCardsAt=Carte in vendita al lblTheirValue=% del loro valore.\n lblSell=Vendi lblItem=oggetto @@ -2274,7 +2283,7 @@ lblHistoriiansWillRecallYourConquestAs=Gli storici ricorderanno la tua Conquista lblConquestName=Nome della Conquista #HumanCostDecision.java lblChooseXValueForCard={0} - Scegli un valore per X -lblSelectOneSameNameCardToDiscardAlreadyChosen=Seleziona una delle carte con lo stesso nome da scartare. Già scelto: +lblSelectOneSameNameCardToDiscardAlreadyChosen=Seleziona una delle carte con lo stesso nome da scartare. Già scelto: lblSelectOneDifferentNameCardToDiscardAlreadyChosen=Seleziona una delle carte con un nome diverso da scartare. Già scelto: lblSelectNMoreTargetTypeCardToDiscard=Seleziona altre{0} {1} da scartare. lblDoYouWantCardDealNDamageToYou=Vuoi che {0} ti infligga {1} danno/i? @@ -2312,7 +2321,7 @@ lblRemoveNTargetCounterFromCardPayCostConfirm=Paga il costot: Vuoi rimuovere {0} lblRemoveCountersFromAInZoneCard=Rimuovi segnalini da una carta in {0} lblSacrificeCardConfirm=Vuoi sacrificare {0}? lblSelectATargetToSacrifice=Seleziona una carta {0} da scarificare ({1} rimasta/e) -lblSelectOneOfCardsToTapAlreadyChosen=Seleziona una delle carta da tappare. Già scelto: +lblSelectOneOfCardsToTapAlreadyChosen=Seleziona una delle carta da tappare. Già scelto: lblSelectACreatureToTap=Seleziona una creatura da tappare. lblEnoughValidCardNotToPayTheCost=Non sono rimaste abbastanza carte valide da tappare per pagare il costo lblCostPaymentInvalid=Pagamento del costo non valido @@ -2386,7 +2395,7 @@ lblHandOrLibraryOrGraveyard=mano, grimorio e cimitero lblHandOrLibraryOrGraveyardOrBattlefield=mano, grimorio, cimitero e campo di battaglia #LifeToSpare.java lblLifeToSpare=Vita da vendere -lblWinGameWith=Vinci una partita con +lblWinGameWith=Vinci una partita con lblMoreThanStartedLifeN={0} punti vita in più di quelli con cui hai iniziato #ManaFlooded.java lblManaFlooded=Inondazione di mana diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index 319507aa70b..6b493a31803 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -18,8 +18,8 @@ lblerrLoadingLayoutFile=%s レイアウトファイルを読み取れません lblLoadingQuest=クエストを読み込んでいます... #FScreen.java #translate lblHomeWithSpaces,lblDeckEditorWithSpaces need keep spaces in text -lblHomeWithSpaces=ホーム -lblDeckEditorWithSpaces=デッキエディター +lblHomeWithSpaces=ホーム +lblDeckEditorWithSpaces=デッキエディター lblWorkshop=ワークショップ lblBacktoHome=ホームに戻る lblCloseEditor=エディターを閉じる @@ -355,12 +355,21 @@ lblReset=リセット lblAuto=自動 #VAssignDamage.java lbLAssignDamageDealtBy=%sによって与えられるダメージを割り当てます -lblLClickDamageMessage=左クリック:1ダメージを割り当てます。 (Ctrl + 左クリック):残りのダメージを致死まで割り当てる -lblRClickDamageMessage=右クリック:1ダメージの割り当てを解除します。 (Ctrl + 右クリック):すべての損傷の割り当てを解除します。 +lblLClickDamageMessage=左クリック:1点ダメージを割り当てます。 (Ctrl + 左クリック):残りのダメージを致死まで割り当てます。 +lblRClickDamageMessage=右クリック:1点ダメージの割り当てを解除します。 (Ctrl + 右クリック):すべてのダメージの割り当てを解除します。 lblTotalDamageText=利用可能なダメージポイント:不明 -lblAssignRemainingText=致命傷を負ったエンティティに残りのダメージポイントを分配する +lblAssignRemainingText=致死ダメージを負ったオブジェクトに残りのダメージを割り当てます。 lblLethal=リーサル lblAvailableDamagePoints=利用可能なダメージポイント +#VAssignGenericAmount.java +# The {0} below should be amount label (like "shield" or "damage"), and {1} will be the name of the effect source +lbLAssignAmountForEffect={1}によって効果の{0}を割り当てます +lblLClickAmountMessage=左クリック: 1点{0}を割り当てます。 (Ctrl + 左クリック):{0}を最大まで割り当てます。 +lblRClickAmountMessage=右クリック:1点{0}の割り当てを解除します。 (Ctrl + 右クリック):すべての{0}の割り当てを解除します。 +lblTotalAmountText=残りの{0}のポイント:不明 +lblAvailableAmount=残りの{0}のポイント +lblMax=最大 +lblShield=盾 #KeyboardShortcuts.java lblSHORTCUT_SHOWSTACK=スタックパネルを表示 lblSHORTCUT_SHOWCOMBAT=戦闘パネルを表示 @@ -1039,7 +1048,7 @@ lblLog=ログ lblDev=開発者 lblCombatTab=戦闘 lblStack=スタック -lblMustWaitPriority=優先権を待つ必要があります... +lblMustWaitPriority=優先権を待つ必要があります... #FDeckEditor.java lblImportFromClipboard=クリップボードからインポート lblSaveAs=名前を付けて保存... @@ -1075,7 +1084,7 @@ lblLoadingCardTranslations=カードの翻訳を読み込んでいます... lblFinishingStartup=起動を終了しています... lblPreloadExtendedArt=拡張アートのプリロード... #LobbyScreen.java -lblMore=もっと... +lblMore=もっと... lblLoadingNewGame=新しいゲームを読み込んでいます... lblSelectVariants=バリエーションを選択 msgSelectAdeckBeforeReadying=始まる前にデッキを選択してください! @@ -1624,7 +1633,7 @@ lblCurrentlyInDraft=今はドラフトを参加中です。\n新しいドラフ lblYouNeed=このトーナメントを参加するにはもっと lblMoreCredits=のクレジットが必要です。 lblNotEnoughCredits=クレジット不足 -lblTournamentCosts=このトーナメントの参加費は +lblTournamentCosts=このトーナメントの参加費は lblSureEnterTournament=クレジットです。\n参加してもよろしいでしょうか? lblEnterDraftTournament=トーナメントに参加しますか? lblLeaveDraftConfirm=これによりこのドラフトを終了して、再度参加することは出来ません。\n使ったクレジットは返金されて、そしてドラフトを削除します。\n\nそれでも退場しますか? @@ -2275,8 +2284,8 @@ lblHistoriiansWillRecallYourConquestAs=歴史家たちはあなたの征服紀 lblConquestName=征服紀録名 #HumanCostDecision.java lblChooseXValueForCard={0} - Xの値を選択 -lblSelectOneSameNameCardToDiscardAlreadyChosen=同名カードを 1つ選んで捨てる。既に選択: -lblSelectOneDifferentNameCardToDiscardAlreadyChosen=違う名前のカードを 1つ選んで捨てる。既に選択: +lblSelectOneSameNameCardToDiscardAlreadyChosen=同名カードを 1つ選んで捨てる。既に選択: +lblSelectOneDifferentNameCardToDiscardAlreadyChosen=違う名前のカードを 1つ選んで捨てる。既に選択: lblSelectNMoreTargetTypeCardToDiscard=もっと {0}枚の {1}を選んで捨てる。 lblDoYouWantCardDealNDamageToYou={0}があなたに {1}点のダメージを与えてもいいですか? lblDrawNCardsConfirm={0}枚のカードを引きますか? diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index c23e8dfcae2..a5884d13a48 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -18,8 +18,8 @@ lblerrLoadingLayoutFile=无法读取你的布局文件:%s。按OK然后删除 lblLoadingQuest=加载冒险之旅... #FScreen.java #翻译lblHomeWithSpaces,lblDeckEditorWithSpaces时需要保留翻译文本中的空格 -lblHomeWithSpaces=主页 -lblDeckEditorWithSpaces=套牌编辑器 +lblHomeWithSpaces=主页 +lblDeckEditorWithSpaces=套牌编辑器 lblWorkshop=作坊页面 lblBacktoHome=回退到主页 lblCloseEditor=关闭编辑器 @@ -53,7 +53,7 @@ btnClearImageCache=清除图片缓存 btnTokenPreviewer=衍生物预览器 btnCopyToClipboard=复制到剪切板 cbpSelectLanguage=语言 -cbpAutoUpdater=自动更新 +cbpAutoUpdater=自动更新 nlAutoUpdater=选择用于更新Forge的发布渠道 nlSelectLanguage=选择语言(除了正在进行中的游戏)(需要重新启动) cbRemoveSmall=删除小生物 @@ -361,6 +361,15 @@ lblTotalDamageText=可用的伤害值:未知 lblAssignRemainingText=对受到致命伤害的对象分配过量的伤害 lblLethal=致死 lblAvailableDamagePoints=可用的伤害值 +#VAssignGenericAmount.java +# The {0} below should be amount label (like "shield" or "damage"), and {1} will be the name of the effect source +lbLAssignAmountForEffect=Assign {0} by {1} +lblLClickAmountMessage=Left click: Assign 1 {0}. (Left Click + Control): Assign maximum {0} points +lblRClickAmountMessage=Right click: Unassign 1 {0}. (Right Click + Control): Unassign all {0} points. +lblTotalAmountText=Available {0} points: Unknown +lblAvailableAmount=Available {0} points +lblMax=Max +lblShield=shield #KeyboardShortcuts.java lblSHORTCUT_SHOWSTACK=匹配:显示堆叠面板 lblSHORTCUT_SHOWCOMBAT=匹配:显示战斗面板 @@ -416,7 +425,7 @@ lblSelectOpponentDeck=为对手选择套牌 lblGenerateNewDeck=生成新的套牌 lblRandomTheme=随机主题 lblTestDeck=测试套牌 -lblLoading=加载中 +lblLoading=加载中 #GameType.java lblSealed=现开 lblDraft=轮抓 @@ -764,7 +773,7 @@ ttWinsperDraftRotation=如果轮抓没有赢这么多场,那么它将被删除 lblRotationType=轮替类型 ttRotationType=如果设置为0,旧系列消失,如果设置为1,则用不同系列替换。 lblWildOpponentNumber=野外对手数量 -lblWildOpponentMultiplier=野外对手倍数 +lblWildOpponentMultiplier=野外对手倍数 #StatTypeFilter.java lblclicktotoogle=单击以切换筛选器,右键单机以仅显示 #SItemManagerUtil.java @@ -1288,7 +1297,7 @@ lblLibrary=牌库 lblGraveyard=坟场 lblTop=顶 lblBottom=底 -lblNColorManaFromCard={2}产{0}个{1}法术力 +lblNColorManaFromCard={2}产{0}个{1}法术力 lblPayManaFromManaPool=从法术力池支付法术力 lblChooseATargetType=选择一个{0}类型 lblUntap=重置 @@ -1621,10 +1630,10 @@ lbl4thPlace=第四名: lblTime= 时间 lblCollectPrizes=收藏品 lblCurrentlyInDraft=你现在正在一场轮抓中。\n你应该先离开轮抓或者玩完这场轮抓,然后再开始新的轮抓。 -lblYouNeed=你还需要 +lblYouNeed=你还需要 lblMoreCredits=积分才可以参与这场锦标赛。 lblNotEnoughCredits=积分不足 -lblTournamentCosts=这次锦标赛的费用为 +lblTournamentCosts=这次锦标赛的费用为 lblSureEnterTournament= 积分。\n你确定要进入吗? lblEnterDraftTournament=进入轮抓锦标赛? lblLeaveDraftConfirm=这将结束当前轮抓,你将无法再次加入此锦标赛。\n你的积分将被退还,轮抓将被删除。\n\n仍然要离开吗? diff --git a/forge-gui/src/main/java/forge/gamemodes/net/ProtocolMethod.java b/forge-gui/src/main/java/forge/gamemodes/net/ProtocolMethod.java index 2e9a3c98bae..68736d498c8 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/ProtocolMethod.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/ProtocolMethod.java @@ -56,6 +56,7 @@ public enum ProtocolMethod { setPanelSelection (Mode.SERVER, Void.TYPE, CardView.class), getAbilityToPlay (Mode.SERVER, SpellAbilityView.class, CardView.class, List/*SpellAbilityView*/.class, ITriggerEvent.class), assignCombatDamage (Mode.SERVER, Map.class, CardView.class, List/*CardView*/.class, Integer.TYPE, GameEntityView.class, Boolean.TYPE), + divideShield (Mode.SERVER, Map.class, CardView.class, Map.class, Integer.TYPE, Boolean.TYPE, String.class), message (Mode.SERVER, Void.TYPE, String.class, String.class), showErrorDialog (Mode.SERVER, Void.TYPE, String.class, String.class), showConfirmDialog (Mode.SERVER, Boolean.TYPE, String.class, String.class, String.class, String.class, Boolean.TYPE), diff --git a/forge-gui/src/main/java/forge/gamemodes/net/server/NetGuiGame.java b/forge-gui/src/main/java/forge/gamemodes/net/server/NetGuiGame.java index 0bd8208d47b..8fcd1ec696f 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/server/NetGuiGame.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/server/NetGuiGame.java @@ -74,7 +74,7 @@ public class NetGuiGame extends AbstractGuiGame { updateGameView(); send(ProtocolMethod.showPromptMessage, playerView, message); } - + @Override public void showPromptMessage(final PlayerView playerView, final String message, final CardView card) { updateGameView(); @@ -206,6 +206,11 @@ public class NetGuiGame extends AbstractGuiGame { return sendAndWait(ProtocolMethod.assignCombatDamage, attacker, blockers, damage, defender, overrideOrder); } + @Override + public Map assignGenericAmount(final CardView effectSource, final Map targets, final int amount, final boolean atLeastOne, final String amountLabel) { + return sendAndWait(ProtocolMethod.divideShield, effectSource, targets, amount, atLeastOne, amountLabel); + } + @Override public void message(final String message, final String title) { send(ProtocolMethod.message, message, title); diff --git a/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java b/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java index 08bd147ac30..18fdf3d07ac 100644 --- a/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java +++ b/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java @@ -69,6 +69,7 @@ public interface IGuiGame { void setPanelSelection(CardView hostCard); SpellAbilityView getAbilityToPlay(CardView hostCard, List abilities, ITriggerEvent triggerEvent); Map assignCombatDamage(CardView attacker, List blockers, int damage, GameEntityView defender, boolean overrideOrder); + Map assignGenericAmount(CardView effectSource, Map target, int amount, final boolean atLeastOne, final String amountLabel); void message(String message); void message(String message, String title); diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 84c2b711cf2..e0f5d2ee606 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -11,6 +11,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -389,6 +390,24 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont } } + @Override + public Map divideShield(Card effectSource, Map affected, int shieldAmount) { + final CardView vSource = CardView.get(effectSource); + final Map vAffected = new HashMap<>(affected.size()); + for (Map.Entry e : affected.entrySet()) { + vAffected.put(GameEntityView.get(e.getKey()), e.getValue()); + } + final Map vResult = getGui().assignGenericAmount(vSource, vAffected, shieldAmount, false, + localizer.getMessage("lblShield")); + Map result = new HashMap<>(vResult.size()); + for (Map.Entry e : affected.entrySet()) { + if (vResult.containsKey(GameEntityView.get(e.getKey()))) { + result.put(e.getKey(), vResult.get(GameEntityView.get(e.getKey()))); + } + } + return result; + } + @Override public Integer announceRequirements(final SpellAbility ability, final String announce) { int max = Integer.MAX_VALUE;