mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
GameAction - legend rule and planeswalker rule are updated to match changes introduced with "Magic 2014 Core Set" InputSelectCardsFromList also accepts any Collection<T>, not just List<T> PlayerControllerHuman - chooseSingleCardForEffect tries to use InputSelectCardsFromList when all cards are in Battlefield or own hand
401 lines
14 KiB
Java
401 lines
14 KiB
Java
/*
|
|
* 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.gui.match;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import javax.swing.ImageIcon;
|
|
|
|
import org.apache.commons.lang3.tuple.Pair;
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.eventbus.EventBus;
|
|
import com.google.common.eventbus.Subscribe;
|
|
|
|
import forge.Card;
|
|
import forge.FThreads;
|
|
import forge.GameEntity;
|
|
import forge.ImageCache;
|
|
import forge.Singletons;
|
|
import forge.game.combat.Combat;
|
|
import forge.game.phase.PhaseType;
|
|
import forge.game.player.LobbyPlayer;
|
|
import forge.game.player.Player;
|
|
import forge.game.zone.Zone;
|
|
import forge.game.zone.ZoneType;
|
|
import forge.gui.events.IUiEventVisitor;
|
|
import forge.gui.events.UiEvent;
|
|
import forge.gui.events.UiEventAttackerDeclared;
|
|
import forge.gui.events.UiEventBlockerAssigned;
|
|
import forge.gui.framework.EDocID;
|
|
import forge.gui.framework.SDisplayUtil;
|
|
import forge.gui.match.controllers.CCombat;
|
|
import forge.gui.match.controllers.CDetail;
|
|
import forge.gui.match.controllers.CMessage;
|
|
import forge.gui.match.controllers.CPicture;
|
|
import forge.gui.match.nonsingleton.VCommand;
|
|
import forge.gui.match.nonsingleton.VField;
|
|
import forge.gui.match.nonsingleton.VHand;
|
|
import forge.gui.match.views.VPlayers;
|
|
import forge.gui.toolbox.FSkin;
|
|
import forge.gui.toolbox.special.PhaseLabel;
|
|
import forge.item.InventoryItem;
|
|
import forge.properties.ForgePreferences.FPref;
|
|
import forge.view.arcane.PlayArea;
|
|
|
|
/**
|
|
* Constructs instance of match UI controller, used as a single point of
|
|
* top-level control for child UIs. Tasks targeting the view of individual
|
|
* components are found in a separate controller for that component and
|
|
* should not be included here.
|
|
*
|
|
* <br><br><i>(C at beginning of class name denotes a control class.)</i>
|
|
*/
|
|
public enum CMatchUI {
|
|
SINGLETON_INSTANCE;
|
|
|
|
private List<Player> sortedPlayers;
|
|
private VMatchUI view;
|
|
|
|
private EventBus uiEvents;
|
|
private MatchUiEventVisitor visitor = new MatchUiEventVisitor();
|
|
|
|
private CMatchUI() {
|
|
uiEvents = new EventBus("ui events");
|
|
uiEvents.register(Singletons.getControl().getSoundSystem());
|
|
uiEvents.register(visitor);
|
|
}
|
|
|
|
private ImageIcon getPlayerAvatar(final Player p, final int defaultIndex) {
|
|
LobbyPlayer lp = p.getLobbyPlayer();
|
|
if (null != lp.getIconImageKey()) {
|
|
return ImageCache.getIcon(lp);
|
|
}
|
|
|
|
int avatarIdx = lp.getAvatarIndex();
|
|
return new ImageIcon(FSkin.getAvatars().get(0 <= avatarIdx ? avatarIdx : defaultIndex));
|
|
}
|
|
|
|
|
|
private void setAvatar(VField view, ImageIcon img) {
|
|
view.getLblAvatar().setIcon(img);
|
|
view.getLblAvatar().getResizeTimer().start();
|
|
}
|
|
|
|
/**
|
|
* Instantiates at a match with a specified number of players
|
|
* and hands.
|
|
*
|
|
* @param numFieldPanels int
|
|
* @param numHandPanels int
|
|
*/
|
|
public void initMatch(final List<Player> players, LobbyPlayer localPlayer) {
|
|
view = VMatchUI.SINGLETON_INSTANCE;
|
|
// TODO fix for use with multiplayer
|
|
|
|
final String[] indices = Singletons.getModel().getPreferences().getPref(FPref.UI_AVATARS).split(",");
|
|
|
|
// Instantiate all required field slots (user at 0)
|
|
sortedPlayers = shiftPlayersPlaceLocalFirst(players, localPlayer);
|
|
|
|
|
|
final List<VField> fields = new ArrayList<VField>();
|
|
final List<VCommand> commands = new ArrayList<VCommand>();
|
|
|
|
int i = 0;
|
|
for (Player p : sortedPlayers) {
|
|
// A field must be initialized after it's instantiated, to update player info.
|
|
// No player, no init.
|
|
VField f = new VField(EDocID.Fields[i], p, localPlayer);
|
|
VCommand c = new VCommand(EDocID.Commands[i], p);
|
|
fields.add(f);
|
|
commands.add(c);
|
|
|
|
//setAvatar(f, new ImageIcon(FSkin.getAvatars().get()));
|
|
setAvatar(f, getPlayerAvatar(p, Integer.parseInt(indices[i > 2 ? 1 : 0])));
|
|
f.getLayoutControl().initialize();
|
|
c.getLayoutControl().initialize();
|
|
i++;
|
|
}
|
|
|
|
// Replace old instances
|
|
view.setCommandViews(commands);
|
|
view.setFieldViews(fields);
|
|
|
|
VPlayers.SINGLETON_INSTANCE.init(players);
|
|
|
|
initHandViews(localPlayer);
|
|
}
|
|
|
|
public void initHandViews(LobbyPlayer localPlayer) {
|
|
final List<VHand> hands = new ArrayList<VHand>();
|
|
|
|
int i = 0;
|
|
for (Player p : sortedPlayers) {
|
|
if (p.getLobbyPlayer() == localPlayer) {
|
|
VHand newHand = new VHand(EDocID.Hands[i], p);
|
|
newHand.getLayoutControl().initialize();
|
|
hands.add(newHand);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if(hands.isEmpty()) { // add empty hand for matches without human
|
|
VHand newHand = new VHand(EDocID.Hands[0], null);
|
|
newHand.getLayoutControl().initialize();
|
|
hands.add(newHand);
|
|
}
|
|
view.setHandViews(hands);
|
|
}
|
|
|
|
private List<Player> shiftPlayersPlaceLocalFirst(final List<Player> players, LobbyPlayer localPlayer) {
|
|
// get an arranged list so that the first local player is at index 0
|
|
List<Player> sortedPlayers = Lists.newArrayList(players);
|
|
int ixFirstHuman = -1;
|
|
for(int i = 0; i < players.size(); i++) {
|
|
if( sortedPlayers.get(i).getLobbyPlayer() == localPlayer ) {
|
|
ixFirstHuman = i;
|
|
break;
|
|
}
|
|
}
|
|
if( ixFirstHuman > 0 ) {
|
|
sortedPlayers.add(0, sortedPlayers.remove(ixFirstHuman));
|
|
}
|
|
return sortedPlayers;
|
|
}
|
|
|
|
/**
|
|
* Resets all phase buttons in all fields to "inactive", so highlight won't
|
|
* be drawn on them. "Enabled" state remains the same.
|
|
*/
|
|
// This method is in the top-level controller because it affects ALL fields
|
|
// (not just one).
|
|
public void resetAllPhaseButtons() {
|
|
for (final VField v : view.getFieldViews()) {
|
|
v.getPhaseInidicator().resetPhaseButtons();
|
|
}
|
|
}
|
|
|
|
/** @param s0   {@link java.lang.String} */
|
|
public void showMessage(final String s0) {
|
|
CMessage.SINGLETON_INSTANCE.setMessage(s0);
|
|
}
|
|
|
|
public VField getFieldViewFor(Player p) {
|
|
int idx = getPlayerIndex(p);
|
|
return idx < 0 ? null :view.getFieldViews().get(idx);
|
|
}
|
|
|
|
public VCommand getCommandFor(Player p) {
|
|
int idx = getPlayerIndex(p);
|
|
return idx < 0 ? null :view.getCommandViews().get(idx);
|
|
}
|
|
|
|
public VHand getHandFor(Player p) {
|
|
int idx = getPlayerIndex(p);
|
|
List<VHand> allHands = view.getHands();
|
|
return idx < 0 || idx >= allHands.size() ? null : allHands.get(idx);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Fires up trample dialog. Very old code, due for refactoring with new UI.
|
|
* Could possibly move to view.
|
|
*
|
|
* @param attacker   {@link forge.Card}
|
|
* @param blockers   {@link forge.CardList}
|
|
* @param damage   int
|
|
* @param overrideOrder overriding combatant order
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public Map<Card, Integer> getDamageToAssign(final Card attacker, final List<Card> blockers, final int damage, final GameEntity defender, final boolean overrideOrder) {
|
|
if (damage <= 0) {
|
|
return new HashMap<Card, Integer>();
|
|
}
|
|
|
|
// If the first blocker can absorb all of the damage, don't show the Assign Damage Frame
|
|
Card firstBlocker = blockers.get(0);
|
|
if (!overrideOrder && !attacker.hasKeyword("Deathtouch") && firstBlocker.getLethalDamage() >= damage) {
|
|
Map<Card, Integer> res = new HashMap<Card, Integer>();
|
|
res.put(firstBlocker, damage);
|
|
return res;
|
|
}
|
|
|
|
final Object[] result = { null }; // how else can I extract a value from EDT thread?
|
|
FThreads.invokeInEdtAndWait(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
VAssignDamage v = new VAssignDamage(attacker, blockers, damage, defender, overrideOrder);
|
|
result[0] = v.getDamageMap();
|
|
}});
|
|
return (Map<Card, Integer>)result[0];
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Checks if game control should stop at a phase, for either
|
|
* a forced programmatic stop, or a user-induced phase toggle.
|
|
* @param turn   {@link forge.game.player.Player}
|
|
* @param phase   {@link java.lang.String}
|
|
* @return boolean
|
|
*/
|
|
public final boolean stopAtPhase(final Player turn, final PhaseType phase) {
|
|
VField vf = getFieldViewFor(turn);
|
|
PhaseLabel label = vf.getPhaseInidicator().getLabelFor(phase);
|
|
return label == null || label.getEnabled();
|
|
}
|
|
|
|
public void setCard(final Card c) {
|
|
FThreads.assertExecutedByEdt(true);
|
|
setCard(c, false);
|
|
}
|
|
|
|
public void setCard(final Card c, final boolean showFlipped ) {
|
|
CDetail.SINGLETON_INSTANCE.showCard(c);
|
|
CPicture.SINGLETON_INSTANCE.showCard(c, showFlipped);
|
|
}
|
|
|
|
public void setCard(final InventoryItem c) {
|
|
CDetail.SINGLETON_INSTANCE.showCard(c);
|
|
CPicture.SINGLETON_INSTANCE.showCard(c);
|
|
}
|
|
|
|
private int getPlayerIndex(Player player) {
|
|
return sortedPlayers.indexOf(player);
|
|
}
|
|
|
|
|
|
public void showCombat(Combat combat) {
|
|
if (combat != null) {
|
|
SDisplayUtil.showTab(EDocID.REPORT_COMBAT.getDoc());
|
|
}
|
|
CCombat.SINGLETON_INSTANCE.setModel(combat);
|
|
CCombat.SINGLETON_INSTANCE.update();
|
|
} // showBlockers()
|
|
|
|
|
|
Set<Player> highlitedPlayers = new HashSet<Player>();
|
|
public void setHighLited(Player ge, boolean b) {
|
|
if (b) highlitedPlayers.add(ge);
|
|
else highlitedPlayers.remove(ge);
|
|
}
|
|
|
|
public boolean isHighlited(Player player) {
|
|
return highlitedPlayers.contains(player);
|
|
}
|
|
|
|
|
|
Set<Card> highlitedCards = new HashSet<Card>();
|
|
// used to highlight cards in UI
|
|
public void setUsedToPay(Card card, boolean value) {
|
|
FThreads.assertExecutedByEdt(true);
|
|
|
|
boolean hasChanged = value ? highlitedCards.add(card) : highlitedCards.remove(card);
|
|
if ( hasChanged ) // since we are in UI thread, may redraw the card right now
|
|
updateSingleCard(card);
|
|
}
|
|
|
|
public boolean isUsedToPay(Card card) {
|
|
return highlitedCards.contains(card);
|
|
}
|
|
|
|
public void updateZones(List<Pair<Player, ZoneType>> zonesToUpdate) {
|
|
//System.out.println("updateZones " + zonesToUpdate);
|
|
for(Pair<Player, ZoneType> kv : zonesToUpdate) {
|
|
Player owner = kv.getKey();
|
|
ZoneType zt = kv.getValue();
|
|
|
|
if ( zt == ZoneType.Command )
|
|
getCommandFor(owner).getTabletop().setupPlayZone();
|
|
else if ( zt == ZoneType.Hand ) {
|
|
VHand vHand = getHandFor(owner);
|
|
if (null != vHand)
|
|
vHand.getLayoutControl().updateHand();
|
|
getFieldViewFor(owner).getDetailsPanel().updateZones();
|
|
} else if ( zt == ZoneType.Battlefield )
|
|
getFieldViewFor(owner).getTabletop().setupPlayZone();
|
|
else
|
|
getFieldViewFor(owner).getDetailsPanel().updateZones();
|
|
}
|
|
}
|
|
|
|
|
|
// Player's mana pool changes
|
|
public void updateManaPool(List<Player> manaPoolUpdate) {
|
|
for(Player p : manaPoolUpdate) {
|
|
getFieldViewFor(p).getDetailsPanel().updateManaPool();
|
|
}
|
|
|
|
}
|
|
|
|
// Player's lives and poison counters
|
|
public void updateLives(List<Player> livesUpdate) {
|
|
for(Player p : livesUpdate) {
|
|
getFieldViewFor(p).updateDetails();
|
|
}
|
|
|
|
}
|
|
|
|
public void updateCards(Set<Card> cardsToUpdate) {
|
|
for(Card c : cardsToUpdate) {
|
|
updateSingleCard(c);
|
|
}
|
|
}
|
|
|
|
public void updateSingleCard(Card c) {
|
|
Zone zone = c.getZone();
|
|
if ( null != zone && zone.getZoneType() == ZoneType.Battlefield ) {
|
|
PlayArea pa = getFieldViewFor(zone.getPlayer()).getTabletop();
|
|
pa.updateSingleCard(c);
|
|
}
|
|
}
|
|
|
|
private final static boolean LOG_UIEVENTS = false;
|
|
|
|
// UI-related events should arrive here
|
|
public void fireEvent(UiEvent uiEvent) {
|
|
if ( LOG_UIEVENTS )
|
|
System.out.println("UI: " + uiEvent.toString() + " \t\t " + FThreads.debugGetStackTraceItem(4, true));
|
|
uiEvents.post(uiEvent);
|
|
}
|
|
|
|
public class MatchUiEventVisitor implements IUiEventVisitor<Void> {
|
|
@Override
|
|
public Void visit(UiEventBlockerAssigned event) {
|
|
updateSingleCard(event.blocker);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Void visit(UiEventAttackerDeclared event) {
|
|
updateSingleCard(event.attacker);
|
|
return null;
|
|
}
|
|
|
|
@Subscribe
|
|
public void receiveEvent(UiEvent evt) {
|
|
evt.visit(this);
|
|
}
|
|
}
|
|
|
|
}
|
|
|