Merge remote-tracking branch 'remotes/core/master' into newBranch

This commit is contained in:
Anthony Calosa
2019-11-24 12:11:12 +08:00
23 changed files with 461 additions and 83 deletions

View File

@@ -12,10 +12,12 @@ public class GameEventSpellAbilityCast extends GameEvent {
public final SpellAbility sa;
public final SpellAbilityStackInstance si;
public final boolean replicate;
public final int stackIndex;
public GameEventSpellAbilityCast(SpellAbility sp, SpellAbilityStackInstance si, boolean replicate) {
public GameEventSpellAbilityCast(SpellAbility sp, SpellAbilityStackInstance si, int stackIndex, boolean replicate) {
sa = sp;
this.si = si;
this.stackIndex = stackIndex;
this.replicate = replicate;
}

View File

@@ -414,6 +414,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
final SpellAbilityStackInstance si = new SpellAbilityStackInstance(sp);
stack.addFirst(si);
int stackIndex = stack.size() - 1;
// 2012-07-21 the following comparison needs to move below the pushes but somehow screws up priority
// When it's down there. That makes absolutely no sense to me, so i'm putting it back for now
@@ -430,7 +431,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
sp.getActivatingPlayer().setActivateLoyaltyAbilityThisTurn(true);
}
game.updateStackForView();
game.fireEvent(new GameEventSpellAbilityCast(sp, si, false));
game.fireEvent(new GameEventSpellAbilityCast(sp, si, stackIndex, false));
return si;
}

View File

