mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 10:48:00 +00:00
Merged changes from trunk to GuiRefactoring: 27266-27293; plus some minor fixes.
This commit is contained in:
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -16749,6 +16749,7 @@ forge-gui/src/main/java/forge/match/input/InputSelectTargets.java -text
|
|||||||
forge-gui/src/main/java/forge/match/input/InputSynchronized.java -text
|
forge-gui/src/main/java/forge/match/input/InputSynchronized.java -text
|
||||||
forge-gui/src/main/java/forge/match/input/InputSyncronizedBase.java -text
|
forge-gui/src/main/java/forge/match/input/InputSyncronizedBase.java -text
|
||||||
forge-gui/src/main/java/forge/match/input/package-info.java -text
|
forge-gui/src/main/java/forge/match/input/package-info.java -text
|
||||||
|
forge-gui/src/main/java/forge/model/Achievement.java -text
|
||||||
forge-gui/src/main/java/forge/model/CardBlock.java -text
|
forge-gui/src/main/java/forge/model/CardBlock.java -text
|
||||||
forge-gui/src/main/java/forge/model/CardCollections.java -text
|
forge-gui/src/main/java/forge/model/CardCollections.java -text
|
||||||
forge-gui/src/main/java/forge/model/FModel.java svneol=native#text/plain
|
forge-gui/src/main/java/forge/model/FModel.java svneol=native#text/plain
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class AiCardMemory {
|
|||||||
*/
|
*/
|
||||||
public enum MemorySet {
|
public enum MemorySet {
|
||||||
MANDATORY_ATTACKERS,
|
MANDATORY_ATTACKERS,
|
||||||
HELD_MANA_SOURCES, // stub, not linked to AI code yet
|
HELD_MANA_SOURCES,
|
||||||
//REVEALED_CARDS // stub, not linked to AI code yet
|
//REVEALED_CARDS // stub, not linked to AI code yet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ import forge.game.replacement.ReplaceMoved;
|
|||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.spellability.Ability;
|
import forge.game.spellability.Ability;
|
||||||
import forge.game.spellability.AbilityManaPart;
|
import forge.game.spellability.AbilityManaPart;
|
||||||
|
import forge.game.spellability.AbilityStatic;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.OptionalCost;
|
import forge.game.spellability.OptionalCost;
|
||||||
import forge.game.spellability.Spell;
|
import forge.game.spellability.Spell;
|
||||||
@@ -740,6 +741,7 @@ public class AiController {
|
|||||||
&& !ComputerUtil.castPermanentInMain1(player, sa)) {
|
&& !ComputerUtil.castPermanentInMain1(player, sa)) {
|
||||||
return AiPlayDecision.WaitForMain2;
|
return AiPlayDecision.WaitForMain2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// save cards with flash for surprise blocking
|
// save cards with flash for surprise blocking
|
||||||
if (card.hasKeyword("Flash")
|
if (card.hasKeyword("Flash")
|
||||||
&& (player.isUnlimitedHandSize() || player.getCardsIn(ZoneType.Hand).size() <= player.getMaxHandSize())
|
&& (player.isUnlimitedHandSize() || player.getCardsIn(ZoneType.Hand).size() <= player.getMaxHandSize())
|
||||||
@@ -750,6 +752,20 @@ public class AiController {
|
|||||||
&& !ComputerUtil.castPermanentInMain1(player, sa)) {
|
&& !ComputerUtil.castPermanentInMain1(player, sa)) {
|
||||||
return AiPlayDecision.AnotherTime;
|
return AiPlayDecision.AnotherTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// don't play cards without being able to pay the upkeep for
|
||||||
|
for (String ability : card.getKeyword()) {
|
||||||
|
if (ability.startsWith("At the beginning of your upkeep, sacrifice CARDNAME unless you pay")) {
|
||||||
|
final String[] k = ability.split(" pay ");
|
||||||
|
final String costs = k[1].replaceAll("[{]", "").replaceAll("[}]", " ");
|
||||||
|
Cost cost = new Cost(costs, true);
|
||||||
|
final Ability emptyAbility = new AbilityStatic(card, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
|
||||||
|
emptyAbility.setActivatingPlayer(player);
|
||||||
|
if (!ComputerUtilCost.canPayCost(emptyAbility, player)) {
|
||||||
|
return AiPlayDecision.AnotherTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
|
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
|
||||||
} else if( sa instanceof Spell ) {
|
} else if( sa instanceof Spell ) {
|
||||||
@@ -759,7 +775,7 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private AiPlayDecision canPlaySpellBasic(final Card card) {
|
private AiPlayDecision canPlaySpellBasic(final Card card) {
|
||||||
if (card.getSVar("NeedsToPlay").length() > 0) {
|
if (card.hasSVar("NeedsToPlay")) {
|
||||||
final String needsToPlay = card.getSVar("NeedsToPlay");
|
final String needsToPlay = card.getSVar("NeedsToPlay");
|
||||||
List<Card> list = game.getCardsIn(ZoneType.Battlefield);
|
List<Card> list = game.getCardsIn(ZoneType.Battlefield);
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ public enum AiProps { /** */
|
|||||||
DEFAULT_PLANAR_DIE_ROLL_CHANCE ("50"), /** */
|
DEFAULT_PLANAR_DIE_ROLL_CHANCE ("50"), /** */
|
||||||
MULLIGAN_THRESHOLD ("5"), /** */
|
MULLIGAN_THRESHOLD ("5"), /** */
|
||||||
PLANAR_DIE_ROLL_HESITATION_CHANCE ("10"),
|
PLANAR_DIE_ROLL_HESITATION_CHANCE ("10"),
|
||||||
CHEAT_WITH_MANA_ON_SHUFFLE ("FALSE"); /** */
|
CHEAT_WITH_MANA_ON_SHUFFLE ("false"),
|
||||||
|
MOVE_EQUIPMENT_TO_BETTER_CREATURES ("always"); /** */
|
||||||
|
|
||||||
private final String strDefaultVal;
|
private final String strDefaultVal;
|
||||||
|
|
||||||
|
|||||||
@@ -985,8 +985,8 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
// TODO AttachSource is currently set for the Source of the Spell, but
|
// TODO AttachSource is currently set for the Source of the Spell, but
|
||||||
// at some point can support attaching a different card
|
// at some point can support attaching a different card
|
||||||
|
|
||||||
// Don't equip if already equipping
|
// Don't equip if DontEquip SVar is set
|
||||||
if (attachSource.getEquippingCard() != null && attachSource.getEquippingCard().getController() == aiPlayer || attachSource.hasSVar("DontEquip")) {
|
if (attachSource.hasSVar("DontEquip")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Don't fortify if already fortifying
|
// Don't fortify if already fortifying
|
||||||
@@ -1017,6 +1017,35 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
Card c = attachGeneralAI(aiPlayer, sa, prefList, mandatory, attachSource, sa.getParam("AILogic"));
|
Card c = attachGeneralAI(aiPlayer, sa, prefList, mandatory, attachSource, sa.getParam("AILogic"));
|
||||||
|
|
||||||
|
AiController aic = ((PlayerControllerAi)aiPlayer.getController()).getAi();
|
||||||
|
if (c != null && attachSource.getType().contains("Equipment")
|
||||||
|
&& attachSource.getEquippingCard() != null
|
||||||
|
&& attachSource.getEquippingCard().getController() == aiPlayer) {
|
||||||
|
if (c.equals(attachSource.getEquippingCard())) {
|
||||||
|
// Do not equip if equipping the same card already
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aic.getProperty(AiProps.MOVE_EQUIPMENT_TO_BETTER_CREATURES).equals("never")) {
|
||||||
|
// Do not equip other creatures if the AI profile does not allow moving equipment around
|
||||||
|
return null;
|
||||||
|
} else if (aic.getProperty(AiProps.MOVE_EQUIPMENT_TO_BETTER_CREATURES).equals("from_useless_only")) {
|
||||||
|
// Do not equip other creatures if the AI profile only allows moving equipment from useless creatures
|
||||||
|
// and the equipped creature is still useful (not non-untapping+tapped and not set to can't attack/block)
|
||||||
|
if (!isUselessCreature(aiPlayer, attachSource.getEquippingCard())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure to prioritize casting spells in main 2 (creatures, other equipment, etc.) rather than moving equipment around
|
||||||
|
if (aic.getCardMemory().isMemorySetEmpty(AiCardMemory.MemorySet.HELD_MANA_SOURCES)) {
|
||||||
|
SpellAbility futureSpell = aic.predictSpellToCastInMain2(ApiType.Attach);
|
||||||
|
if (futureSpell != null && futureSpell.getHostCard() != null) {
|
||||||
|
aic.reserveManaSourcesForMain2(futureSpell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ((c == null) && mandatory) {
|
if ((c == null) && mandatory) {
|
||||||
CardLists.shuffle(list);
|
CardLists.shuffle(list);
|
||||||
c = list.get(0);
|
c = list.get(0);
|
||||||
@@ -1086,7 +1115,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Consider exceptional cases which break the normal evaluation rules
|
// Consider exceptional cases which break the normal evaluation rules
|
||||||
if (!isUsefulAttachAction(c, sa)) {
|
if (!isUsefulAttachAction(ai, c, sa)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1279,7 +1308,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
* @param sa SpellAbility
|
* @param sa SpellAbility
|
||||||
* @return true, if the action is useful (beneficial) in the current minimal context (Card vs. Attach SpellAbility)
|
* @return true, if the action is useful (beneficial) in the current minimal context (Card vs. Attach SpellAbility)
|
||||||
*/
|
*/
|
||||||
private static boolean isUsefulAttachAction(Card c, SpellAbility sa) {
|
private static boolean isUsefulAttachAction(Player ai, Card c, SpellAbility sa) {
|
||||||
if (c == null) {
|
if (c == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1292,17 +1321,28 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
ArrayList<String> cardTypes = sa.getHostCard().getType();
|
ArrayList<String> cardTypes = sa.getHostCard().getType();
|
||||||
|
|
||||||
if (cardTypes.contains("Equipment") && c.hasKeyword("CARDNAME can't attack or block.")) {
|
if (cardTypes.contains("Equipment") && isUselessCreature(ai, c)) {
|
||||||
// useless to equip a creature that can't attack or block.
|
// useless to equip a creature that can't attack or block.
|
||||||
return false;
|
return false;
|
||||||
} else if (cardTypes.contains("Equipment") && c.isTapped() && c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
|
|
||||||
// useless to equip a creature that won't untap and is tapped.
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isUselessCreature(Player ai, Card c) {
|
||||||
|
if (c == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.hasKeyword("CARDNAME can't attack or block.")
|
||||||
|
|| (c.hasKeyword("CARDNAME doesn't untap during your untap step.") && c.isTapped())
|
||||||
|
|| (c.getOwner() == ai && ai.getOpponents().contains(c.getController()))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import forge.error.BugReportDialog;
|
|||||||
import forge.events.UiEvent;
|
import forge.events.UiEvent;
|
||||||
import forge.game.GameType;
|
import forge.game.GameType;
|
||||||
import forge.game.Match;
|
import forge.game.Match;
|
||||||
|
import forge.game.card.Card;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.IHasIcon;
|
import forge.game.player.IHasIcon;
|
||||||
import forge.game.player.RegisteredPlayer;
|
import forge.game.player.RegisteredPlayer;
|
||||||
@@ -152,17 +153,30 @@ public class GuiDesktop implements IGuiBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T showInputDialog(String message, String title, FSkinProp icon, T initialInput, T[] inputOptions) {
|
public <T> T showInputDialog(String message, String title, FSkinProp icon, T initialInput, T[] inputOptions) {
|
||||||
|
if (initialInput instanceof Card || (inputOptions != null && inputOptions.length > 0 && inputOptions[0] instanceof Card)) {
|
||||||
|
System.err.println("Warning: Cards passed to GUI! Printing stack trace.");
|
||||||
|
Thread.dumpStack();
|
||||||
|
}
|
||||||
return FOptionPane.showInputDialog(message, title, icon == null ? null : FSkin.getImage(icon), initialInput, inputOptions);
|
return FOptionPane.showInputDialog(message, title, icon == null ? null : FSkin.getImage(icon), initialInput, inputOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final T selected, final Function<T, String> display) {
|
public <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final T selected, final Function<T, String> display) {
|
||||||
|
if ((choices != null && !choices.isEmpty() && choices.iterator().next() instanceof Card) || selected instanceof Card) {
|
||||||
|
System.err.println("Warning: Cards passed to GUI! Printing stack trace.");
|
||||||
|
Thread.dumpStack();
|
||||||
|
}
|
||||||
return GuiChoose.getChoices(message, min, max, choices, selected, display);
|
return GuiChoose.getChoices(message, min, max, choices, selected, display);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> List<T> order(final String title, final String top, final int remainingObjectsMin, final int remainingObjectsMax,
|
public <T> List<T> order(final String title, final String top, final int remainingObjectsMin, final int remainingObjectsMax,
|
||||||
final List<T> sourceChoices, final List<T> destChoices, final CardView referenceCard, final boolean sideboardingMode) {
|
final List<T> sourceChoices, final List<T> destChoices, final CardView referenceCard, final boolean sideboardingMode) {
|
||||||
|
if ((sourceChoices != null && !sourceChoices.isEmpty() && sourceChoices.iterator().next() instanceof Card)
|
||||||
|
|| (destChoices != null && !destChoices.isEmpty() && destChoices.iterator().next() instanceof Card)) {
|
||||||
|
System.err.println("Warning: Cards passed to GUI! Printing stack trace.");
|
||||||
|
Thread.dumpStack();
|
||||||
|
}
|
||||||
return GuiChoose.order(title, top, remainingObjectsMin, remainingObjectsMax, sourceChoices, destChoices, referenceCard, sideboardingMode);
|
return GuiChoose.order(title, top, remainingObjectsMin, remainingObjectsMax, sourceChoices, destChoices, referenceCard, sideboardingMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,11 @@ public enum FSkinImage implements FImage {
|
|||||||
ARCSON (FSkinProp.ICO_ARCSON, SourceFile.ICONS),
|
ARCSON (FSkinProp.ICO_ARCSON, SourceFile.ICONS),
|
||||||
ARCSHOVER (FSkinProp.ICO_ARCSHOVER, SourceFile.ICONS),
|
ARCSHOVER (FSkinProp.ICO_ARCSHOVER, SourceFile.ICONS),
|
||||||
|
|
||||||
|
//Achievement Trophies
|
||||||
|
BRONZE_TROPHY (FSkinProp.IMG_BRONZE_TROPHY, SourceFile.ICONS),
|
||||||
|
SILVER_TROPHY (FSkinProp.IMG_SILVER_TROPHY, SourceFile.ICONS),
|
||||||
|
GOLD_TROPHY (FSkinProp.IMG_GOLD_TROPHY, SourceFile.ICONS),
|
||||||
|
|
||||||
//Quest Icons
|
//Quest Icons
|
||||||
QUEST_ZEP (FSkinProp.ICO_QUEST_ZEP, SourceFile.ICONS),
|
QUEST_ZEP (FSkinProp.ICO_QUEST_ZEP, SourceFile.ICONS),
|
||||||
QUEST_GEAR (FSkinProp.ICO_QUEST_GEAR, SourceFile.ICONS),
|
QUEST_GEAR (FSkinProp.ICO_QUEST_GEAR, SourceFile.ICONS),
|
||||||
|
|||||||
@@ -170,9 +170,12 @@ public class MatchScreen extends FScreen {
|
|||||||
if (defender instanceof CardView) {
|
if (defender instanceof CardView) {
|
||||||
TargetingOverlay.drawArrow(g, attacker, (CardView) defender);
|
TargetingOverlay.drawArrow(g, attacker, (CardView) defender);
|
||||||
}
|
}
|
||||||
//connect each blocker with the attacker it's blocking
|
final Iterable<CardView> blockers = combat.getBlockers(attacker);
|
||||||
for (final CardView blocker : combat.getBlockers(attacker)) {
|
if (blockers != null) {
|
||||||
TargetingOverlay.drawArrow(g, blocker, attacker);
|
//connect each blocker with the attacker it's blocking
|
||||||
|
for (final CardView blocker : combat.getBlockers(attacker)) {
|
||||||
|
TargetingOverlay.drawArrow(g, blocker, attacker);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import forge.Graphics;
|
|||||||
import forge.assets.FSkinFont;
|
import forge.assets.FSkinFont;
|
||||||
import forge.assets.FImage;
|
import forge.assets.FImage;
|
||||||
import forge.assets.FSkinImage;
|
import forge.assets.FSkinImage;
|
||||||
import forge.assets.ImageUtil;
|
|
||||||
import forge.card.CardRenderer;
|
import forge.card.CardRenderer;
|
||||||
import forge.card.CardZoom;
|
import forge.card.CardZoom;
|
||||||
import forge.screens.match.views.VPrompt;
|
import forge.screens.match.views.VPrompt;
|
||||||
@@ -246,11 +245,7 @@ public class FOptionPane extends FDialog {
|
|||||||
|
|
||||||
float promptHeight = 0;
|
float promptHeight = 0;
|
||||||
if (lblIcon != null) {
|
if (lblIcon != null) {
|
||||||
float maxLabelWidth = Utils.AVG_FINGER_WIDTH * 0.8f;
|
float labelWidth = Utils.scaleX(lblIcon.getIcon().getWidth());
|
||||||
float labelWidth = ImageUtil.getNearestHQSize(maxLabelWidth, lblIcon.getIcon().getWidth());
|
|
||||||
if (labelWidth > maxLabelWidth) {
|
|
||||||
labelWidth /= 2;
|
|
||||||
}
|
|
||||||
promptHeight = lblIcon.getIcon().getHeight() * labelWidth / lblIcon.getIcon().getWidth();
|
promptHeight = lblIcon.getIcon().getHeight() * labelWidth / lblIcon.getIcon().getWidth();
|
||||||
if (promptHeight > maxPromptHeight) {
|
if (promptHeight > maxPromptHeight) {
|
||||||
promptHeight = maxPromptHeight;
|
promptHeight = maxPromptHeight;
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ This also applies to spells with Replicate and Multikicker to allow picking the
|
|||||||
When playing spells and abilities with the text "target opponent", if you only have one opponent, you will no longer be asked to choose the opponent to target.
|
When playing spells and abilities with the text "target opponent", if you only have one opponent, you will no longer be asked to choose the opponent to target.
|
||||||
When triggered abilities have only one valid target, that target will now be auto-selected.
|
When triggered abilities have only one valid target, that target will now be auto-selected.
|
||||||
|
|
||||||
|
- AI improvements -
|
||||||
|
Some artificial intelligence improvements were made in this version of Forge.
|
||||||
|
The AI will now always attack with creatures that it has temporarily gained control of until end of turn in order not to miss the opportunity and thus waste the gain control spell.
|
||||||
|
The AI will no longer bounce guild lands back to hand right after playing them, which sometimes caused the AI to lock itself on land drops completely.
|
||||||
|
The AI will now try to predict if it wants to cast a spell in Main 2 and reserve some mana for it instead of aggressively pumping creatures with all available mana.
|
||||||
|
The AI will now also consider pumping a creature if it can predict that this creature will deal more damage to the opponent with its increased power, which should eliminate the senseless attacks with 0/1 creatures that do not get pumped even when they do not get blocked.
|
||||||
|
The AI will no longer waste equipment on cards that are useless (e.g. are tapped and do not normally untap, or can't attack or block anymore, etc.).
|
||||||
|
The AI can now optionally move equipment from one creature to another. For this purpose, a new AI profile variable was added: MOVE_EQUIPMENT_TO_BETTER_CREATURES. It defines whether the AI will always move equipment to better creatures if it has mana ('always'), will only move if the currently equipped creature becomes useless as defined above or because the AI loses control of the creature ('from_useless_only'), or will never move equipment around ('never'). The "Default" profile is set to only move the equipment to other creatures if the currently equipped creatures become useless for the AI, while the "Reckless" AI profile always moves equipment to better creatures when given a chance to (if it has mana and if it doesn't need to reserve mana for a future spell in Main 2).
|
||||||
|
|
||||||
|
|
||||||
- New Commander 2014 and Khans of Tarkir cards -
|
- New Commander 2014 and Khans of Tarkir cards -
|
||||||
We have added a branch to our SVN for the new cards that are currently being scripted. These cards are not yet available in this build of forge. Please be patient and they will soon become available.
|
We have added a branch to our SVN for the new cards that are currently being scripted. These cards are not yet available in this build of forge. Please be patient and they will soon become available.
|
||||||
@@ -64,6 +73,38 @@ Mardu Skullhunter
|
|||||||
Heir of the Wilds
|
Heir of the Wilds
|
||||||
Ivorytusk Fortress
|
Ivorytusk Fortress
|
||||||
Sagu Mauler
|
Sagu Mauler
|
||||||
|
Ghostfire Blade
|
||||||
|
Abzan Ascendancy
|
||||||
|
Flying Crane Technique
|
||||||
|
Temur Charger
|
||||||
|
Utter End
|
||||||
|
Clever Impersonator
|
||||||
|
End Hostilities
|
||||||
|
Ugin's Nexus
|
||||||
|
Anafenza, the Foremost
|
||||||
|
Crater's Claws
|
||||||
|
Hardened Scales
|
||||||
|
See the Unwritten
|
||||||
|
Sarkhan, the Dragonspeaker
|
||||||
|
Sorin, Solemn Visitor
|
||||||
|
Frontier Bivouac
|
||||||
|
Mystic Monastery
|
||||||
|
Nomad Outpost
|
||||||
|
Opulent Palace
|
||||||
|
Sandsteppe Citadel
|
||||||
|
Dragon's Eye Savants
|
||||||
|
Horde Ambusher
|
||||||
|
Ruthless Ripper
|
||||||
|
Watcher of the Roost
|
||||||
|
Abzan Guide
|
||||||
|
Jeering Instigator
|
||||||
|
Ankle Shanker
|
||||||
|
Jeskai Windscout
|
||||||
|
Mantis Rider
|
||||||
|
Murderous Cut
|
||||||
|
Rakshasa Deathdealer
|
||||||
|
Savage Knuckleblade
|
||||||
|
Shambling Attendants
|
||||||
|
|
||||||
|
|
||||||
------------
|
------------
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE=3
|
|||||||
DEFAULT_PLANAR_DIE_ROLL_CHANCE=50
|
DEFAULT_PLANAR_DIE_ROLL_CHANCE=50
|
||||||
MULLIGAN_THRESHOLD=5
|
MULLIGAN_THRESHOLD=5
|
||||||
PLANAR_DIE_ROLL_HESITATION_CHANCE=10
|
PLANAR_DIE_ROLL_HESITATION_CHANCE=10
|
||||||
|
MOVE_EQUIPMENT_TO_BETTER_CREATURES=from_useless_only
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ CHEAT_WITH_MANA_ON_SHUFFLE=true
|
|||||||
DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN=1
|
DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN=1
|
||||||
DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE=1
|
DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE=1
|
||||||
DEFAULT_PLANAR_DIE_ROLL_CHANCE=100
|
DEFAULT_PLANAR_DIE_ROLL_CHANCE=100
|
||||||
MULLIGAN_THRESHOLD=2
|
MULLIGAN_THRESHOLD=3
|
||||||
PLANAR_DIE_ROLL_HESITATION_CHANCE=0
|
PLANAR_DIE_ROLL_HESITATION_CHANCE=0
|
||||||
|
MOVE_EQUIPMENT_TO_BETTER_CREATURES=always
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
Name:Homicidal Seclusion
|
Name:Homicidal Seclusion
|
||||||
ManaCost:4 B
|
ManaCost:4 B
|
||||||
Types:Enchantment
|
Types:Enchantment
|
||||||
S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddPower$ 3 | AddToughness$ 1 | AddKeyword$ Lifelink | CheckSVar$ X | SVarCompare$ EQ1 | Description$ As long as you control exactly one creature, that creature gets +3/+1 and has lifelink.
|
S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddPower$ 3 | AddToughness$ 1 | AddKeyword$ Lifelink | CheckSVar$ X | SVarCompare$ EQ1 | Description$ As long as you control exactly one creature, that creature gets +3/+1 and has lifelink.
|
||||||
SVar:X:Count$Valid Creature.YouCtrl
|
SVar:X:Count$Valid Creature.YouCtrl
|
||||||
SVar:PlayMain1:TRUE
|
SVar:RemAIDeck:True
|
||||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/homicidal_seclusion.jpg
|
SVar:PlayMain1:TRUE
|
||||||
Oracle:As long as you control exactly one creature, that creature gets +3/+1 and has lifelink.
|
SVar:Picture:http://www.wizards.com/global/images/magic/general/homicidal_seclusion.jpg
|
||||||
|
Oracle:As long as you control exactly one creature, that creature gets +3/+1 and has lifelink.
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 637 KiB After Width: | Height: | Size: 652 KiB |
@@ -231,6 +231,11 @@ public enum FSkinProp {
|
|||||||
IMG_PACK (new int[] {80, 760, 40, 40}, PropType.IMAGE),
|
IMG_PACK (new int[] {80, 760, 40, 40}, PropType.IMAGE),
|
||||||
IMG_SORCERY (new int[] {160, 720, 40, 40}, PropType.IMAGE),
|
IMG_SORCERY (new int[] {160, 720, 40, 40}, PropType.IMAGE),
|
||||||
|
|
||||||
|
//achievement trophies
|
||||||
|
IMG_BRONZE_TROPHY (new int[] {0, 880, 60, 80}, PropType.IMAGE),
|
||||||
|
IMG_SILVER_TROPHY (new int[] {60, 880, 60, 80}, PropType.IMAGE),
|
||||||
|
IMG_GOLD_TROPHY (new int[] {120, 880, 60, 80}, PropType.IMAGE),
|
||||||
|
|
||||||
//button images
|
//button images
|
||||||
IMG_BTN_START_UP (new int[] {480, 200, 160, 80}, PropType.ICON),
|
IMG_BTN_START_UP (new int[] {480, 200, 160, 80}, PropType.ICON),
|
||||||
IMG_BTN_START_OVER (new int[] {480, 280, 160, 80}, PropType.ICON),
|
IMG_BTN_START_OVER (new int[] {480, 280, 160, 80}, PropType.ICON),
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import forge.interfaces.IGuiBase;
|
|||||||
import forge.match.input.ButtonUtil;
|
import forge.match.input.ButtonUtil;
|
||||||
import forge.match.input.InputBase;
|
import forge.match.input.InputBase;
|
||||||
import forge.model.FModel;
|
import forge.model.FModel;
|
||||||
|
import forge.player.LobbyPlayerHuman;
|
||||||
import forge.properties.ForgePreferences.FPref;
|
import forge.properties.ForgePreferences.FPref;
|
||||||
import forge.util.Lang;
|
import forge.util.Lang;
|
||||||
import forge.util.gui.SGuiChoose;
|
import forge.util.gui.SGuiChoose;
|
||||||
@@ -177,6 +178,9 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
|
|||||||
gui.showPromptMessage(""); //clear prompt behind WinLose overlay
|
gui.showPromptMessage(""); //clear prompt behind WinLose overlay
|
||||||
ButtonUtil.update(gui, "", "", false, false, false);
|
ButtonUtil.update(gui, "", "", false, false, false);
|
||||||
gui.finishGame();
|
gui.finishGame();
|
||||||
|
if (gui.getGuiPlayer() instanceof LobbyPlayerHuman) {
|
||||||
|
gameView.updateAchievements((LobbyPlayerHuman) gui.getGuiPlayer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
275
forge-gui/src/main/java/forge/model/Achievement.java
Normal file
275
forge-gui/src/main/java/forge/model/Achievement.java
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
package forge.model;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
import forge.assets.FSkinProp;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.interfaces.IGuiBase;
|
||||||
|
import forge.properties.ForgeConstants;
|
||||||
|
import forge.util.XmlUtil;
|
||||||
|
import forge.util.gui.SOptionPane;
|
||||||
|
|
||||||
|
public enum Achievement {
|
||||||
|
WinStreak("Win Streak", true,
|
||||||
|
"Win 10 games in a row.", 10,
|
||||||
|
"Win 25 games in a row.", 25,
|
||||||
|
"Win 50 games in a row.", 50,
|
||||||
|
new Evaluator() {
|
||||||
|
@Override
|
||||||
|
public int evaluate(Player player, Game game, int current) {
|
||||||
|
if (player.getOutcome().hasWon()) {
|
||||||
|
return current + 1;
|
||||||
|
}
|
||||||
|
return 0; //reset if player didn't win
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
TotalWins("Total Wins", true,
|
||||||
|
"Win 100 games.", 100,
|
||||||
|
"Win 250 games.", 250,
|
||||||
|
"Win 500 games.", 500,
|
||||||
|
new Evaluator() {
|
||||||
|
@Override
|
||||||
|
public int evaluate(Player player, Game game, int current) {
|
||||||
|
if (player.getOutcome().hasWon()) {
|
||||||
|
return current + 1;
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Overkill("Overkill", true,
|
||||||
|
"Win game with opponent at -25 life or less.", 25,
|
||||||
|
"Win game with opponent at -50 life or less.", 50,
|
||||||
|
"Win game with opponent at -100 life or less.", 100,
|
||||||
|
new Evaluator() {
|
||||||
|
@Override
|
||||||
|
public int evaluate(Player player, Game game, int current) {
|
||||||
|
if (player.getOutcome().hasWon()) {
|
||||||
|
Player opponent = getSingleOpponent(player);
|
||||||
|
if (opponent != null && opponent.getLife() < 0) {
|
||||||
|
return -opponent.getLife();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
LifeToSpare("Life to Spare", true,
|
||||||
|
"Win game with 20 life more than you started with.", 20,
|
||||||
|
"Win game with 40 life more than you started with.", 40,
|
||||||
|
"Win game with 80 life more than you started with.", 80,
|
||||||
|
new Evaluator() {
|
||||||
|
@Override
|
||||||
|
public int evaluate(Player player, Game game, int current) {
|
||||||
|
if (player.getOutcome().hasWon()) {
|
||||||
|
int gainedLife = player.getLife() - player.getStartingLife();
|
||||||
|
if (gainedLife > 0) {
|
||||||
|
return gainedLife;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Hellbent("Hellbent", false,
|
||||||
|
"Win game with no cards in hand.", 1,
|
||||||
|
"Win game with no cards in hand or library.", 2,
|
||||||
|
"Win game with no cards in hand, library, or graveyard.", 3,
|
||||||
|
new Evaluator() {
|
||||||
|
@Override
|
||||||
|
public int evaluate(Player player, Game game, int current) {
|
||||||
|
if (player.getOutcome().hasWon()) {
|
||||||
|
if (player.getZone(ZoneType.Hand).size() == 0) {
|
||||||
|
if (player.getZone(ZoneType.Library).size() == 0) {
|
||||||
|
if (player.getZone(ZoneType.Graveyard).size() == 0) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private final String displayName, bronzeDesc, silverDesc, goldDesc;
|
||||||
|
private final int bronzeThreshold, silverThreshold, goldThreshold;
|
||||||
|
private final boolean showBest;
|
||||||
|
private final Evaluator evaluator;
|
||||||
|
private int best, current;
|
||||||
|
|
||||||
|
private Achievement(String displayName0, boolean showBest0,
|
||||||
|
String bronzeDesc0, int bronzeThreshold0,
|
||||||
|
String silverDesc0, int silverThreshold0,
|
||||||
|
String goldDesc0, int goldThreshold0,
|
||||||
|
Evaluator evaluator0) {
|
||||||
|
displayName = displayName0;
|
||||||
|
showBest = showBest0;
|
||||||
|
bronzeDesc = bronzeDesc0;
|
||||||
|
bronzeThreshold = bronzeThreshold0;
|
||||||
|
silverDesc = silverDesc0;
|
||||||
|
silverThreshold = silverThreshold0;
|
||||||
|
goldDesc = goldDesc0;
|
||||||
|
goldThreshold = goldThreshold0;
|
||||||
|
evaluator = evaluator0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
public String getBronzeDesc() {
|
||||||
|
return bronzeDesc;
|
||||||
|
}
|
||||||
|
public String getSilverDesc() {
|
||||||
|
return silverDesc;
|
||||||
|
}
|
||||||
|
public String getGoldDesc() {
|
||||||
|
return goldDesc;
|
||||||
|
}
|
||||||
|
public int getBest() {
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
public boolean showBest() {
|
||||||
|
return showBest;
|
||||||
|
}
|
||||||
|
public boolean earnedGold() {
|
||||||
|
return best >= goldThreshold;
|
||||||
|
}
|
||||||
|
public boolean earnedSilver() {
|
||||||
|
return best >= silverThreshold;
|
||||||
|
}
|
||||||
|
public boolean earnedBronze() {
|
||||||
|
return best >= bronzeThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateAll(IGuiBase gui, Player player) {
|
||||||
|
for (Achievement achievement : Achievement.values()) {
|
||||||
|
achievement.update(gui, player, false);
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(IGuiBase gui, Player player) {
|
||||||
|
update(gui, player, true);
|
||||||
|
}
|
||||||
|
private void update(IGuiBase gui, Player player, boolean save) {
|
||||||
|
current = evaluator.evaluate(player, player.getGame(), current);
|
||||||
|
if (current > best) {
|
||||||
|
int oldBest = best;
|
||||||
|
best = current;
|
||||||
|
|
||||||
|
String type = null;
|
||||||
|
FSkinProp image = null;
|
||||||
|
String desc = null;
|
||||||
|
if (earnedGold()) {
|
||||||
|
if (oldBest < goldThreshold) {
|
||||||
|
type = "Gold";
|
||||||
|
image = FSkinProp.IMG_GOLD_TROPHY;
|
||||||
|
desc = goldDesc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (earnedSilver()) {
|
||||||
|
if (oldBest < silverThreshold) {
|
||||||
|
type = "Silver";
|
||||||
|
image = FSkinProp.IMG_SILVER_TROPHY;
|
||||||
|
desc = silverDesc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (earnedBronze()) {
|
||||||
|
if (oldBest < bronzeThreshold) {
|
||||||
|
type = "Bronze";
|
||||||
|
image = FSkinProp.IMG_BRONZE_TROPHY;
|
||||||
|
desc = bronzeDesc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type != null) {
|
||||||
|
SOptionPane.showMessageDialog(gui, "You've earned a " + type + " trophy!\n\n" +
|
||||||
|
displayName + " - " + desc, "Achievement Earned", image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (save) {
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void load() {
|
||||||
|
try {
|
||||||
|
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
|
||||||
|
final Document document = builder.parse(ForgeConstants.ACHIEVEMENTS_FILE);
|
||||||
|
final NodeList cards = document.getElementsByTagName("a");
|
||||||
|
for (int i = 0; i < cards.getLength(); i++) {
|
||||||
|
final Element el = (Element)cards.item(i);
|
||||||
|
final Achievement achievement = Achievement.valueOf(el.getAttribute("name"));
|
||||||
|
achievement.best = getIntAttribute(el, "best");
|
||||||
|
achievement.current = getIntAttribute(el, "current");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException e) {
|
||||||
|
//ok if file not found
|
||||||
|
}
|
||||||
|
catch (MalformedURLException e) {
|
||||||
|
//ok if file not found
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getIntAttribute(Element el, String name) {
|
||||||
|
String value = el.getAttribute(name);
|
||||||
|
if (value.length() > 0) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(value);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void save() {
|
||||||
|
try {
|
||||||
|
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
|
||||||
|
Document document = builder.newDocument();
|
||||||
|
Element root = document.createElement("achievements");
|
||||||
|
document.appendChild(root);
|
||||||
|
|
||||||
|
for (Achievement achievement : Achievement.values()) {
|
||||||
|
if (achievement.best > 0 || achievement.current > 0) {
|
||||||
|
Element a = document.createElement("a");
|
||||||
|
a.setAttribute("name", achievement.name());
|
||||||
|
a.setAttribute("best", String.valueOf(achievement.best));
|
||||||
|
a.setAttribute("current", String.valueOf(achievement.current));
|
||||||
|
root.appendChild(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
XmlUtil.saveDocument(document, ForgeConstants.ACHIEVEMENTS_FILE);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static abstract class Evaluator {
|
||||||
|
public abstract int evaluate(Player player, Game game, int current);
|
||||||
|
|
||||||
|
//get single opponent of player
|
||||||
|
protected Player getSingleOpponent(Player player) {
|
||||||
|
if (player.getGame().getRegisteredPlayers().size() == 2) {
|
||||||
|
for (Player p : player.getGame().getRegisteredPlayers()) {
|
||||||
|
if (p.isOpponentOf(player)) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -155,6 +155,7 @@ public class FModel {
|
|||||||
CardPreferences.load();
|
CardPreferences.load();
|
||||||
DeckPreferences.load();
|
DeckPreferences.load();
|
||||||
ItemManagerConfig.load();
|
ItemManagerConfig.load();
|
||||||
|
Achievement.load();
|
||||||
|
|
||||||
//preload AI profiles
|
//preload AI profiles
|
||||||
AiProfileUtil.loadAllProfiles(ForgeConstants.AI_PROFILE_DIR);
|
AiProfileUtil.loadAllProfiles(ForgeConstants.AI_PROFILE_DIR);
|
||||||
|
|||||||
@@ -38,4 +38,8 @@ public class LobbyPlayerHuman extends LobbyPlayer implements IGameEntitiesFactor
|
|||||||
public void hear(LobbyPlayer player, String message) {
|
public void hear(LobbyPlayer player, String message) {
|
||||||
gui.hear(player, message);
|
gui.hear(player, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IGuiBase getGui() {
|
||||||
|
return this.gui;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -508,6 +508,7 @@ public class PlayerControllerHuman extends PlayerController {
|
|||||||
List<Card> toBottom = null;
|
List<Card> toBottom = null;
|
||||||
List<Card> toTop = null;
|
List<Card> toTop = null;
|
||||||
|
|
||||||
|
mayLookAt.addAll(topN);
|
||||||
if (topN.size() == 1) {
|
if (topN.size() == 1) {
|
||||||
if (willPutCardOnTop(topN.get(0))) {
|
if (willPutCardOnTop(topN.get(0))) {
|
||||||
toTop = topN;
|
toTop = topN;
|
||||||
@@ -517,7 +518,8 @@ public class PlayerControllerHuman extends PlayerController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
toBottom = SGuiChoose.many(getGui(), "Select cards to be put on the bottom of your library", "Cards to put on the bottom", -1, topN, null);
|
final List<CardView> toBottomViews = SGuiChoose.many(getGui(), "Select cards to be put on the bottom of your library", "Cards to put on the bottom", -1, gameView.getCardViews(topN), null);
|
||||||
|
toBottom = gameView.getCards(toBottomViews);
|
||||||
topN.removeAll(toBottom);
|
topN.removeAll(toBottom);
|
||||||
if (topN.isEmpty()) {
|
if (topN.isEmpty()) {
|
||||||
toTop = null;
|
toTop = null;
|
||||||
@@ -526,9 +528,11 @@ public class PlayerControllerHuman extends PlayerController {
|
|||||||
toTop = topN;
|
toTop = topN;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
toTop = SGuiChoose.order(getGui(), "Arrange cards to be put on top of your library", "Cards arranged", topN, null);
|
final List<CardView> toTopViews = SGuiChoose.order(getGui(), "Arrange cards to be put on top of your library", "Cards arranged", gameView.getCardViews(topN), null);
|
||||||
|
toTop = gameView.getCards(toTopViews);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mayLookAt.clear();
|
||||||
return ImmutablePair.of(toTop, toBottom);
|
return ImmutablePair.of(toTop, toBottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,8 +569,10 @@ public class PlayerControllerHuman extends PlayerController {
|
|||||||
@Override
|
@Override
|
||||||
public List<Card> chooseCardsToDiscardFrom(Player p, SpellAbility sa, List<Card> valid, int min, int max) {
|
public List<Card> chooseCardsToDiscardFrom(Player p, SpellAbility sa, List<Card> valid, int min, int max) {
|
||||||
if (p != player) {
|
if (p != player) {
|
||||||
|
mayLookAt.addAll(valid);
|
||||||
final List<CardView> choices = SGuiChoose.many(getGui(), "Choose " + min + " card" + (min != 1 ? "s" : "") + " to discard",
|
final List<CardView> choices = SGuiChoose.many(getGui(), "Choose " + min + " card" + (min != 1 ? "s" : "") + " to discard",
|
||||||
"Discarded", min, min, gameView.getCardViews(valid), null);
|
"Discarded", min, min, gameView.getCardViews(valid), null);
|
||||||
|
mayLookAt.clear();
|
||||||
return getCards(choices);
|
return getCards(choices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ public final class ForgeConstants {
|
|||||||
DECK_PREFS_FILE = USER_PREFS_DIR + "deck.preferences";
|
DECK_PREFS_FILE = USER_PREFS_DIR + "deck.preferences";
|
||||||
QUEST_PREFS_FILE = USER_PREFS_DIR + "quest.preferences";
|
QUEST_PREFS_FILE = USER_PREFS_DIR + "quest.preferences";
|
||||||
ITEM_VIEW_PREFS_FILE = USER_PREFS_DIR + "item_view.preferences";
|
ITEM_VIEW_PREFS_FILE = USER_PREFS_DIR + "item_view.preferences";
|
||||||
|
ACHIEVEMENTS_FILE = USER_PREFS_DIR + "achievements.xml";
|
||||||
|
|
||||||
_DEFAULTS_DIR = RES_DIR + "defaults/";
|
_DEFAULTS_DIR = RES_DIR + "defaults/";
|
||||||
NO_CARD_FILE = _DEFAULTS_DIR + "no_card.jpg";
|
NO_CARD_FILE = _DEFAULTS_DIR + "no_card.jpg";
|
||||||
@@ -228,6 +229,7 @@ public final class ForgeConstants {
|
|||||||
public static String DECK_PREFS_FILE;
|
public static String DECK_PREFS_FILE;
|
||||||
public static String QUEST_PREFS_FILE;
|
public static String QUEST_PREFS_FILE;
|
||||||
public static String ITEM_VIEW_PREFS_FILE;
|
public static String ITEM_VIEW_PREFS_FILE;
|
||||||
|
public static String ACHIEVEMENTS_FILE;
|
||||||
|
|
||||||
// data that has defaults in the program dir but overrides/additions in the user dir
|
// data that has defaults in the program dir but overrides/additions in the user dir
|
||||||
private static String _DEFAULTS_DIR;
|
private static String _DEFAULTS_DIR;
|
||||||
|
|||||||
@@ -50,6 +50,12 @@ public class CombatView {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param attacker
|
||||||
|
* @return the blockers associated with an attacker, or {@code null} if the
|
||||||
|
* attacker is unblocked.
|
||||||
|
*/
|
||||||
public Iterable<CardView> getBlockers(final CardView attacker) {
|
public Iterable<CardView> getBlockers(final CardView attacker) {
|
||||||
return attackersWithBlockers.get(attacker);
|
return attackersWithBlockers.get(attacker);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import forge.game.GameOutcome;
|
|||||||
import forge.game.GameType;
|
import forge.game.GameType;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.RegisteredPlayer;
|
import forge.game.player.RegisteredPlayer;
|
||||||
|
import forge.player.LobbyPlayerHuman;
|
||||||
import forge.util.ITriggerEvent;
|
import forge.util.ITriggerEvent;
|
||||||
|
|
||||||
public interface IGameView {
|
public interface IGameView {
|
||||||
@@ -36,6 +37,7 @@ public interface IGameView {
|
|||||||
public abstract GameOutcome.AnteResult getAnteResult();
|
public abstract GameOutcome.AnteResult getAnteResult();
|
||||||
|
|
||||||
public abstract boolean isGameOver();
|
public abstract boolean isGameOver();
|
||||||
|
public abstract void updateAchievements(LobbyPlayerHuman player);
|
||||||
|
|
||||||
public abstract int getPoisonCountersToLose();
|
public abstract int getPoisonCountersToLose();
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ import forge.game.player.RegisteredPlayer;
|
|||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.SpellAbilityStackInstance;
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.model.Achievement;
|
||||||
|
import forge.player.LobbyPlayerHuman;
|
||||||
import forge.util.ITriggerEvent;
|
import forge.util.ITriggerEvent;
|
||||||
|
import forge.util.ThreadUtil;
|
||||||
|
|
||||||
public class LocalGameView implements IGameView {
|
public class LocalGameView implements IGameView {
|
||||||
|
|
||||||
@@ -139,6 +142,24 @@ public class LocalGameView implements IGameView {
|
|||||||
return game.isGameOver();
|
return game.isGameOver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateAchievements(final LobbyPlayerHuman player) {
|
||||||
|
//update all achievements for GUI player after game finished
|
||||||
|
ThreadUtil.invokeInGameThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (game == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (final Player p : game.getRegisteredPlayers()) {
|
||||||
|
if (p.getController().getLobbyPlayer() == player) {
|
||||||
|
Achievement.updateAll(player.getGui(), p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPoisonCountersToLose() {
|
public int getPoisonCountersToLose() {
|
||||||
return game.getRules().getPoisonCountersToLose();
|
return game.getRules().getPoisonCountersToLose();
|
||||||
|
|||||||
@@ -311,6 +311,17 @@ public class PlayerView extends GameEntityView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getNCards(final ZoneType zone) {
|
||||||
|
switch (zone) {
|
||||||
|
case Hand:
|
||||||
|
return getnHandCards();
|
||||||
|
case Library:
|
||||||
|
return getnLibraryCards();
|
||||||
|
default:
|
||||||
|
return getCards(zone).size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the nHandCards
|
* @return the nHandCards
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user