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);
}
@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
public Integer announceRequirements(SpellAbility ability, String announce) {
// 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"),
Destination("Destination"),
Devoured("Devoured"),
DividedShieldAmount("DividedShieldAmount"),
EchoPaid("EchoPaid"),
EffectOnly("EffectOnly"),
Exploited("Exploited"),

View File

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

View File

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

View File

@@ -108,6 +108,7 @@ public abstract class PlayerController {
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<GameEntity, Integer> divideShield(Card effectSource, Map<GameEntity, Integer> 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);

View File

@@ -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<String, String> 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<AbilityKey, Object> 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<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
// 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<AbilityKey, Object> runParams = null;
for (Map<AbilityKey, Object> rp : runParamList) {
if (source.equals(rp.get(AbilityKey.DamageSource))) {
runParams = rp;
break;
Iterator<Map<AbilityKey, Object>> itr = runParamList.iterator();
while (itr.hasNext()) {
Map<AbilityKey, Object> 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<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 {
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)) {
Map<ReplacementEffect, ReplacementResult> resultMap = new HashMap<>();
runParams.put(AbilityKey.ReplacementResultMap, resultMap);

View File

@@ -1028,6 +1028,24 @@ public final class CMatchUI
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
public void openView(final TrackableCollection<PlayerView> myPlayers) {
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...");
}
@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
public Integer announceRequirements(SpellAbility ability, String announce) {
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.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<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
public void updateManaPool(final Iterable<PlayerView> 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
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

View File

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

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

View File

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

View File

@@ -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=戦闘パネルを表示

View File

@@ -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=匹配:显示战斗面板

View File

@@ -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),

View File

@@ -206,6 +206,11 @@ public class NetGuiGame extends AbstractGuiGame {
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
public void message(final String message, final String title) {
send(ProtocolMethod.message, message, title);

View File

@@ -69,6 +69,7 @@ public interface IGuiGame {
void setPanelSelection(CardView hostCard);
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<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, String title);

View File

@@ -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<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
public Integer announceRequirements(final SpellAbility ability, final String announce) {
int max = Integer.MAX_VALUE;