Merge remote-tracking branch 'upstream/master' into LOTR28
@@ -35,14 +35,19 @@ public class AIDeckStatistics {
|
||||
this.numLands = numLands;
|
||||
}
|
||||
|
||||
public static AIDeckStatistics fromCardList(List<CardRules> cards) {
|
||||
public static AIDeckStatistics fromCards(List<Card> cards) {
|
||||
int totalCMC = 0;
|
||||
int totalCount = 0;
|
||||
int numLands = 0;
|
||||
int maxCost = 0;
|
||||
int[] maxPips = new int[6];
|
||||
int maxColoredCost = 0;
|
||||
for (CardRules rules : cards) {
|
||||
for (Card c : cards) {
|
||||
CardRules rules = c.getRules();
|
||||
if (rules == null) {
|
||||
System.err.println(c + " CardRules is null" + (c.isToken() ? "/token" : "."));
|
||||
continue;
|
||||
}
|
||||
CardType type = rules.getType();
|
||||
if (type.isLand()) {
|
||||
numLands += 1;
|
||||
@@ -80,15 +85,15 @@ public class AIDeckStatistics {
|
||||
}
|
||||
|
||||
|
||||
public static AIDeckStatistics fromDeck(Deck deck) {
|
||||
List<CardRules> rules_list = new ArrayList<>();
|
||||
public static AIDeckStatistics fromDeck(Deck deck, Player player) {
|
||||
List<Card> cardlist = new ArrayList<>();
|
||||
for (final Map.Entry<DeckSection, CardPool> deckEntry : deck) {
|
||||
switch (deckEntry.getKey()) {
|
||||
case Main:
|
||||
case Commander:
|
||||
for (final Map.Entry<PaperCard, Integer> poolEntry : deckEntry.getValue()) {
|
||||
CardRules rules = poolEntry.getKey().getRules();
|
||||
rules_list.add(rules);
|
||||
Card card = Card.fromPaperCard(poolEntry.getKey(), player);
|
||||
cardlist.add(card);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -96,25 +101,25 @@ public class AIDeckStatistics {
|
||||
}
|
||||
}
|
||||
|
||||
return fromCardList(rules_list);
|
||||
return fromCards(cardlist);
|
||||
}
|
||||
|
||||
public static AIDeckStatistics fromPlayer(Player player) {
|
||||
Deck deck = player.getRegisteredPlayer().getDeck();
|
||||
if (deck.isEmpty()) {
|
||||
// we're in a test or some weird match, search through the hand and library and build the decklist
|
||||
List<CardRules> rules_list = new ArrayList<>();
|
||||
List<Card> cardlist = new ArrayList<>();
|
||||
for (Card c : player.getAllCards()) {
|
||||
if (c.getPaperCard() == null) {
|
||||
continue;
|
||||
}
|
||||
rules_list.add(c.getRules());
|
||||
cardlist.add(c);
|
||||
}
|
||||
|
||||
return fromCardList(rules_list);
|
||||
return fromCards(cardlist);
|
||||
}
|
||||
|
||||
return fromDeck(deck);
|
||||
return fromDeck(deck, player);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -277,7 +277,7 @@ public class AiBlockController {
|
||||
|
||||
if (mode == TriggerType.DamageDone) {
|
||||
if (trigger.matchesValidParam("ValidSource", attacker)
|
||||
&& attacker.getNetCombatDamage() > 0
|
||||
&& !"False".equals(trigger.getParam("CombatDamage")) && attacker.getNetCombatDamage() > 0
|
||||
&& trigger.matchesValidParam("ValidTarget", combat.getDefenderByAttacker(attacker))) {
|
||||
value += 50;
|
||||
}
|
||||
|
||||
@@ -807,29 +807,26 @@ public class AiController {
|
||||
// Account for possible Ward after the spell is fully targeted
|
||||
// TODO: ideally, this should be done while targeting, so that a different target can be preferred if the best
|
||||
// one is warded and can't be paid for. (currently it will be stuck with the target until it could pay)
|
||||
if (sa.usesTargeting() && (!sa.isSpell() || CardFactoryUtil.isCounterable(host))) {
|
||||
for (Card tgt : sa.getTargets().getTargetCards()) {
|
||||
// TODO some older cards don't use the keyword, so check for trigger instead
|
||||
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(host.getController())) {
|
||||
int amount = 0;
|
||||
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
||||
if (wardCost.hasManaCost()) {
|
||||
amount = wardCost.getTotalMana().getCMC();
|
||||
if (amount > 0 && !ComputerUtilCost.canPayCost(sa, player, true)) {
|
||||
return AiPlayDecision.CantAfford;
|
||||
if (!sa.isSpell() || CardFactoryUtil.isCounterable(host)) {
|
||||
for (TargetChoices tc : sa.getAllTargetChoices()) {
|
||||
for (Card tgt : tc.getTargetCards()) {
|
||||
// TODO some older cards don't use the keyword, so check for trigger instead
|
||||
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(host.getController())) {
|
||||
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
||||
if (wardCost.hasManaCost()) {
|
||||
xCost = wardCost.getTotalMana().getCMC() > 0;
|
||||
}
|
||||
SpellAbilityAi topAI = new SpellAbilityAi() {};
|
||||
if (!topAI.willPayCosts(player, sa, wardCost, host)) {
|
||||
return AiPlayDecision.CostNotAcceptable;
|
||||
}
|
||||
}
|
||||
SpellAbilityAi topAI = new SpellAbilityAi() {
|
||||
};
|
||||
if (!topAI.willPayCosts(player, sa, wardCost, host)) {
|
||||
return AiPlayDecision.CostNotAcceptable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if some target raised cost
|
||||
if (oldCMC > -1) {
|
||||
if (!xCost && oldCMC > -1) {
|
||||
int finalCMC = CostAdjustment.adjust(sa.getPayCosts(), sa).getTotalMana().getCMC();
|
||||
if (finalCMC > oldCMC) {
|
||||
xCost = true;
|
||||
@@ -1332,10 +1329,7 @@ public class AiController {
|
||||
if (sa == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final List<SpellAbility> abilities = Lists.newArrayList();
|
||||
abilities.add(sa);
|
||||
return abilities;
|
||||
return Lists.newArrayList(sa);
|
||||
}
|
||||
|
||||
public List<SpellAbility> chooseSpellAbilityToPlay() {
|
||||
|
||||
@@ -1720,14 +1720,10 @@ public class ComputerUtil {
|
||||
return threatened;
|
||||
}
|
||||
} else {
|
||||
objects = topStack.getTargets();
|
||||
final List<GameObject> canBeTargeted = new ArrayList<>();
|
||||
for (Object o : objects) {
|
||||
if (o instanceof GameEntity) {
|
||||
final GameEntity ge = (GameEntity) o;
|
||||
if (ge.canBeTargetedBy(topStack)) {
|
||||
canBeTargeted.add(ge);
|
||||
}
|
||||
for (GameEntity ge : topStack.getTargets().getTargetEntities()) {
|
||||
if (ge.canBeTargetedBy(topStack)) {
|
||||
canBeTargeted.add(ge);
|
||||
}
|
||||
}
|
||||
if (canBeTargeted.isEmpty()) {
|
||||
|
||||
@@ -1865,7 +1865,6 @@ public class ComputerUtilCard {
|
||||
*/
|
||||
public static boolean canPumpAgainstRemoval(Player ai, SpellAbility sa) {
|
||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
||||
final CardCollection threatenedTargets = new CardCollection();
|
||||
|
||||
if (!sa.usesTargeting()) {
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
@@ -1877,14 +1876,11 @@ public class ComputerUtilCard {
|
||||
// For pumps without targeting restrictions, just return immediately until this is fleshed out.
|
||||
return false;
|
||||
}
|
||||
CardCollection targetables = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||
|
||||
targetables = ComputerUtil.getSafeTargets(ai, sa, targetables);
|
||||
for (final Card c : targetables) {
|
||||
if (objects.contains(c)) {
|
||||
threatenedTargets.add(c);
|
||||
}
|
||||
}
|
||||
CardCollection threatenedTargets = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||
threatenedTargets = ComputerUtil.getSafeTargets(ai, sa, threatenedTargets);
|
||||
threatenedTargets.retainAll(objects);
|
||||
|
||||
if (!threatenedTargets.isEmpty()) {
|
||||
sortByEvaluateCreature(threatenedTargets);
|
||||
for (Card c : threatenedTargets) {
|
||||
|
||||
@@ -673,7 +673,6 @@ public class ComputerUtilCombat {
|
||||
&& !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // flanking
|
||||
|
||||
final int defBushidoMagnitude = blocker.getKeywordMagnitude(Keyword.BUSHIDO);
|
||||
@@ -859,7 +858,7 @@ public class ComputerUtilCombat {
|
||||
}
|
||||
} else if (mode == TriggerType.DamageDone) {
|
||||
willTrigger = true;
|
||||
if (trigger.hasParam("ValidSource")) {
|
||||
if (trigger.hasParam("ValidSource") && !"False".equals(trigger.getParam("CombatDamage"))) {
|
||||
if (!(trigger.matchesValidParam("ValidSource", defender)
|
||||
&& defender.getNetCombatDamage() > 0
|
||||
&& trigger.matchesValidParam("ValidTarget", attacker))) {
|
||||
@@ -2461,7 +2460,7 @@ public class ComputerUtilCombat {
|
||||
CardCollectionView trigCards = attacker.getController().getCardsIn(ZoneType.Battlefield);
|
||||
for (Card c : trigCards) {
|
||||
for (Trigger t : c.getTriggers()) {
|
||||
if (t.getMode() == TriggerType.DamageDone && "True".equals(t.getParam("CombatDamage")) && t.matchesValidParam("ValidSource", attacker)) {
|
||||
if (t.getMode() == TriggerType.DamageDone && !"False".equals(t.getParam("CombatDamage")) && t.matchesValidParam("ValidSource", attacker)) {
|
||||
SpellAbility ab = t.getOverridingAbility();
|
||||
if (ab.getApi() == ApiType.Poison && "TriggeredTarget".equals(ab.getParam("Defined"))) {
|
||||
pd += AbilityUtils.calculateAmount(attacker, ab.getParam("Num"), ab);
|
||||
|
||||
@@ -22,6 +22,7 @@ import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.Spell;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetChoices;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
@@ -556,12 +557,14 @@ public class ComputerUtilCost {
|
||||
}
|
||||
|
||||
// Ward - will be accounted for when rechecking a targeted ability
|
||||
if (!sa.isTrigger() && sa.usesTargeting() && (!sa.isSpell() || !cannotBeCountered)) {
|
||||
for (Card tgt : sa.getTargets().getTargetCards()) {
|
||||
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
|
||||
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
||||
if (wardCost.hasManaCost()) {
|
||||
extraManaNeeded += wardCost.getTotalMana().getCMC();
|
||||
if (!sa.isTrigger() && (!sa.isSpell() || !cannotBeCountered)) {
|
||||
for (TargetChoices tc : sa.getAllTargetChoices()) {
|
||||
for (Card tgt : tc.getTargetCards()) {
|
||||
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
|
||||
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
||||
if (wardCost.hasManaCost()) {
|
||||
extraManaNeeded += wardCost.getTotalMana().getCMC();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.ability.AnimateAi;
|
||||
import forge.ai.ability.FightAi;
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
@@ -79,6 +80,49 @@ import java.util.List;
|
||||
*/
|
||||
public class SpecialCardAi {
|
||||
|
||||
// Arena and Magus of the Arena
|
||||
public static class Arena {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
|
||||
if (!game.getPhaseHandler().is(PhaseType.END_OF_TURN) || game.getPhaseHandler().getNextTurn() != ai) {
|
||||
return false; // at opponent's EOT only, to conserve mana
|
||||
}
|
||||
|
||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||
if (aiCreatures.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
CardCollection oppCreatures = opp.getCreaturesInPlay();
|
||||
if (oppCreatures.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Card aiCreature : aiCreatures) {
|
||||
boolean canKillAll = true;
|
||||
for (Card oppCreature : oppCreatures) {
|
||||
if (FightAi.canKill(oppCreature, aiCreature, 0)) {
|
||||
canKillAll = false;
|
||||
break;
|
||||
}
|
||||
if (!FightAi.canKill(aiCreature, oppCreature, 0)) {
|
||||
canKillAll = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (canKillAll) {
|
||||
sa.getTargets().clear();
|
||||
sa.getTargets().add(aiCreature);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sa.isTargetNumberValid();
|
||||
}
|
||||
}
|
||||
|
||||
// Black Lotus and Lotus Bloom
|
||||
public static class BlackLotus {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa, final ManaCostBeingPaid cost) {
|
||||
|
||||
@@ -86,7 +86,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
// Don't use instant speed animate abilities before AI's COMBAT_BEGIN
|
||||
if (!ph.is(PhaseType.COMBAT_BEGIN) && ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa, ai)
|
||||
if (!ph.is(PhaseType.COMBAT_BEGIN) && ph.isPlayerTurn(ai) && !isSorcerySpeed(sa, ai)
|
||||
&& !sa.hasParam("ActivationPhases") && !"Permanent".equals(sa.getParam("Duration"))) {
|
||||
return false;
|
||||
}
|
||||
@@ -174,7 +174,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (!SpellAbilityAi.isSorcerySpeed(sa, aiPlayer) && !"Permanent".equals(sa.getParam("Duration"))) {
|
||||
if (!isSorcerySpeed(sa, aiPlayer) && !"Permanent".equals(sa.getParam("Duration"))) {
|
||||
if (sa.hasParam("Crew") && c.isCreature()) {
|
||||
// Do not try to crew a vehicle which is already a creature
|
||||
return false;
|
||||
|
||||
@@ -1069,7 +1069,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
if (!immediately && (!game.getPhaseHandler().getNextTurn().equals(ai)
|
||||
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai)
|
||||
&& !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
|
||||
&& !ComputerUtil.activateForCost(sa, ai)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ public class CloneAi extends SpellAbilityAi {
|
||||
// don't use instant speed clone abilities outside computers
|
||||
// Combat_Begin step
|
||||
if (!ph.is(PhaseType.COMBAT_BEGIN)
|
||||
&& ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa, ai)
|
||||
&& ph.isPlayerTurn(ai) && !isSorcerySpeed(sa, ai)
|
||||
&& !sa.hasParam("ActivationPhases") && sa.hasParam("Duration")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
|
||||
if (toPay <= usableManaSources) {
|
||||
// If this is a reusable Resource, feel free to play it most of the time
|
||||
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (!playReusable(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -287,7 +287,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
if (toPay <= usableManaSources) {
|
||||
// If this is a reusable Resource, feel free to play it most
|
||||
// of the time
|
||||
if (!SpellAbilityAi.playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) {
|
||||
if (!playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (!playReusable(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -437,7 +437,7 @@ public class CountersPutAi extends CountersAi {
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
if (!ai.getGame().getStack().isEmpty() && !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
if (!ai.getGame().getStack().isEmpty() && !isSorcerySpeed(sa, ai)) {
|
||||
// only evaluates case where all tokens are placed on a single target
|
||||
if (sa.getMinTargets() < 2) {
|
||||
if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) {
|
||||
@@ -550,7 +550,7 @@ public class CountersPutAi extends CountersAi {
|
||||
if (sa.isCurse()) {
|
||||
choice = chooseCursedTarget(list, type, amount, ai);
|
||||
} else {
|
||||
if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
if (type.equals("P1P1") && !isSorcerySpeed(sa, ai)) {
|
||||
for (Card c : list) {
|
||||
if (ComputerUtilCard.shouldPumpCard(ai, sa, c, amount, amount, Lists.newArrayList())) {
|
||||
choice = c;
|
||||
@@ -623,7 +623,7 @@ public class CountersPutAi extends CountersAi {
|
||||
return false;
|
||||
}
|
||||
// Instant +1/+1
|
||||
if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
if (type.equals("P1P1") && !isSorcerySpeed(sa, ai)) {
|
||||
if (!(ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN) && abCost.isReusuableResource())) {
|
||||
return false; // only if next turn and cost is reusable
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (playReusable(ai, sa)) {
|
||||
return chance;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -46,11 +43,6 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
|
||||
// TODO: once the "planeswalker redirection" rule is removed completely, just remove this code and
|
||||
// remove the "burn Planeswalkers" code in the called method.
|
||||
return shouldTgtP(comp, sa, d, noPrevention, false);
|
||||
}
|
||||
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention, final boolean noPlaneswalkerRedirection) {
|
||||
int restDamage = d;
|
||||
final Game game = comp.getGame();
|
||||
Player enemy = comp.getWeakestOpponent();
|
||||
@@ -89,13 +81,6 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
// burn Planeswalkers
|
||||
// TODO: Must be removed completely when the "planeswalker redirection" rule is removed.
|
||||
if (!noPlaneswalkerRedirection
|
||||
&& Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (avoidTargetP(comp, sa)) {
|
||||
return false;
|
||||
}
|
||||
@@ -131,7 +116,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
// chance to burn player based on current hand size
|
||||
if (hand.size() > 2) {
|
||||
float value = 0;
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, comp)) {
|
||||
if (isSorcerySpeed(sa, comp)) {
|
||||
//lower chance for sorcery as other spells may be cast in main2
|
||||
if (phase.isPlayerTurn(comp) && phase.is(PhaseType.MAIN2)) {
|
||||
value = 1.0f * restDamage / enemy.getLife();
|
||||
|
||||
@@ -1,28 +1,10 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
@@ -30,11 +12,7 @@ import forge.game.GameEntity;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPartMana;
|
||||
import forge.game.keyword.Keyword;
|
||||
@@ -51,6 +29,12 @@ import forge.game.staticability.StaticAbilityMustTarget;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DamageDealAi extends DamageAiBase {
|
||||
@Override
|
||||
@@ -630,7 +614,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
if (tgt.canTgtPlaneswalker()) {
|
||||
// We can damage planeswalkers with this, consider targeting.
|
||||
Card c = dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, enemy, false);
|
||||
if (c != null && !shouldTgtP(ai, sa, dmg, noPrevention, true)) {
|
||||
if (c != null && !shouldTgtP(ai, sa, dmg, noPrevention)) {
|
||||
tcs.add(c);
|
||||
if (divided) {
|
||||
int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||
@@ -746,17 +730,17 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// TODO: Improve Damage, we shouldn't just target the player just because we can
|
||||
if (sa.canTarget(enemy) && sa.canAddMoreTarget()) {
|
||||
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
||||
|| (SpellAbilityAi.isSorcerySpeed(sa, ai) && phase.is(PhaseType.MAIN2))
|
||||
|| ("PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai))
|
||||
|| immediately || shouldTgtP(ai, sa, dmg, noPrevention)) &&
|
||||
(!avoidTargetP(ai, sa))) {
|
||||
tcs.add(enemy);
|
||||
if (divided) {
|
||||
sa.addDividedAllocation(enemy, dmg);
|
||||
break;
|
||||
if ((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
||||
|| (isSorcerySpeed(sa, ai) && phase.is(PhaseType.MAIN2))
|
||||
|| immediately) {
|
||||
boolean pingAfterAttack = "PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai);
|
||||
if ((pingAfterAttack && !avoidTargetP(ai, sa)) || shouldTgtP(ai, sa, dmg, noPrevention)) {
|
||||
tcs.add(enemy);
|
||||
if (divided) {
|
||||
sa.addDividedAllocation(enemy, dmg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -836,7 +820,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
if (!positive && !(saMe instanceof AbilitySub)) {
|
||||
return false;
|
||||
}
|
||||
if (!urgent && !SpellAbilityAi.playReusable(ai, saMe)) {
|
||||
if (!urgent && !playReusable(ai, saMe)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -16,7 +16,7 @@ public class DayTimeAi extends SpellAbilityAi {
|
||||
|
||||
if ((sa.getHostCard().isCreature() && sa.getPayCosts().hasTapCost()) || sa.getPayCosts().hasManaCost()) {
|
||||
// If it involves a cost that may put us at a disadvantage, better activate before own turn if possible
|
||||
if (!SpellAbilityAi.isSorcerySpeed(sa, aiPlayer)) {
|
||||
if (!isSorcerySpeed(sa, aiPlayer)) {
|
||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer;
|
||||
} else {
|
||||
return ph.is(PhaseType.MAIN2, aiPlayer); // Give other things a chance to be cast (e.g. Celestus)
|
||||
|
||||
@@ -60,7 +60,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
|| !game.getStack().isEmpty()) {
|
||||
// Instant-speed pumps should not be cast outside of combat when the
|
||||
// stack is empty, unless there are specific activation phase requirements
|
||||
if (!SpellAbilityAi.isSorcerySpeed(sa, ai) && !sa.hasParam("ActivationPhases")) {
|
||||
if (!isSorcerySpeed(sa, ai) && !sa.hasParam("ActivationPhases")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||
list = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, list, false);
|
||||
}
|
||||
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (!playReusable(ai, sa)) {
|
||||
list = CardLists.filter(list, Predicates.not(CardPredicates.hasCounter(CounterEnumType.SHIELD, 1)));
|
||||
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
|
||||
@@ -101,13 +101,13 @@ public class DigAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (playReusable(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|
||||
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai)
|
||||
&& !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
|
||||
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
|
||||
&& !ComputerUtil.activateForCost(sa, ai)) {
|
||||
return false;
|
||||
|
||||
@@ -62,13 +62,13 @@ public class DigMultipleAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (playReusable(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|
||||
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai)
|
||||
&& !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
|
||||
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
|
||||
&& !ComputerUtil.activateForCost(sa, ai)) {
|
||||
return false;
|
||||
|
||||
@@ -23,7 +23,7 @@ public class DigUntilAi extends SpellAbilityAi {
|
||||
Card source = sa.getHostCard();
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
double chance = .4; // 40 percent chance with instant speed stuff
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
if (isSorcerySpeed(sa, ai)) {
|
||||
chance = .667; // 66.7% chance for sorcery speed (since it will
|
||||
// never activate EOT)
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
|
||||
if ((!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai)
|
||||
&& !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
|
||||
&& ai.getCardsIn(ZoneType.Hand).size() > 1 && !ComputerUtil.activateForCost(sa, ai)
|
||||
&& !"YawgmothsBargain".equals(logic)) {
|
||||
return false;
|
||||
|
||||
@@ -30,7 +30,6 @@ public class FightAi extends SpellAbilityAi {
|
||||
|
||||
// everything is defined or targeted above, can't do anything there unless a specific logic is set
|
||||
if (sa.hasParam("Defined") && !sa.usesTargeting()) {
|
||||
// TODO extend Logic for cards like Arena
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ public class FlipOntoBattlefieldAi extends SpellAbilityAi {
|
||||
PhaseHandler ph = sa.getHostCard().getGame().getPhaseHandler();
|
||||
String logic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if (!SpellAbilityAi.isSorcerySpeed(sa, aiPlayer) && sa.getPayCosts().hasManaCost()) {
|
||||
if (!isSorcerySpeed(sa, aiPlayer) && sa.getPayCosts().hasManaCost()) {
|
||||
return ph.is(PhaseType.END_OF_TURN);
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
||||
|
||||
return lifeCritical || activateForCost
|
||||
|| (ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
|| sa.hasParam("PlayerTurn") || SpellAbilityAi.isSorcerySpeed(sa, ai);
|
||||
|| sa.hasParam("PlayerTurn") || isSorcerySpeed(sa, ai);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -180,8 +180,8 @@ public class LifeGainAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, ai)
|
||||
|| sa.getSubAbility() != null || SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (isSorcerySpeed(sa, ai)
|
||||
|| sa.getSubAbility() != null || playReusable(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ public class LifeLoseAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, ai) || sa.hasParam("ActivationPhases") || SpellAbilityAi.playReusable(ai, sa)
|
||||
if (isSorcerySpeed(sa, ai) || sa.hasParam("ActivationPhases") || playReusable(ai, sa)
|
||||
|| ComputerUtil.activateForCost(sa, ai)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public class ManifestAi extends SpellAbilityAi {
|
||||
if (!buff) {
|
||||
return false;
|
||||
}
|
||||
} else if (!SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
} else if (!isSorcerySpeed(sa, ai)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -152,7 +152,7 @@ public class ManifestAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
// Probably should be a little more discerning on playing during OPPs turn
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (playReusable(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
|
||||
@@ -51,7 +51,7 @@ public class MillAi extends SpellAbilityAi {
|
||||
return (ph.is(PhaseType.MAIN1) || ph.is(PhaseType.MAIN2)) && ph.isPlayerTurn(ai); // Chandra, Torch of Defiance and similar
|
||||
}
|
||||
if (!sa.isPwAbility()) { // Planeswalker abilities are only activated at sorcery speed
|
||||
if ("You".equals(sa.getParam("Defined")) && !(!SpellAbilityAi.isSorcerySpeed(sa, ai) && ph.is(PhaseType.END_OF_TURN)
|
||||
if ("You".equals(sa.getParam("Defined")) && !(!isSorcerySpeed(sa, ai) && ph.is(PhaseType.END_OF_TURN)
|
||||
&& ph.getNextTurn().equals(ai))) {
|
||||
return false; // only self-mill at opponent EOT
|
||||
}
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.CardType.Supertype;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
@@ -24,6 +18,7 @@ import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class PermanentAi extends SpellAbilityAi {
|
||||
|
||||
@@ -49,19 +44,19 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
// check on legendary
|
||||
if (!card.ignoreLegendRule() && ai.isCardInPlay(card.getName())) {
|
||||
if (!source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
|
||||
// TODO check the risk we'd lose the effect with bad timing
|
||||
if (!card.hasSVar("AILegendaryException")) {
|
||||
if (!source.hasSVar("AILegendaryException")) {
|
||||
// AiPlayDecision.WouldDestroyLegend
|
||||
return false;
|
||||
} else {
|
||||
String specialRule = card.getSVar("AILegendaryException");
|
||||
String specialRule = source.getSVar("AILegendaryException");
|
||||
if ("TwoCopiesAllowed".equals(specialRule)) {
|
||||
// One extra copy allowed on the battlefield, e.g. Brothers Yamazaki
|
||||
if (CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(card.getName())) > 1) {
|
||||
if (CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(source.getName())) > 1) {
|
||||
return false;
|
||||
}
|
||||
} else if ("AlwaysAllowed".equals(specialRule)) {
|
||||
@@ -73,23 +68,7 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
/* -- not used anymore after Ixalan (Planeswalkers are now legendary, not unique by subtype) --
|
||||
if (card.isPlaneswalker()) {
|
||||
CardCollection list = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
|
||||
CardPredicates.Presets.PLANESWALKERS);
|
||||
for (String type : card.getType().getSubtypes()) { // determine
|
||||
// planewalker
|
||||
// subtype
|
||||
final CardCollection cl = CardLists.getType(list, type);
|
||||
if (!cl.isEmpty()) {
|
||||
// AiPlayDecision.WouldDestroyOtherPlaneswalker
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
|
||||
if (card.getType().hasSupertype(Supertype.World)) {
|
||||
if (source.getType().hasSupertype(Supertype.World)) {
|
||||
CardCollection list = CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "World");
|
||||
if (!list.isEmpty()) {
|
||||
// AiPlayDecision.WouldDestroyWorldEnchantment
|
||||
@@ -101,7 +80,6 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
if (mana.countX() > 0) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, false);
|
||||
final Card source = sa.getHostCard();
|
||||
if (source.hasConverge()) {
|
||||
int nColors = ComputerUtilMana.getConvergeCount(sa, ai);
|
||||
for (int i = 1; i <= xPay; i++) {
|
||||
@@ -123,7 +101,7 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
}
|
||||
} else if (mana.isZero()) {
|
||||
// if mana is zero, but card mana cost does have X, then something is wrong
|
||||
ManaCost cardCost = card.getManaCost();
|
||||
ManaCost cardCost = source.getManaCost();
|
||||
if (cardCost != null && cardCost.countX() > 0) {
|
||||
// AiPlayDecision.CantPlayAi
|
||||
return false;
|
||||
@@ -143,6 +121,38 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
sa.setXManaCostPaid(xPay);
|
||||
}
|
||||
|
||||
if ("ChaliceOfTheVoid".equals(source.getSVar("AICurseEffect"))) {
|
||||
int maxX = sa.getXManaCostPaid(); // as set above
|
||||
CardCollection otherChalices = CardLists.filter(ai.getGame().getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Chalice of the Void"));
|
||||
outer: for (int i = 0; i <= maxX; i++) {
|
||||
for (Card chalice : otherChalices) {
|
||||
if (chalice.getCounters(CounterEnumType.CHARGE) == i) {
|
||||
continue outer; // already disabled, no point in adding another one
|
||||
}
|
||||
}
|
||||
// assume the AI knows the deck lists of its opponents and if we see a card in a certain zone except for the library or hand,
|
||||
// it likely won't be cast unless it's bounced back (ideally, this should also somehow account for hidden information such as face down cards in exile)
|
||||
final int manaValue = i;
|
||||
CardCollection aiCards = CardLists.filter(ai.getAllCards(), card -> (card.isInZone(ZoneType.Library) || !card.isInZone(ZoneType.Hand))
|
||||
&& card.getState(CardStateName.Original).getManaCost() != null
|
||||
&& card.getState(CardStateName.Original).getManaCost().getCMC() == manaValue);
|
||||
CardCollection oppCards = CardLists.filter(ai.getStrongestOpponent().getAllCards(), card -> (card.isInZone(ZoneType.Library) || !card.isInZone(ZoneType.Hand))
|
||||
&& card.getState(CardStateName.Original).getManaCost() != null
|
||||
&& card.getState(CardStateName.Original).getManaCost().getCMC() == manaValue);
|
||||
if (manaValue == 0) {
|
||||
aiCards = CardLists.filter(aiCards, Predicates.not(CardPredicates.isType("Land")));
|
||||
oppCards = CardLists.filter(oppCards, Predicates.not(CardPredicates.isType("Land")));
|
||||
// also filter out other Chalices in our own deck
|
||||
aiCards = CardLists.filter(aiCards, Predicates.not(CardPredicates.nameEquals("Chalice of the Void")));
|
||||
}
|
||||
if (oppCards.size() > 3 && oppCards.size() >= aiCards.size() * 2) {
|
||||
sa.setXManaCostPaid(manaValue);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.hasParam("Announce") && sa.getParam("Announce").startsWith("Multikicker")) {
|
||||
// String announce = sa.getParam("Announce");
|
||||
ManaCost mkCost = sa.getMultiKickerManaCost();
|
||||
@@ -152,13 +162,13 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
mCost = ManaCost.combine(mCost, mkCost);
|
||||
ManaCostBeingPaid mcbp = new ManaCostBeingPaid(mCost);
|
||||
if (!ComputerUtilMana.canPayManaCost(mcbp, sa, ai, false)) {
|
||||
card.setKickerMagnitude(i);
|
||||
source.setKickerMagnitude(i);
|
||||
sa.setSVar("Multikicker", String.valueOf(i));
|
||||
break;
|
||||
}
|
||||
card.setKickerMagnitude(i + 1);
|
||||
source.setKickerMagnitude(i + 1);
|
||||
}
|
||||
if (isZeroCost && card.getKickerMagnitude() == 0) {
|
||||
if (isZeroCost && source.getKickerMagnitude() == 0) {
|
||||
// Bail if the card cost was {0} and no multikicker was paid (e.g. Everflowing Chalice).
|
||||
// TODO: update this if there's ever a card where it makes sense to play it for {0} with no multikicker
|
||||
return false;
|
||||
@@ -166,13 +176,13 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// don't play cards without being able to pay the upkeep for
|
||||
for (KeywordInterface inst : card.getKeywords()) {
|
||||
for (KeywordInterface inst : source.getKeywords()) {
|
||||
String ability = inst.getOriginal();
|
||||
if (ability.startsWith("UpkeepCost")) {
|
||||
final String[] k = ability.split(":");
|
||||
final String costs = k[1];
|
||||
|
||||
final SpellAbility emptyAbility = new SpellAbility.EmptySa(card, ai);
|
||||
final SpellAbility emptyAbility = new SpellAbility.EmptySa(source, ai);
|
||||
emptyAbility.setPayCosts(new Cost(costs, true));
|
||||
emptyAbility.setTargetRestrictions(sa.getTargetRestrictions());
|
||||
emptyAbility.setCardState(sa.getCardState());
|
||||
@@ -186,8 +196,8 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// check for specific AI preferences
|
||||
if (card.hasSVar("AICastPreference")) {
|
||||
String pref = card.getSVar("AICastPreference");
|
||||
if (source.hasSVar("AICastPreference")) {
|
||||
String pref = source.getSVar("AICastPreference");
|
||||
String[] groups = StringUtils.split(pref, "|");
|
||||
boolean dontCast = false;
|
||||
for (String group : groups) {
|
||||
@@ -205,7 +215,7 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
// Only cast unless there are X or more cards like this on the battlefield under AI control already,
|
||||
CardCollectionView valid = param.contains("Globally") ? ai.getGame().getCardsIn(ZoneType.Battlefield)
|
||||
: ai.getCardsIn(ZoneType.Battlefield);
|
||||
CardCollection ctrld = CardLists.filter(valid, CardPredicates.nameEquals(card.getName()));
|
||||
CardCollection ctrld = CardLists.filter(valid, CardPredicates.nameEquals(source.getName()));
|
||||
|
||||
int numControlled = 0;
|
||||
if (param.endsWith("WithoutOppAuras")) {
|
||||
@@ -242,7 +252,7 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
// if there are X-1 mana sources in play but the AI has an extra land in hand
|
||||
CardCollection m = ComputerUtilMana.getAvailableManaSources(ai, true);
|
||||
int extraMana = CardLists.count(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS) > 0 ? 1 : 0;
|
||||
if (card.getName().equals("Illusions of Grandeur")) {
|
||||
if (source.getName().equals("Illusions of Grandeur")) {
|
||||
// TODO: this is currently hardcoded for specific Illusions-Donate cost reduction spells, need to make this generic.
|
||||
extraMana += Math.min(3, CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Predicates.or(CardPredicates.nameEquals("Sapphire Medallion"), CardPredicates.nameEquals("Helm of Awakening"))).size()) * 2; // each cost-reduction spell accounts for {1} in both Illusions and Donate
|
||||
}
|
||||
@@ -260,7 +270,7 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
break; // disregard other preferences, always cast as a last resort
|
||||
}
|
||||
} else if (param.equals("OnlyFromZone")) {
|
||||
if (!card.getZone().getZoneType().toString().equals(value)) {
|
||||
if (!source.getZone().getZoneType().toString().equals(value)) {
|
||||
dontCast = true;
|
||||
break; // limit casting to a specific zone only
|
||||
}
|
||||
|
||||
@@ -235,11 +235,19 @@ public class PermanentCreatureAi extends PermanentAi {
|
||||
* worth it. Not sure what 4. is for. 5. needs to be updated to ensure
|
||||
* that the net toughness is still positive after static effects.
|
||||
*/
|
||||
// AiPlayDecision.WouldBecomeZeroToughnessCreature
|
||||
if (card.hasStartOfKeyword("etbCounter") || mana.countX() != 0
|
||||
|| card.hasETBTrigger(false) || card.hasETBReplacement() || card.hasSVar("NoZeroToughnessAI")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final Card copy = CardUtil.getLKICopy(card);
|
||||
ComputerUtilCard.applyStaticContPT(game, copy, null);
|
||||
// AiPlayDecision.WouldBecomeZeroToughnessCreature
|
||||
return copy.getNetToughness() > 0 || copy.hasStartOfKeyword("etbCounter") || mana.countX() != 0
|
||||
|| copy.hasETBTrigger(false) || copy.hasETBReplacement() || copy.hasSVar("NoZeroToughnessAI");
|
||||
if (copy.getNetToughness() > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ public class ProtectAi extends SpellAbilityAi {
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
final boolean notAiMain1 = !(ph.getPlayerTurn() == ai && ph.getPhase() == PhaseType.MAIN1);
|
||||
// sorceries can only give protection in order to create an unblockable attacker
|
||||
return !SpellAbilityAi.isSorcerySpeed(sa, ai) || !notAiMain1;
|
||||
return !isSorcerySpeed(sa, ai) || !notAiMain1;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -87,7 +87,7 @@ public class PumpAi extends PumpAiBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
return SpellAbilityAi.isSorcerySpeed(sa, ai) || (ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN));
|
||||
return isSorcerySpeed(sa, ai) || (ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN));
|
||||
} else if (logic.equals("Aristocrat")) {
|
||||
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(sa.getHostCard());
|
||||
if (!ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && !isThreatened) {
|
||||
@@ -118,7 +118,7 @@ public class PumpAi extends PumpAiBase {
|
||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
|
||||
// Instant-speed pumps should not be cast outside of combat when the
|
||||
// stack is empty
|
||||
return sa.isCurse() || SpellAbilityAi.isSorcerySpeed(sa, ai) || main1Preferred;
|
||||
return sa.isCurse() || isSorcerySpeed(sa, ai) || main1Preferred;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -292,10 +292,6 @@ public class PumpAi extends PumpAiBase {
|
||||
if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||
if (sourceName.equals("Necropolis Fiend")) {
|
||||
xPay = Math.min(xPay, sa.getActivatingPlayer().getCardsIn(ZoneType.Graveyard).size());
|
||||
sa.setSVar("X", Integer.toString(xPay));
|
||||
}
|
||||
sa.setXManaCostPaid(xPay);
|
||||
defense = xPay;
|
||||
if (numDefense.equals("-X")) {
|
||||
@@ -371,7 +367,7 @@ public class PumpAi extends PumpAiBase {
|
||||
if (ComputerUtilCard.shouldPumpCard(ai, sa, card, defense, attack, keywords, false)) {
|
||||
return true;
|
||||
} else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) {
|
||||
if (game.getPhaseHandler().isPreCombatMain() && SpellAbilityAi.isSorcerySpeed(sa, ai) ||
|
||||
if (game.getPhaseHandler().isPreCombatMain() && isSorcerySpeed(sa, ai) ||
|
||||
game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai) ||
|
||||
game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN, ai)) {
|
||||
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.AiPlayDecision;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
@@ -26,7 +25,7 @@ public class RevealAi extends RevealAiBase {
|
||||
|
||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.667, sa.getActivationsThisTurn() + 1);
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (playReusable(ai, sa)) {
|
||||
randomReturn = true;
|
||||
}
|
||||
return randomReturn;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.MyRandom;
|
||||
@@ -20,7 +19,7 @@ public class RevealHandAi extends RevealAiBase {
|
||||
|
||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.667, sa.getActivationsThisTurn() + 1);
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (playReusable(ai, sa)) {
|
||||
randomReturn = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ public class ScryAi extends SpellAbilityAi {
|
||||
// even if there's no mana cost.
|
||||
if (sa.getPayCosts().hasTapCost()
|
||||
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
|
||||
&& !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
&& !isSorcerySpeed(sa, ai)) {
|
||||
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ public class ScryAi extends SpellAbilityAi {
|
||||
|
||||
// in the playerturn Scry should only be done in Main1 or in upkeep if able
|
||||
if (ph.isPlayerTurn(ai)) {
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
if (isSorcerySpeed(sa, ai)) {
|
||||
return ph.is(PhaseType.MAIN1) || sa.isPwAbility();
|
||||
} else {
|
||||
return ph.is(PhaseType.UPKEEP);
|
||||
@@ -121,12 +121,12 @@ public class ScryAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
double chance = .4; // 40 percent chance of milling with instant speed stuff
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
if (isSorcerySpeed(sa, ai)) {
|
||||
chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT)
|
||||
}
|
||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (playReusable(ai, sa)) {
|
||||
randomReturn = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,13 +54,13 @@ public class SurveilAi extends SpellAbilityAi {
|
||||
// even if there's no mana cost.
|
||||
if (sa.getPayCosts().hasTapCost()
|
||||
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
|
||||
&& !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
&& !isSorcerySpeed(sa, ai)) {
|
||||
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
|
||||
}
|
||||
|
||||
// in the player's turn Surveil should only be done in Main1 or in Upkeep if able
|
||||
if (ph.isPlayerTurn(ai)) {
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
if (isSorcerySpeed(sa, ai)) {
|
||||
return ph.is(PhaseType.MAIN1) || sa.isPwAbility();
|
||||
} else {
|
||||
return ph.is(PhaseType.UPKEEP);
|
||||
@@ -104,12 +104,12 @@ public class SurveilAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
double chance = .4; // 40 percent chance for instant speed
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
if (isSorcerySpeed(sa, ai)) {
|
||||
chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT)
|
||||
}
|
||||
|
||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (playReusable(ai, sa)) {
|
||||
randomReturn = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ public class TapAi extends TapAiBase {
|
||||
if (turn.isOpponentOf(ai) && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
// Tap things down if it's Human's turn
|
||||
} else if (turn.equals(ai)) {
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, ai) && phase.getPhase().isBefore(PhaseType.COMBAT_BEGIN)) {
|
||||
if (isSorcerySpeed(sa, ai) && phase.getPhase().isBefore(PhaseType.COMBAT_BEGIN)) {
|
||||
// Cast it if it's a sorcery.
|
||||
} else if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
// Aggro Brains are willing to use TapEffects aggressively instead of defensively
|
||||
@@ -33,7 +33,7 @@ public class TapAi extends TapAiBase {
|
||||
// Don't tap down after blockers
|
||||
return false;
|
||||
}
|
||||
} else if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||
} else if (!playReusable(ai, sa)) {
|
||||
// Generally don't want to tap things with an Instant during Players turn outside of combat
|
||||
return false;
|
||||
}
|
||||
@@ -46,8 +46,11 @@ public class TapAi extends TapAiBase {
|
||||
final Card source = sa.getHostCard();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
|
||||
if ("GoblinPolkaBand".equals(sa.getParam("AILogic"))) {
|
||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||
if ("GoblinPolkaBand".equals(aiLogic)) {
|
||||
return SpecialCardAi.GoblinPolkaBand.consider(ai, sa);
|
||||
} else if ("Arena".equals(aiLogic)) {
|
||||
return SpecialCardAi.Arena.consider(ai, sa);
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
||||
|
||||
@@ -325,6 +325,15 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
final Card source = sa.getHostCard();
|
||||
final boolean oppTargetsChoice = sa.hasParam("TargetingPlayer");
|
||||
|
||||
if (oppTargetsChoice && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) {
|
||||
// canPlayAI (sa activated by ai)
|
||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||
sa.setTargetingPlayer(targetingPlayer);
|
||||
sa.getTargets().clear();
|
||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||
}
|
||||
|
||||
boolean randomReturn = true;
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
if ((ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS))
|
||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai)
|
||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
|
||||
&& !haste && !pwMinus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public class VentureAi extends SpellAbilityAi {
|
||||
// If this has a mana cost, do it at opponent's EOT if able to prevent spending mana early; if sorcery, do it in Main2
|
||||
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
||||
if (sa.getPayCosts().hasManaCost() || sa.getPayCosts().hasTapCost()) {
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, aiPlayer)) {
|
||||
if (isSorcerySpeed(sa, aiPlayer)) {
|
||||
return ph.is(PhaseType.MAIN2, aiPlayer);
|
||||
} else {
|
||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.util;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.Normalizer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -42,6 +43,10 @@ public class TextUtil {
|
||||
return Normalizer.normalize(text, Normalizer.Form.NFD);
|
||||
|
||||
}
|
||||
private static final DecimalFormat df = new DecimalFormat("#.##");
|
||||
public static String decimalFormat(float value) {
|
||||
return df.format(value);
|
||||
}
|
||||
/**
|
||||
* Safely converts an object to a String.
|
||||
*
|
||||
|
||||
@@ -1582,10 +1582,6 @@ public class AbilityUtils {
|
||||
host.addRemembered(sa.getTargets());
|
||||
}
|
||||
|
||||
if (sa.hasParam("ImprintTargets") && sa.usesTargeting()) {
|
||||
host.addImprintedCards(sa.getTargets().getTargetCards());
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberCostMana")) {
|
||||
host.clearRemembered();
|
||||
ManaCostBeingPaid activationMana = new ManaCostBeingPaid(sa.getPayCosts().getTotalMana());
|
||||
|
||||
@@ -1252,6 +1252,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
player.shuffle(sa);
|
||||
}
|
||||
|
||||
if (sa.hasParam("Reorder")) {
|
||||
chosenCards = new CardCollection(decider.getController().orderMoveToZoneList(chosenCards, destination, sa));
|
||||
}
|
||||
|
||||
// remove Controlled While Searching
|
||||
if (controlTimestamp != null) {
|
||||
player.removeController(controlTimestamp);
|
||||
|
||||
@@ -62,10 +62,6 @@ public class FightEffect extends DamageBaseEffect {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberObjects")) {
|
||||
host.addRemembered(AbilityUtils.getDefinedObjects(host, sa.getParam("RememberObjects"), sa));
|
||||
}
|
||||
|
||||
Player controller = host.getController();
|
||||
boolean isOptional = sa.hasParam("Optional");
|
||||
|
||||
@@ -147,7 +143,6 @@ public class FightEffect extends DamageBaseEffect {
|
||||
}
|
||||
|
||||
private void dealDamage(final SpellAbility sa, Card fighterA, Card fighterB) {
|
||||
|
||||
boolean usedDamageMap = true;
|
||||
CardDamageMap damageMap = sa.getDamageMap();
|
||||
CardDamageMap preventMap = sa.getPreventMap();
|
||||
|
||||
@@ -6941,10 +6941,29 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|
||||
if (StringUtils.isNotBlank(set)) {
|
||||
cp = StaticData.instance().getVariantCards().getCard(name, set);
|
||||
return cp == null ? StaticData.instance().getCommonCards().getCard(name, set) : cp;
|
||||
if (cp != null) {
|
||||
return cp;
|
||||
}
|
||||
cp = StaticData.instance().getCommonCards().getCard(name, set);
|
||||
if (cp != null) {
|
||||
return cp;
|
||||
}
|
||||
}
|
||||
//no specific set for variant
|
||||
cp = StaticData.instance().getVariantCards().getCard(name);
|
||||
return cp != null ? cp : StaticData.instance().getCommonCards().getCardFromEditions(name, CardArtPreference.LATEST_ART_ALL_EDITIONS);
|
||||
if (cp != null) {
|
||||
return cp;
|
||||
}
|
||||
//try to get from user preference if available
|
||||
CardDb.CardArtPreference cardArtPreference = StaticData.instance().getCardArtPreference();
|
||||
if (cardArtPreference == null) //fallback
|
||||
cardArtPreference = CardArtPreference.ORIGINAL_ART_CORE_EXPANSIONS_REPRINT_ONLY;
|
||||
cp = StaticData.instance().getCommonCards().getCardFromEditions(name, cardArtPreference);
|
||||
if (cp != null) {
|
||||
return cp;
|
||||
}
|
||||
//lastoption
|
||||
return StaticData.instance().getCommonCards().getCard(name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -70,7 +70,11 @@ public class StaticAbilityDisableTriggers {
|
||||
|
||||
if (trigMode.equals(TriggerType.ChangesZone)) {
|
||||
// Cause of the trigger – the card changing zones
|
||||
if (!stAb.matchesValidParam("ValidCause", runParams.get(AbilityKey.Card))) {
|
||||
Card moved = (Card) runParams.get(AbilityKey.Card);
|
||||
if ("Battlefield".equals(regtrig.getParam("Origin"))) {
|
||||
moved = (Card) runParams.get(AbilityKey.CardLKI);
|
||||
}
|
||||
if (!stAb.matchesValidParam("ValidCause", moved)) {
|
||||
return false;
|
||||
}
|
||||
if (!stAb.matchesValidParam("Destination", runParams.get(AbilityKey.Destination))) {
|
||||
|
||||
@@ -89,7 +89,11 @@ public class StaticAbilityPanharmonicon {
|
||||
|
||||
if (trigMode.equals(TriggerType.ChangesZone)) {
|
||||
// Cause of the trigger – the card changing zones
|
||||
if (!stAb.matchesValidParam("ValidCause", runParams.get(AbilityKey.Card))) {
|
||||
Card moved = (Card) runParams.get(AbilityKey.Card);
|
||||
if ("Battlefield".equals(trigger.getParam("Origin"))) {
|
||||
moved = (Card) runParams.get(AbilityKey.CardLKI);
|
||||
}
|
||||
if (!stAb.matchesValidParam("ValidCause", moved)) {
|
||||
return false;
|
||||
}
|
||||
if (!stAb.matchesValidParam("Origin", runParams.get(AbilityKey.Origin))) {
|
||||
|
||||
@@ -120,19 +120,8 @@ public class TriggerChangesZone extends Trigger {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasParam("ValidCause")) {
|
||||
if (!runParams.containsKey(AbilityKey.Cause)) {
|
||||
return false;
|
||||
}
|
||||
SpellAbility cause = (SpellAbility) runParams.get(AbilityKey.Cause);
|
||||
if (cause == null) {
|
||||
return false;
|
||||
}
|
||||
if (!matchesValid(cause, getParam("ValidCause").split(","))) {
|
||||
if (!matchesValid(cause.getHostCard(), getParam("ValidCause").split(","))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!matchesValidParam("ValidCause", runParams.get(AbilityKey.Cause))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasParam("Fizzle")) {
|
||||
|
||||
@@ -25,7 +25,9 @@ public class TriggerChangesZoneAll extends Trigger {
|
||||
|
||||
if (!matchesValidParam("ValidCause", runParams.get(AbilityKey.Cause))) {
|
||||
return false;
|
||||
} else if (hasParam("ValidAmount")) {
|
||||
}
|
||||
|
||||
if (hasParam("ValidAmount")) {
|
||||
int right = AbilityUtils.calculateAmount(hostCard, getParam("ValidAmount").substring(2), this);
|
||||
if (!Expressions.compare(this.filterCards(table).size(), getParam("ValidAmount").substring(0, 2), right)) { return false; }
|
||||
}
|
||||
|
||||
@@ -187,7 +187,9 @@ public class AdventureEventData implements Serializable {
|
||||
|
||||
List<CardBlock> legalBlocks = new ArrayList<>();
|
||||
for (CardBlock b : src) { // for each block
|
||||
boolean isOkay = !(b.getSets().isEmpty() && b.getCntBoostersDraft() > 0);
|
||||
if (b.getSets().isEmpty() || (b.getCntBoostersDraft() < 1))
|
||||
continue;
|
||||
boolean isOkay = true;
|
||||
for (CardEdition c : b.getSets()) {
|
||||
if (!allEditions.contains(c)) {
|
||||
isOkay = false;
|
||||
|
||||
@@ -13,6 +13,7 @@ import forge.Forge;
|
||||
import forge.adventure.data.DifficultyData;
|
||||
import forge.adventure.data.HeroListData;
|
||||
import forge.adventure.util.*;
|
||||
import forge.adventure.stage.WorldStage;
|
||||
import forge.adventure.world.WorldSave;
|
||||
import forge.card.CardEdition;
|
||||
import forge.card.ColorSet;
|
||||
@@ -199,6 +200,7 @@ public class NewGameScene extends UIScene {
|
||||
editionIds[starterEdition.getCurrentIndex()], 0);//maybe replace with enum
|
||||
GamePlayerUtil.getGuiPlayer().setName(selectedName.getText());
|
||||
SoundSystem.instance.changeBackgroundTrack();
|
||||
WorldStage.getInstance().setupNewGame();
|
||||
Forge.switchScene(GameScene.instance());
|
||||
};
|
||||
Forge.setTransitionScreen(new TransitionScreen(runnable, null, false, true, "Generating World..."));
|
||||
|
||||
@@ -32,6 +32,7 @@ import forge.localinstance.achievements.CardActivationAchievements;
|
||||
import forge.localinstance.achievements.PlaneswalkerAchievements;
|
||||
import forge.model.FModel;
|
||||
import forge.player.GamePlayerUtil;
|
||||
import forge.util.TextUtil;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -216,7 +217,7 @@ public class PlayerStatisticScene extends UIScene {
|
||||
totalLoss.setText(String.valueOf(Current.player().getStatistic().totalLoss()));
|
||||
}
|
||||
if (lossWinRatio != null) {
|
||||
lossWinRatio.setText(Float.toString(Current.player().getStatistic().winLossRatio()));
|
||||
lossWinRatio.setText(TextUtil.decimalFormat(Current.player().getStatistic().winLossRatio()));
|
||||
}
|
||||
if (eventMatchWins != null) {
|
||||
eventMatchWins.setText(String.valueOf(Current.player().getStatistic().eventWins()));
|
||||
|
||||
@@ -43,6 +43,7 @@ public class WorldStage extends GameStage implements SaveFileContent {
|
||||
protected ArrayList<Pair<Float, EnemySprite>> enemies = new ArrayList<>();
|
||||
private final static Float dieTimer = 20f;//todo config
|
||||
private Float globalTimer = 0f;
|
||||
private transient boolean newGame = false;
|
||||
|
||||
NavArrowActor navArrow;
|
||||
public WorldStage() {
|
||||
@@ -333,15 +334,24 @@ public class WorldStage extends GameStage implements SaveFileContent {
|
||||
}
|
||||
}
|
||||
|
||||
public void setupNewGame(){
|
||||
newGame = true; //On a new game, we want to automatically enter any POI the player overlaps with.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enter() {
|
||||
getPlayerSprite().LoadPos();
|
||||
getPlayerSprite().setMovementDirection(Vector2.Zero);
|
||||
for (Actor actor : foregroundSprites.getChildren()) {
|
||||
if (actor.getClass() == PointOfInterestMapSprite.class) {
|
||||
PointOfInterestMapSprite point = (PointOfInterestMapSprite) actor;
|
||||
if (player.collideWith(point.getBoundingRect())) {
|
||||
collidingPoint = point;
|
||||
if (newGame) {
|
||||
newGame = false;
|
||||
}
|
||||
else {
|
||||
for (Actor actor : foregroundSprites.getChildren()) {
|
||||
if (actor.getClass() == PointOfInterestMapSprite.class) {
|
||||
PointOfInterestMapSprite point = (PointOfInterestMapSprite) actor;
|
||||
if (player.collideWith(point.getBoundingRect())) {
|
||||
collidingPoint = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 490 KiB |
|
After Width: | Height: | Size: 130 KiB |
@@ -0,0 +1,7 @@
|
||||
Name:Hallowed Sigil
|
||||
ManaCost:no cost
|
||||
Colors:white
|
||||
Types:Enchantment
|
||||
A:AB$ Pump | Cost$ PayShards<4> T | ValidTgts$ Creature.YouCtrl | KW$ Hexproof | SubAbility$ Eject | ActivationLimit$ 1 | SpellDescription$ Target creature you control gains hexproof until end of turn. Exile Hallowed Sigil.
|
||||
SVar:Eject:DB$ ChangeZone | Defined$ Self | Origin$ Battlefield | Destination$ Exile
|
||||
Oracle:{M},{T}:Target creature you control gains hexproof until end of turn. Exile Hallowed Sigil.
|
||||
@@ -0,0 +1,8 @@
|
||||
Name:Sigil of Torment
|
||||
ManaCost:no cost
|
||||
Colors:black
|
||||
Types:Enchantment
|
||||
A:AB$ Destroy | Cost$ 4 B T PayShards<5> | ValidTgts$ Creature | TgtPrompt$ Select target creature | ActivationLimit$ 1 | SubAbility$ DBLifeGain | SpellDescription$ Destroy target creature. You gain 3 life. Exile Sigil of Torment.
|
||||
SVar:DBLifeGain:DB$ GainLife | Defined$ You | LifeAmount$ 3 | SubAbility$ Eject
|
||||
SVar:Eject:DB$ ChangeZone | Defined$ Self | Origin$ Battlefield | Destination$ Exile
|
||||
Oracle:{M},{T}: Destroy target creature. You gain 3 life. Exile Sigil of Torment.
|
||||
@@ -0,0 +1,8 @@
|
||||
Name:Power of Valyx
|
||||
ManaCost:no cost
|
||||
Colors:black
|
||||
Types:Enchantment
|
||||
K:Hexproof
|
||||
A:AB$ Destroy | Cost$ 4 B T Sac<1/Creature.YouCtrl/creature you control>| ValidTgts$ Creature | TgtPrompt$ Select target creature | SubAbility$ DBLifeGain | SpellDescription$ Destroy target creature. You gain 3 life.
|
||||
SVar:DBLifeGain:DB$ GainLife | Defined$ You | LifeAmount$ 3
|
||||
Oracle:{M},{T}, Sacrifice a creature: Destroy target creature. You gain 3 life.
|
||||
23
forge-gui/res/adventure/common/decks/miniboss/valyx.dck
Normal file
@@ -0,0 +1,23 @@
|
||||
[metadata]
|
||||
Name=valyx
|
||||
[Main]
|
||||
3 Damnation|MM3|1
|
||||
2 Deathrender|CNS|1
|
||||
2 Deathrender|LRW|1
|
||||
1 Doomed Dissenter|DBL|1
|
||||
2 Doomed Dissenter|GN3|1
|
||||
1 Ecstatic Awakener|DBL|1
|
||||
2 Ecstatic Awakener|MID|1
|
||||
3 Indulgent Tormentor|PM15|1
|
||||
3 Lord of the Void|GTC|1
|
||||
4 Mark of the Oni|BOK|1
|
||||
3 Murder|CMR|1
|
||||
1 Phyrexian Reclamation|C15|1
|
||||
1 Phyrexian Reclamation|J22|1
|
||||
2 Reaper from the Abyss|J22|1
|
||||
3 Sign in Blood|SCD|1
|
||||
1 Skirsdag High Priest|C14|1
|
||||
2 Skirsdag High Priest|J22|1
|
||||
19 Swamp|MOM|1
|
||||
1 Swamp|MOM|3
|
||||
4 Westvale Abbey|SOI|1
|
||||
48
forge-gui/res/adventure/common/decks/standard/cultist.dck
Normal file
@@ -0,0 +1,48 @@
|
||||
[metadata]
|
||||
Name=cultist
|
||||
[Avatar]
|
||||
|
||||
[Main]
|
||||
2 Bloodgift Demon|SCD|1
|
||||
2 Bloodsoaked Champion|CLB|1
|
||||
2 Bloodsoaked Champion|NCC|1
|
||||
2 Demon of Catastrophes|J22|1
|
||||
1 Doomed Dissenter|BBD|1
|
||||
1 Doomed Dissenter|MB1|1
|
||||
2 Ecstatic Awakener|DBL|1
|
||||
2 Ecstatic Awakener|MID|1
|
||||
2 Feaster of Fools|MH1|1
|
||||
1 Grave Pact|10E|1
|
||||
2 Grave Pact|CM2|1
|
||||
1 Grave Pact|COM|1
|
||||
2 Graven Archfiend|YSNC|1
|
||||
1 Grim Haruspex|C19|1
|
||||
1 Grim Haruspex|CLB|1
|
||||
2 Harvester of Souls|CN2|1
|
||||
2 Herald of Torment|BNG|1
|
||||
1 Murder|CMR|1
|
||||
2 Murder|SNC|1
|
||||
2 Sign in Blood|ARC|1
|
||||
1 Sign in Blood|STA|1
|
||||
4 Skirsdag High Priest|2XM|1
|
||||
11 Swamp|MOM|1
|
||||
2 Swamp|SHM|1
|
||||
2 Swamp|SHM|2
|
||||
5 Swamp|SHM|3
|
||||
2 Swamp|SHM|4
|
||||
[Sideboard]
|
||||
2 Culling Dais|2XM|1
|
||||
2 Furnace Celebration|CMR|1
|
||||
4 Glaring Spotlight|GTC|1
|
||||
2 Grim Return|M14|1
|
||||
1 Lord of the Void|GTC|1
|
||||
2 Ravenous Demon|DKA|1
|
||||
2 Reaper from the Abyss|C14|1
|
||||
[Planes]
|
||||
|
||||
[Schemes]
|
||||
|
||||
[Conspiracy]
|
||||
|
||||
[Dungeon]
|
||||
|
||||
12
forge-gui/res/adventure/common/maps/main.tiled-project
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"automappingRulesFile": "",
|
||||
"commands": [
|
||||
],
|
||||
"compatibilityVersion": 1100,
|
||||
"extensionsPath": "extensions",
|
||||
"folders": [
|
||||
"."
|
||||
],
|
||||
"propertyTypes": [
|
||||
]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<tileset version="1.10" tiledversion="1.10.1" name="desertbuildingtiles" tilewidth="16" tileheight="16" tilecount="1036" columns="28">
|
||||
<image source="../tileset/desertbuildingtiles.png" width="448" height="592"/>
|
||||
</tileset>
|
||||
@@ -10,6 +10,7 @@
|
||||
</properties>
|
||||
<tileset firstgid="1" source="../tileset/main.tsx"/>
|
||||
<tileset firstgid="10113" source="../tileset/buildings.tsx"/>
|
||||
<tileset firstgid="11905" source="../tileset/main-nocollide.tsx"/>
|
||||
<layer id="6" name="Collision" width="30" height="17">
|
||||
<data encoding="base64" compression="zlib">
|
||||
eJzFVNEOgyAMLG/yYXOGH102/Ssz4mcMszbrTsTCTHYJgYDcXYst0RuPjv6Cqz+HZ/U/pjEZ44hJ9w7fBvbi1NC8vf8e69mT78zerj1AzKgrvNqHK/AFYw51zLc0L6A7KM3gt/Gi75qYI99dFIdoiS/RxXhLb3WE3n9mzKdoO/aVy/Nezn7VlX08R20iu2arbonjDN09zpK3EqQmdb3X5rlFV///M6+nrl1Xeq61hjSk9rAfjexnz1drz8ohxy+cuC+9Bf1bIbWn443wXhfoZ0TbnlULa59BoLda1NR8C16fd0IQ
|
||||
@@ -17,7 +18,7 @@
|
||||
</layer>
|
||||
<layer id="1" name="Background" width="30" height="17">
|
||||
<data encoding="base64" compression="zlib">
|
||||
eJzL4GFgyBjFo3gUj+JRPIpH8YjAALfm5xk=
|
||||
eJw7Zs7AcGwUj+JRPIpH8SgexSMCAwA4PvgW
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="12" name="Shroud" width="30" height="17" opacity="0.15">
|
||||
|
||||
@@ -31,11 +31,6 @@
|
||||
</object>
|
||||
<object id="48" template="../obj/treasure.tx" x="176.334" y="32.5833"/>
|
||||
<object id="63" template="../obj/gold.tx" x="192.333" y="29.167"/>
|
||||
<object id="65" template="../obj/enemy.tx" x="185.415" y="-58.8264">
|
||||
<properties>
|
||||
<property name="enemy" value="Hydra"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="66" template="../obj/enemy.tx" x="190.267" y="79.6539">
|
||||
<properties>
|
||||
<property name="enemy" value="Hidden Bush"/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="64" height="64" tilewidth="16" tileheight="16" infinite="0" nextlayerid="16" nextobjectid="47">
|
||||
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="64" height="64" tilewidth="16" tileheight="16" infinite="0" nextlayerid="16" nextobjectid="48">
|
||||
<tileset firstgid="1" name="autotiles" tilewidth="16" tileheight="16" tilecount="432" columns="12">
|
||||
<image source="../../world/tilesets/autotiles.png" width="192" height="576"/>
|
||||
</tileset>
|
||||
@@ -518,7 +518,82 @@
|
||||
</tile>
|
||||
</tileset>
|
||||
<tileset firstgid="10581" source="../tileset/buildings.tsx"/>
|
||||
<tileset firstgid="12373" source="../tileset/buildings.tsx"/>
|
||||
<tileset firstgid="12373" source="../tileset/buildings-nocollide.tsx"/>
|
||||
<objectgroup id="7" name="Invisible">
|
||||
<object id="4" template="../obj/exit.tx" name="Hekma" x="8" y="0" width="1016" height="1" visible="0"/>
|
||||
<object id="5" template="../obj/exit.tx" name="Hekma" x="1023" y="-1" width="1" height="1025" visible="0"/>
|
||||
<object id="6" template="../obj/exit.tx" name="Hekma" x="0" y="1018" width="1022" height="6" visible="0"/>
|
||||
<object id="7" template="../obj/exit.tx" name="Hekma" x="0" y="0" width="4" height="1016" visible="0"/>
|
||||
<object id="25" template="../obj/shop.tx" x="721" y="80" width="14" height="14" visible="0">
|
||||
<properties>
|
||||
<property name="signXOffset" type="float" value="3"/>
|
||||
<property name="signYOffset" type="float" value="8"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="26" template="../obj/shop.tx" x="721" y="192" width="14" height="14" visible="0">
|
||||
<properties>
|
||||
<property name="signXOffset" type="float" value="3"/>
|
||||
<property name="signYOffset" type="float" value="8"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="27" template="../obj/shop.tx" x="673" y="144" width="14" height="14" visible="0">
|
||||
<properties>
|
||||
<property name="signXOffset" type="float" value="3"/>
|
||||
<property name="signYOffset" type="float" value="8"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="33" template="../obj/shop.tx" x="641" y="96" width="14" height="14" visible="0">
|
||||
<properties>
|
||||
<property name="signXOffset" type="float" value="3"/>
|
||||
<property name="signYOffset" type="float" value="8"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="28" template="../obj/shop.tx" x="753" y="63" width="14" height="13" visible="0">
|
||||
<properties>
|
||||
<property name="signXOffset" type="float" value="3"/>
|
||||
<property name="signYOffset" type="float" value="8"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="31" template="../obj/shop.tx" x="833" y="48" width="14" height="15" visible="0">
|
||||
<properties>
|
||||
<property name="signXOffset" type="float" value="3"/>
|
||||
<property name="signYOffset" type="float" value="8"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="29" template="../obj/shop.tx" x="769" y="128" width="14" height="14" visible="0">
|
||||
<properties>
|
||||
<property name="signXOffset" type="float" value="3"/>
|
||||
<property name="signYOffset" type="float" value="8"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="30" template="../obj/inn.tx" x="721" y="128" width="14" height="16" visible="0"/>
|
||||
<object id="36" template="../obj/portal.tx" x="709" y="333" visible="1">
|
||||
<properties>
|
||||
<property name="direction" value="left"/>
|
||||
<property name="portalState" value="active"/>
|
||||
<property name="teleport" value="../common/maps/map/naktamun.tmx"/>
|
||||
<property name="teleportObjectId" value="37"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="37" template="../obj/portal.tx" x="653" y="349" visible="1">
|
||||
<properties>
|
||||
<property name="direction" value="right"/>
|
||||
<property name="portalState" value="active"/>
|
||||
<property name="teleport" value="../common/maps/map/naktamun.tmx"/>
|
||||
<property name="teleportObjectId" value="36"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="38" template="../obj/waypoint.tx" x="150" y="396" visible="0"/>
|
||||
<object id="8" template="../obj/entry_down.tx" x="592" y="32" width="16" height="16" visible="1"/>
|
||||
<object id="47" template="../obj/portal.tx" name="Training Area" x="144" y="417" visible="1">
|
||||
<properties>
|
||||
<property name="direction" value="down"/>
|
||||
<property name="portalState" value="active"/>
|
||||
<property name="teleport" value="../common/maps/map/naktamun/gym.tmx"/>
|
||||
<property name="teleportObjectId" value="1"/>
|
||||
</properties>
|
||||
</object>
|
||||
</objectgroup>
|
||||
<layer id="1" name="Ground" width="64" height="64" locked="1">
|
||||
<data encoding="csv">
|
||||
74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,4657,32,4184,4184,4184,4184,4184,4184,4184,32,4657,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,
|
||||
@@ -933,73 +1008,6 @@
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
</data>
|
||||
</layer>
|
||||
<objectgroup id="7" name="Invisible">
|
||||
<object id="4" template="../obj/exit.tx" name="Hekma" x="8" y="0" width="1016" height="1" visible="0"/>
|
||||
<object id="5" template="../obj/exit.tx" name="Hekma" x="1023" y="-1" width="1" height="1025" visible="0"/>
|
||||
<object id="6" template="../obj/exit.tx" name="Hekma" x="0" y="1018" width="1022" height="6" visible="0"/>
|
||||
<object id="7" template="../obj/exit.tx" name="Hekma" x="0" y="0" width="4" height="1016" visible="0"/>
|
||||
<object id="25" template="../obj/shop.tx" x="721" y="80" width="14" height="14" visible="0">
|
||||
<properties>
|
||||
<property name="signXOffset" type="float" value="3"/>
|
||||
<property name="signYOffset" type="float" value="8"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="26" template="../obj/shop.tx" x="721" y="192" width="14" height="14" visible="0">
|
||||
<properties>
|
||||
<property name="signXOffset" type="float" value="3"/>
|
||||
<property name="signYOffset" type="float" value="8"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="27" template="../obj/shop.tx" x="673" y="144" width="14" height="14" visible="0">
|
||||
<properties>
|
||||
<property name="signXOffset" type="float" value="3"/>
|
||||
<property name="signYOffset" type="float" value="8"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="33" template="../obj/shop.tx" x="641" y="96" width="14" height="14" visible="0">
|
||||
<properties>
|
||||
<property name="signXOffset" type="float" value="3"/>
|
||||
<property name="signYOffset" type="float" value="8"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="28" template="../obj/shop.tx" x="753" y="63" width="14" height="13" visible="0">
|
||||
<properties>
|
||||
<property name="signXOffset" type="float" value="3"/>
|
||||
<property name="signYOffset" type="float" value="8"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="31" template="../obj/shop.tx" x="833" y="48" width="14" height="15" visible="0">
|
||||
<properties>
|
||||
<property name="signXOffset" type="float" value="3"/>
|
||||
<property name="signYOffset" type="float" value="8"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="29" template="../obj/shop.tx" x="769" y="128" width="14" height="14" visible="0">
|
||||
<properties>
|
||||
<property name="signXOffset" type="float" value="3"/>
|
||||
<property name="signYOffset" type="float" value="8"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="30" template="../obj/inn.tx" x="721" y="128" width="14" height="16" visible="0"/>
|
||||
<object id="36" template="../obj/portal.tx" x="709" y="333" visible="1">
|
||||
<properties>
|
||||
<property name="direction" value="left"/>
|
||||
<property name="portalState" value="active"/>
|
||||
<property name="teleport" value="../common/maps/map/naktamun.tmx"/>
|
||||
<property name="teleportObjectId" value="37"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="37" template="../obj/portal.tx" x="653" y="349" visible="1">
|
||||
<properties>
|
||||
<property name="direction" value="right"/>
|
||||
<property name="portalState" value="active"/>
|
||||
<property name="teleport" value="../common/maps/map/naktamun.tmx"/>
|
||||
<property name="teleportObjectId" value="36"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="38" template="../obj/waypoint.tx" x="150" y="396" visible="0"/>
|
||||
<object id="8" template="../obj/entry_down.tx" x="592" y="32" width="16" height="16" visible="1"/>
|
||||
</objectgroup>
|
||||
<objectgroup id="15" name="Enemies">
|
||||
<object id="42" template="../obj/enemy.tx" name="Khenra" x="137" y="396">
|
||||
<properties>
|
||||
@@ -1052,7 +1060,7 @@
|
||||
]
|
||||
}
|
||||
]</property>
|
||||
<property name="enemy" value="Master White Wizard"/>
|
||||
<property name="enemy" value="Warrior"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="45" template="../obj/enemy.tx" name="Mummy" gid="2147495535" x="151" y="396" width="16" height="16">
|
||||
@@ -1061,6 +1069,5 @@
|
||||
<property name="waypoints" value="38"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="46" template="../obj/portal.tx" name="Training Area" x="144" y="416"/>
|
||||
</objectgroup>
|
||||
</map>
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="100" height="50" tilewidth="16" tileheight="16" infinite="0" nextlayerid="6" nextobjectid="20">
|
||||
<tileset firstgid="1" source="autotiles.tsx"/>
|
||||
<tileset firstgid="433" source="../tileset/main.tsx"/>
|
||||
<tileset firstgid="10545" source="desertbuildingtiles.tsx"/>
|
||||
<tileset firstgid="11581" source="../tileset/buildings.tsx"/>
|
||||
<tileset firstgid="1" source="../autotiles.tsx"/>
|
||||
<tileset firstgid="433" source="../../tileset/main.tsx"/>
|
||||
<tileset firstgid="10545" source="../../tileset/desertbuildingtiles.tsx"/>
|
||||
<tileset firstgid="11581" source="../../tileset/buildings.tsx"/>
|
||||
<objectgroup id="3" name="Hidden Objects" locked="1">
|
||||
<object id="1" template="../../obj/portal.tx" x="672" y="333" width="16" height="16" visible="1">
|
||||
<properties>
|
||||
<property name="direction" value="up"/>
|
||||
<property name="portalState" value="active"/>
|
||||
<property name="teleport" value="../common/maps/map/naktamun.tmx"/>
|
||||
<property name="teleportObjectId" value="47"/>
|
||||
</properties>
|
||||
</object>
|
||||
</objectgroup>
|
||||
<layer id="1" name="Ground" width="100" height="50" locked="1">
|
||||
<data encoding="csv">
|
||||
2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,
|
||||
@@ -58,14 +68,6 @@
|
||||
2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452
|
||||
</data>
|
||||
</layer>
|
||||
<objectgroup id="3" name="Hidden Objects" locked="1">
|
||||
<object id="1" template="../obj/portal.tx" x="671.5" y="351.5">
|
||||
<properties>
|
||||
<property name="direction" value="up"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="10" template="../obj/entry_up.tx" x="672" y="352"/>
|
||||
</objectgroup>
|
||||
<layer id="2" name="Buildings" width="100" height="50" locked="1">
|
||||
<properties>
|
||||
<property name="spriteLayer" type="bool" value="true"/>
|
||||
@@ -178,37 +180,37 @@
|
||||
</data>
|
||||
</layer>
|
||||
<objectgroup id="4" name="Enemies" locked="1">
|
||||
<object id="12" template="../obj/enemy.tx" name="Falcon-headed Aven Initiate" x="592" y="320">
|
||||
<object id="12" template="../../obj/enemy.tx" name="Falcon-headed Aven Initiate" x="592" y="320">
|
||||
<properties>
|
||||
<property name="enemy" value="Falcon-headed Aven Warrior"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="13" template="../obj/enemy.tx" name="Ibis-headed Aven Initiate" x="592" y="272">
|
||||
<object id="13" template="../../obj/enemy.tx" name="Ibis-headed Aven Initiate" x="592" y="272">
|
||||
<properties>
|
||||
<property name="enemy" value="Ibis-headed Aven Warrior"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="14" template="../obj/enemy.tx" name="Minotaur Initiate" x="592" y="224">
|
||||
<object id="14" template="../../obj/enemy.tx" name="Minotaur Initiate" x="592" y="224">
|
||||
<properties>
|
||||
<property name="enemy" value="Amonkhet Minotaur Warrior"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="15" template="../obj/enemy.tx" name="Khenra Initiate" x="752" y="224">
|
||||
<object id="15" template="../../obj/enemy.tx" name="Khenra Initiate" x="752" y="224">
|
||||
<properties>
|
||||
<property name="enemy" value="Khenra Warrior"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="16" template="../obj/enemy.tx" name="Naga Initiate" x="752" y="272">
|
||||
<object id="16" template="../../obj/enemy.tx" name="Naga Initiate" x="752" y="272">
|
||||
<properties>
|
||||
<property name="enemy" value="Naga Warrior"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="17" template="../obj/enemy.tx" name="Human Initiate" x="752" y="320">
|
||||
<object id="17" template="../../obj/enemy.tx" name="Human Initiate" x="752" y="320">
|
||||
<properties>
|
||||
<property name="enemy" value="Warrior"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="18" template="../obj/enemy.tx" name="Anointed" x="672" y="224">
|
||||
<object id="18" template="../../obj/enemy.tx" name="Anointed" x="672" y="224">
|
||||
<properties>
|
||||
<property name="enemy" value="Mummy"/>
|
||||
</properties>
|
||||
196
forge-gui/res/adventure/common/maps/map/unhallowed_abbey_1F.tmx
Normal file
@@ -0,0 +1,196 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.9" tiledversion="1.9.2" orientation="orthogonal" renderorder="right-down" width="30" height="22" tilewidth="16" tileheight="16" infinite="0" nextlayerid="9" nextobjectid="90">
|
||||
<editorsettings>
|
||||
<export target="wastetown..tmx" format="tmx"/>
|
||||
</editorsettings>
|
||||
<tileset firstgid="1" source="../tileset/main.tsx"/>
|
||||
<tileset firstgid="10113" source="../tileset/buildings.tsx"/>
|
||||
<layer id="6" name="Collision" width="30" height="22">
|
||||
<data encoding="base64" compression="zlib">
|
||||
eJzt1dEJgCAQBmAfDW6DaJxs1GiEapxoj4o8uEwl9ZSIfriX5Pz4oUhVQqiXjxnOu1xxuTnj6/tF12WU6kt7x74nId66TwNC1HA9x2cLs40mNWzuES6bmkcmOAfPzGcctmmGJMW27WG3Wfcb4d7Xtx/rltj/+5brO8i43V6mf0vUftI31XTZvnCZITa3SW2cTt/fZvwf2XzTjc0GSO1G/w==
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="1" name="Background" width="30" height="22">
|
||||
<data encoding="base64" compression="zlib">
|
||||
eJxjYBhZwI2TeEyJXkrwcLD3CTvxGARIUT+KR/EoHsWjeBSP4sGFARkE62E=
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="2" name="Ground" width="30" height="22">
|
||||
<data encoding="base64" compression="zlib">
|
||||
eJzt1M0NgzAMBWDfOCSZIHQI1imz0K7VH9img5AIWaRRRZ4NgqqqpXeAyHwxikL0/fWq3nOUu5cdncYfP++aulk8uSvprbMgPa0jetg5sdLns9vO5fVoPu3yP7sDtmRexERt1JWYiI24GrNkI+5JaXJ55bx/97fdIeRqdGYX+nqlWyttNj99V3JPSuwlU3pPonbJlLqpfTFTeA+dmd+VTI2b9/BZ94r9pxkBhlPcCw==
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="7" name="Ornamental Walls" width="30" height="22">
|
||||
<data encoding="base64" compression="zlib">
|
||||
eJzt1TEOgCAQBMCrkQ9YGZ+KPNVY+wUbL8ELGljW7ja5hkCmgA0ibTnn+vydmuGuu2j2ILJNz1GzXDsC31zj974l8uxWk2n3miwbMa2NuiNx1923c2gXtINI0B6ivR+xGWavzTStrZPvu0vmj2KaNsm4eeDdXx51O/I=
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="3" name="Clutter" width="30" height="22">
|
||||
<properties>
|
||||
<property name="spriteLayer" type="bool" value="true"/>
|
||||
</properties>
|
||||
<data encoding="base64" compression="zlib">
|
||||
eJxjYBgFo2AU0AtIazEwyGihimlqMjBoadLWXnOgnRZo9roC7XSjsb3I4J8k/eyiF2hD4yvwQLAiD4Qvz4MQw6dvqAJi/TtUwWj8Du/4xQYKNPHzRwFuAABfLAtS
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="8" name="Overhang" width="30" height="22">
|
||||
<data encoding="base64" compression="zlib">
|
||||
eJxjYBgFo2BkAwWegXbBKBgFo2AUjIJRMLQBAEtjAC0=
|
||||
</data>
|
||||
</layer>
|
||||
<objectgroup id="4" name="Objects">
|
||||
<object id="38" template="../obj/entry_up.tx" x="208.333" y="350.665">
|
||||
<properties>
|
||||
<property name="teleport" value=""/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="50" template="../obj/enemy.tx" x="208.402" y="144.809">
|
||||
<properties>
|
||||
<property name="enemy" value="High Cultist"/>
|
||||
<property name="reward">[
|
||||
{
|
||||
"type": "item",
|
||||
"probability": 1,
|
||||
"count": 1,
|
||||
"itemName": "Cultist's Key"
|
||||
}
|
||||
]
|
||||
</property>
|
||||
<property name="threatRange" type="int" value="30"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="66" template="../obj/treasure.tx" x="224" y="95.5">
|
||||
<properties>
|
||||
<property name="reward">[{
|
||||
"type": "randomCard",
|
||||
"count": 2,
|
||||
"colors": [ "colorID" ]
|
||||
},{
|
||||
"type": "randomCard",
|
||||
"count": 1,
|
||||
"probability": 0.5,
|
||||
"rarity": [ "rare" ],
|
||||
"colors": [ "colorID" ]
|
||||
},{
|
||||
"type": "randomCard",
|
||||
"count": 3,
|
||||
"addMaxCount": 2
|
||||
}]</property>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="67" template="../obj/booster.tx" x="224.25" y="80.25">
|
||||
<properties>
|
||||
<property name="reward">[
|
||||
{
|
||||
"editions": [ "SOI", "EMN", "MID", "VOW" ],
|
||||
"type": "card",
|
||||
"count": 10,
|
||||
"rarity": [ "Common" ]
|
||||
},
|
||||
{
|
||||
"editions": [ "SOI", "EMN", "MID", "VOW" ],
|
||||
"type": "card",
|
||||
"count": 3,
|
||||
"rarity": [ "Uncommon" ]
|
||||
},
|
||||
{
|
||||
"editions": [ "SOI", "EMN", "MID", "VOW" ],
|
||||
"type": "card",
|
||||
"count": 1,
|
||||
"rarity": [ "Rare", "Mythic Rare" ]
|
||||
}
|
||||
]
|
||||
</property>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="68" template="../obj/enemy.tx" x="172" y="205.5">
|
||||
<properties>
|
||||
<property name="enemy" value="False Monk"/>
|
||||
<property name="threatRange" type="int" value="40"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="69" template="../obj/enemy.tx" x="174" y="236.5">
|
||||
<properties>
|
||||
<property name="enemy" value="False Monk"/>
|
||||
<property name="threatRange" type="int" value="40"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="70" template="../obj/enemy.tx" x="170" y="270">
|
||||
<properties>
|
||||
<property name="enemy" value="False Monk"/>
|
||||
<property name="threatRange" type="int" value="40"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="71" template="../obj/enemy.tx" x="243" y="270">
|
||||
<properties>
|
||||
<property name="enemy" value="False Monk"/>
|
||||
<property name="threatRange" type="int" value="40"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="72" template="../obj/enemy.tx" x="243.5" y="235">
|
||||
<properties>
|
||||
<property name="enemy" value="False Monk"/>
|
||||
<property name="threatRange" type="int" value="40"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="73" template="../obj/enemy.tx" x="243" y="204.5">
|
||||
<properties>
|
||||
<property name="enemy" value="False Monk"/>
|
||||
<property name="threatRange" type="int" value="40"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="74" template="../obj/enemy.tx" x="120" y="182">
|
||||
<properties>
|
||||
<property name="enemy" value="False Knight"/>
|
||||
<property name="threatRange" type="int" value="50"/>
|
||||
<property name="waypoints" value="78,83,79,81,80,82,77,76"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="75" template="../obj/enemy.tx" x="300.5" y="186">
|
||||
<properties>
|
||||
<property name="enemy" value="False Knight"/>
|
||||
<property name="threatRange" type="int" value="50"/>
|
||||
<property name="waypoints" value="81,80,82,77,76,78,83,79"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="76" template="../obj/waypoint.tx" x="124" y="216"/>
|
||||
<object id="77" template="../obj/waypoint.tx" x="121.5" y="267"/>
|
||||
<object id="78" template="../obj/waypoint.tx" x="134.5" y="165.5"/>
|
||||
<object id="79" template="../obj/waypoint.tx" x="281.5" y="171"/>
|
||||
<object id="80" template="../obj/waypoint.tx" x="293" y="268"/>
|
||||
<object id="81" template="../obj/waypoint.tx" x="301" y="215.5"/>
|
||||
<object id="82" template="../obj/waypoint.tx" x="208" y="270"/>
|
||||
<object id="83" template="../obj/waypoint.tx" x="209" y="175.5"/>
|
||||
<object id="84" template="../obj/enemy.tx" x="207.5" y="224.5">
|
||||
<properties>
|
||||
<property name="enemy" value="False Knight"/>
|
||||
<property name="threatRange" type="int" value="50"/>
|
||||
<property name="waypoints" value="83,82"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="85" template="../obj/manashards.tx" x="98.5" y="224"/>
|
||||
<object id="86" template="../obj/gate.tx" x="207.75" y="124.5" width="16.4999" height="22">
|
||||
<properties>
|
||||
<property name="dialog">[{
|
||||
"text":"This door is locked",
|
||||
"options":[
|
||||
{ "name":"Leave" },
|
||||
{
|
||||
"name":"Unlock with Cultist's Key",
|
||||
"condition":[{"item":"Cultist's Key"}],
|
||||
"text":"The gate is unlocked.",
|
||||
"options":[{"name":"Continue.", "action":[ {"deleteMapObject":-1},{"removeItem":"Cultist's Key"}]} ]
|
||||
}
|
||||
]
|
||||
}]</property>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="87" template="../obj/manashards.tx" x="321.5" y="217.75"/>
|
||||
<object id="88" template="../obj/manashards.tx" x="192.25" y="128.5"/>
|
||||
<object id="89" template="../obj/entry_up.tx" gid="1073753167" x="192.087" y="64" rotation="0">
|
||||
<properties>
|
||||
<property name="teleport" value="../common/maps/map/unhallowed_abbey_2F.tmx"/>
|
||||
</properties>
|
||||
</object>
|
||||
</objectgroup>
|
||||
</map>
|
||||
411
forge-gui/res/adventure/common/maps/map/unhallowed_abbey_2F.tmx
Normal file
@@ -0,0 +1,411 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.9" tiledversion="1.9.2" orientation="orthogonal" renderorder="right-down" width="30" height="19" tilewidth="16" tileheight="16" infinite="0" nextlayerid="8" nextobjectid="117">
|
||||
<editorsettings>
|
||||
<export target="wastetown..tmx" format="tmx"/>
|
||||
</editorsettings>
|
||||
<properties>
|
||||
<property name="dungeonEffect" value=""/>
|
||||
</properties>
|
||||
<tileset firstgid="1" source="../tileset/main.tsx"/>
|
||||
<tileset firstgid="10113" source="../tileset/buildings.tsx"/>
|
||||
<tileset firstgid="11905" source="../tileset/DarkAbbeyTiles.tsx"/>
|
||||
<layer id="6" name="Collision" width="30" height="19">
|
||||
<data encoding="base64" compression="zlib">
|
||||
eJxjYBi8wI2TMKaFnU8JmPuYynYTYye17SbFTmrZjctOdDOx2UGJ3SB903gxMUh8Nw9x6si1FxsAmYmPj6yfHLuxhScuc7DJDXV76aUfpG8lB3l6l3NQnpdgdhMbzpTaiW43MfZSy050u/EBatuJbDe+dEurOgnZHnz84WgvtcIWAIOHLCo=
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="1" name="Background" width="30" height="19">
|
||||
<data encoding="base64" compression="zlib">
|
||||
eJx7ws7A8GQI4UJ54jE1zCAVD4Sd+Oynt33UjD9y7B6u4QyzE91uevkRXYxe4YzN/8MtXvHZTUt34Ms/tPY/NrMHMv8OZN1ArDtM+BhQQBM3+XaBzCIFIwNS9Q4GDAAw7Fue
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="2" name="Ground" width="30" height="19">
|
||||
<data encoding="base64" compression="zlib">
|
||||
eJzV1F0KwkAMBOB9788F2tIzCD2NnsIDqNeqtb2bK7IQFpvMBFcwMBS2bL/koZnrEOYfpM+C3Dk1ISz1fo7N99z0/mU+4lOrO2Az8yImaqMuYyI24npMy0bcQZjn0U5enXPeQZkV6aGEm2ytp1KuVaXcQ4e5U8u5W8yt8s16ifdW57y9007mp+8ye5KxNZPdk6htmawr7Wv1jvxP05lletz8jtwRbP8ybGn78R/qCRGT22E=
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="7" name="Ornamental Walls" width="30" height="19">
|
||||
<data encoding="base64" compression="zlib">
|
||||
eJzl1G0OQDAMBuD+ZnoFcRXBwdzFvTDugcwSZsOqPhJv0tik2xM/CuCbkQFAGR5XH/CbidjviwWffdbktF1mFaky11y26zszXO9T3PZom+p2450S18/JLWYrR+Xa+q64tjTjne3s1qj2Puep7t3n/+hSZ0HPICXUOfT913DYHKavzWmatq5llu85TTM294m86ZpFzQDLEj8K
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="3" name="Clutter" width="30" height="19">
|
||||
<properties>
|
||||
<property name="spriteLayer" type="bool" value="true"/>
|
||||
</properties>
|
||||
<data encoding="base64" compression="zlib">
|
||||
eJxjYBgFo2AUDDZQKE89sx5rMjA80SRdjlLwH2SuFulyo4A0MBq/o2AU4AcAe0YOPQ==
|
||||
</data>
|
||||
</layer>
|
||||
<objectgroup id="4" name="Objects">
|
||||
<object id="66" template="../obj/treasure.tx" x="256.447" y="95.763">
|
||||
<properties>
|
||||
<property name="reward">[{
|
||||
"type": "randomCard",
|
||||
"count": 2,
|
||||
"colors": [ "colorID" ]
|
||||
},{
|
||||
"type": "randomCard",
|
||||
"count": 1,
|
||||
"probability": 0.5,
|
||||
"rarity": [ "rare" ],
|
||||
"colors": [ "colorID" ]
|
||||
},{
|
||||
"type": "randomCard",
|
||||
"count": 3,
|
||||
"addMaxCount": 2
|
||||
}]</property>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="75" template="../obj/enemy.tx" x="291.571" y="166.141">
|
||||
<properties>
|
||||
<property name="enemy" value="Demon"/>
|
||||
<property name="threatRange" type="int" value="30"/>
|
||||
<property name="waypoints" value="100,101,99"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="83" template="../obj/waypoint.tx" x="193.014" y="286.598"/>
|
||||
<object id="86" template="../obj/entry_up.tx" x="208.316" y="175.684">
|
||||
<properties>
|
||||
<property name="teleport" value="../common/maps/map/unhallowed_abbey_1F.tmx"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="87" template="../obj/booster.tx" x="272.228" y="95.7719">
|
||||
<properties>
|
||||
<property name="reward">[
|
||||
{
|
||||
"editions": [ "VOW" ],
|
||||
"type": "card",
|
||||
"count": 10,
|
||||
"rarity": [ "Common" ]
|
||||
},
|
||||
{
|
||||
"editions": [ "VOW" ],
|
||||
"type": "card",
|
||||
"count": 3,
|
||||
"rarity": [ "Uncommon" ]
|
||||
},
|
||||
{
|
||||
"editions": [ "VOW" ],
|
||||
"type": "card",
|
||||
"count": 1,
|
||||
"rarity": [ "Rare", "Mythic Rare" ]
|
||||
}
|
||||
]
|
||||
</property>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="88" template="../obj/treasure.tx" x="240.333" y="47.6667">
|
||||
<properties>
|
||||
<property name="reward">[{
|
||||
"type": "randomCard",
|
||||
"count": 2,
|
||||
"colors": [ "colorID" ]
|
||||
},{
|
||||
"type": "randomCard",
|
||||
"count": 1,
|
||||
"probability": 0.5,
|
||||
"rarity": [ "rare" ],
|
||||
"colors": [ "colorID" ]
|
||||
},{
|
||||
"type": "randomCard",
|
||||
"count": 3,
|
||||
"addMaxCount": 2
|
||||
}]</property>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="89" template="../obj/booster.tx" x="257.333" y="47.3333">
|
||||
<properties>
|
||||
<property name="reward">[
|
||||
{
|
||||
"editions": [ "MID" ],
|
||||
"type": "card",
|
||||
"count": 10,
|
||||
"rarity": [ "Common" ]
|
||||
},
|
||||
{
|
||||
"editions": [ "MID" ],
|
||||
"type": "card",
|
||||
"count": 3,
|
||||
"rarity": [ "Uncommon" ]
|
||||
},
|
||||
{
|
||||
"editions": [ "MID" ],
|
||||
"type": "card",
|
||||
"count": 1,
|
||||
"rarity": [ "Rare", "Mythic Rare" ]
|
||||
}
|
||||
]
|
||||
</property>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="90" template="../obj/treasure.tx" x="175.667" y="48.3333">
|
||||
<properties>
|
||||
<property name="reward">[{
|
||||
"type": "randomCard",
|
||||
"count": 2,
|
||||
"colors": [ "colorID" ]
|
||||
},{
|
||||
"type": "randomCard",
|
||||
"count": 1,
|
||||
"probability": 0.5,
|
||||
"rarity": [ "rare" ],
|
||||
"colors": [ "colorID" ]
|
||||
},{
|
||||
"type": "randomCard",
|
||||
"count": 3,
|
||||
"addMaxCount": 2
|
||||
}]</property>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="91" template="../obj/treasure.tx" x="144.333" y="95">
|
||||
<properties>
|
||||
<property name="reward">[{
|
||||
"type": "randomCard",
|
||||
"count": 2,
|
||||
"colors": [ "colorID" ]
|
||||
},{
|
||||
"type": "randomCard",
|
||||
"count": 1,
|
||||
"probability": 0.5,
|
||||
"rarity": [ "rare" ],
|
||||
"colors": [ "colorID" ]
|
||||
},{
|
||||
"type": "randomCard",
|
||||
"count": 3,
|
||||
"addMaxCount": 2
|
||||
}]</property>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="92" template="../obj/booster.tx" x="127.667" y="94">
|
||||
<properties>
|
||||
<property name="reward">[
|
||||
{
|
||||
"editions": [ "SOI" ],
|
||||
"type": "card",
|
||||
"count": 10,
|
||||
"rarity": [ "Common" ]
|
||||
},
|
||||
{
|
||||
"editions": [ "SOI" ],
|
||||
"type": "card",
|
||||
"count": 3,
|
||||
"rarity": [ "Uncommon" ]
|
||||
},
|
||||
{
|
||||
"editions": [ "SOI" ],
|
||||
"type": "card",
|
||||
"count": 1,
|
||||
"rarity": [ "Rare", "Mythic Rare" ]
|
||||
}
|
||||
]
|
||||
</property>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="93" template="../obj/booster.tx" x="159" y="47">
|
||||
<properties>
|
||||
<property name="reward">[
|
||||
{
|
||||
"editions": [ "EMN" ],
|
||||
"type": "card",
|
||||
"count": 10,
|
||||
"rarity": [ "Common" ]
|
||||
},
|
||||
{
|
||||
"editions": [ "EMN" ],
|
||||
"type": "card",
|
||||
"count": 3,
|
||||
"rarity": [ "Uncommon" ]
|
||||
},
|
||||
{
|
||||
"editions": [ "EMN" ],
|
||||
"type": "card",
|
||||
"count": 1,
|
||||
"rarity": [ "Rare", "Mythic Rare" ]
|
||||
}
|
||||
]
|
||||
</property>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="94" template="../obj/enemy.tx" x="208.666" y="270.394">
|
||||
<properties>
|
||||
<property name="enemy" value="Demon"/>
|
||||
<property name="threatRange" type="int" value="30"/>
|
||||
<property name="waypoints" value="98,97,83"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="95" template="../obj/enemy.tx" x="122.212" y="172.727">
|
||||
<properties>
|
||||
<property name="enemy" value="Demon"/>
|
||||
<property name="threatRange" type="int" value="30"/>
|
||||
<property name="waypoints" value="104,102,103"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="97" template="../obj/waypoint.tx" x="192.727" y="255.455"/>
|
||||
<object id="98" template="../obj/waypoint.tx" x="223.091" y="255.455"/>
|
||||
<object id="99" template="../obj/waypoint.tx" x="288.545" y="127.273"/>
|
||||
<object id="100" template="../obj/waypoint.tx" x="287.636" y="192"/>
|
||||
<object id="101" template="../obj/waypoint.tx" x="319.091" y="159.273"/>
|
||||
<object id="102" template="../obj/waypoint.tx" x="97.4545" y="159.636"/>
|
||||
<object id="103" template="../obj/waypoint.tx" x="126.545" y="127.455"/>
|
||||
<object id="104" template="../obj/waypoint.tx" x="125.636" y="200.909"/>
|
||||
<object id="108" name="Ward" class="dialog" gid="11912" x="192.063" y="128.292" width="48" height="16">
|
||||
<properties>
|
||||
<property name="dialog">[{
|
||||
"text":"A translucent, shimmering red field blocks your path. Pained screams echo through the room behind you.",
|
||||
"options":[
|
||||
{ "name":"Leave." }
|
||||
]
|
||||
}]</property>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="109" name="Sacrifice1" class="dialog" gid="11905" x="159.667" y="123.333" width="16" height="16">
|
||||
<properties>
|
||||
<property name="dialog">[
|
||||
{
|
||||
"text":"A captive lies tied to the altar. Glowing red runes encircle them.",
|
||||
"options":[
|
||||
{
|
||||
"text":"As the third captive is freed, you hear the sound of shattering as the barrier in the center of the chamber fails. Time to end this.",
|
||||
"action":[{"deleteMapObject":-1},{"advanceMapFlag":"gate"}],
|
||||
"name":"Free them."
|
||||
"options":[{
|
||||
"condition":[{"getMapFlag":{"key":"gate","op":">=","val":3}}],
|
||||
"action":[{"deleteMapObject":108}],
|
||||
"name":"ok" }]
|
||||
},
|
||||
{ "name":"Leave." }
|
||||
]
|
||||
}
|
||||
|
||||
]</property>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="110" name="Sacrifice2" class="dialog" gid="11906" x="256.667" y="123.667" width="16" height="16">
|
||||
<properties>
|
||||
<property name="dialog">[
|
||||
{
|
||||
"text":"A captive lies tied to the altar. Glowing red runes encircle them.",
|
||||
"options":[
|
||||
{
|
||||
"text":"As the third captive is freed, you hear the sound of shattering as the barrier in the center of the chamber fails. Time to end this.",
|
||||
"action":[{"deleteMapObject":-1},{"advanceMapFlag":"gate"}],
|
||||
"name":"Free them."
|
||||
"options":[{
|
||||
"condition":[{"getMapFlag":{"key":"gate","op":">=","val":3}}],
|
||||
"action":[{"deleteMapObject":108}],
|
||||
"name":"ok" }]
|
||||
},
|
||||
{ "name":"Leave." }
|
||||
]
|
||||
}
|
||||
|
||||
]</property>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="111" name="Sacrifice3" class="dialog" gid="11907" x="224" y="285.333" width="16" height="16">
|
||||
<properties>
|
||||
<property name="dialog">[
|
||||
{
|
||||
"text":"A captive lies tied to the altar. Glowing red runes encircle them.",
|
||||
"options":[
|
||||
{
|
||||
"text":"As the third captive is freed, you hear the sound of shattering as the barrier in the center of the chamber fails. Time to end this.",
|
||||
"action":[{"deleteMapObject":-1},{"advanceMapFlag":"gate"}],
|
||||
"name":"Free them."
|
||||
"options":[{
|
||||
"condition":[{"getMapFlag":{"key":"gate","op":">=","val":3}}],
|
||||
"action":[{"deleteMapObject":108}],
|
||||
"name":"ok" }]
|
||||
},
|
||||
{ "name":"Leave." }
|
||||
]
|
||||
}
|
||||
|
||||
]</property>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="113" template="../obj/dialog.tx" x="209.364" y="114.091" width="13.8182" height="13.3636">
|
||||
<properties>
|
||||
<property name="dialog">[{
|
||||
"text":"*The large, imposing demon before you smirks*\n Ah, so you must be the one who's been freeing my sacrifices...and the volunteer to be my new one. Tell me, mortal, as your last words that aren't a howl of pain - why challenge me?",
|
||||
"options":[{
|
||||
"name":"Because you're a monster, and you should be stopped!",
|
||||
"text":"*The demon sneers.*\n Ah, a noble *hero*. I should have guessed. Your kind die like anyone else when your power runs dry - allow me to demonstrate!",
|
||||
"options":[{
|
||||
"name":"End",
|
||||
"action":[{"deleteMapObject":113}]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name":"I want power. I'll take it from what's left of you.",
|
||||
"text":"*The demon chuckles.*\n I'll commend your ambition, if not your sense. Fight hard enough, and I might let you replace that failure you dealt with upstairs.",
|
||||
"options":[{
|
||||
"name":"End",
|
||||
"action":[{"deleteMapObject":113}]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name":"Honestly, you just looked like you'd be a good fight.",
|
||||
"text":"*The demon blinks in surprise, then laughs.*\n Well, if that's what you seek, you'll find more than you bargained for here. I hope you enjoy the last battle of your life, *mortal*.",
|
||||
"options":[{
|
||||
"name":"End",
|
||||
"action":[{"deleteMapObject":113}]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name":"...",
|
||||
"text":"*The fiend's eyes narrow.*\n Too scared for words? So be it, mortal. You'll die all the same.",
|
||||
"options":[{
|
||||
"name":"End",
|
||||
"action":[{"deleteMapObject":113}]
|
||||
}]
|
||||
}]
|
||||
}]</property>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="116" template="../obj/enemy.tx" x="203.566" y="96.2358" width="24" height="24">
|
||||
<properties>
|
||||
<property name="defeatDialog">[{
|
||||
"text":"*With a snarl of pain, the demon collapses to the floor.* \n Congratulations, mortal, you've bested me. In exchange for my life, I offer a lesson - the same killing power I wield.",
|
||||
"options":[{
|
||||
"name":"I have no need for power from something as vile as you. Die!",
|
||||
"text":"*The demon's eyes widen in fear.* \n No! I will not be destroyed by- \n *A final blast of power reduces him to mana in the air of this place, and something clatters to the ground. A holy symbol of this place - or rather, what it once was. As the unholy energy around it fades, you can still feel magic coursing through it.*"
|
||||
"options":[{
|
||||
"name":"End",
|
||||
"action":[{"addItem":"Hallowed Sigil"},{"deleteMapObject":116}]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name":"If you knew anything worth teaching me directly, I wouldn't have been able to defeat you.",
|
||||
"text":"*The demon's eyes widen in fear.* \n No! I will not be destroyed by- \n *A final blast of power reduces him to mana in the air of this place, and something clatters to the ground. A holy symbol of this place - or rather, what it once was. As the unholy energy around it fades, you can still feel magic coursing through it.*"
|
||||
"options":[{
|
||||
"name":"End",
|
||||
"action":[{"addItem":"Hallowed Sigil"},{"deleteMapObject":116}]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name":"...Very well, even a monster like you deserves mercy. *Once.*",
|
||||
"text":"*The demon smiles, moving his hand in an arcane gesture.* \n *As you unconsciously mimic it, you feel a dark, repulsive power crystallize in your hand. \n *The demon smiles as he begins to fade into a cloud of smoke.* \n Very well, mortal. My power is yours to wield...until next time."
|
||||
"options":[{
|
||||
"name":"End",
|
||||
"action":[{"addItem":"Unhallowed Sigil"},{"deleteMapObject":116}]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name":"As you should. I'll take your offer.",
|
||||
"text":"*The demon smiles, moving his hand in an arcane gesture.* \n *As you unconsciously mimic it, you feel a dark, repulsive power crystallize in your hand. \n *The demon smiles as he begins to fade into a cloud of smoke.* \n Very well, mortal. My power is yours to wield...until next time."
|
||||
"options":[{
|
||||
"name":"End",
|
||||
"action":[{"addItem":"Unhallowed Sigil"},{"deleteMapObject":116}]
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
</property>
|
||||
<property name="effect">{ "startBattleWithCard": [ "Mox Jet", "Power of Valyx"]
|
||||
}</property>
|
||||
<property name="enemy" value="Valyx Feaster of Torment"/>
|
||||
</properties>
|
||||
</object>
|
||||
</objectgroup>
|
||||
</map>
|
||||
BIN
forge-gui/res/adventure/common/maps/tileset/DarkAbbeyTiles.png
Normal file
|
After Width: | Height: | Size: 784 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<tileset version="1.9" tiledversion="1.9.2" name="DarkAbbeyTiles" tilewidth="16" tileheight="16" tilecount="30" columns="6">
|
||||
<image source="DarkAbbeyTiles.png" width="96" height="80"/>
|
||||
</tileset>
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 13 KiB |
1406
forge-gui/res/adventure/common/maps/tileset/desertbuildingtiles.tsx
Normal file
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,68 @@
|
||||
valyx.png
|
||||
size: 136,96
|
||||
format: RGBA8888
|
||||
filter: Nearest,Nearest
|
||||
repeat: none
|
||||
Avatar
|
||||
xy: 0, 0
|
||||
size: 16, 16
|
||||
Idle
|
||||
xy: 0, 16
|
||||
size: 24, 24
|
||||
Idle
|
||||
xy: 24, 16
|
||||
size: 24, 24
|
||||
Idle
|
||||
xy: 48, 16
|
||||
size: 24, 24
|
||||
Idle
|
||||
xy: 72, 16
|
||||
size: 24, 24
|
||||
Walk
|
||||
xy: 0, 40
|
||||
size: 24, 24
|
||||
Walk
|
||||
xy: 24, 40
|
||||
size: 24, 24
|
||||
Walk
|
||||
xy: 48, 40
|
||||
size: 24, 24
|
||||
Walk
|
||||
xy: 72, 40
|
||||
size: 24, 24
|
||||
Attack
|
||||
xy: 0, 64
|
||||
size: 24, 24
|
||||
Attack
|
||||
xy: 24, 64
|
||||
size: 24, 24
|
||||
Attack
|
||||
xy: 48, 64
|
||||
size: 24, 24
|
||||
Attack
|
||||
xy: 72, 64
|
||||
size: 24, 24
|
||||
Hit
|
||||
xy: 0, 88
|
||||
size: 24, 24
|
||||
Hit
|
||||
xy: 24, 88
|
||||
size: 24, 24
|
||||
Hit
|
||||
xy: 48, 88
|
||||
size: 24, 24
|
||||
Hit
|
||||
xy: 72, 88
|
||||
size: 24, 24
|
||||
Death
|
||||
xy: 0, 112
|
||||
size: 24, 24
|
||||
Death
|
||||
xy: 24, 112
|
||||
size: 24, 24
|
||||
Death
|
||||
xy: 48, 112
|
||||
size: 24, 24
|
||||
Death
|
||||
xy: 72, 112
|
||||
size: 24, 24
|
||||
BIN
forge-gui/res/adventure/common/sprites/enemy/fiend/valyx.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,68 @@
|
||||
false_knight.png
|
||||
size: 64,96
|
||||
format: RGBA8888
|
||||
filter: Nearest,Nearest
|
||||
repeat: none
|
||||
Avatar
|
||||
xy: 0, 0
|
||||
size: 16, 16
|
||||
Idle
|
||||
xy: 0, 16
|
||||
size: 16, 16
|
||||
Idle
|
||||
xy: 16, 16
|
||||
size: 16, 16
|
||||
Idle
|
||||
xy: 32, 16
|
||||
size: 16, 16
|
||||
Idle
|
||||
xy: 48, 16
|
||||
size: 16, 16
|
||||
Walk
|
||||
xy: 0, 32
|
||||
size: 16, 16
|
||||
Walk
|
||||
xy: 16, 32
|
||||
size: 16, 16
|
||||
Walk
|
||||
xy: 32, 32
|
||||
size: 16, 16
|
||||
Walk
|
||||
xy: 48, 32
|
||||
size: 16, 16
|
||||
Attack
|
||||
xy: 0, 48
|
||||
size: 16, 16
|
||||
Attack
|
||||
xy: 16, 48
|
||||
size: 16, 16
|
||||
Attack
|
||||
xy: 32, 48
|
||||
size: 16, 16
|
||||
Attack
|
||||
xy: 48, 48
|
||||
size: 16, 16
|
||||
Hit
|
||||
xy: 0, 64
|
||||
size: 16, 16
|
||||
Hit
|
||||
xy: 16, 64
|
||||
size: 16, 16
|
||||
Hit
|
||||
xy: 32, 64
|
||||
size: 16, 16
|
||||
Hit
|
||||
xy: 48, 64
|
||||
size: 16, 16
|
||||
Death
|
||||
xy: 0, 80
|
||||
size: 16, 16
|
||||
Death
|
||||
xy: 16, 80
|
||||
size: 16, 16
|
||||
Death
|
||||
xy: 32, 80
|
||||
size: 16, 16
|
||||
Death
|
||||
xy: 48, 80
|
||||
size: 16, 16
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,68 @@
|
||||
false_monk.png
|
||||
size: 64,96
|
||||
format: RGBA8888
|
||||
filter: Nearest,Nearest
|
||||
repeat: none
|
||||
Avatar
|
||||
xy: 0, 0
|
||||
size: 16, 16
|
||||
Idle
|
||||
xy: 0, 16
|
||||
size: 16, 16
|
||||
Idle
|
||||
xy: 16, 16
|
||||
size: 16, 16
|
||||
Idle
|
||||
xy: 32, 16
|
||||
size: 16, 16
|
||||
Idle
|
||||
xy: 48, 16
|
||||
size: 16, 16
|
||||
Walk
|
||||
xy: 0, 32
|
||||
size: 16, 16
|
||||
Walk
|
||||
xy: 16, 32
|
||||
size: 16, 16
|
||||
Walk
|
||||
xy: 32, 32
|
||||
size: 16, 16
|
||||
Walk
|
||||
xy: 48, 32
|
||||
size: 16, 16
|
||||
Attack
|
||||
xy: 0, 48
|
||||
size: 16, 16
|
||||
Attack
|
||||
xy: 16, 48
|
||||
size: 16, 16
|
||||
Attack
|
||||
xy: 32, 48
|
||||
size: 16, 16
|
||||
Attack
|
||||
xy: 48, 48
|
||||
size: 16, 16
|
||||
Hit
|
||||
xy: 0, 64
|
||||
size: 16, 16
|
||||
Hit
|
||||
xy: 16, 64
|
||||
size: 16, 16
|
||||
Hit
|
||||
xy: 32, 64
|
||||
size: 16, 16
|
||||
Hit
|
||||
xy: 48, 64
|
||||
size: 16, 16
|
||||
Death
|
||||
xy: 0, 80
|
||||
size: 16, 16
|
||||
Death
|
||||
xy: 16, 80
|
||||
size: 16, 16
|
||||
Death
|
||||
xy: 32, 80
|
||||
size: 16, 16
|
||||
Death
|
||||
xy: 48, 80
|
||||
size: 16, 16
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,68 @@
|
||||
high_cultist.png
|
||||
size: 64,96
|
||||
format: RGBA8888
|
||||
filter: Nearest,Nearest
|
||||
repeat: none
|
||||
Avatar
|
||||
xy: 0, 0
|
||||
size: 16, 16
|
||||
Idle
|
||||
xy: 0, 16
|
||||
size: 16, 16
|
||||
Idle
|
||||
xy: 16, 16
|
||||
size: 16, 16
|
||||
Idle
|
||||
xy: 32, 16
|
||||
size: 16, 16
|
||||
Idle
|
||||
xy: 48, 16
|
||||
size: 16, 16
|
||||
Walk
|
||||
xy: 0, 32
|
||||
size: 16, 16
|
||||
Walk
|
||||
xy: 16, 32
|
||||
size: 16, 16
|
||||
Walk
|
||||
xy: 32, 32
|
||||
size: 16, 16
|
||||
Walk
|
||||
xy: 48, 32
|
||||
size: 16, 16
|
||||
Attack
|
||||
xy: 0, 48
|
||||
size: 16, 16
|
||||
Attack
|
||||
xy: 16, 48
|
||||
size: 16, 16
|
||||
Attack
|
||||
xy: 32, 48
|
||||
size: 16, 16
|
||||
Attack
|
||||
xy: 48, 48
|
||||
size: 16, 16
|
||||
Hit
|
||||
xy: 0, 64
|
||||
size: 16, 16
|
||||
Hit
|
||||
xy: 16, 64
|
||||
size: 16, 16
|
||||
Hit
|
||||
xy: 32, 64
|
||||
size: 16, 16
|
||||
Hit
|
||||
xy: 48, 64
|
||||
size: 16, 16
|
||||
Death
|
||||
xy: 0, 80
|
||||
size: 16, 16
|
||||
Death
|
||||
xy: 16, 80
|
||||
size: 16, 16
|
||||
Death
|
||||
xy: 32, 80
|
||||
size: 16, 16
|
||||
Death
|
||||
xy: 48, 80
|
||||
size: 16, 16
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -468,4 +468,9 @@ CartoucheOfAmbition
|
||||
CartoucheOfZeal
|
||||
xy:320,240
|
||||
size:16,16
|
||||
|
||||
HallowedSigil
|
||||
xy:320,256
|
||||
size:16,16
|
||||
UnhallowedSigil
|
||||
xy:320,272
|
||||
size:16,16
|
||||
|
||||
|
Before Width: | Height: | Size: 273 KiB After Width: | Height: | Size: 272 KiB |
@@ -43,8 +43,8 @@
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"structureAtlasPath": "world/tilesets/terrain.atlas",
|
||||
"sourcePath": "world/models/fill.png",
|
||||
"maskPath": "world/masks/fill.png",
|
||||
"sourcePath": "world/structures/models/fill.png",
|
||||
"maskPath": "world/structures/masks/fill.png",
|
||||
"height": 0.99,
|
||||
"width": 0.9,
|
||||
"periodicOutput": false,
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"structureAtlasPath": "world/structures/structures.atlas",
|
||||
"sourcePath": "world/models/fill.png",
|
||||
"maskPath": "world/masks/fill.png",
|
||||
"sourcePath": "world/structures/models/fill.png",
|
||||
"maskPath": "world/structures/masks/fill.png",
|
||||
"height": 0.99,
|
||||
"width": 0.9,
|
||||
"periodicOutput": false,
|
||||
|
||||
@@ -107,7 +107,8 @@
|
||||
"CaveW6",
|
||||
"OrthodoxyBasilica",
|
||||
"Nahiri Encampment",
|
||||
"MageTower White"
|
||||
"MageTower White",
|
||||
"UnhallowedAbbey"
|
||||
],
|
||||
"structures": [
|
||||
{
|
||||
|
||||
@@ -7840,6 +7840,168 @@
|
||||
"BiomeWhite"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "False Knight",
|
||||
"sprite": "sprites/enemy/humanoid/human/knight/false_knight.atlas",
|
||||
"deck": [
|
||||
"decks/standard/death_knight.dck"
|
||||
],
|
||||
"ai": "",
|
||||
"spawnRate": 1,
|
||||
"difficulty": 0.1,
|
||||
"speed": 30,
|
||||
"life": 18,
|
||||
"rewards": [
|
||||
{
|
||||
"type": "deckCard",
|
||||
"probability": 1,
|
||||
"count": 2,
|
||||
"addMaxCount": 4,
|
||||
"rarity": [
|
||||
"common"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "deckCard",
|
||||
"probability": 0.5,
|
||||
"count": 1,
|
||||
"addMaxCount": 2,
|
||||
"rarity": [
|
||||
"uncommon"
|
||||
],
|
||||
"cardTypes": [
|
||||
"Creature",
|
||||
"Artifact",
|
||||
"Enchantment",
|
||||
"Instant",
|
||||
"Sorcery"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "deckCard",
|
||||
"probability": 0.25,
|
||||
"count": 1,
|
||||
"addMaxCount": 1,
|
||||
"rarity": [
|
||||
"rare",
|
||||
"mythicrare"
|
||||
],
|
||||
"cardTypes": [
|
||||
"Creature",
|
||||
"Artifact",
|
||||
"Enchantment",
|
||||
"Instant",
|
||||
"Sorcery"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "deckCard",
|
||||
"probability": 0.1,
|
||||
"count": 1,
|
||||
"rarity": [
|
||||
"rare"
|
||||
],
|
||||
"cardTypes": [
|
||||
"Land"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "gold",
|
||||
"probability": 0.3,
|
||||
"count": 10,
|
||||
"addMaxCount": 90
|
||||
}
|
||||
],
|
||||
"colors": "B",
|
||||
"questTags": [
|
||||
"Disguised",
|
||||
"Soldier",
|
||||
"Human",
|
||||
"Knight",
|
||||
"Unholy",
|
||||
"IdentityBlack"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "False Monk",
|
||||
"sprite": "sprites/enemy/humanoid/human/warlock/false_monk.atlas",
|
||||
"deck": [
|
||||
"decks/standard/cultist.dck"
|
||||
],
|
||||
"randomizeDeck": false,
|
||||
"spawnRate": 1,
|
||||
"difficulty": 0.1,
|
||||
"speed": 24,
|
||||
"life": 15,
|
||||
"rewards": [
|
||||
{
|
||||
"type": "deckCard",
|
||||
"probability": 1
|
||||
"count": 2,
|
||||
"addMaxCount": 2,
|
||||
"rarity": [
|
||||
"common"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "deckCard",
|
||||
"probability": 0.5,
|
||||
"count": 1,
|
||||
"addMaxCount": 1,
|
||||
"rarity": [
|
||||
"uncommon"
|
||||
],
|
||||
"cardTypes": [
|
||||
"Creature",
|
||||
"Artifact",
|
||||
"Enchantment",
|
||||
"Instant",
|
||||
"Sorcery"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "deckCard",
|
||||
"probability": 0.25,
|
||||
"count": 1,
|
||||
"addMaxCount": 1,
|
||||
"rarity": [
|
||||
"rare",
|
||||
"mythicrare"
|
||||
],
|
||||
"cardTypes": [
|
||||
"Creature",
|
||||
"Artifact",
|
||||
"Enchantment",
|
||||
"Instant",
|
||||
"Sorcery"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "deckCard",
|
||||
"probability": 0.1,
|
||||
"count": 1,
|
||||
"rarity": [
|
||||
"rare"
|
||||
],
|
||||
"cardTypes": [
|
||||
"Land"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "gold",
|
||||
"probability": 0.3,
|
||||
"count": 10,
|
||||
"addMaxCount": 90
|
||||
}
|
||||
],
|
||||
"colors": "B",
|
||||
"questTags": [
|
||||
"Human",
|
||||
"Disguised",
|
||||
"Unholy",
|
||||
"IdentityBlack",
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Farmer",
|
||||
"nameOverride": "",
|
||||
@@ -11673,6 +11835,86 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "High Cultist",
|
||||
"sprite": "sprites/enemy/humanoid/human/warlock/high_cultist.atlas",
|
||||
"deck": [
|
||||
"decks/standard/cultist.dck"
|
||||
],
|
||||
"randomizeDeck": false,
|
||||
"spawnRate": 1,
|
||||
"difficulty": 0.1,
|
||||
"speed": 24,
|
||||
"life": 30,
|
||||
"rewards": [
|
||||
{
|
||||
"type": "deckCard",
|
||||
"probability": 1,
|
||||
"count": 2,
|
||||
"addMaxCount": 4,
|
||||
"rarity": [
|
||||
"common"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "deckCard",
|
||||
"probability": 0.5,
|
||||
"count": 1,
|
||||
"addMaxCount": 2,
|
||||
"rarity": [
|
||||
"uncommon"
|
||||
],
|
||||
"cardTypes": [
|
||||
"Creature",
|
||||
"Artifact",
|
||||
"Enchantment",
|
||||
"Instant",
|
||||
"Sorcery"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "deckCard",
|
||||
"probability": 0.25,
|
||||
"count": 1,
|
||||
"addMaxCount": 1,
|
||||
"rarity": [
|
||||
"rare",
|
||||
"mythicrare"
|
||||
],
|
||||
"cardTypes": [
|
||||
"Creature",
|
||||
"Artifact",
|
||||
"Enchantment",
|
||||
"Instant",
|
||||
"Sorcery"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "deckCard",
|
||||
"probability": 0.1,
|
||||
"count": 1,
|
||||
"rarity": [
|
||||
"rare"
|
||||
],
|
||||
"cardTypes": [
|
||||
"Land"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "gold",
|
||||
"probability": 0.3,
|
||||
"count": 10,
|
||||
"addMaxCount": 90
|
||||
}
|
||||
],
|
||||
"colors": "B",
|
||||
"questTags": [
|
||||
"Human",
|
||||
"Disguised",
|
||||
"Unholy",
|
||||
"IdentityBlack",
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "High Elf",
|
||||
"sprite": "sprites/enemy/humanoid/elf/druid_2.atlas",
|
||||
@@ -21035,6 +21277,69 @@
|
||||
"BiomeRed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Valyx Feaster of Torment",
|
||||
"sprite": "sprites/enemy/fiend/valyx.atlas",
|
||||
"deck": [
|
||||
"decks/miniboss/valyx.dck"
|
||||
],
|
||||
"ai": "",
|
||||
"spawnRate": 1,
|
||||
"difficulty": 0.1,
|
||||
"speed": 31,
|
||||
"life": 80,
|
||||
"rewards": [
|
||||
{
|
||||
"type": "deckCard",
|
||||
"probability": 1,
|
||||
"count": 2,
|
||||
"addMaxCount": 4,
|
||||
"rarity": [
|
||||
"common"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "deckCard",
|
||||
"probability": 1,
|
||||
"count": 2,
|
||||
"addMaxCount": 2,
|
||||
"rarity": [
|
||||
"uncommon"
|
||||
],
|
||||
"cardTypes": [
|
||||
"Creature",
|
||||
"Artifact",
|
||||
"Enchantment",
|
||||
"Instant",
|
||||
"Sorcery"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "deckCard",
|
||||
"probability": 1,
|
||||
"count": 2,
|
||||
"addMaxCount": 1,
|
||||
"rarity": [
|
||||
"rare",
|
||||
"mythicrare"
|
||||
],
|
||||
"cardTypes": [
|
||||
"Creature",
|
||||
"Artifact",
|
||||
"Enchantment",
|
||||
"Instant",
|
||||
"Sorcery"
|
||||
]
|
||||
}
|
||||
],
|
||||
"colors": "B",
|
||||
"questTags": [
|
||||
"Demon",
|
||||
"Humanoid",
|
||||
"Unholy",
|
||||
"IdentityBlack"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Vampire",
|
||||
"sprite": "sprites/enemy/undead/vampire.atlas",
|
||||
|
||||
@@ -1234,5 +1234,32 @@
|
||||
"Slobad's Iron Boots"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hallowed Sigil",
|
||||
"description": "Turn a creature hexproof until end of turn.",
|
||||
"equipmentSlot": "Neck",
|
||||
"iconName": "HallowedSigil",
|
||||
"effect": {
|
||||
"startBattleWithCard": [
|
||||
"Hallowed Sigil"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Unhallowed Sigil",
|
||||
"description": "Devour the life of an enemy creature, killing it.",
|
||||
"equipmentSlot": "Right",
|
||||
"iconName": "UnhallowedSigil",
|
||||
"effect": {
|
||||
"startBattleWithCard": [
|
||||
"Sigil of Torment"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Cultist's Key",
|
||||
"iconName": "StrangeKey",
|
||||
"questItem": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -3130,6 +3130,18 @@
|
||||
"Planeswalker"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UnhallowedAbbey",
|
||||
"type": "dungeon",
|
||||
"count": 1,
|
||||
"spriteAtlas": "maps/tileset/buildings.atlas",
|
||||
"sprite": "Monastery",
|
||||
"map": "../common/maps/map/unhallowed_abbey_1F.tmx",
|
||||
"radiusFactor": 0.8,
|
||||
"questTags": [
|
||||
"UnhallowedAbbey"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "VampireCastle",
|
||||
"type": "dungeon",
|
||||
|
||||
@@ -12,5 +12,5 @@ ALTERNATE
|
||||
Name:Explosive Crystal
|
||||
ManaCost:4 R
|
||||
Types:Sorcery Adventure
|
||||
A:SP$ DealDamage | ValidTgts$ Any to distribute damage to | NumDmg$ 4 | TargetMin$ 0 | TargetMax$ 4 | DividedAsYouChoose$ 4 | SpellDescription$ CARDNAME deals 4 damage divided as you choose among any number of targets.
|
||||
A:SP$ DealDamage | ValidTgts$ Any | NumDmg$ 4 | TargetMin$ 0 | TargetMax$ 4 | DividedAsYouChoose$ 4 | SpellDescription$ CARDNAME deals 4 damage divided as you choose among any number of targets.
|
||||
Oracle:Explosive Crystal deals 4 damage divided as you choose among any number of targets.
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
Name:Arena
|
||||
ManaCost:no cost
|
||||
Types:Land
|
||||
A:AB$ Tap | Cost$ 3 T | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ DBTap | AlwaysRemember$ True | SpellDescription$ Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other. (Each deals damage equal to its power to the other.)
|
||||
SVar:DBTap:DB$ Tap | TargetingPlayer$ Player.Opponent | TargetingPlayerControls$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature you control | SubAbility$ DBFight | AlwaysRemember$ True
|
||||
SVar:DBFight:DB$ Fight | Defined$ Remembered | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
AI:RemoveDeck:All
|
||||
A:AB$ Tap | Cost$ 3 T | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ DBTap | AILogic$ Arena | SpellDescription$ Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other. (Each deals damage equal to its power to the other.)
|
||||
SVar:DBTap:DB$ Tap | TargetingPlayer$ Player.Opponent | TargetingPlayerControls$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature you control | SubAbility$ DBFight
|
||||
SVar:DBFight:DB$ Fight | Defined$ Targeted
|
||||
Oracle:{3}, {T}: Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other. (Each deals damage equal to its power to the other.)
|
||||
|
||||
@@ -7,5 +7,5 @@ SVar:TrigCounter:DB$ Counter | Defined$ TriggeredSpellAbility
|
||||
SVar:X:Count$xPaid
|
||||
SVar:Y:Count$CardCounters.CHARGE
|
||||
SVar:AICurseEffect:ChaliceOfTheVoid
|
||||
AI:RemoveDeck:All
|
||||
AI:RemoveDeck:Random
|
||||
Oracle:Chalice of the Void enters the battlefield with X charge counters on it.\nWhenever a player casts a spell with mana value equal to the number of charge counters on Chalice of the Void, counter that spell.
|
||||
|
||||
@@ -4,7 +4,7 @@ Types:Legendary Creature Human Shaman
|
||||
PT:2/2
|
||||
T:Mode$ SpellCast | ValidCard$ Card.Red | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUntap | TriggerDescription$ Whenever you cast a red spell, untap CARDNAME.
|
||||
SVar:TrigUntap:DB$ Untap | Defined$ Self
|
||||
A:AB$ DealDamage | Cost$ T | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select target player or planeswalker | NumDmg$ 1 | SubAbility$ DBTransform | AILogic$ PingAfterAttack | SpellDescription$ CARDNAME deals 1 damage to target player or planeswalker. If CARDNAME has dealt 3 or more damage this turn, exile her, then return her to the battlefield transformed under her owner's control.
|
||||
A:AB$ DealDamage | Cost$ T | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select target player or planeswalker | NumDmg$ 1 | SubAbility$ DBTransform | AILogic$ PingAfterAttack | SpellDescription$ CARDNAME deals 1 damage to target player or planeswalker. If NICKNAME has dealt 3 or more damage this turn, exile her, then return her to the battlefield transformed under her owner's control.
|
||||
SVar:DBTransform:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | SubAbility$ DBReturn | ConditionCheckSVar$ X | ConditionSVarCompare$ GE3 | StackDescription$ If CARDNAME has dealt 3 or more damage this turn, exile her, then return her to the battlefield transformed under her owner's control.
|
||||
SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | Transformed$ True | SubAbility$ DBCleanup | StackDescription$
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Cleaver Skaab
|
||||
ManaCost:3 U
|
||||
Types:Creature Zombie Horror
|
||||
PT:2/4
|
||||
A:AB$ CopyPermanent | Cost$ 3 T Sac<1/Zombie.Other/another zombie> | Defined$ Sacrificed | NumCopies$ 2 | AILogic$ AtOppEOT
|
||||
A:AB$ CopyPermanent | Cost$ 3 T Sac<1/Zombie.Other/another zombie> | Defined$ Sacrificed | NumCopies$ 2 | AILogic$ AtOppEOT | SpellDescription$ Create two tokens that are copies of the sacrificed creature.
|
||||
DeckNeeds:Type$Zombie
|
||||
DeckHas:Ability$Sacrifice|Token
|
||||
SVar:AIPreference:SacCost$Zombie.Other
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name:Congregation at Dawn
|
||||
ManaCost:G G W
|
||||
Types:Instant
|
||||
A:SP$ ChangeZone | Cost$ G G W | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Creature | ChangeNum$ 3 | SpellDescription$ Search your library for up to three creature cards, reveal them, then shuffle and put those cards on top in any order.
|
||||
A:SP$ ChangeZone | Cost$ G G W | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Creature | ChangeNum$ 3 | Reorder$ True | SpellDescription$ Search your library for up to three creature cards, reveal them, then shuffle and put those cards on top in any order.
|
||||
Oracle:Search your library for up to three creature cards, reveal them, then shuffle and put those cards on top in any order.
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:2 R
|
||||
Types:Creature Dwarf
|
||||
PT:2/2
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME enters the battlefield, search your library for any number of Dwarf cards, reveal them, then shuffle and put those cards on top in any order.
|
||||
SVar:TrigChangeZone:DB$ ChangeZone | ChangeNum$ X | ChangeType$ Dwarf | Origin$ Library | Destination$ Library | LibraryPosition$ 0
|
||||
SVar:TrigChangeZone:DB$ ChangeZone | ChangeNum$ X | ChangeType$ Dwarf | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | Reorder$ True
|
||||
SVar:X:Count$InYourLibrary.Dwarf
|
||||
AI:RemoveDeck:All
|
||||
DeckNeeds:Type$Dwarf
|
||||
|
||||