@@ -288,14 +288,15 @@ public class GuiChoose {
public static List<CardView> manipulateCardList(final CMatchUI gui, final String title, final Iterable<CardView> cards, final Iterable<CardView> manipulable,
final boolean toTop, final boolean toBottom, final boolean toAnywhere) {
gui.setSelectables(manipulable);
@SuppressWarnings("Convert2Lambda") // Avoid lambdas to maintain compatibility with Android 5 API
final Callable<List<CardView>> callable = new Callable<List<CardView>>() {
@Override
public List<CardView> call() {
ListCardArea tempArea = ListCardArea.show(gui,title,cards,manipulable,toTop,toBottom,toAnywhere);
// tempArea.pack();
tempArea.show();
final List<CardView> cardList = tempArea.getCards();
return cardList;
tempArea.setVisible(true);
return tempArea.getCards();
}
};
final FutureTask<List<CardView>> ft = new FutureTask<>(callable);

View File

@@ -17,10 +17,9 @@ import forge.screens.home.EMenuGroup;
import forge.screens.home.IVSubmenu;
import forge.screens.home.VHomeUI;
import forge.toolbox.*;
import forge.util.storage.IStorage;
import forge.util.Localizer;
import forge.util.WordUtil;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.lang3.text.WordUtils;
import javax.swing.*;
import javax.swing.plaf.basic.BasicComboBoxRenderer;
@@ -243,17 +242,15 @@ public enum VSubmenuQuestData implements IVSubmenu<CSubmenuQuestData> {
cboAllowUnlocks.setSelected(true);
final Map<String, String> preconDescriptions = new HashMap<>();
final IStorage<PreconDeck> preconDecks = QuestController.getPrecons();
for (final PreconDeck preconDeck : preconDecks) {
for (final PreconDeck preconDeck : QuestController.getPrecons()) {
if (QuestController.getPreconDeals(preconDeck).getMinWins() > 0) {
continue;
}
final String name = preconDeck.getName();
cbxPreconDeck.addItem(name);
String description = preconDeck.getDescription();
description = "<html>" + WordUtils.wrap(description, 40, "<br>", false) + "</html>";
preconDescriptions.put(name, description);
preconDescriptions.put(name, WordUtil.wordWrapAsHTML(description));
}
// The cbx needs strictly typed renderer

View File

@@ -227,6 +227,7 @@ public enum CSubmenuPreferences implements ICDoc {
initializeDefaultFontSizeComboBox();
initializeMulliganRuleComboBox();
initializeAiProfilesComboBox();
initializeStackAdditionsComboBox();
initializeColorIdentityCombobox();
initializeAutoYieldModeComboBox();
initializeCounterDisplayTypeComboBox();
@@ -408,6 +409,16 @@ public enum CSubmenuPreferences implements ICDoc {
panel.setComboBox(comboBox, selectedItem);
}
private void initializeStackAdditionsComboBox() {
final String[] elems = {ForgeConstants.STACK_EFFECT_NOTIFICATION_NEVER, ForgeConstants.STACK_EFFECT_NOTIFICATION_ALWAYS,
ForgeConstants.STACK_EFFECT_NOTIFICATION_AI_AND_TRIGGERED};
final FPref userSetting = FPref.UI_STACK_EFFECT_NOTIFICATION_POLICY;
final FComboBoxPanel<String> panel = this.view.getCbpStackAdditionsComboBoxPanel();
final FComboBox<String> comboBox = createComboBox(elems, userSetting);
final String selectedItem = this.prefs.getPref(userSetting);
panel.setComboBox(comboBox, selectedItem);
}
private void initializeColorIdentityCombobox() {
final String[] elems = {ForgeConstants.DISP_CURRENT_COLORS_NEVER, ForgeConstants.DISP_CURRENT_COLORS_CHANGED,
ForgeConstants.DISP_CURRENT_COLORS_MULTICOLOR, ForgeConstants.DISP_CURRENT_COLORS_MULTI_OR_CHANGED,

View File

@@ -116,6 +116,7 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
private final FComboBoxPanel<String> cbpDefaultFontSize = new FComboBoxPanel<>(localizer.getMessage("cbpDefaultFontSize")+":");
private final FComboBoxPanel<String> cbpMulliganRule = new FComboBoxPanel<>(localizer.getMessage("cbpMulliganRule")+":");
private final FComboBoxPanel<String> cbpAiProfiles = new FComboBoxPanel<>(localizer.getMessage("cbpAiProfiles")+":");
private final FComboBoxPanel<String> cbpStackAdditions = new FComboBoxPanel<>(localizer.getMessage("cbpStackAdditions")+":");
private final FComboBoxPanel<String> cbpDisplayCurrentCardColors = new FComboBoxPanel<>(localizer.getMessage("cbpDisplayCurrentCardColors")+":");
private final FComboBoxPanel<String> cbpAutoYieldMode = new FComboBoxPanel<>(localizer.getMessage("cbpAutoYieldMode")+":");
private final FComboBoxPanel<String> cbpCounterDisplayType = new FComboBoxPanel<>(localizer.getMessage("cbpCounterDisplayType")+":");
@@ -194,6 +195,9 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
pnlPrefs.add(cbManaLostPrompt, titleConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlManaLostPrompt")), descriptionConstraints);
pnlPrefs.add(cbpStackAdditions, comboBoxConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlpStackAdditions")), descriptionConstraints);
pnlPrefs.add(cbEnforceDeckLegality, titleConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlEnforceDeckLegality")), descriptionConstraints);
@@ -649,6 +653,10 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
return cbpAiProfiles;
}
public FComboBoxPanel<String> getCbpStackAdditionsComboBoxPanel() {
return cbpStackAdditions;
}
public FComboBoxPanel<GameLogEntryType> getGameLogVerbosityComboBoxPanel() {
return cbpGameLogEntryType;
}

View File

@@ -18,7 +18,9 @@
package forge.screens.match;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -30,6 +32,8 @@ import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.JMenu;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
@@ -41,25 +45,43 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import forge.FThreads;
import forge.GuiBase;
import forge.ImageCache;
import forge.LobbyPlayer;
import forge.Singletons;
import forge.StaticData;
import forge.assets.FSkinProp;
import forge.card.CardStateName;
import forge.control.KeyboardShortcuts;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deckchooser.FDeckViewer;
import forge.game.GameEntity;
import forge.game.GameEntityView;
import forge.game.GameView;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.card.CardView;
import forge.game.card.CardView.CardStateView;
import forge.game.combat.CombatView;
import forge.game.event.GameEventSpellAbilityCast;
import forge.game.event.GameEventSpellRemovedFromStack;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
import forge.game.player.DelayedReveal;
import forge.game.player.IHasIcon;
import forge.game.player.PlayerView;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.SpellAbilityView;
import forge.game.spellability.StackItemView;
import forge.game.spellability.TargetChoices;
import forge.game.zone.ZoneType;
import forge.gui.*;
import forge.gui.FNetOverlay;
import forge.gui.GuiChoose;
import forge.gui.GuiDialog;
import forge.gui.GuiUtils;
import forge.gui.SOverlayUtils;
import forge.gui.framework.DragCell;
import forge.gui.framework.EDocID;
import forge.gui.framework.FScreen;
@@ -74,6 +96,7 @@ import forge.match.AbstractGuiGame;
import forge.menus.IMenuProvider;
import forge.model.FModel;
import forge.player.PlayerZoneUpdate;
import forge.properties.ForgeConstants;
import forge.properties.ForgePreferences;
import forge.properties.ForgePreferences.FPref;
import forge.screens.match.controllers.CAntes;
@@ -88,19 +111,25 @@ import forge.screens.match.menus.CMatchUIMenus;
import forge.screens.match.views.VField;
import forge.screens.match.views.VHand;
import forge.toolbox.FButton;
import forge.toolbox.FLabel;
import forge.toolbox.FOptionPane;
import forge.toolbox.FSkin;
import forge.toolbox.FSkin.SkinImage;
import forge.toolbox.FTextArea;
import forge.toolbox.imaging.FImagePanel;
import forge.toolbox.imaging.FImagePanel.AutoSizeImageMode;
import forge.toolbox.imaging.FImageUtil;
import forge.toolbox.special.PhaseIndicator;
import forge.toolbox.special.PhaseLabel;
import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
import forge.util.ITriggerEvent;
import forge.util.gui.SOptionPane;
import forge.view.FView;
import forge.view.arcane.CardPanel;
import forge.view.arcane.FloatingZone;
import net.miginfocom.swing.MigLayout;
/**
* Constructs instance of match UI controller, used as a single point of
@@ -136,6 +165,7 @@ public final class CMatchUI
private final CLog cLog = new CLog(this);
private final CPrompt cPrompt = new CPrompt(this);
private final CStack cStack = new CStack(this);
private int nextNotifiableStackIndex = 0;
public CMatchUI() {
this.view = new VMatchUI(this);
@@ -1180,4 +1210,204 @@ public final class CMatchUI
return reply == 0;
}
@Override
public void notifyStackAddition(GameEventSpellAbilityCast event) {
SpellAbility sa = event.sa;
String stackNotificationPolicy = FModel.getPreferences().getPref(FPref.UI_STACK_EFFECT_NOTIFICATION_POLICY);
boolean isAi = sa.getActivatingPlayer().isAI();
boolean isTrigger = sa.isTrigger();
int stackIndex = event.stackIndex;
if(stackIndex == nextNotifiableStackIndex) {
if(ForgeConstants.STACK_EFFECT_NOTIFICATION_ALWAYS.equals(stackNotificationPolicy) || (ForgeConstants.STACK_EFFECT_NOTIFICATION_AI_AND_TRIGGERED.equals(stackNotificationPolicy) && (isAi || isTrigger))) {
// We can go and show the modal
SpellAbilityStackInstance si = event.si;
MigLayout migLayout = new MigLayout("insets 15, left, gap 30, fill");
JPanel mainPanel = new JPanel(migLayout);
final Dimension parentSize = JOptionPane.getRootFrame().getSize();
Dimension maxSize = new Dimension(1400, parentSize.height - 100);
mainPanel.setMaximumSize(maxSize);
mainPanel.setOpaque(false);
// Big Image
addBigImageToStackModalPanel(mainPanel, si);
// Text
addTextToStackModalPanel(mainPanel,sa,si);
// Small images
int numSmallImages = 0;
// If current effect is a triggered/activated ability of an enchantment card, I want to show the enchanted card
GameEntityView enchantedEntityView = null;
Card hostCard = sa.getHostCard();
if(hostCard.isEnchantment()) {
GameEntity enchantedEntity = hostCard.getEntityAttachedTo();
if(enchantedEntity != null) {
enchantedEntityView = enchantedEntity.getView();
numSmallImages++;
} else if ((sa.getRootAbility() != null)
&& (sa.getRootAbility().getPaidList("Sacrificed") != null)
&& !sa.getRootAbility().getPaidList("Sacrificed").isEmpty()) {
// If the player activated its ability by sacrificing the enchantment, the enchantment has not anything attached anymore and the ex-enchanted card has to be searched in other ways.. for example, the green enchantment "Carapace"
enchantedEntity = sa.getRootAbility().getPaidList("Sacrificed").get(0).getEnchantingCard();
if(enchantedEntity != null) {
enchantedEntityView = enchantedEntity.getView();
numSmallImages++;
}
}
}
// If current effect is a triggered ability, I want to show the triggering card if present
SpellAbility sourceSA = (SpellAbility) si.getTriggeringObject(AbilityKey.SourceSA);
CardView sourceCardView = null;
if(sourceSA != null) {
sourceCardView = sourceSA.getHostCard().getView();
numSmallImages++;
}
// I also want to show each type of targets (both cards and players)
List<GameEntityView> targets = getTargets(si,new ArrayList<GameEntityView>());
numSmallImages = numSmallImages + targets.size();
// Now I know how many small images - on to render them
if(enchantedEntityView != null) {
addSmallImageToStackModalPanel(enchantedEntityView,mainPanel,numSmallImages);
}
if(sourceCardView != null) {
addSmallImageToStackModalPanel(sourceCardView,mainPanel,numSmallImages);
}
for(GameEntityView gev : targets) {
addSmallImageToStackModalPanel(gev, mainPanel, numSmallImages);
}
FOptionPane.showOptionDialog(null, "Forge", null, mainPanel, ImmutableList.of("OK"));
// here the user closed the modal - time to update the next notifiable stack index
}
// In any case, I have to increase the counter
nextNotifiableStackIndex++;
} else {
// Not yet time to show the modal - schedule the method again, and try again later
Runnable tryAgainThread = new Runnable() {
@Override
public void run() {
notifyStackAddition(event);
}
};
GuiBase.getInterface().invokeInEdtLater(tryAgainThread);
}
}
private List<GameEntityView> getTargets(SpellAbilityStackInstance si, List<GameEntityView> result){
if(si == null) {
return result;
} else {
FCollectionView<CardView> targetCards = CardView.getCollection(si.getTargetChoices().getTargetCards());
for(CardView currCardView: targetCards) {
result.add(currCardView);
}
for(SpellAbility currSA : si.getTargetChoices().getTargetSpells()) {
CardView currCardView = currSA.getCardView();
result.add(currCardView);
}
FCollectionView<PlayerView> targetPlayers = PlayerView.getCollection(si.getTargetChoices().getTargetPlayers());
for(PlayerView currPlayerView : targetPlayers) {
result.add(currPlayerView);
}
return getTargets(si.getSubInstance(),result);
}
}
private void addBigImageToStackModalPanel(JPanel mainPanel, SpellAbilityStackInstance si) {
StackItemView siv = si.getView();
int rotation = getRotation(si.getCardView());
FImagePanel imagePanel = new FImagePanel();
BufferedImage bufferedImage = FImageUtil.getImage(siv.getSourceCard().getCurrentState());
imagePanel.setImage(bufferedImage, rotation, AutoSizeImageMode.SOURCE);
int imageWidth = 433;
int imageHeight = 600;
Dimension imagePanelDimension = new Dimension(imageWidth,imageHeight);
imagePanel.setMinimumSize(imagePanelDimension);
mainPanel.add(imagePanel, "cell 0 0, spany 3");
}
private void addTextToStackModalPanel(JPanel mainPanel, SpellAbility sa, SpellAbilityStackInstance si) {
String who = sa.getActivatingPlayer().getName();
String action = sa.isSpell() ? " cast " : sa.isTrigger() ? " triggered " : " activated ";
String what = sa.getStackDescription().startsWith("Morph ") ? "Morph" : sa.getHostCard().toString();
StringBuilder sb = new StringBuilder();
sb.append(who).append(action).append(what);
if (sa.getTargetRestrictions() != null) {
sb.append(" targeting ");
TargetChoices targets = si.getTargetChoices();
sb.append(targets.getTargetedString());
}
sb.append(".");
String message1 = sb.toString();
String message2 = si.getStackDescription();
String messageTotal = message1 + "\n\n" + message2;
final FTextArea prompt1 = new FTextArea(messageTotal);
prompt1.setFont(FSkin.getFont(21));
prompt1.setAutoSize(true);
prompt1.setMinimumSize(new Dimension(475,200));
mainPanel.add(prompt1, "cell 1 0, aligny top");
}
private void addSmallImageToStackModalPanel(GameEntityView gameEntityView, JPanel mainPanel, int numTarget) {
if(gameEntityView instanceof CardView) {
CardView cardView = (CardView) gameEntityView;
int currRotation = getRotation(cardView);
FImagePanel targetPanel = new FImagePanel();
BufferedImage bufferedImage = FImageUtil.getImage(cardView.getCurrentState());
targetPanel.setImage(bufferedImage, currRotation, AutoSizeImageMode.SOURCE);
int imageWidth = 217;
int imageHeight = 300;
Dimension targetPanelDimension = new Dimension(imageWidth,imageHeight);
targetPanel.setMinimumSize(targetPanelDimension);
mainPanel.add(targetPanel, "cell 1 1, split " + numTarget+ ", aligny bottom");
} else if(gameEntityView instanceof PlayerView) {
PlayerView playerView = (PlayerView) gameEntityView;
SkinImage playerAvatar = getPlayerAvatar(playerView, 0);
final FLabel lblIcon = new FLabel.Builder().icon(playerAvatar).build();
Dimension dimension = playerAvatar.getSizeForPaint(JOptionPane.getRootFrame().getGraphics());
mainPanel.add(lblIcon, "cell 1 1, split " + numTarget+ ", w " + dimension.getWidth() + ", h " + dimension.getHeight() + ", aligny bottom");
}
}
private int getRotation(CardView cardView) {
final int rotation;
if (cardView.isSplitCard()) {
String cardName = cardView.getName();
if (cardName.isEmpty()) { cardName = cardView.getAlternateState().getName(); }
PaperCard pc = StaticData.instance().getCommonCards().getCard(cardName);
boolean hasKeywordAftermath = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH);
rotation = cardView.isFaceDown() ? 0 : hasKeywordAftermath ? (CardStateName.LeftSplit.equals(cardView.getState(false).getState()) ? 0 : 270) : 90; // rotate Aftermath splits the other way to correctly show the right split (graveyard) half
} else {
CardStateView cardStateView = cardView.getState(false);
rotation = cardStateView.isPlane() || cardStateView.isPhenomenon() ? 90 : 0;
}
return rotation;
}
@Override
public void notifyStackRemoval(GameEventSpellRemovedFromStack event) {
// I always decrease the counter
nextNotifiableStackIndex--;
}
}

View File

@@ -78,6 +78,10 @@ public class FOptionPane extends FDialog {
return showOptionDialog(message, title, icon, options, 0);
}
public static int showOptionDialog(final String message, final String title, final SkinImage icon, Component comp, final List<String> options) {
return showOptionDialog(message, title, icon, comp, options, 0);
}
public static int showOptionDialog(final String message, final String title, final SkinImage icon, final List<String> options, final int defaultOption) {
final FOptionPane optionPane = new FOptionPane(message, title, icon, null, options, defaultOption);
optionPane.setVisible(true);
@@ -86,6 +90,14 @@ public class FOptionPane extends FDialog {
return dialogResult;
}
public static int showOptionDialog(final String message, final String title, final SkinImage icon, final Component comp, final List<String> options, final int defaultOption) {
final FOptionPane optionPane = new FOptionPane(message, title, icon, comp, options, defaultOption);
optionPane.setVisible(true);
final int dialogResult = optionPane.result;
optionPane.dispose();
return dialogResult;
}
public static String showInputDialog(final String message, final String title) {
return showInputDialog(message, title, null, "", null);
}

View File

@@ -28,8 +28,8 @@ import forge.properties.ForgeConstants;
import forge.properties.ForgePreferences;
import forge.properties.ForgePreferences.FPref;
import forge.util.OperatingSystem;
import forge.util.WordUtil;
import forge.view.FView;
import org.apache.commons.lang3.text.WordUtils;
import javax.imageio.ImageIO;
import javax.swing.*;
@@ -1085,7 +1085,7 @@ public class FSkin {
allSkins = new ArrayList<>();
final List<String> skinDirectoryNames = getSkinDirectoryNames();
for (String skinDirectoryName : skinDirectoryNames) {
allSkins.add(WordUtils.capitalize(skinDirectoryName.replace('_', ' ')));
allSkins.add(WordUtil.capitalize(skinDirectoryName.replace('_', ' ')));
}
Collections.sort(allSkins);
}

View File

@@ -1,31 +1,24 @@
package forge.toolbox.special;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import forge.card.mana.ManaAtom;
import forge.trackable.TrackableProperty;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Function;
import forge.assets.FSkinProp;
import forge.card.mana.ManaAtom;
import forge.game.player.PlayerView;
import forge.toolbox.FLabel;
import forge.toolbox.FMouseAdapter;
import forge.toolbox.FSkin;
import forge.toolbox.FSkin.SkinFont;
import forge.toolbox.FSkin.SkinnedPanel;
import forge.trackable.TrackableProperty;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
public class PlayerDetailsPanel extends JPanel {
private static final long serialVersionUID = -6531759554646891983L;

View File

@@ -12,8 +12,8 @@ import forge.game.*;
import forge.properties.ForgeConstants;
import forge.tournament.system.*;
import forge.util.TextUtil;
import forge.util.WordUtil;
import forge.util.storage.IStorage;
import org.apache.commons.lang3.text.WordUtils;
import org.apache.commons.lang3.time.StopWatch;
import forge.deck.Deck;
@@ -75,7 +75,7 @@ public class SimulateMatch {
GameType type = GameType.Constructed;
if (params.containsKey("f")) {
type = GameType.valueOf(WordUtils.capitalize(params.get("f").get(0)));
type = GameType.valueOf(WordUtil.capitalize(params.get("f").get(0)));
}
GameRules rules = new GameRules(type);

View File

@@ -687,7 +687,7 @@ public class GifDecoder {
} while ((blockSize > 0) && !err());
}
public Animation getAnimation(PlayMode playType) {
private Animation<TextureRegion> getAnimation(PlayMode playType) {
int nrFrames = getFrameCount();
Pixmap frame = getFrame(0);
int width = frame.getWidth();
@@ -725,12 +725,11 @@ public class GifDecoder {
}
float frameDuration = (float)getDelay(0);
frameDuration /= 1000; // convert milliseconds into seconds
Animation result = new Animation(frameDuration, texReg, playType);
return result;
return new Animation<>(frameDuration, texReg, playType);
}
public static Animation loadGIFAnimation(PlayMode playType, InputStream is) {
public static Animation<TextureRegion> loadGIFAnimation(PlayMode playType, InputStream is) {
GifDecoder gdec = new GifDecoder();
gdec.read(is);
return gdec.getAnimation(playType);

View File

@@ -3,9 +3,9 @@ package forge.assets;
import java.util.HashMap;
import java.util.Map;
import forge.util.WordUtil;
import com.badlogic.gdx.utils.Array;
import forge.Forge;
import org.apache.commons.lang3.text.WordUtils;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
@@ -102,7 +102,7 @@ public class FSkin {
allSkins = new Array<>();
final Array<String> skinDirectoryNames = getSkinDirectoryNames();
for (final String skinDirectoryName : skinDirectoryNames) {
allSkins.add(WordUtils.capitalize(skinDirectoryName.replace('_', ' ')));
allSkins.add(WordUtil.capitalize(skinDirectoryName.replace('_', ' ')));
}
allSkins.sort();
}

View File

@@ -47,15 +47,10 @@ import forge.util.FileUtil;
import forge.util.ThreadUtil;
import forge.util.Utils;
import forge.util.gui.SOptionPane;
import forge.util.storage.IStorage;
import org.apache.commons.lang3.text.WordUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@@ -282,18 +277,12 @@ public class NewQuestScreen extends FScreen {
cbAllowUnlocks.setSelected(true);
final Map<String, String> preconDescriptions = new HashMap<>();
IStorage<PreconDeck> preconDecks = QuestController.getPrecons();
for (PreconDeck preconDeck : preconDecks) {
for (PreconDeck preconDeck : QuestController.getPrecons()) {
if (QuestController.getPreconDeals(preconDeck).getMinWins() > 0) {
continue;
}
String name = preconDeck.getName();
cbxPreconDeck.addItem(name);
String description = preconDeck.getDescription();
description = "<html>" + WordUtils.wrap(description, 40, "<br>", false) + "</html>";
preconDescriptions.put(name, description);
}
// disable the very powerful sets -- they can be unlocked later for a high price

View File

@@ -3,7 +3,7 @@ ManaCost:3 B B
Types:Legendary Creature Human Knight
PT:5/4
T:Mode$ ChangesZone | Origin$ Any | Destination$ Graveyard | ValidCard$ Creature.Other | TriggerZones$ Battlefield | Execute$ TrigDmg | TriggerDescription$ Whenever another creature dies, or a creature card is put into a graveyard from anywhere than the battlefield, or a creature card leaves your graveyard, CARDNAME deals 1 damage to each opponent.
T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Any | ValidCard$ Card.Creature+Other | TriggerZones$ Battlefield | Execute$ TrigDmg | Secondary$ True | TriggerDescription$ Whenever another creature dies, or a creature card is put into a graveyard from anywhere than the battlefield, or a creature card leaves your graveyard, CARDNAME deals 1 damage to each opponent.
T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Any | ValidCard$ Card.Creature+Other+YouOwn | TriggerZones$ Battlefield | Execute$ TrigDmg | Secondary$ True | TriggerDescription$ Whenever another creature dies, or a creature card is put into a graveyard from anywhere than the battlefield, or a creature card leaves your graveyard, CARDNAME deals 1 damage to each opponent.
SVar:TrigDmg:DB$ DealDamage | Defined$ Player.Opponent | NumDmg$ 1
A:AB$ Mill | Cost$ 1 B | NumCards$ 1 | Defined$ Player | SpellDescription$ Each player puts the top card of their library into their graveyard.
AI:RemoveDeck:All

View File

@@ -102,6 +102,7 @@ cbpGameLogEntryType=Game Log Verbosity
cbpCloseAction=Close Action
cbpDefaultFontSize=Default Font Size
cbpAiProfiles=AI Personality
cbpStackAdditions=Stack effect notifications
cbpDisplayCurrentCardColors=Show Detailed Card Color
cbpAutoYieldMode=Auto-Yield
cbpCounterDisplayType=Counter Display Type
@@ -116,6 +117,7 @@ nlUseSentry=When enabled, automatically submits bug reports to developers.
GamePlay=Gameplay
nlpMulliganRule=Choose the version of the Mulligan rule
nlpAiProfiles=Choose your AI opponent
nlpStackAdditions=Choose when you want to get visual notifications for an effect added to the stack: Never, always, or only for the effects cast/activated by a AI player or triggered by any player
nlAnte=Determines whether or not the game is played for ante.
nlAnteMatchRarity=Attempts to make antes the same rarity for all players.
nlEnableAICheats=Allow the AI to cheat to gain advantage (for personalities that have cheat shuffling options set).

View File

@@ -240,7 +240,17 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
@Override
public Void visit(final GameEventSpellAbilityCast event) {
needStackUpdate = true;
return processEvent();
processEvent();
final Runnable notifyStackAddition = new Runnable() {
@Override
public void run() {
matchController.notifyStackAddition(event);
}
};
GuiBase.getInterface().invokeInEdtLater(notifyStackAddition);
return null;
}
@Override
@@ -252,7 +262,17 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
@Override
public Void visit(final GameEventSpellRemovedFromStack event) {
needStackUpdate = true;
return processEvent();
processEvent();
final Runnable notifyStackAddition = new Runnable() {
@Override
public void run() {
matchController.notifyStackRemoval(event);
}
};
GuiBase.getInterface().invokeInEdtLater(notifyStackAddition);
return null;
}
@Override

View File

@@ -264,12 +264,8 @@ public abstract class GuiDownloadService implements Runnable {
count++;
cardSkipped = true; //assume skipped unless saved successfully
String url = kv.getValue();
/*
* decode URL Key, Reverted to old version,
* on Android 6.0 it throws an error
* when you download the card price
*/
String decodedKey = URLDecoder.decode(kv.getKey());
String decodedKey = decodeURL(kv.getKey());
final File fileDest = new File(decodedKey);
final String filePath = fileDest.getPath();
final String subLastIndex = filePath.contains("pics") ? "\\pics\\" : "\\db\\";
@@ -365,6 +361,16 @@ public abstract class GuiDownloadService implements Runnable {
GuiBase.getInterface().preventSystemSleep(false);
}
@SuppressWarnings("deprecation")
private static String decodeURL(String key) {
/*
* decode URL Key, Reverted to old version,
* on Android 6.0 it throws an error
* when you download the card price
*/
return URLDecoder.decode(key);
}
protected Proxy getProxy() {
if (type == 0) {
return Proxy.NO_PROXY;
@@ -385,7 +391,7 @@ public abstract class GuiDownloadService implements Runnable {
protected static void addMissingItems(Map<String, String> list, String nameUrlFile, String dir) {
for (Pair<String, String> nameUrlPair : FileUtil.readNameUrlFile(nameUrlFile)) {
File f = new File(dir, URLDecoder.decode(nameUrlPair.getLeft()));
File f = new File(dir, decodeURL(nameUrlPair.getLeft()));
//System.out.println(f.getAbsolutePath());
if (!f.exists()) {
list.put(f.getAbsolutePath(), nameUrlPair.getRight());

View File

@@ -12,6 +12,8 @@ import forge.deck.CardPool;
import forge.game.GameEntityView;
import forge.game.GameView;
import forge.game.card.CardView;
import forge.game.event.GameEventSpellAbilityCast;
import forge.game.event.GameEventSpellRemovedFromStack;
import forge.game.phase.PhaseType;
import forge.game.player.DelayedReveal;
import forge.game.player.IHasIcon;
@@ -47,6 +49,8 @@ public interface IGuiGame {
void showManaPool(PlayerView player);
void hideManaPool(PlayerView player);
void updateStack();
void notifyStackAddition(final GameEventSpellAbilityCast event);
void notifyStackRemoval(final GameEventSpellRemovedFromStack event);
Iterable<PlayerZoneUpdate> tempShowZones(PlayerView controller, Iterable<PlayerZoneUpdate> zonesToUpdate);
void hideZones(PlayerView controller, Iterable<PlayerZoneUpdate> zonesToUpdate);
void updateZones(Iterable<PlayerZoneUpdate> zonesToUpdate);

View File

@@ -24,6 +24,8 @@ import forge.assets.FSkinProp;
import forge.game.GameView;
import forge.game.card.CardView;
import forge.game.card.CardView.CardStateView;
import forge.game.event.GameEventSpellAbilityCast;
import forge.game.event.GameEventSpellRemovedFromStack;
import forge.game.player.PlayerView;
import forge.interfaces.IGameController;
import forge.interfaces.IGuiGame;
@@ -695,5 +697,13 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
return showConfirmDialog(message, title, yesButtonText, noButtonText, true);
}
@Override
public void notifyStackAddition(GameEventSpellAbilityCast event) {
}
@Override
public void notifyStackRemoval(GameEventSpellRemovedFromStack event) {
}
// End of Choice code
}

View File

@@ -310,6 +310,11 @@ public final class ForgeConstants {
public static final String GRAVEYARD_ORDERING_OWN_CARDS = "With Relevant Cards";
public static final String GRAVEYARD_ORDERING_ALWAYS = "Always";
// Constants for Stack effect addition notification policy
public static final String STACK_EFFECT_NOTIFICATION_NEVER = "Never";
public static final String STACK_EFFECT_NOTIFICATION_ALWAYS = "Always";
public static final String STACK_EFFECT_NOTIFICATION_AI_AND_TRIGGERED = "AI cast/activated, or triggered by any player";
// Set boolean constant for landscape mode for gdx port
public static final boolean isGdxPortLandscape = FileUtil.doesFileExist(ASSETS_DIR + "switch_orientation.ini");

View File

@@ -119,6 +119,7 @@ public class ForgePreferences extends PreferencesStore<ForgePreferences.FPref> {
UI_HIDE_GAME_TABS ("false"), // Visibility of tabs in match screen.
UI_CLOSE_ACTION ("NONE"),
UI_MANA_LOST_PROMPT ("false"), // Prompt on losing mana when passing priority
UI_STACK_EFFECT_NOTIFICATION_POLICY ("Never"),
UI_PAUSE_WHILE_MINIMIZED("false"),
UI_TOKENS_IN_SEPARATE_ROW("false"), // Display tokens in their own battlefield row.
UI_DISPLAY_CURRENT_COLORS(ForgeConstants.DISP_CURRENT_COLORS_NEVER),

View File

@@ -0,0 +1,87 @@
package forge.util;
import org.apache.commons.lang3.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class WordUtil {
public static String capitalize(String str) {
if (StringUtils.isEmpty(str)) {
return str;
}
final char[] buffer = str.toCharArray();
boolean capitalizeNext = true;
for (int i = 0; i < buffer.length; i++) {
final char ch = buffer[i];
if (Character.isWhitespace(ch)) {
capitalizeNext = true;
} else if (capitalizeNext) {
buffer[i] = Character.toTitleCase(ch);
capitalizeNext = false;
}
}
return new String(buffer);
}
private final static Pattern patternToWrapOn = Pattern.compile(" ");
public static String wordWrapAsHTML(String str) {
String result = null;
int wrapLength = 40;
String newLineStr = "<br>";
if (str != null) {
final int inputLineLength = str.length();
int offset = 0;
final StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32);
while (offset < inputLineLength) {
int spaceToWrapAt = -1;
Matcher matcher = patternToWrapOn.matcher(str.substring(offset, Math.min(offset + wrapLength + 1, inputLineLength)));
if (matcher.find()) {
if (matcher.start() == 0) {
offset += matcher.end();
continue;
}
spaceToWrapAt = matcher.start() + offset;
}
// only last line without leading spaces is left
if (inputLineLength - offset <= wrapLength) {
break;
}
while (matcher.find()) {
spaceToWrapAt = matcher.start() + offset;
}
if (spaceToWrapAt >= offset) {
// normal case
wrappedLine.append(str, offset, spaceToWrapAt);
wrappedLine.append(newLineStr);
offset = spaceToWrapAt + 1;
} else {
// do not wrap really long word, just extend beyond limit
matcher = patternToWrapOn.matcher(str.substring(offset + wrapLength));
if (matcher.find()) {
spaceToWrapAt = matcher.start() + offset + wrapLength;
}
if (spaceToWrapAt >= 0) {
wrappedLine.append(str, offset, spaceToWrapAt);
wrappedLine.append(newLineStr);
offset = spaceToWrapAt + 1;
} else {
wrappedLine.append(str, offset, str.length());
offset = inputLineLength;
}
}
}// Whatever is left in line is short enough to just pass through
wrappedLine.append(str, offset, str.length());
result = wrappedLine.toString();
}
return "<html>" + result + "</html>";
}
private WordUtil() {}
}