Implement missing cards with dividable shields for damage replacement effects

This commit is contained in:
Lyu Zong-Hong
2021-06-05 18:18:34 +09:00
parent f95fb7d08b
commit ab31c8fa9d
25 changed files with 1077 additions and 173 deletions

View File

@@ -131,6 +131,12 @@ public class PlayerControllerAi extends PlayerController {
return ComputerUtilCombat.distributeAIDamage(attacker, blockers, damageDealt, defender, overrideOrder); return ComputerUtilCombat.distributeAIDamage(attacker, blockers, damageDealt, defender, overrideOrder);
} }
@Override
public Map<GameEntity, Integer> divideShield(Card effectSource, Map<GameEntity, Integer> affected, int shieldAmount) {
// TODO: AI current can't use this so this is not implemented.
return new HashMap<>();
}
@Override @Override
public Integer announceRequirements(SpellAbility ability, String announce) { public Integer announceRequirements(SpellAbility ability, String announce) {
// For now, these "announcements" are made within the AI classes of the appropriate SA effects // For now, these "announcements" are made within the AI classes of the appropriate SA effects

View File

@@ -57,6 +57,7 @@ public enum AbilityKey {
DefendingPlayer("DefendingPlayer"), DefendingPlayer("DefendingPlayer"),
Destination("Destination"), Destination("Destination"),
Devoured("Devoured"), Devoured("Devoured"),
DividedShieldAmount("DividedShieldAmount"),
EchoPaid("EchoPaid"), EchoPaid("EchoPaid"),
EffectOnly("EffectOnly"), EffectOnly("EffectOnly"),
Exploited("Exploited"), Exploited("Exploited"),

View File

@@ -39,6 +39,10 @@ public class ReplaceDamageEffect extends SpellAbilityEffect {
if (prevent > 0) { if (prevent > 0) {
int n = Math.min(dmg, prevent); 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; dmg -= n;
prevent -= n; prevent -= n;

View File

@@ -38,6 +38,10 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect {
if (prevent > 0 && list.size() > 0 && list.get(0) instanceof GameEntity) { if (prevent > 0 && list.size() > 0 && list.get(0) instanceof GameEntity) {
int n = Math.min(dmg, prevent); 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; dmg -= n;
prevent -= n; prevent -= n;

View File

@@ -108,6 +108,7 @@ public abstract class PlayerController {
public abstract List<PaperCard> chooseCardsYouWonToAddToDeck(List<PaperCard> losses); public abstract List<PaperCard> chooseCardsYouWonToAddToDeck(List<PaperCard> losses);
public abstract Map<Card, Integer> assignCombatDamage(Card attacker, CardCollectionView blockers, int damageDealt, GameEntity defender, boolean overrideOrder); public abstract Map<Card, Integer> assignCombatDamage(Card attacker, CardCollectionView blockers, int damageDealt, GameEntity defender, boolean overrideOrder);
public abstract Map<GameEntity, Integer> divideShield(Card effectSource, Map<GameEntity, Integer> affected, int shieldAmount);
public abstract Integer announceRequirements(SpellAbility ability, String announce); public abstract Integer announceRequirements(SpellAbility ability, String announce);
public abstract CardCollectionView choosePermanentsToSacrifice(SpellAbility sa, int min, int max, CardCollectionView validTargets, String message); public abstract CardCollectionView choosePermanentsToSacrifice(SpellAbility sa, int min, int max, CardCollectionView validTargets, String message);

View File

@@ -688,6 +688,7 @@ public class ReplacementHandler {
ApiType apiType = null; ApiType apiType = null;
SpellAbility bufferedSA = effectSA; SpellAbility bufferedSA = effectSA;
boolean needRestoreSubSA = false; boolean needRestoreSubSA = false;
boolean needDivideShield = false;
boolean needChooseSource = false; boolean needChooseSource = false;
int shieldAmount = 0; int shieldAmount = 0;
if (effectSA != null) { 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<String, String> mapParams = chosenRE.getMapParams(); Map<String, String> mapParams = chosenRE.getMapParams();
if ((mapParams.containsKey("PreventionEffect") && mapParams.get("PreventionEffect").equals("NextN")) if ((mapParams.containsKey("PreventionEffect") && mapParams.get("PreventionEffect").equals("NextN"))
|| apiType == ApiType.ReplaceSplitDamage) { || apiType == ApiType.ReplaceSplitDamage) {
@@ -711,17 +713,50 @@ public class ReplacementHandler {
shieldAmount = AbilityUtils.calculateAmount(effectSA.getHostCard(), effectSA.getParamOrDefault("VarName", "1"), effectSA); shieldAmount = AbilityUtils.calculateAmount(effectSA.getHostCard(), effectSA.getParamOrDefault("VarName", "1"), effectSA);
} }
int damageAmount = 0; int damageAmount = 0;
boolean hasMultipleSource = false;
boolean hasMultipleTarget = false;
Card firstSource = null;
GameEntity firstTarget = null;
for (Map<AbilityKey, Object> runParams : runParamList) { for (Map<AbilityKey, Object> runParams : runParamList) {
// Only count damage that can be prevented // Only count damage that can be prevented
if (apiType == ApiType.ReplaceDamage && Boolean.TRUE.equals(runParams.get(AbilityKey.NoPreventDamage))) continue; if (apiType == ApiType.ReplaceDamage && Boolean.TRUE.equals(runParams.get(AbilityKey.NoPreventDamage))) continue;
damageAmount += (int) runParams.get(AbilityKey.DamageAmount); 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) { if (damageAmount > shieldAmount && runParamList.size() > 1) {
needChooseSource = true; if (hasMultipleSource)
needChooseSource = true;
if (effectSA.hasParam("DivideShield") && hasMultipleTarget)
needDivideShield = true;
} }
} }
} }
// Ask the decider to divide shield among affected damage target
Map<GameEntity, Integer> shieldMap = null;
if (needDivideShield) {
Map<GameEntity, Integer> affected = new HashMap<>();
for (Map<AbilityKey, Object> 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 // CR 615.7
// If damage would be dealt to the shielded permanent or player by two or more applicable sources at the same time, // 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. // 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()) { while (shieldAmount > 0 && !sourcesToChooseFrom.isEmpty()) {
Card source = decider.getController().chooseSingleEntityForEffect(sourcesToChooseFrom, effectSA, choiceTitle, null); Card source = decider.getController().chooseSingleEntityForEffect(sourcesToChooseFrom, effectSA, choiceTitle, null);
sourcesToChooseFrom.remove(source); sourcesToChooseFrom.remove(source);
Map<AbilityKey, Object> runParams = null; Iterator<Map<AbilityKey, Object>> itr = runParamList.iterator();
for (Map<AbilityKey, Object> rp : runParamList) { while (itr.hasNext()) {
if (source.equals(rp.get(AbilityKey.DamageSource))) { Map<AbilityKey, Object> runParams = itr.next();
runParams = rp; if (source.equals(runParams.get(AbilityKey.DamageSource))) {
break; 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<ReplacementEffect, ReplacementResult> 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<ReplacementEffect, ReplacementResult> resultMap = new HashMap<>();
runParams.put(AbilityKey.ReplacementResultMap, resultMap);
}
runSingleReplaceDamageEffect(chosenRE, runParams, replaceCandidateMap, executedDamageMap, decider, damageMap, preventMap);
} }
} else { } else {
for (Map<AbilityKey, Object> runParams : runParamList) { for (Map<AbilityKey, Object> 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)) { if (!runParams.containsKey(AbilityKey.ReplacementResultMap)) {
Map<ReplacementEffect, ReplacementResult> resultMap = new HashMap<>(); Map<ReplacementEffect, ReplacementResult> resultMap = new HashMap<>();
runParams.put(AbilityKey.ReplacementResultMap, resultMap); runParams.put(AbilityKey.ReplacementResultMap, resultMap);

View File

@@ -1028,6 +1028,24 @@ public final class CMatchUI
return result.get(); return result.get();
} }
@Override
public Map<GameEntityView, Integer> assignGenericAmount(final CardView effectSource, final Map<GameEntityView, Integer> target,
final int amount, final boolean atLeastOne, final String amountLabel) {
if (amount <= 0) {
return Collections.emptyMap();
}
final AtomicReference<Map<GameEntityView, Integer>> 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 @Override
public void openView(final TrackableCollection<PlayerView> myPlayers) { public void openView(final TrackableCollection<PlayerView> myPlayers) {
final GameView gameView = getGameView(); final GameView gameView = getGameView();

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <br><br><i>(V at beginning of class name denotes a view class.)</i>
*/
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<AssignTarget> targetsList = new ArrayList<>();
private final Map<GameEntityView, AssignTarget> 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<GameEntityView, Integer> 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<GameEntityView, Integer> 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<GameEntityView, Integer> getAssignedMap() {
Map<GameEntityView, Integer> result = new HashMap<>(targetsList.size());
for (AssignTarget at : targetsList)
result.put(at.entity, at.amount);
return result;
}
}

View File

@@ -146,6 +146,12 @@ public class PlayerControllerForTests extends PlayerController {
throw new IllegalStateException("Erring on the side of caution here..."); throw new IllegalStateException("Erring on the side of caution here...");
} }
@Override
public Map<GameEntity, Integer> divideShield(Card effectSource, Map<GameEntity, Integer> affected, int shieldAmount) {
throw new IllegalStateException("Erring on the side of caution here...");
}
@Override @Override
public Integer announceRequirements(SpellAbility ability, String announce) { public Integer announceRequirements(SpellAbility ability, String announce) {
throw new IllegalStateException("Erring on the side of caution here..."); throw new IllegalStateException("Erring on the side of caution here...");

View File

@@ -49,6 +49,7 @@ import forge.model.FModel;
import forge.player.PlayerZoneUpdate; import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates; import forge.player.PlayerZoneUpdates;
import forge.screens.match.views.VAssignCombatDamage; import forge.screens.match.views.VAssignCombatDamage;
import forge.screens.match.views.VAssignGenericAmount;
import forge.screens.match.views.VPhaseIndicator; import forge.screens.match.views.VPhaseIndicator;
import forge.screens.match.views.VPhaseIndicator.PhaseLabel; import forge.screens.match.views.VPhaseIndicator.PhaseLabel;
import forge.screens.match.views.VPlayerPanel; import forge.screens.match.views.VPlayerPanel;
@@ -395,6 +396,18 @@ public class MatchController extends AbstractGuiGame {
}.invokeAndWait(); }.invokeAndWait();
} }
@Override
public Map<GameEntityView, Integer> assignGenericAmount(final CardView effectSource, final Map<GameEntityView, Integer> targets,
final int amount, final boolean atLeastOne, final String amountLabel) {
return new WaitCallback<Map<GameEntityView, Integer>>() {
@Override
public void run() {
final VAssignGenericAmount v = new VAssignGenericAmount(effectSource, targets, amount, atLeastOne, amountLabel, this);
v.show();
}
}.invokeAndWait();
}
@Override @Override
public void updateManaPool(final Iterable<PlayerView> manaPoolUpdate) { public void updateManaPool(final Iterable<PlayerView> manaPoolUpdate) {
for (final PlayerView p : manaPoolUpdate) { for (final PlayerView p : manaPoolUpdate) {

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<Map<GameEntityView, Integer>> callback;
private final int totalAmountToAssign;
private final String lblAmount;
private final FLabel lblTotalAmount;
private final EffectSourcePanel pnlSource;
private final TargetsPanel pnlTargets;
private final List<AssignTarget> targetsList = new ArrayList<>();
private final Map<GameEntityView, AssignTarget> targetsMap = new HashMap<>();
/** Constructor.
*
* @param attacker0 {@link forge.game.card.Card}
* @param targets Map<GameEntity, Integer>, 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<GameEntityView, Integer> targets, final int amount, final boolean atLeastOne, final String amountLabel, final WaitCallback<Map<GameEntityView, Integer>> 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<GameEntityView, Integer> targets) {
for (final Map.Entry<GameEntityView, Integer> 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<GameEntityView, Integer> getAssignedMap() {
Map<GameEntityView, Integer> result = new HashMap<>(targetsList.size());
for (AssignTarget at : targetsList)
result.put(at.entity, at.amount);
return result;
}
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -361,6 +361,15 @@ lblTotalDamageText=Gesamtschaden: Unbekannt
lblAssignRemainingText=Verteile verbleibenden Schaden unter tödlich Verwundeten lblAssignRemainingText=Verteile verbleibenden Schaden unter tödlich Verwundeten
lblLethal=Tödlich lblLethal=Tödlich
lblAvailableDamagePoints=Verfügbare Schadenspunkte 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 #KeyboardShortcuts.java
lblSHORTCUT_SHOWSTACK=Duell: Zeige Stapelfenster lblSHORTCUT_SHOWSTACK=Duell: Zeige Stapelfenster
lblSHORTCUT_SHOWCOMBAT=Duell: Zeige Kampffenster lblSHORTCUT_SHOWCOMBAT=Duell: Zeige Kampffenster

View File

@@ -361,6 +361,15 @@ lblTotalDamageText=Available damage points: Unknown
lblAssignRemainingText=Distribute the remaining damage points among lethally wounded entities lblAssignRemainingText=Distribute the remaining damage points among lethally wounded entities
lblLethal=Lethal lblLethal=Lethal
lblAvailableDamagePoints=Available damage points 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 #KeyboardShortcuts.java
lblSHORTCUT_SHOWSTACK=Match: show stack panel lblSHORTCUT_SHOWSTACK=Match: show stack panel
lblSHORTCUT_SHOWCOMBAT=Match: show combat panel lblSHORTCUT_SHOWCOMBAT=Match: show combat panel

View File

@@ -361,6 +361,15 @@ lblTotalDamageText=Puntos de daño disponibles: Desconocido
lblAssignRemainingText=Distribuye los puntos de daño restantes entre las entidades letalmente heridas. lblAssignRemainingText=Distribuye los puntos de daño restantes entre las entidades letalmente heridas.
lblLethal=Letal lblLethal=Letal
lblAvailableDamagePoints=Puntos de daño disponibles 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 #KeyboardShortcuts.java
lblSHORTCUT_SHOWSTACK=Partida: mostrar panel de pila lblSHORTCUT_SHOWSTACK=Partida: mostrar panel de pila
lblSHORTCUT_SHOWCOMBAT=Partida: mostrar panel de combate lblSHORTCUT_SHOWCOMBAT=Partida: mostrar panel de combate

View File

@@ -361,6 +361,15 @@ lblTotalDamageText=Punti di danno disponibili: Sconosciuto
lblAssignRemainingText=Distribuire i punti di danno rimanenti tra le entità ferite letalmente lblAssignRemainingText=Distribuire i punti di danno rimanenti tra le entità ferite letalmente
lblLethal=Letale lblLethal=Letale
lblAvailableDamagePoints=Punti danno disponibili 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 #KeyboardShortcuts.java
lblSHORTCUT_SHOWSTACK=Incontro: mostra il pannello della pila lblSHORTCUT_SHOWSTACK=Incontro: mostra il pannello della pila
lblSHORTCUT_SHOWCOMBAT=Incontro: mostra il pannello di combattimento lblSHORTCUT_SHOWCOMBAT=Incontro: mostra il pannello di combattimento

View File

@@ -355,12 +355,21 @@ lblReset=リセット
lblAuto=自動 lblAuto=自動
#VAssignDamage.java #VAssignDamage.java
lbLAssignDamageDealtBy=%sによって与えられるダメージを割り当てます lbLAssignDamageDealtBy=%sによって与えられるダメージを割り当てます
lblLClickDamageMessage=左クリック1ダメージを割り当てます。 Ctrl + 左クリック):残りのダメージを致死まで割り当て lblLClickDamageMessage=左クリック1ダメージを割り当てます。 Ctrl + 左クリック):残りのダメージを致死まで割り当てます。
lblRClickDamageMessage=右クリック1ダメージの割り当てを解除します。 Ctrl + 右クリック):すべての損傷の割り当てを解除します。 lblRClickDamageMessage=右クリック1ダメージの割り当てを解除します。 Ctrl + 右クリック):すべてのダメージの割り当てを解除します。
lblTotalDamageText=利用可能なダメージポイント:不明 lblTotalDamageText=利用可能なダメージポイント:不明
lblAssignRemainingText=命傷を負ったエンティティに残りのダメージポイントを分配する lblAssignRemainingText=死ダメージを負ったオブジェクトに残りのダメージを割り当てます。
lblLethal=リーサル lblLethal=リーサル
lblAvailableDamagePoints=利用可能なダメージポイント 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 #KeyboardShortcuts.java
lblSHORTCUT_SHOWSTACK=スタックパネルを表示 lblSHORTCUT_SHOWSTACK=スタックパネルを表示
lblSHORTCUT_SHOWCOMBAT=戦闘パネルを表示 lblSHORTCUT_SHOWCOMBAT=戦闘パネルを表示

View File

@@ -361,6 +361,15 @@ lblTotalDamageText=可用的伤害值:未知
lblAssignRemainingText=对受到致命伤害的对象分配过量的伤害 lblAssignRemainingText=对受到致命伤害的对象分配过量的伤害
lblLethal=致死 lblLethal=致死
lblAvailableDamagePoints=可用的伤害值 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 #KeyboardShortcuts.java
lblSHORTCUT_SHOWSTACK=匹配:显示堆叠面板 lblSHORTCUT_SHOWSTACK=匹配:显示堆叠面板
lblSHORTCUT_SHOWCOMBAT=匹配:显示战斗面板 lblSHORTCUT_SHOWCOMBAT=匹配:显示战斗面板

View File

@@ -56,6 +56,7 @@ public enum ProtocolMethod {
setPanelSelection (Mode.SERVER, Void.TYPE, CardView.class), setPanelSelection (Mode.SERVER, Void.TYPE, CardView.class),
getAbilityToPlay (Mode.SERVER, SpellAbilityView.class, CardView.class, List/*SpellAbilityView*/.class, ITriggerEvent.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), 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), message (Mode.SERVER, Void.TYPE, String.class, String.class),
showErrorDialog (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), showConfirmDialog (Mode.SERVER, Boolean.TYPE, String.class, String.class, String.class, String.class, Boolean.TYPE),

View File

@@ -206,6 +206,11 @@ public class NetGuiGame extends AbstractGuiGame {
return sendAndWait(ProtocolMethod.assignCombatDamage, attacker, blockers, damage, defender, overrideOrder); return sendAndWait(ProtocolMethod.assignCombatDamage, attacker, blockers, damage, defender, overrideOrder);
} }
@Override
public Map<GameEntityView, Integer> assignGenericAmount(final CardView effectSource, final Map<GameEntityView, Integer> targets, final int amount, final boolean atLeastOne, final String amountLabel) {
return sendAndWait(ProtocolMethod.divideShield, effectSource, targets, amount, atLeastOne, amountLabel);
}
@Override @Override
public void message(final String message, final String title) { public void message(final String message, final String title) {
send(ProtocolMethod.message, message, title); send(ProtocolMethod.message, message, title);

View File

@@ -69,6 +69,7 @@ public interface IGuiGame {
void setPanelSelection(CardView hostCard); void setPanelSelection(CardView hostCard);
SpellAbilityView getAbilityToPlay(CardView hostCard, List<SpellAbilityView> abilities, ITriggerEvent triggerEvent); SpellAbilityView getAbilityToPlay(CardView hostCard, List<SpellAbilityView> abilities, ITriggerEvent triggerEvent);
Map<CardView, Integer> assignCombatDamage(CardView attacker, List<CardView> blockers, int damage, GameEntityView defender, boolean overrideOrder); Map<CardView, Integer> assignCombatDamage(CardView attacker, List<CardView> blockers, int damage, GameEntityView defender, boolean overrideOrder);
Map<GameEntityView, Integer> assignGenericAmount(CardView effectSource, Map<GameEntityView, Integer> target, int amount, final boolean atLeastOne, final String amountLabel);
void message(String message); void message(String message);
void message(String message, String title); void message(String message, String title);

View File

@@ -11,6 +11,7 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -389,6 +390,24 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
} }
} }
@Override
public Map<GameEntity, Integer> divideShield(Card effectSource, Map<GameEntity, Integer> affected, int shieldAmount) {
final CardView vSource = CardView.get(effectSource);
final Map<GameEntityView, Integer> vAffected = new HashMap<>(affected.size());
for (Map.Entry<GameEntity, Integer> e : affected.entrySet()) {
vAffected.put(GameEntityView.get(e.getKey()), e.getValue());
}
final Map<GameEntityView, Integer> vResult = getGui().assignGenericAmount(vSource, vAffected, shieldAmount, false,
localizer.getMessage("lblShield"));
Map<GameEntity, Integer> result = new HashMap<>(vResult.size());
for (Map.Entry<GameEntity, Integer> e : affected.entrySet()) {
if (vResult.containsKey(GameEntityView.get(e.getKey()))) {
result.put(e.getKey(), vResult.get(GameEntityView.get(e.getKey())));
}
}
return result;
}
@Override @Override
public Integer announceRequirements(final SpellAbility ability, final String announce) { public Integer announceRequirements(final SpellAbility ability, final String announce) {
int max = Integer.MAX_VALUE; int max = Integer.MAX_VALUE;