Merge branch 'Card-Forge:master' into master

This commit is contained in:
TabletopGeneral
2023-07-06 21:16:34 -04:00
committed by GitHub
164 changed files with 1305 additions and 630 deletions

View File

@@ -48,10 +48,7 @@ import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/** /**
@@ -360,35 +357,41 @@ public class AiAttackController {
} }
// this checks to make sure that the computer player doesn't lose when the human player attacks // this checks to make sure that the computer player doesn't lose when the human player attacks
public final List<Card> notNeededAsBlockers(final List<Card> attackers) { public final List<Card> notNeededAsBlockers(final List<Card> currentAttackers, final List<Card> potentialAttackers) {
//check for time walks //check for time walks
if (ai.getGame().getPhaseHandler().getNextTurn().equals(ai)) { if (ai.getGame().getPhaseHandler().getNextTurn().equals(ai)) {
return attackers; return potentialAttackers;
} }
// no need to block (already holding mana to cast fog next turn) // no need to block (already holding mana to cast fog next turn)
if (!AiCardMemory.isMemorySetEmpty(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT)) { if (!AiCardMemory.isMemorySetEmpty(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT)) {
// Don't send the card that'll do the fog effect to attack, it's unsafe! // Don't send the card that'll do the fog effect to attack, it's unsafe!
List<Card> toRemove = Lists.newArrayList(); List<Card> toRemove = Lists.newArrayList();
for (Card c : attackers) { for (Card c : potentialAttackers) {
if (AiCardMemory.isRememberedCard(ai, c, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT)) { if (AiCardMemory.isRememberedCard(ai, c, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT)) {
toRemove.add(c); toRemove.add(c);
} }
} }
attackers.removeAll(toRemove); potentialAttackers.removeAll(toRemove);
return attackers; return potentialAttackers;
} }
final List<Card> vigilantes = new ArrayList<>(); if (ai.isCardInPlay("Masako the Humorless")) {
for (final Card c : myList) {
if (c.getName().equals("Masako the Humorless")) {
// "Tapped creatures you control can block as though they were untapped." // "Tapped creatures you control can block as though they were untapped."
return attackers; return potentialAttackers;
} }
final CardCollection notNeededAsBlockers = new CardCollection(potentialAttackers);
final List<Card> vigilantes = new ArrayList<>();
for (final Card c : Iterables.concat(currentAttackers, potentialAttackers)) {
// no need to block if an effect is in play which untaps all creatures // no need to block if an effect is in play which untaps all creatures
// (pseudo-Vigilance akin to Awakening or Prophet of Kruphix) // (pseudo-Vigilance akin to Awakening or Prophet of Kruphix)
if (c.hasKeyword(Keyword.VIGILANCE) || ComputerUtilCard.willUntap(ai, c)) { if (c.hasKeyword(Keyword.VIGILANCE) || ComputerUtilCard.willUntap(ai, c)) {
vigilantes.add(c); vigilantes.add(c);
} else if (currentAttackers.contains(c)) {
// already attacking so can't block
notNeededAsBlockers.add(c);
} }
} }
// reduce the search space // reduce the search space
@@ -402,10 +405,8 @@ public class AiAttackController {
} }
}); });
final CardCollection notNeededAsBlockers = new CardCollection(attackers);
// don't hold back creatures that can't block any of the human creatures // don't hold back creatures that can't block any of the human creatures
final List<Card> blockers = getPossibleBlockers(attackers, opponentsAttackers, true); final List<Card> blockers = getPossibleBlockers(potentialAttackers, opponentsAttackers, true);
if (!blockers.isEmpty()) { if (!blockers.isEmpty()) {
notNeededAsBlockers.removeAll(blockers); notNeededAsBlockers.removeAll(blockers);
@@ -476,12 +477,15 @@ public class AiAttackController {
// these creatures will be available to block anyway // these creatures will be available to block anyway
notNeededAsBlockers.addAll(vigilantes); notNeededAsBlockers.addAll(vigilantes);
// remove those that were only included to ensure a full picture for the baseline
notNeededAsBlockers.removeAll(currentAttackers);
// Increase the total number of blockers needed by 1 if Finest Hour in play // Increase the total number of blockers needed by 1 if Finest Hour in play
// (human will get an extra first attack with a creature that untaps) // (human will get an extra first attack with a creature that untaps)
// In addition, if the computer guesses it needs no blockers, make sure // In addition, if the computer guesses it needs no blockers, make sure
// that it won't be surprised by Exalted // that it won't be surprised by Exalted
final int humanExaltedBonus = defendingOpponent.countExaltedBonus(); final int humanExaltedBonus = defendingOpponent.countExaltedBonus();
int blockersNeeded = attackers.size() - notNeededAsBlockers.size(); int blockersNeeded = potentialAttackers.size() - notNeededAsBlockers.size();
if (humanExaltedBonus > 0) { if (humanExaltedBonus > 0) {
final boolean finestHour = defendingOpponent.isCardInPlay("Finest Hour"); final boolean finestHour = defendingOpponent.isCardInPlay("Finest Hour");
@@ -511,6 +515,88 @@ public class AiAttackController {
return notNeededAsBlockers; return notNeededAsBlockers;
} }
public void reinforceWithBanding(final Combat combat) {
reinforceWithBanding(combat, null);
}
public void reinforceWithBanding(final Combat combat, final Card test) {
CardCollection attackers = combat.getAttackers();
if (attackers.isEmpty()) {
return;
}
List<String> bandsWithString = Arrays.asList("Bands with Other Legendary Creatures",
"Bands with Other Creatures named Wolves of the Hunt",
"Bands with Other Dinosaurs");
List<Card> bandingCreatures = null;
if (test == null) {
bandingCreatures = CardLists.filter(myList, card -> card.hasKeyword(Keyword.BANDING) || card.hasAnyKeyword(bandsWithString));
// filter out anything that can't legally attack or is already declared as an attacker
bandingCreatures = CardLists.filter(bandingCreatures, card -> !combat.isAttacking(card) && CombatUtil.canAttack(card));
bandingCreatures = notNeededAsBlockers(attackers, bandingCreatures);
} else {
// Test a specific creature for Banding
if (test.hasKeyword(Keyword.BANDING) || test.hasAnyKeyword(bandsWithString)) {
bandingCreatures = new CardCollection(test);
}
}
// respect global attack constraints
GlobalAttackRestrictions restrict = GlobalAttackRestrictions.getGlobalRestrictions(ai, combat.getDefenders());
int attackMax = restrict.getMax();
if (attackMax >= attackers.size()) {
return;
}
if (bandingCreatures != null) {
List<String> evasionKeywords = Arrays.asList("Flying", "Horsemanship", "Shadow", "Plainswalk", "Islandwalk",
"Forestwalk", "Mountainwalk", "Swampwalk");
// TODO: Assign to band with the best attacker for now, but needs better logic.
for (Card c : bandingCreatures) {
Card bestBand;
if (c.getNetPower() <= 0) {
// Don't band a zero power creature if there's already a banding creature in a band
attackers = CardLists.filter(attackers, card -> combat.getBandOfAttacker(card).getAttackers().size() == 1);
}
Card bestAttacker = ComputerUtilCard.getBestCreatureAI(attackers);
if (c.hasKeyword("Bands with Other Legendary Creatures")) {
bestBand = ComputerUtilCard.getBestCreatureAI(CardLists.getType(attackers, "Legendary"));
} else if (c.hasKeyword("Bands with Other Dinosaurs")) {
bestBand = ComputerUtilCard.getBestCreatureAI(CardLists.getType(attackers, "Dinosaur"));
} else if (c.hasKeyword("Bands with Other Creatures named Wolves of the Hunt")) {
bestBand = ComputerUtilCard.getBestCreatureAI(CardLists.filter(attackers, CardPredicates.nameEquals("Wolves of the Hunt")));
} else if (!c.hasAnyKeyword(evasionKeywords) && bestAttacker != null && bestAttacker.hasAnyKeyword(evasionKeywords)) {
bestBand = ComputerUtilCard.getBestCreatureAI(CardLists.filter(attackers, card -> !card.hasAnyKeyword(evasionKeywords)));
} else {
bestBand = bestAttacker;
}
if (c.getNetPower() <= 0) {
attackers = combat.getAttackers(); // restore the unfiltered attackers
}
if (bestBand != null) {
GameEntity defender = combat.getDefenderByAttacker(bestBand);
if (attackMax == -1) {
// check with the local limitations vs. the chosen defender
attackMax = restrict.getDefenderMax().get(defender) == null ? -1 : restrict.getDefenderMax().get(defender);
}
if (attackMax == -1 || attackMax > combat.getAttackers().size()) {
if (CombatUtil.canAttack(c, defender)) {
combat.addAttacker(c, defender, combat.getBandOfAttacker(bestBand));
}
}
}
}
}
}
private boolean doAssault() { private boolean doAssault() {
if (ai.isCardInPlay("Beastmaster Ascension") && this.attackers.size() > 1) { if (ai.isCardInPlay("Beastmaster Ascension") && this.attackers.size() > 1) {
final CardCollectionView beastions = ai.getCardsIn(ZoneType.Battlefield, "Beastmaster Ascension"); final CardCollectionView beastions = ai.getCardsIn(ZoneType.Battlefield, "Beastmaster Ascension");
@@ -1199,7 +1285,7 @@ public class AiAttackController {
if ( LOG_AI_ATTACKS ) if ( LOG_AI_ATTACKS )
System.out.println("Normal attack"); System.out.println("Normal attack");
attackersLeft = notNeededAsBlockers(attackersLeft); attackersLeft = notNeededAsBlockers(combat.getAttackers(), attackersLeft);
attackersLeft = sortAttackers(attackersLeft); attackersLeft = sortAttackers(attackersLeft);
if ( LOG_AI_ATTACKS ) if ( LOG_AI_ATTACKS )

View File

@@ -17,11 +17,7 @@
*/ */
package forge.ai; package forge.ai;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
@@ -36,6 +32,7 @@ import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.card.CounterEnumType; import forge.game.card.CounterEnumType;
import forge.game.combat.AttackingBand;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.combat.CombatUtil; import forge.game.combat.CombatUtil;
import forge.game.cost.Cost; import forge.game.cost.Cost;
@@ -770,14 +767,44 @@ public class AiBlockController {
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(changesPTWhenBlocked(true))); tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(changesPTWhenBlocked(true)));
for (final Card attacker : tramplingAttackers) { for (final Card attacker : tramplingAttackers) {
boolean staticAssignCombatDamageAsUnblocked = StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker);
if (CombatUtil.getMinNumBlockersForAttacker(attacker, combat.getDefenderPlayerByAttacker(attacker)) > combat.getBlockers(attacker).size() if (CombatUtil.getMinNumBlockersForAttacker(attacker, combat.getDefenderPlayerByAttacker(attacker)) > combat.getBlockers(attacker).size()
|| StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue; continue;
} }
boolean needsMoreChumpBlockers = true;
// See if it's possible to tank up the damage with Banding
List<String> bandsWithString = Arrays.asList("Bands with Other Legendary Creatures",
"Bands with Other Creatures named Wolves of the Hunt",
"Bands with Other Dinosaurs");
if (AttackingBand.isValidBand(combat.getBlockers(attacker), true)) {
continue;
}
chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, false); chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
chumpBlockers.removeAll(combat.getBlockers(attacker)); chumpBlockers.removeAll(combat.getBlockers(attacker));
// See if there's a Banding blocker that can tank the damage
for (final Card blocker : chumpBlockers) {
if (blocker.hasKeyword(Keyword.BANDING) || blocker.hasAnyKeyword(bandsWithString)) {
if (ComputerUtilCombat.getAttack(attacker) > ComputerUtilCombat.totalShieldDamage(attacker, combat.getBlockers(attacker))
&& ComputerUtilCombat.shieldDamage(attacker, blocker) > 0
&& CombatUtil.canBlock(attacker, blocker, combat) && ComputerUtilCombat.lifeInDanger(ai, combat)) {
combat.addBlocker(attacker, blocker);
needsMoreChumpBlockers = false;
break;
}
}
}
if (staticAssignCombatDamageAsUnblocked) {
continue;
}
if (needsMoreChumpBlockers) {
for (final Card blocker : chumpBlockers) { for (final Card blocker : chumpBlockers) {
// Add an additional blocker if the current blockers are not // Add an additional blocker if the current blockers are not
// enough and the new one would suck some of the damage // enough and the new one would suck some of the damage
@@ -789,6 +816,7 @@ public class AiBlockController {
} }
} }
} }
}
/** Support blockers not destroying the attacker with more blockers to try to kill the attacker */ /** Support blockers not destroying the attacker with more blockers to try to kill the attacker */
private void reinforceBlockersToKill(final Combat combat) { private void reinforceBlockersToKill(final Combat combat) {

View File

@@ -1304,6 +1304,9 @@ public class AiController {
AiAttackController aiAtk = new AiAttackController(attacker); AiAttackController aiAtk = new AiAttackController(attacker);
lastAttackAggression = aiAtk.declareAttackers(combat); lastAttackAggression = aiAtk.declareAttackers(combat);
// Check if we can reinforce with Banding creatures
aiAtk.reinforceWithBanding(combat);
// if invalid: just try an attack declaration that we know to be legal // if invalid: just try an attack declaration that we know to be legal
if (!CombatUtil.validateAttackers(combat)) { if (!CombatUtil.validateAttackers(combat)) {
combat.clearAttackers(); combat.clearAttackers();
@@ -1618,8 +1621,7 @@ public class AiController {
AiPlayDecision opinion = canPlayAndPayFor(sa); AiPlayDecision opinion = canPlayAndPayFor(sa);
// reset LastStateBattlefield // reset LastStateBattlefield
sa.setLastStateBattlefield(CardCollection.EMPTY); sa.clearLastState();
sa.setLastStateGraveyard(CardCollection.EMPTY);
// PhaseHandler ph = game.getPhaseHandler(); // PhaseHandler ph = game.getPhaseHandler();
// System.out.printf("Ai thinks '%s' of %s -> %s @ %s %s >>> \n", opinion, sa.getHostCard(), sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase()); // System.out.printf("Ai thinks '%s' of %s -> %s @ %s %s >>> \n", opinion, sa.getHostCard(), sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase());

View File

@@ -1406,6 +1406,39 @@ public class ComputerUtilCard {
} }
} }
if (keywords.contains("Banding") && !c.hasKeyword(Keyword.BANDING)) {
if (phase.is(PhaseType.COMBAT_BEGIN) && phase.isPlayerTurn(ai) && !ComputerUtilCard.doesCreatureAttackAI(ai, c)) {
// will this card participate in an attacking band?
Card bandingCard = getPumpedCreature(ai, sa, c, toughness, power, keywords);
// TODO: It may be possible to use AiController.getPredictedCombat here, but that makes it difficult to
// use reinforceWithBanding through the attack controller, especially with the extra card parameter in mind
AiAttackController aiAtk = new AiAttackController(ai);
Combat predicted = new Combat(ai);
aiAtk.declareAttackers(predicted);
aiAtk.reinforceWithBanding(predicted, bandingCard);
if (predicted.isAttacking(bandingCard) && predicted.getBandOfAttacker(bandingCard).getAttackers().size() > 1) {
return true;
}
} else if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && combat != null) {
// does this card block a Trample card or participate in a multi block?
for (Card atk : combat.getAttackers()) {
if (atk.getController().isOpponentOf(ai)) {
CardCollection blockers = combat.getBlockers(atk);
boolean hasBanding = false;
for (Card blocker : blockers) {
if (blocker.hasKeyword(Keyword.BANDING)) {
hasBanding = true;
break;
}
}
if (!hasBanding && ((blockers.contains(c) && blockers.size() > 1) || atk.hasKeyword(Keyword.TRAMPLE))) {
return true;
}
}
}
}
}
final Player opp = ai.getWeakestOpponent(); final Player opp = ai.getWeakestOpponent();
Card pumped = getPumpedCreature(ai, sa, c, toughness, power, keywords); Card pumped = getPumpedCreature(ai, sa, c, toughness, power, keywords);
List<Card> oppCreatures = opp.getCreaturesInPlay(); List<Card> oppCreatures = opp.getCreaturesInPlay();

View File

@@ -17,26 +17,17 @@
*/ */
package forge.ai; package forge.ai;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.card.Card; import forge.game.card.*;
import forge.game.card.CardCollection; import forge.game.combat.AttackingBand;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardUtil;
import forge.game.card.CounterEnumType;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.combat.CombatUtil; import forge.game.combat.CombatUtil;
import forge.game.cost.CostPayment; import forge.game.cost.CostPayment;
@@ -57,6 +48,9 @@ import forge.util.MyRandom;
import forge.util.TextUtil; import forge.util.TextUtil;
import forge.util.collect.FCollection; import forge.util.collect.FCollection;
import java.util.List;
import java.util.Map;
/** /**
* <p> * <p>
@@ -2023,6 +2017,8 @@ public class ComputerUtilCombat {
* distributeAIDamage. * distributeAIDamage.
* </p> * </p>
* *
* @param self
* a {@link forge.game.player.Player} object.
* @param attacker * @param attacker
* a {@link forge.game.card.Card} object. * a {@link forge.game.card.Card} object.
* @param block * @param block
@@ -2031,16 +2027,20 @@ public class ComputerUtilCombat {
* @param defender * @param defender
* @param overrideOrder overriding combatant order * @param overrideOrder overriding combatant order
*/ */
public static Map<Card, Integer> distributeAIDamage(final Card attacker, final CardCollectionView block, final CardCollectionView remaining, int dmgCanDeal, GameEntity defender, boolean overrideOrder) { public static Map<Card, Integer> distributeAIDamage(final Player self, final Card attacker, final CardCollectionView block, final CardCollectionView remaining, int dmgCanDeal, GameEntity defender, boolean overrideOrder) {
// TODO: Distribute defensive Damage (AI controls how damage is dealt to own cards) for Banding and Defensive Formation
Map<Card, Integer> damageMap = Maps.newHashMap(); Map<Card, Integer> damageMap = Maps.newHashMap();
Combat combat = attacker.getGame().getCombat(); Combat combat = attacker.getGame().getCombat();
boolean isAttacking = defender != null; boolean isAttacking = defender != null;
// Check for Banding, Defensive Formation
boolean isAttackingMe = isAttacking && combat.getDefenderPlayerByAttacker(attacker).equals(self);
boolean isBlockingMyBand = attacker.getController().isOpponentOf(self) && AttackingBand.isValidBand(block, true);
final boolean aiDistributesBandingDmg = isAttackingMe || isBlockingMyBand;
final boolean hasTrample = attacker.hasKeyword(Keyword.TRAMPLE); final boolean hasTrample = attacker.hasKeyword(Keyword.TRAMPLE);
if (combat != null && remaining != null && hasTrample && attacker.isAttacking()) { if (combat != null && remaining != null && hasTrample && attacker.isAttacking() && !aiDistributesBandingDmg) {
// if attacker has trample and some of its blockers are also blocking others it's generally a good idea // if attacker has trample and some of its blockers are also blocking others it's generally a good idea
// to assign those without trample first so we can maximize the damage to the defender // to assign those without trample first so we can maximize the damage to the defender
for (final Card c : remaining) { for (final Card c : remaining) {
@@ -2061,7 +2061,7 @@ public class ComputerUtilCombat {
final Card blocker = block.getFirst(); final Card blocker = block.getFirst();
int dmgToBlocker = dmgCanDeal; int dmgToBlocker = dmgCanDeal;
if (hasTrample && isAttacking) { // otherwise no entity to deliver damage via trample if (hasTrample && isAttacking && !aiDistributesBandingDmg) { // otherwise no entity to deliver damage via trample
dmgToBlocker = getEnoughDamageToKill(blocker, dmgCanDeal, attacker, true); dmgToBlocker = getEnoughDamageToKill(blocker, dmgCanDeal, attacker, true);
if (dmgCanDeal < dmgToBlocker) { if (dmgCanDeal < dmgToBlocker) {
@@ -2077,7 +2077,7 @@ public class ComputerUtilCombat {
} }
damageMap.put(blocker, dmgToBlocker); damageMap.put(blocker, dmgToBlocker);
} // 1 blocker } // 1 blocker
else { else if (!aiDistributesBandingDmg) {
// Does the attacker deal lethal damage to all blockers // Does the attacker deal lethal damage to all blockers
//Blocking Order now determined after declare blockers //Blocking Order now determined after declare blockers
Card lastBlocker = null; Card lastBlocker = null;
@@ -2105,6 +2105,19 @@ public class ComputerUtilCombat {
damageMap.put(lastBlocker, dmgCanDeal + damageMap.get(lastBlocker)); damageMap.put(lastBlocker, dmgCanDeal + damageMap.get(lastBlocker));
} }
} }
} else {
// In the event of Banding or Defensive Formation, assign max damage to the blocker who
// can tank all the damage or to the worst blocker to lose as little as possible
for (final Card b : block) {
final int dmgToKill = getEnoughDamageToKill(b, dmgCanDeal, attacker, true);
if (dmgToKill > dmgCanDeal) {
damageMap.put(b, dmgCanDeal);
break;
}
}
if (damageMap.isEmpty()) {
damageMap.put(ComputerUtilCard.getWorstCreatureAI(block), dmgCanDeal);
}
} }
return damageMap; return damageMap;
} }

View File

@@ -962,7 +962,12 @@ public class ComputerUtilMana {
ManaCostShard toPay, SpellAbility saPayment) { ManaCostShard toPay, SpellAbility saPayment) {
AbilityManaPart m = saPayment.getManaPart(); AbilityManaPart m = saPayment.getManaPart();
if (m.isComboMana()) { if (m.isComboMana()) {
m.setExpressChoice(ColorSet.fromMask(toPay.getColorMask())); // usually we'll want to produce color that matches the shard
ColorSet shared = ColorSet.fromMask(toPay.getColorMask()).getSharedColors(ColorSet.fromNames(m.getComboColors(saPayment).split(" ")));
// but other effects might still lead to a more permissive payment
if (!shared.isColorless()) {
m.setExpressChoice(ColorSet.fromMask(shared.iterator().next()));
}
getComboManaChoice(ai, saPayment, sa, cost); getComboManaChoice(ai, saPayment, sa, cost);
} }
else if (saPayment.getApi() == ApiType.ManaReflected) { else if (saPayment.getApi() == ApiType.ManaReflected) {

View File

@@ -106,7 +106,7 @@ public class PlayerControllerAi extends PlayerController {
@Override @Override
public Map<Card, Integer> assignCombatDamage(Card attacker, CardCollectionView blockers, CardCollectionView remaining, int damageDealt, GameEntity defender, boolean overrideOrder) { public Map<Card, Integer> assignCombatDamage(Card attacker, CardCollectionView blockers, CardCollectionView remaining, int damageDealt, GameEntity defender, boolean overrideOrder) {
return ComputerUtilCombat.distributeAIDamage(attacker, blockers, remaining, damageDealt, defender, overrideOrder); return ComputerUtilCombat.distributeAIDamage(player, attacker, blockers, remaining, damageDealt, defender, overrideOrder);
} }
@Override @Override

View File

@@ -651,6 +651,21 @@ public class SpecialCardAi {
} }
} }
// Grothama, All-Devouring
public static class GrothamaAllDevouring {
public static boolean consider(final Player ai, final SpellAbility sa) {
final Card source = sa.getHostCard();
final Card devourer = AbilityUtils.getDefinedCards(source, sa.getParam("ExtraDefined"), sa).getFirst(); // maybe just getOriginalHost()?
if (ai.getTeamMates(true).contains(devourer.getController())) {
return false; // TODO: Currently, the AI doesn't ever fight its own (or allied) Grothama for card draw. This can be improved.
}
final Card fighter = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).getFirst();
boolean goodTradeOrNoTrade = devourer.canBeDestroyed() && (devourer.getNetPower() < fighter.getNetToughness() || !fighter.canBeDestroyed()
|| ComputerUtilCard.evaluateCreature(devourer) > ComputerUtilCard.evaluateCreature(fighter));
return goodTradeOrNoTrade && fighter.getNetPower() >= devourer.getNetToughness();
}
}
// Guilty Conscience // Guilty Conscience
public static class GuiltyConscience { public static class GuiltyConscience {
public static Card getBestAttachTarget(final Player ai, final SpellAbility sa, final List<Card> list) { public static Card getBestAttachTarget(final Player ai, final SpellAbility sa, final List<Card> list) {

View File

@@ -112,7 +112,7 @@ public class ControlGainAi extends SpellAbilityAi {
// Don't steal something if I can't Attack without, or prevent it from blocking at least // Don't steal something if I can't Attack without, or prevent it from blocking at least
if (lose.contains("EOT") if (lose.contains("EOT")
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& !sa.isTrigger()) { && !sa.isTrigger()) {
return false; return false;
} }

View File

@@ -144,7 +144,7 @@ public class CounterAi extends SpellAbilityAi {
String logic = sa.getParam("AILogic"); String logic = sa.getParam("AILogic");
if ("Never".equals(logic)) { if ("Never".equals(logic)) {
return false; return false;
} else if (logic.startsWith("MinCMC.")) { } else if (logic.startsWith("MinCMC.")) { // TODO fix Daze and fold into AITgts
int minCMC = Integer.parseInt(logic.substring(7)); int minCMC = Integer.parseInt(logic.substring(7));
if (tgtCMC < minCMC) { if (tgtCMC < minCMC) {
return false; return false;

View File

@@ -59,8 +59,8 @@ public class DebuffAi extends SpellAbilityAi {
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| !game.getStack().isEmpty()) { || !game.getStack().isEmpty()) {
// Instant-speed pumps should not be cast outside of combat when the // Instant-speed pumps should not be cast outside of combat when the
// stack is empty // stack is empty, unless there are specific activation phase requirements
if (!SpellAbilityAi.isSorcerySpeed(sa, ai)) { if (!SpellAbilityAi.isSorcerySpeed(sa, ai) && !sa.hasParam("ActivationPhases")) {
return false; return false;
} }
} }

View File

@@ -2,11 +2,7 @@ package forge.ai.ability;
import java.util.List; import java.util.List;
import forge.ai.ComputerUtil; import forge.ai.*;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.card.Card; import forge.game.card.Card;
@@ -32,9 +28,13 @@ public class FightAi extends SpellAbilityAi {
sa.resetTargets(); sa.resetTargets();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
// everything is defined or targeted above, can't do anything there? // everything is defined or targeted above, can't do anything there unless a specific logic is set
if (sa.hasParam("Defined") && !sa.usesTargeting()) { if (sa.hasParam("Defined") && !sa.usesTargeting()) {
// TODO extend Logic for cards like Arena or Grothama // TODO extend Logic for cards like Arena
if ("Grothama".equals(sa.getParam("AILogic"))) { // Grothama, All-Devouring
return SpecialCardAi.GrothamaAllDevouring.consider(ai, sa);
}
return true; return true;
} }
@@ -120,7 +120,7 @@ public class FightAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (canPlayAI(ai, sa)) { if (checkApiLogic(ai, sa)) {
return true; return true;
} }
if (!mandatory) { if (!mandatory) {

View File

@@ -223,7 +223,7 @@ public class GameAction {
// Don't copy Tokens, copy only cards leaving the battlefield // Don't copy Tokens, copy only cards leaving the battlefield
// and returning to hand (to recreate their spell ability information) // and returning to hand (to recreate their spell ability information)
if (suppress || toBattlefield) { if (toBattlefield || (suppress && zoneTo.getZoneType().isHidden())) {
copied = c; copied = c;
if (lastKnownInfo == null) { if (lastKnownInfo == null) {
@@ -295,7 +295,6 @@ public class GameAction {
} }
copied.setUnearthed(c.isUnearthed()); copied.setUnearthed(c.isUnearthed());
copied.setTapped(false);
// need to copy counters when card enters another zone than hand or library // need to copy counters when card enters another zone than hand or library
if (lastKnownInfo.hasKeyword("Counters remain on CARDNAME as it moves to any zone other than a player's hand or library.") && if (lastKnownInfo.hasKeyword("Counters remain on CARDNAME as it moves to any zone other than a player's hand or library.") &&
@@ -386,7 +385,7 @@ public class GameAction {
} }
} }
if (!zoneTo.is(ZoneType.Stack) && !suppress) { if (!zoneTo.is(ZoneType.Stack)) {
// reset timestamp in changezone effects so they have same timestamp if ETB simultaneously // reset timestamp in changezone effects so they have same timestamp if ETB simultaneously
copied.setTimestamp(game.getNextTimestamp()); copied.setTimestamp(game.getNextTimestamp());
} }
@@ -600,7 +599,7 @@ public class GameAction {
} }
// only now that the LKI preserved it // only now that the LKI preserved it
if (!zoneTo.is(ZoneType.Exile) && !zoneTo.is(ZoneType.Stack)) { if (!zoneTo.is(ZoneType.Stack)) {
c.cleanupExiledWith(); c.cleanupExiledWith();
} }
@@ -636,7 +635,6 @@ public class GameAction {
} }
game.getTriggerHandler().runTrigger(TriggerType.ChangesController, runParams2, false); game.getTriggerHandler().runTrigger(TriggerType.ChangesController, runParams2, false);
} }
// AllZone.getStack().chooseOrderOfSimultaneousStackEntryAll();
if (suppress) { if (suppress) {
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
@@ -923,10 +921,6 @@ public class GameAction {
return result; return result;
} }
public final Card exile(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) { public final Card exile(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
if (game.isCardExiled(c)) {
return c;
}
final Zone origin = c.getZone(); final Zone origin = c.getZone();
final PlayerZone removed = c.getOwner().getZone(ZoneType.Exile); final PlayerZone removed = c.getOwner().getZone(ZoneType.Exile);
final Card copied = moveTo(removed, c, cause, params); final Card copied = moveTo(removed, c, cause, params);

View File

@@ -561,9 +561,6 @@ public class AbilityUtils {
} }
} else if (calcX[0].equals("OriginalHost")) { } else if (calcX[0].equals("OriginalHost")) {
val = xCount(ability.getOriginalHost(), calcX[1], ability); val = xCount(ability.getOriginalHost(), calcX[1], ability);
} else if (calcX[0].equals("LastStateBattlefield") && ability instanceof SpellAbility) {
Card c = ((SpellAbility) ability).getLastStateBattlefield().get(card);
val = c == null ? 0 : xCount(c, calcX[1], ability);
} else if (calcX[0].startsWith("ExiledWith")) { } else if (calcX[0].startsWith("ExiledWith")) {
val = handlePaid(card.getExiledCards(), calcX[1], card, ability); val = handlePaid(card.getExiledCards(), calcX[1], card, ability);
} else if (calcX[0].startsWith("Convoked")) { } else if (calcX[0].startsWith("Convoked")) {

View File

@@ -393,11 +393,16 @@ public abstract class SpellAbilityEffect {
} }
public static void addForgetOnMovedTrigger(final Card card, final String zone) { public static void addForgetOnMovedTrigger(final Card card, final String zone) {
String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ " + zone + " | ExcludedDestinations$ Stack | Destination$ Any | TriggerZones$ Command | Static$ True"; String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ " + zone + " | ExcludedDestinations$ Stack,Exile | Destination$ Any | TriggerZones$ Command | Static$ True";
String trig2 = "Mode$ Exiled | ValidCard$ Card.IsRemembered | TriggerZones$ Command | Static$ True";
final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true); final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true);
parsedTrigger.setOverridingAbility(getForgetSpellAbility(card)); final Trigger parsedTrigger2 = TriggerHandler.parseTrigger(trig2, card, true);
SpellAbility forget = getForgetSpellAbility(card);
parsedTrigger.setOverridingAbility(forget);
parsedTrigger2.setOverridingAbility(forget);
card.addTrigger(parsedTrigger); card.addTrigger(parsedTrigger);
card.addTrigger(parsedTrigger2);
} }
protected static void addForgetOnCastTrigger(final Card card) { protected static void addForgetOnCastTrigger(final Card card) {

View File

@@ -122,9 +122,8 @@ public class SacrificeEffect extends SpellAbilityEffect {
String [] msgArray = msg.split(" & "); String [] msgArray = msg.split(" & ");
List<CardCollection> validTargetsList = new ArrayList<>(validArray.length); List<CardCollection> validTargetsList = new ArrayList<>(validArray.length);
for (String subValid : validArray) { for (String subValid : validArray) {
CardCollectionView validTargets = AbilityUtils.filterListByType(battlefield, subValid, sa); CardCollection validTargets = CardLists.filter(AbilityUtils.filterListByType(battlefield, subValid, sa), CardPredicates.canBeSacrificedBy(sa, true));
validTargets = CardLists.filter(validTargets, CardPredicates.canBeSacrificedBy(sa, true)); validTargetsList.add(validTargets);
validTargetsList.add(new CardCollection(validTargets));
} }
CardCollection chosenCards = new CardCollection(); CardCollection chosenCards = new CardCollection();
for (int i = 0; i < validArray.length; ++i) { for (int i = 0; i < validArray.length; ++i) {

View File

@@ -523,7 +523,13 @@ public class CardFactoryUtil {
public static List<String> sharedKeywords(final Iterable<String> kw, final String[] restrictions, public static List<String> sharedKeywords(final Iterable<String> kw, final String[] restrictions,
final Iterable<ZoneType> zones, final Card host, CardTraitBase ctb) { final Iterable<ZoneType> zones, final Card host, CardTraitBase ctb) {
final List<String> filteredkw = Lists.newArrayList(); final List<String> filteredkw = Lists.newArrayList();
final Player p = host.getController(); Player p = null;
if (ctb instanceof SpellAbility) {
p = ((SpellAbility)ctb).getActivatingPlayer();
}
if (p == null) {
p = host.getController();
}
CardCollectionView cardlist = p.getGame().getCardsIn(zones); CardCollectionView cardlist = p.getGame().getCardsIn(zones);
final Set<String> landkw = Sets.newHashSet(); final Set<String> landkw = Sets.newHashSet();
final Set<String> protectionkw = Sets.newHashSet(); final Set<String> protectionkw = Sets.newHashSet();

View File

@@ -1,14 +1,13 @@
package forge.game.combat; package forge.game.combat;
import java.util.ArrayList;
import java.util.List;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import java.util.List;
public class AttackingBand { public class AttackingBand {
private CardCollection attackers = new CardCollection(); private CardCollection attackers = new CardCollection();
private Boolean blocked = null; // even if all blockers were killed before FS or CD, band remains blocked private Boolean blocked = null; // even if all blockers were killed before FS or CD, band remains blocked
@@ -26,7 +25,7 @@ public class AttackingBand {
public void addAttacker(Card card) { attackers.add(card); } public void addAttacker(Card card) { attackers.add(card); }
public void removeAttacker(Card card) { attackers.remove(card); } public void removeAttacker(Card card) { attackers.remove(card); }
public static boolean isValidBand(List<Card> band, boolean shareDamage) { public static boolean isValidBand(CardCollectionView band, boolean shareDamage) {
if (band.isEmpty()) { if (band.isEmpty()) {
// An empty band is not a valid band // An empty band is not a valid band
return false; return false;
@@ -64,7 +63,7 @@ public class AttackingBand {
public boolean canJoinBand(Card card) { public boolean canJoinBand(Card card) {
// Trying to join an existing band, attackers should be non-empty and card should exist // Trying to join an existing band, attackers should be non-empty and card should exist
List<Card> newBand = new ArrayList<>(attackers); CardCollection newBand = new CardCollection(attackers);
if (card != null) { if (card != null) {
newBand.add(card); newBand.add(card);
} }

View File

@@ -327,7 +327,6 @@ public class Player extends GameEntity implements Comparable<Player> {
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
activeScheme = getZone(ZoneType.SchemeDeck).get(0); activeScheme = getZone(ZoneType.SchemeDeck).get(0);
// gameAction moveTo ?
game.getAction().moveTo(ZoneType.Command, activeScheme, null, moveParams); game.getAction().moveTo(ZoneType.Command, activeScheme, null, moveParams);
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
@@ -800,7 +799,7 @@ public class Player extends GameEntity implements Comparable<Player> {
restDamage = 2; restDamage = 2;
} }
} else if (c.getName().equals("Elderscale Wurm")) { } else if (c.getName().equals("Elderscale Wurm")) {
if (c.getController().equals(this) && getLife() - restDamage < 7) { if (c.getController().equals(this) && getLife() >= 7 && getLife() - restDamage < 7) {
restDamage = getLife() - 7; restDamage = getLife() - 7;
if (restDamage < 0) { if (restDamage < 0) {
restDamage = 0; restDamage = 0;
@@ -3268,9 +3267,7 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
final TriggerHandler triggerHandler = game.getTriggerHandler(); final TriggerHandler triggerHandler = game.getTriggerHandler();
triggerHandler.suppressMode(TriggerType.ChangesZone); com.add(initiativeEffect);
game.getAction().moveTo(ZoneType.Command, initiativeEffect, null, null);
triggerHandler.clearSuppression(TriggerType.ChangesZone);
triggerHandler.clearActiveTriggers(initiativeEffect, null); triggerHandler.clearActiveTriggers(initiativeEffect, null);
triggerHandler.registerActiveTrigger(initiativeEffect, false); triggerHandler.registerActiveTrigger(initiativeEffect, false);

View File

@@ -231,7 +231,7 @@ public class ReplacementHandler {
chosenRE.setHasRun(true); chosenRE.setHasRun(true);
hasRun.add(chosenRE); hasRun.add(chosenRE);
chosenRE.setOtherChoices(possibleReplacers); chosenRE.setOtherChoices(possibleReplacers);
ReplacementResult res = executeReplacement(runParams, chosenRE, decider, game); ReplacementResult res = executeReplacement(runParams, chosenRE, decider);
if (res == ReplacementResult.NotReplaced) { if (res == ReplacementResult.NotReplaced) {
if (!possibleReplacers.isEmpty()) { if (!possibleReplacers.isEmpty()) {
res = run(event, runParams); res = run(event, runParams);
@@ -287,8 +287,7 @@ public class ReplacementHandler {
* the replacement effect to run * the replacement effect to run
*/ */
private ReplacementResult executeReplacement(final Map<AbilityKey, Object> runParams, private ReplacementResult executeReplacement(final Map<AbilityKey, Object> runParams,
final ReplacementEffect replacementEffect, final Player decider, final Game game) { final ReplacementEffect replacementEffect, final Player decider) {
SpellAbility effectSA = null; SpellAbility effectSA = null;
Card host = replacementEffect.getHostCard(); Card host = replacementEffect.getHostCard();
@@ -441,7 +440,7 @@ public class ReplacementHandler {
int damage = (int) runParams.get(AbilityKey.DamageAmount); int damage = (int) runParams.get(AbilityKey.DamageAmount);
Map<String, String> mapParams = re.getMapParams(); Map<String, String> mapParams = re.getMapParams();
ReplacementResult res = executeReplacement(runParams, re, decider, game); ReplacementResult res = executeReplacement(runParams, re, decider);
GameEntity newTarget = (GameEntity) runParams.get(AbilityKey.Affected); GameEntity newTarget = (GameEntity) runParams.get(AbilityKey.Affected);
int newDamage = (int) runParams.get(AbilityKey.DamageAmount); int newDamage = (int) runParams.get(AbilityKey.DamageAmount);

View File

@@ -201,6 +201,11 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
this.lastStateGraveyard = new CardCollection(lastStateGraveyard); this.lastStateGraveyard = new CardCollection(lastStateGraveyard);
} }
public void clearLastState() {
lastStateBattlefield = null;
lastStateGraveyard = null;
}
protected SpellAbility(final Card iSourceCard, final Cost toPay) { protected SpellAbility(final Card iSourceCard, final Cost toPay) {
this(iSourceCard, toPay, null); this(iSourceCard, toPay, null);
} }
@@ -828,10 +833,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
if (isActivatedAbility()) { if (isActivatedAbility()) {
setXManaCostPaid(null); setXManaCostPaid(null);
} }
// reset last state when finished resolving
setLastStateBattlefield(CardCollection.EMPTY);
setLastStateGraveyard(CardCollection.EMPTY);
} }
// key for autoyield - the card description (including number) (if there is a card) plus the effect description // key for autoyield - the card description (including number) (if there is a card) plus the effect description

View File

@@ -243,7 +243,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
if (params.containsKey("ConditionTargetsSingleTarget")) { if (params.containsKey("ConditionTargetsSingleTarget")) {
this.setTargetsSingleTarget(true); this.setTargetsSingleTarget(true);
} }
} // setConditions }
/** /**
* <p> * <p>

View File

@@ -189,7 +189,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
this.setClassLevelOperator(params.get("ClassLevel").substring(0, 2)); this.setClassLevelOperator(params.get("ClassLevel").substring(0, 2));
this.setClassLevel(params.get("ClassLevel").substring(2)); this.setClassLevel(params.get("ClassLevel").substring(2));
} }
} // end setRestrictions() }
/** /**
* <p> * <p>

View File

@@ -528,9 +528,6 @@ public class TriggerHandler {
sa = sa.copy(host, controller, false); sa = sa.copy(host, controller, false);
} }
sa.setLastStateBattlefield(game.getLastStateBattlefield());
sa.setLastStateGraveyard(game.getLastStateGraveyard());
sa.setTrigger(regtrig); sa.setTrigger(regtrig);
sa.setSourceTrigger(regtrig.getId()); sa.setSourceTrigger(regtrig.getId());
regtrig.setTriggeringObjects(sa, runParams); regtrig.setTriggeringObjects(sa, runParams);
@@ -565,7 +562,6 @@ public class TriggerHandler {
//wrapperAbility.setDescription(wrapperAbility.getStackDescription()); //wrapperAbility.setDescription(wrapperAbility.getStackDescription());
//wrapperAbility.setDescription(wrapperAbility.toUnsuppressedString()); //wrapperAbility.setDescription(wrapperAbility.toUnsuppressedString());
wrapperAbility.setLastStateBattlefield(game.getLastStateBattlefield());
if (regtrig.isStatic()) { if (regtrig.isStatic()) {
wrapperAbility.getActivatingPlayer().getController().playTrigger(host, wrapperAbility, isMandatory); wrapperAbility.getActivatingPlayer().getController().playTrigger(host, wrapperAbility, isMandatory);
} else { } else {

View File

@@ -279,6 +279,7 @@ public class TriggerSpellAbilityCastOrCopy extends Trigger {
sa.setTriggeringObject(AbilityKey.LifeAmount, castSA.getAmountLifePaid()); sa.setTriggeringObject(AbilityKey.LifeAmount, castSA.getAmountLifePaid());
sa.setTriggeringObjectsFrom( sa.setTriggeringObjectsFrom(
runParams, runParams,
AbilityKey.CardLKI,
AbilityKey.Player, AbilityKey.Player,
AbilityKey.Activator, AbilityKey.Activator,
AbilityKey.CurrentStormCount, AbilityKey.CurrentStormCount,

View File

@@ -44,7 +44,6 @@ import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.event.EventValueChangeType; import forge.game.event.EventValueChangeType;
import forge.game.event.GameEventCardStatsChanged; import forge.game.event.GameEventCardStatsChanged;
@@ -321,6 +320,14 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
// Copied spells aren't cast per se so triggers shouldn't run for them. // Copied spells aren't cast per se so triggers shouldn't run for them.
Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(sp.getHostCard().getController()); Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(sp.getHostCard().getController());
if (sp.isSpell() && !sp.isCopied()) {
final Card lki = CardUtil.getLKICopy(sp.getHostCard());
runParams.put(AbilityKey.CardLKI, lki);
thisTurnCast.add(lki);
sp.getActivatingPlayer().addSpellCastThisTurn();
}
runParams.put(AbilityKey.Cost, sp.getPayCosts()); runParams.put(AbilityKey.Cost, sp.getPayCosts());
runParams.put(AbilityKey.Activator, sp.getActivatingPlayer()); runParams.put(AbilityKey.Activator, sp.getActivatingPlayer());
runParams.put(AbilityKey.CastSA, si.getSpellAbility(true)); runParams.put(AbilityKey.CastSA, si.getSpellAbility(true));
@@ -462,10 +469,6 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
GameActionUtil.checkStaticAfterPaying(sp.getHostCard()); GameActionUtil.checkStaticAfterPaying(sp.getHostCard());
if (sp.isSpell() && !sp.isCopied()) {
thisTurnCast.add(CardUtil.getLKICopy(sp.getHostCard()));
sp.getActivatingPlayer().addSpellCastThisTurn();
}
if (sp.isActivatedAbility() && sp.isPwAbility()) { if (sp.isActivatedAbility() && sp.isPwAbility()) {
sp.getActivatingPlayer().setActivateLoyaltyAbilityThisTurn(true); sp.getActivatingPlayer().setActivateLoyaltyAbilityThisTurn(true);
} }
@@ -694,8 +697,6 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
frozenStack.remove(si); frozenStack.remove(si);
game.updateStackForView(); game.updateStackForView();
SpellAbility sa = si.getSpellAbility(false); SpellAbility sa = si.getSpellAbility(false);
sa.setLastStateBattlefield(CardCollection.EMPTY);
sa.setLastStateGraveyard(CardCollection.EMPTY);
game.fireEvent(new GameEventSpellRemovedFromStack(sa)); game.fireEvent(new GameEventSpellRemovedFromStack(sa));
} }

View File

@@ -490,12 +490,17 @@ public class GameHUD extends Stage {
private void setAudio(MusicPlaylist playlist) { private void setAudio(MusicPlaylist playlist) {
if (playlist.equals(currentAudioPlaylist)) if (playlist.equals(currentAudioPlaylist))
return; return;
System.out.println("Playlist: "+playlist);
unloadAudio(); unloadAudio();
System.out.println("Playlist: "+playlist);
audio = getMusic(playlist); audio = getMusic(playlist);
} }
private Pair<FileHandle, Music> getMusic(MusicPlaylist playlist) { private Pair<FileHandle, Music> getMusic(MusicPlaylist playlist) {
FileHandle file = Gdx.files.absolute(playlist.getNewRandomFilename()); String filename = playlist.getNewRandomFilename();
if (filename == null)
return null;
FileHandle file = Gdx.files.absolute(filename);
Music music = Forge.getAssets().getMusic(file); Music music = Forge.getAssets().getMusic(file);
if (music != null) { if (music != null) {
currentAudioPlaylist = playlist; currentAudioPlaylist = playlist;
@@ -776,7 +781,7 @@ public class GameHUD extends Stage {
changeBGM(MusicPlaylist.WHITE); changeBGM(MusicPlaylist.WHITE);
break; break;
case "waste": case "waste":
changeBGM(MusicPlaylist.MENUS); changeBGM(MusicPlaylist.COLORLESS);
break; break;
default: default:
break; break;

View File

@@ -15,7 +15,7 @@
"maxRoadDistance": 1000, "maxRoadDistance": 1000,
"biomesNames": [ "biomesNames": [
"world/biomes/base.json", "world/biomes/base.json",
"world/biomes/waste.json", "world/biomes/colorless.json",
"world/biomes/white.json", "world/biomes/white.json",
"world/biomes/blue.json", "world/biomes/blue.json",
"world/biomes/black.json", "world/biomes/black.json",

Binary file not shown.

View File

@@ -23,11 +23,8 @@
"height": 0.7, "height": 0.7,
"color": "110903", "color": "110903",
"spriteNames": [ "spriteNames": [
"SwampTree",
"SwampTree2",
"DarkGras", "DarkGras",
"Skull", "Skull",
"SwampRock",
"DarkWood", "DarkWood",
"Reed", "Reed",
"Waterlily", "Waterlily",
@@ -120,22 +117,46 @@
"N": 2, "N": 2,
"x": 0.5, "x": 0.5,
"y": 0.5, "y": 0.5,
"structureAtlasPath": "world/tilesets/structures.atlas", "structureAtlasPath": "world/structures/black_structures.atlas",
"sourcePath": "world/models/swamp_forest.png", "sourcePath": "world/structures/models/black.png",
"maskPath": "world/masks/ring.png", "maskPath": "world/structures/masks/ring.png",
"height": 0.5, "height": 0.5,
"width": 0.5, "width": 0.5,
"symmetry": 8, "symmetry": 8,
"periodicOutput": false,
"mappingInfo": [ "mappingInfo": [
{ {
"name": "swamp_forest", "name": "water",
"color": "007000", "color": "00ffff",
"collision": true "collision": true
}, },
{ {
"name": "swamp_water", "name": "tree",
"color": "005050", "color": "004000",
"collision": true
},
{
"name": "tree2",
"color": "008000",
"collision": true
},
{
"name": "tree3",
"color": "ff00ff",
"collision": true
},
{
"name": "tree4",
"color": "00f000",
"collision": true
},
{
"name": "rock",
"color": "808080",
"collision": true
},
{
"name": "rock2",
"color": "ff0000",
"collision": true "collision": true
} }
] ]
@@ -144,27 +165,41 @@
"N": 2, "N": 2,
"x": 0.5, "x": 0.5,
"y": 0.5, "y": 0.5,
"structureAtlasPath": "world/tilesets/structures.atlas", "structureAtlasPath": "world/structures/black_structures.atlas",
"sourcePath": "world/models/swamp_ruins.png", "sourcePath": "world/structures/models/black.png",
"maskPath": "world/masks/circle.png", "maskPath": "world/structures/masks/circle.png",
"height": 0.20000002, "height": 0.20000002,
"width": 0.20000002, "width": 0.20000002,
"symmetry": 1, "symmetry": 8,
"periodicOutput": false,
"mappingInfo": [ "mappingInfo": [
{ {
"name": "deep_swamp", "name": "muck",
"color": "002000", "color": "00ffff",
"collision": true "collision": true
}, },
{ {
"name": "structure", "name": "dead_tree",
"color": "505050", "color": "004000",
"collision": true "collision": true
}, },
{ {
"name": "swamp_forest2", "name": "dead_tree2",
"color": "007000", "color": "008000",
"collision": true
},
{
"name": "dead_tree3",
"color": "ff00ff",
"collision": true
},
{
"name": "rock",
"color": "808080",
"collision": true
},
{
"name": "rock2",
"color": "ff0000",
"collision": true "collision": true
} }
] ]

View File

@@ -23,8 +23,6 @@
"height": 0.7, "height": 0.7,
"color": "10a2e0", "color": "10a2e0",
"spriteNames": [ "spriteNames": [
"IslandTree",
"Coral",
"Shell" "Shell"
], ],
"enemies": [ "enemies": [
@@ -108,47 +106,93 @@
], ],
"structures": [ "structures": [
{ {
"N":2,
"x": 0.5, "x": 0.5,
"y": 0.5, "y": 0.5,
"structureAtlasPath": "world/tilesets/structures.atlas", "structureAtlasPath": "world/structures/blue_structures.atlas",
"sourcePath": "world/models/water.png", "sourcePath": "world/structures/models/blue.png",
"maskPath": "world/masks/circle.png", "maskPath": "world/structures/masks/circle.png",
"height": 0.20000002, "height": 0.1,
"width": 0.20000002, "width": 0.1,
"symmetry": 8, "symmetry": 8,
"periodicOutput": false, "periodicOutput": false,
"mappingInfo": [ "mappingInfo": [
{ {
"name": "water", "name": "water",
"color": "0070a0", "color": "00ffff",
"collision": true "collision": true
}, },
{ {
"name": "island_forest", "name": "tree",
"color": "00a000", "color": "00ff00",
"collision": true
},
{
"name": "tree2",
"color": "008000",
"collision": true
},
{
"name": "pineapple",
"color": "ffff00",
"collision": true
},
{
"name": "rock",
"color": "ff8000",
"collision": true
},
{
"name": "rock2",
"color": "804000",
"collision": true
},
{
"name": "rock3",
"color": "402000",
"collision": true
},
{
"name": "rock4",
"color": "201000",
"collision": true "collision": true
} }
] ]
}, },
{ {
"N": 2,
"x": 0.5, "x": 0.5,
"y": 0.5, "y": 0.5,
"structureAtlasPath": "world/tilesets/structures.atlas", "structureAtlasPath": "world/structures/blue_structures.atlas",
"sourcePath": "world/models/island_forest.png", "sourcePath": "world/structures/models/beach.png",
"maskPath": "world/masks/ring.png", "maskPath": "world/structures/masks/ring.png",
"height": 0.5, "height": 0.5,
"width": 0.5, "width": 0.5,
"symmetry": 8, "symmetry": 8,
"periodicOutput": false,
"mappingInfo": [ "mappingInfo": [
{ {
"name": "water", "name": "water",
"color": "0070a0", "color": "00ffff",
"collision": true "collision": true
}, },
{ {
"name": "island_forest", "name": "tree",
"color": "00a000", "color": "00ff00",
"collision": true
},
{
"name": "tree2",
"color": "008000",
"collision": true
},
{
"name": "dune",
"color": "ff8000",
"collision": true
},
{
"name": "dune2",
"color": "402000",
"collision": true "collision": true
} }
] ]

View File

@@ -5,15 +5,15 @@
"distWeight": 1, "distWeight": 1,
"name": "waste", "name": "waste",
"tilesetAtlas": "world/tilesets/terrain.atlas", "tilesetAtlas": "world/tilesets/terrain.atlas",
"tilesetName": "Waste", "tilesetName": "Colorless",
"terrain": [ "terrain": [
{ {
"spriteName": "Waste_1", "spriteName": "Colorless_1",
"max": 0.2, "max": 0.2,
"resolution": 5 "resolution": 5
}, },
{ {
"spriteName": "Waste_2", "spriteName": "Colorless_2",
"min": 0.8, "min": 0.8,
"max": 1, "max": 1,
"resolution": 5 "resolution": 5
@@ -23,9 +23,7 @@
"height": 0.85, "height": 0.85,
"color": "aeaeae", "color": "aeaeae",
"spriteNames": [ "spriteNames": [
"WasteTree", "Stone"
"Stone",
"WasteRock"
], ],
"enemies": [ "enemies": [
"Adept Black Wizard", "Adept Black Wizard",
@@ -118,44 +116,94 @@
"N": 2, "N": 2,
"x": 0.5, "x": 0.5,
"y": 0.5, "y": 0.5,
"structureAtlasPath": "world/tilesets/structures.atlas", "structureAtlasPath": "world/structures/colorless_structures.atlas",
"sourcePath": "world/models/waste_structure.png", "sourcePath": "world/structures/models/colorless.png",
"maskPath": "world/masks/circle.png", "maskPath": "world/structures/masks/circle.png",
"periodicInput": false, "height": 0.25,
"height": 0.20000002, "width": 0.25,
"width": 0.20000002, "symmetry": 8,
"symmetry": 4,
"mappingInfo": [ "mappingInfo": [
{ {
"name": "waste_structure", "name": "crater",
"color": "444444", "color": "808080",
"collision": true "collision": true
}, },
{ {
"name": "waste_mountain", "name": "tree",
"color": "9a9a9a", "color": "ff0000",
"collision": true
},
{
"name": "tree2",
"color": "00ff00",
"collision": true
},
{
"name": "tree3",
"color": "0000ff",
"collision": true
},
{
"name": "tree4",
"color": "00ffff",
"collision": true
},
{
"name": "rock",
"color": "ff00ff",
"collision": true
},
{
"name": "mountain",
"color": "000000",
"collision": true "collision": true
} }
] ]
}, },
{ {
"N": 2,
"x": 0.5, "x": 0.5,
"y": 0.5, "y": 0.5,
"structureAtlasPath": "world/tilesets/structures.atlas", "structureAtlasPath": "world/structures/colorless_structures.atlas",
"sourcePath": "world/models/hole.png", "sourcePath": "world/structures/models/colorless.png",
"maskPath": "world/masks/ring.png", "maskPath": "world/structures/masks/ring.png",
"height": 0.5, "height": 0.5,
"width": 0.5, "width": 0.5,
"periodicOutput": false, "symmetry": 8,
"mappingInfo": [ "mappingInfo": [
{ {
"name": "hole", "name": "hole",
"color": "111111", "color": "808080",
"collision": true "collision": true
}, },
{ {
"name": "waste_mountain", "name": "tree",
"color": "9a9a9a", "color": "ff0000",
"collision": true
},
{
"name": "tree2",
"color": "00ff00",
"collision": true
},
{
"name": "tree3",
"color": "0000ff",
"collision": true
},
{
"name": "tree4",
"color": "00ffff",
"collision": true
},
{
"name": "rock",
"color": "ff00ff",
"collision": true
},
{
"name": "mountain",
"color": "000000",
"collision": true "collision": true
} }
] ]

View File

@@ -23,9 +23,6 @@
"height": 0.7, "height": 0.7,
"color": "59a650", "color": "59a650",
"spriteNames": [ "spriteNames": [
"WoodTree",
"WoodTree2",
"Bush",
"Stump", "Stump",
"Moss", "Moss",
"Stone", "Stone",
@@ -122,39 +119,66 @@
"N": 2, "N": 2,
"x": 0.5, "x": 0.5,
"y": 0.5, "y": 0.5,
"structureAtlasPath": "world/tilesets/structures.atlas", "structureAtlasPath": "world/structures/green_structures.atlas",
"sourcePath": "world/models/forest.png", "sourcePath": "world/structures/models/green.png",
"maskPath": "world/masks/circle.png", "maskPath": "world/structures/masks/circle.png",
"height": 0.20000002, "height": 0.5,
"width": 0.20000002, "width": 0.5,
"symmetry": 1, "symmetry": 1,
"mappingInfo": [ "mappingInfo": [
{ {
"name": "forest", "name": "water",
"color": "007000", "color": "000080",
"collision": true
}
]
},
{
"N": 2,
"x": 0.5,
"y": 0.5,
"structureAtlasPath": "world/tilesets/structures.atlas",
"sourcePath": "world/models/lake.png",
"maskPath": "world/masks/ring.png",
"height": 0.5,
"width": 0.5,
"periodicOutput": false,
"mappingInfo": [
{
"name": "lake",
"color": "0070a0",
"collision": true "collision": true
}, },
{ {
"name": "forest2", "name": "tree",
"color": "009000", "color": "008000",
"collision": true
},
{
"name": "tree2",
"color": "004000",
"collision": true
},
{
"name": "vine",
"color": "8080ff",
"collision": true
},
{
"name": "tree3",
"color": "00c000",
"collision": true
},
{
"name": "tree4",
"color": "00f000",
"collision": true
},
{
"name": "tree5",
"color": "006000",
"collision": true
},
{
"name": "rock",
"color": "808080",
"collision": true
},
{
"name": "mountain",
"color": "ff0000",
"collision": true
},
{
"name": "plant",
"color": "800000",
"collision": true
},
{
"name": "bush",
"color": "ff8080",
"collision": true "collision": true
} }
] ]

View File

@@ -23,8 +23,7 @@
"height": 1, "height": 1,
"color": "110903", "color": "110903",
"spriteNames": [ "spriteNames": [
"Skull", "Skull"
"PlainsRock"
], ],
"enemies": [ "enemies": [
"Ammit", "Ammit",
@@ -60,9 +59,9 @@
"N": 2, "N": 2,
"x": 0.5, "x": 0.5,
"y": 0.5, "y": 0.5,
"structureAtlasPath": "world/tilesets/structures.atlas", "structureAtlasPath": "world/structures/structures.atlas",
"sourcePath": "world/models/mountain.png", "sourcePath": "world/structures/models/mountain.png",
"maskPath": "world/masks/circle.png", "maskPath": "world/structures/masks/circle.png",
"height": 0.5, "height": 0.5,
"width": 0.5, "width": 0.5,
"symmetry": 8, "symmetry": 8,

View File

@@ -31,7 +31,7 @@
"N": 2, "N": 2,
"x": 0.5, "x": 0.5,
"y": 0.5, "y": 0.5,
"structureAtlasPath": "world/tilesets/structures.atlas", "structureAtlasPath": "world/structures/structures.atlas",
"sourcePath": "world/models/fill.png", "sourcePath": "world/models/fill.png",
"maskPath": "world/masks/fill.png", "maskPath": "world/masks/fill.png",
"height": 0.99, "height": 0.99,

View File

@@ -45,9 +45,9 @@
"N": 2, "N": 2,
"x": 0.5, "x": 0.5,
"y": 0.5, "y": 0.5,
"structureAtlasPath": "world/tilesets/structures.atlas", "structureAtlasPath": "world/structures/structures.atlas",
"sourcePath": "world/models/mountain.png", "sourcePath": "world/structures/models/mountain.png",
"maskPath": "world/masks/circle.png", "maskPath": "world/structures/masks/circle.png",
"height": 0.5, "height": 0.5,
"width": 0.5, "width": 0.5,
"periodicOutput": false, "periodicOutput": false,

View File

@@ -23,9 +23,6 @@
"height": 0.7, "height": 0.7,
"color": "b63729", "color": "b63729",
"spriteNames": [ "spriteNames": [
"MountainTree",
"MountainTree2",
"MountainRock",
"Gravel" "Gravel"
], ],
"enemies": [ "enemies": [
@@ -127,21 +124,41 @@
"N": 2, "N": 2,
"x": 0.5, "x": 0.5,
"y": 0.5, "y": 0.5,
"structureAtlasPath": "world/tilesets/structures.atlas", "structureAtlasPath": "world/structures/red_structures.atlas",
"sourcePath": "world/models/mountain.png", "sourcePath": "world/structures/models/red.png",
"maskPath": "world/masks/ring.png", "maskPath": "world/structures/masks/ring.png",
"height": 0.5, "height": 0.5,
"width": 0.5, "width": 0.5,
"periodicOutput": false, "symmetry": 8,
"mappingInfo": [ "mappingInfo": [
{ {
"name": "mountain", "name": "mountain",
"color": "a07020", "color": "ff0000",
"collision": true "collision": true
}, },
{ {
"name": "mountain_forest", "name": "tree",
"color": "007000", "color": "00ff00",
"collision": true
},
{
"name": "tree2",
"color": "00ffff",
"collision": true
},
{
"name": "tree3",
"color": "0000ff",
"collision": true
},
{
"name": "tree4",
"color": "ff00ff",
"collision": true
},
{
"name": "rock",
"color": "ffff00",
"collision": true "collision": true
} }
] ]
@@ -149,15 +166,37 @@
{ {
"x": 0.5, "x": 0.5,
"y": 0.5, "y": 0.5,
"structureAtlasPath": "world/tilesets/structures.atlas", "structureAtlasPath": "world/structures/red_structures.atlas",
"sourcePath": "world/models/lava.png", "sourcePath": "world/structures/models/volcano.png",
"maskPath": "world/masks/circle.png", "maskPath": "world/structures/masks/circle.png",
"height": 0.2, "height": 0.2,
"width": 0.2, "width": 0.2,
"N": 2,
"symmetry": 8,
"mappingInfo": [ "mappingInfo": [
{ {
"name": "lava", "name": "lava",
"color": "ff5000", "color": "ffff00",
"collision": true
},
{
"name": "mountain",
"color": "ff0000",
"collision": true
},
{
"name": "dead_tree",
"color": "000000",
"collision": true
},
{
"name": "dead_tree2",
"color": "808080",
"collision": true
},
{
"name": "rock",
"color": "0000ff",
"collision": true "collision": true
} }
] ]

View File

@@ -23,7 +23,6 @@
"height": 0.5, "height": 0.5,
"color": "efe697", "color": "efe697",
"spriteNames": [ "spriteNames": [
"PlainsRock",
"Skull" "Skull"
], ],
"enemies": [ "enemies": [

View File

@@ -23,9 +23,6 @@
"height": 0.7, "height": 0.7,
"color": "efe697", "color": "efe697",
"spriteNames": [ "spriteNames": [
"PlainsTree",
"Cactus",
"PlainsRock",
"DarkGras" "DarkGras"
], ],
"enemies": [ "enemies": [
@@ -117,33 +114,74 @@
"N": 2, "N": 2,
"x": 0.5, "x": 0.5,
"y": 0.5, "y": 0.5,
"structureAtlasPath": "world/tilesets/structures.atlas", "structureAtlasPath": "world/structures/white_structures.atlas",
"sourcePath": "world/models/plains_forest.png", "sourcePath": "world/structures/models/white.png",
"maskPath": "world/masks/circle.png", "maskPath": "world/structures/masks/circle.png",
"height": 0.20000002, "height": 0.20000002,
"width": 0.20000002, "width": 0.20000002,
"symmetry": 8, "symmetry": 8,
"mappingInfo": [ "mappingInfo": [
{ {
"name": "plains_forest", "name": "tree",
"color": "9c4000", "color": "ff8000",
"collision": true
},
{
"name": "tree2",
"color": "008000",
"collision": true
},
{
"name": "tree3",
"color": "00ff00",
"collision": true "collision": true
} }
] ]
}, },
{ {
"N": 2,
"x": 0.5, "x": 0.5,
"y": 0.5, "y": 0.5,
"structureAtlasPath": "world/tilesets/structures.atlas", "symmetry": 8,
"sourcePath": "world/models/plateau.png", "structureAtlasPath": "world/structures/white_structures.atlas",
"maskPath": "world/masks/ring.png", "sourcePath": "world/structures/models/desert.png",
"maskPath": "world/structures/masks/ring.png",
"height": 0.5, "height": 0.5,
"width": 0.5, "width": 0.5,
"periodicOutput": false,
"mappingInfo": [ "mappingInfo": [
{ {
"name": "plateau", "name": "plateau",
"color": "caaa66", "color": "804000",
"collision": true
},
{
"name": "rock",
"color": "402000",
"collision": true
},
{
"name": "mesa",
"color": "201000",
"collision": true
},
{
"name": "plateau",
"color": "804000",
"collision": true
},
{
"name": "cactus",
"color": "00ff00",
"collision": true
},
{
"name": "cactus2",
"color": "008000",
"collision": true
},
{
"name": "cactus3",
"color": "004000",
"collision": true "collision": true
} }
] ]

View File

@@ -6369,9 +6369,7 @@
"IdentityBlack", "IdentityBlack",
"IdentityGreen", "IdentityGreen",
"IdentityAbzan", "IdentityAbzan",
"BiomeColorless", "BiomeBlue"
"BiomeWhite",
"BiomeBlack"
] ]
}, },
{ {
@@ -7242,7 +7240,7 @@
] ]
} }
], ],
"colors": "W", "colors": "GW",
"questTags": [ "questTags": [
"Elephant", "Elephant",
"Beast", "Beast",
@@ -12544,7 +12542,7 @@
"spawnRate": 1, "spawnRate": 1,
"difficulty": 0.1, "difficulty": 0.1,
"speed": 25, "speed": 25,
"scale": 0.6, "scale": 1.3,
"life": 11, "life": 11,
"rewards": [ "rewards": [
{ {

View File

@@ -21,36 +21,12 @@ Reed
Reed Reed
xy: 80, 0 xy: 80, 0
size: 16, 16 size: 16, 16
Reed
xy: 64, 16
size: 16, 16
Reed
xy: 80, 16
size: 16, 16
DarkWood DarkWood
xy: 96, 0 xy: 96, 0
size: 16, 16 size: 16, 16
DarkWood DarkWood
xy: 112, 0 xy: 112, 0
size: 16, 16 size: 16, 16
Waterlily
xy: 96, 16
size: 16, 16
Waterlily
xy: 112, 16
size: 16, 16
Shroom
xy: 96, 32
size: 16, 16
Shroom
xy: 96, 48
size: 16, 16
Shroom2
xy: 112, 32
size: 16, 16
Shroom2
xy: 112, 48
size: 16, 16
DarkWood DarkWood
xy: 0, 16 xy: 0, 16
size: 16, 16 size: 16, 16
@@ -63,36 +39,60 @@ DarkWood
DarkWood DarkWood
xy: 48, 16 xy: 48, 16
size: 16, 16 size: 16, 16
Reed
xy: 64, 16
size: 16, 16
Reed
xy: 80, 16
size: 16, 16
Waterlily
xy: 96, 16
size: 16, 16
Waterlily
xy: 112, 16
size: 16, 16
DarkGras DarkGras
xy: 0, 32 xy: 0, 32
size: 16, 16 size: 16, 16
DarkGras DarkGras
xy: 16, 32 xy: 16, 32
size: 16, 16 size: 16, 16
DarkGras
xy: 0, 48
size: 16, 16
DarkGras
xy: 16, 48
size: 16, 16
Stone Stone
xy: 32, 32 xy: 32, 32
size: 16, 16 size: 16, 16
Stone Stone
xy: 48, 32 xy: 48, 32
size: 16, 16 size: 16, 16
Stone
xy: 32, 48
size: 16, 16
Stone
xy: 48, 48
size: 16, 16
Gravel Gravel
xy: 64, 32 xy: 64, 32
size: 16, 16 size: 16, 16
Gravel Gravel
xy: 80, 32 xy: 80, 32
size: 16, 16 size: 16, 16
Shroom
xy: 96, 32
size: 16, 16
Shroom2
xy: 112, 32
size: 16, 16
DarkGras
xy: 0, 48
size: 16, 16
DarkGras
xy: 16, 48
size: 16, 16
Shroom
xy: 96, 48
size: 16, 16
Shroom2
xy: 112, 48
size: 16, 16
Stone
xy: 32, 48
size: 16, 16
Stone
xy: 48, 48
size: 16, 16
Gravel Gravel
xy: 64, 48 xy: 64, 48
size: 16, 16 size: 16, 16
@@ -105,42 +105,42 @@ Flower
Flower Flower
xy: 16, 64 xy: 16, 64
size: 16, 16 size: 16, 16
Flower
xy: 0, 80
size: 16, 16
Flower
xy: 16, 80
size: 16, 16
Stone Stone
xy: 32, 64 xy: 32, 64
size: 16, 16 size: 16, 16
Stone Stone
xy: 48, 64 xy: 48, 64
size: 16, 16 size: 16, 16
Moss
xy: 64, 64
size: 16, 16
Moss
xy: 80, 64
size: 16, 16
Wood
xy: 96, 64
size: 16, 16
Wood
xy: 112, 64
size: 16, 16
Flower
xy: 0, 80
size: 16, 16
Flower
xy: 16, 80
size: 16, 16
Stone Stone
xy: 32, 80 xy: 32, 80
size: 16, 16 size: 16, 16
Stone Stone
xy: 48, 80 xy: 48, 80
size: 16, 16 size: 16, 16
Moss
xy: 64, 64
size: 16, 16
Moss
xy: 80, 64
size: 16, 16
Moss Moss
xy: 64, 80 xy: 64, 80
size: 16, 16 size: 16, 16
Moss Moss
xy: 80, 80 xy: 80, 80
size: 16, 16 size: 16, 16
Wood
xy: 96, 64
size: 16, 16
Wood
xy: 112, 64
size: 16, 16
Wood Wood
xy: 96, 80 xy: 96, 80
size: 16, 16 size: 16, 16
@@ -162,6 +162,15 @@ WasteRock
WasteRock WasteRock
xy: 64, 96 xy: 64, 96
size: 16, 16 size: 16, 16
Placeholder
xy: 80, 96
size: 16,16
SwampTree2
xy: 96, 96
size: 16, 16
SwampTree2
xy: 112, 96
size: 16, 16
SwampTree SwampTree
xy: 0, 112 xy: 0, 112
size: 16, 16 size: 16, 16
@@ -174,12 +183,6 @@ SwampTree
Skull Skull
xy: 48, 112 xy: 48, 112
size: 16, 16 size: 16, 16
Skull
xy: 112, 144
size: 16, 16
Skull
xy: 112, 128
size: 16, 16
SwampRock SwampRock
xy: 64, 112 xy: 64, 112
size: 16, 16 size: 16, 16
@@ -192,12 +195,6 @@ SwampTree2
SwampTree2 SwampTree2
xy: 112, 112 xy: 112, 112
size: 16, 16 size: 16, 16
SwampTree2
xy: 96, 96
size: 16, 16
SwampTree2
xy: 112, 96
size: 16, 16
PlainsTree PlainsTree
xy: 0, 128 xy: 0, 128
size: 16, 16 size: 16, 16
@@ -214,11 +211,14 @@ Cactus
xy: 64, 128 xy: 64, 128
size: 16, 16 size: 16, 16
PlainsRock PlainsRock
xy: 70, 128 xy: 80, 128
size: 16, 16 size: 16, 16
PlainsRock PlainsRock
xy: 96, 128 xy: 96, 128
size: 16, 16 size: 16, 16
Skull
xy: 112, 128
size: 16, 16
IslandTree IslandTree
xy: 0, 144 xy: 0, 144
size: 16, 16 size: 16, 16
@@ -234,6 +234,15 @@ Shell
Shell Shell
xy: 64, 144 xy: 64, 144
size: 16, 16 size: 16, 16
Placeholder
xy: 80, 144
size: 16, 16
Placeholder
xy: 96, 144
size: 16, 16
Skull
xy: 112, 144
size: 16, 16
WoodTree WoodTree
xy: 0, 160 xy: 0, 160
size: 16, 16 size: 16, 16
@@ -270,24 +279,108 @@ MountainTree2
MountainTree2 MountainTree2
xy: 48, 176 xy: 48, 176
size: 16, 16 size: 16, 16
MountainTree2
xy: 96, 176
size: 16, 16
MountainTree2
xy: 112, 176
size: 16, 16
MountainRock MountainRock
xy: 64, 176 xy: 64, 176
size: 16, 16 size: 16, 16
MountainRock MountainRock
xy: 80, 176 xy: 80, 176
size: 16, 16 size: 16, 16
MountainTree2
xy: 96, 176
size: 16, 16
MountainTree2
xy: 112, 176
size: 16, 16
WoodTree
xy: 0, 192
size: 16, 16
AutumnTree
xy: 16, 192
size: 16, 16
WinterTree
xy: 32, 192
size: 16, 16
AutumnTree
xy: 48, 192
size: 16, 16
Coral
xy: 64, 192
size: 16, 16
SnowMountain
xy: 80, 192
size: 16, 16
Coral
xy: 96, 192
size: 16, 16
AutumnTree
xy: 112, 192
size: 16, 16
Placeholder
xy: 0, 208
size: 16, 16
AutumnTree
xy: 16, 208
size: 16, 16
SwampTree
xy: 32, 208
size: 16, 16
Coral
xy: 48, 208
size: 16, 16
WoodTree
xy: 64, 208
size: 16, 16
IslandRock
xy: 80, 208
size: 16, 16
WoodRock
xy: 96, 208
size: 16, 16
Placeholder
xy: 112, 208
size: 16, 16
LargeWoodRock
xy: 0, 224
size: 32, 32
LargeIslandRock
xy: 32, 224
size: 32, 32
LargeWasteRock
xy: 64, 224
size: 32, 32
LargeMountainRock LargeMountainRock
xy: 96, 224 xy: 96, 224
size: 32, 32 size: 32, 32
LargePlainsRock LargePlainsRock
xy: 96, 256 xy: 96, 256
size: 32, 32 size: 32, 32
Placeholder
xy: 0, 256
size: 16, 16
WasteRock
xy: 16, 256
size: 16, 16
LargeSwampRock LargeSwampRock
xy: 32, 256 xy: 32, 256
size: 32, 32 size: 32, 32
PlainsRock
xy: 64, 256
size: 16, 16
PlainsRock
xy: 80, 256
size: 16, 16
LargePlainsRock
xy: 96, 256
size: 32, 32
WoodRock
xy: 0, 272
size: 16, 16
SwampRock
xy: 16, 272
size: 16, 16
WinterTree:
xy: 64, 272
size: 16, 16
WinterTree:
xy: 80, 272
size: 16, 16

View File

@@ -79,21 +79,21 @@
"name":"WasteTree", "name":"WasteTree",
"startArea":0.0, "startArea":0.0,
"endArea":0.2, "endArea":0.2,
"layer":0, "layer":1,
"resolution" :10, "resolution" :10,
"density":0.7 "density":0.7
},{ },{
"name":"WasteRock", "name":"WasteRock",
"startArea":0.8, "startArea":0.8,
"endArea":1.0, "endArea":1.0,
"layer":0, "layer":1,
"resolution" :10, "resolution" :10,
"density":0.5 "density":0.5
},{ },{
"name":"SwampTree", "name":"SwampTree",
"startArea":0.8, "startArea":0.8,
"endArea":1.0, "endArea":1.0,
"layer":0, "layer":1,
"resolution" :10, "resolution" :10,
"density":0.5 "density":0.5
},{ },{
@@ -106,45 +106,45 @@
"name":"SwampRock", "name":"SwampRock",
"startArea":0.5, "startArea":0.5,
"endArea":0.6, "endArea":0.6,
"layer":0, "layer":1,
"density":0.1 "density":0.1
},{ },{
"name":"SwampTree2", "name":"SwampTree2",
"startArea":0.0, "startArea":0.0,
"endArea":0.2, "endArea":0.2,
"layer":0, "layer":1,
"resolution" :10, "resolution" :10,
"density":0.7 "density":0.7
},{ },{
"name":"PlainsTree", "name":"PlainsTree",
"startArea":0.0, "startArea":0.0,
"endArea":0.2, "endArea":0.2,
"layer":0, "layer":1,
"resolution" :10, "resolution" :10,
"density":0.7 "density":0.7
},{ },{
"name":"Cactus", "name":"Cactus",
"startArea":0.5, "startArea":0.5,
"layer":0, "layer":1,
"endArea":0.7, "endArea":0.7,
"density":0.06 "density":0.06
},{ },{
"name":"PlainsRock", "name":"PlainsRock",
"startArea":0.7, "startArea":0.7,
"layer":0, "layer":1,
"endArea":0.99, "endArea":0.99,
"density":0.06 "density":0.06
},{ },{
"name":"IslandTree", "name":"IslandTree",
"startArea":0.0, "startArea":0.0,
"endArea":0.2, "endArea":0.2,
"layer":0, "layer":1,
"resolution" :10, "resolution" :10,
"density":0.7 "density":0.7
},{ },{
"name":"Coral", "name":"Coral",
"startArea":0.0, "startArea":0.0,
"layer":0, "layer":1,
"endArea":0.9, "endArea":0.9,
"density":0.01 "density":0.01
},{ },{
@@ -157,65 +157,65 @@
"name":"WoodTree", "name":"WoodTree",
"startArea":0.0, "startArea":0.0,
"endArea":0.2, "endArea":0.2,
"layer":0, "layer":1,
"resolution" :10, "resolution" :10,
"density":0.7 "density":0.7
},{ },{
"name":"WoodTree2", "name":"WoodTree2",
"startArea":0.8, "startArea":0.8,
"endArea":0.99, "endArea":0.99,
"layer":0, "layer":1,
"resolution" :5, "resolution" :5,
"density":0.7 "density":0.7
},{ },{
"name":"Bush", "name":"Bush",
"startArea":0.0, "startArea":0.0,
"endArea":0.2, "endArea":0.2,
"layer":0, "layer":1,
"resolution" :5, "resolution" :5,
"density":0.4 "density":0.4
},{ },{
"name":"Stump", "name":"Stump",
"startArea":0.0, "startArea":0.0,
"layer":0, "layer":-1,
"endArea":0.9, "endArea":0.9,
"density":0.01 "density":0.01
},{ },{
"name":"MountainTree", "name":"MountainTree",
"startArea":0.0, "startArea":0.0,
"endArea":0.2, "endArea":0.2,
"layer":0, "layer":1,
"resolution" :5, "resolution" :5,
"density":0.7 "density":0.7
},{ },{
"name":"MountainTree2", "name":"MountainTree2",
"startArea":0.8, "startArea":0.8,
"endArea":0.99, "endArea":0.99,
"layer":0, "layer":1,
"resolution" :5, "resolution" :5,
"density":0.7 "density":0.7
},{ },{
"name":"MountainRock", "name":"MountainRock",
"startArea":0.1, "startArea":0.1,
"layer":0, "layer":1,
"endArea":0.9, "endArea":0.9,
"density":0.08 "density":0.08
},{ },{
"name":"LargeMountainRock", "name":"LargeMountainRock",
"startArea":0.0, "startArea":0.0,
"layer":0, "layer":1,
"endArea":0.9, "endArea":0.9,
"density":0.02 "density":0.02
},{ },{
"name":"LargePlainsRock", "name":"LargePlainsRock",
"startArea":0.0, "startArea":0.0,
"layer":0, "layer":1,
"endArea":0.9, "endArea":0.9,
"density":0.01 "density":0.01
},{ },{
"name":"LargeSwampRock", "name":"LargeSwampRock",
"startArea":0.0, "startArea":0.0,
"layer":0, "layer":1,
"endArea":0.9, "endArea":0.9,
"density":0.01 "density":0.01
} }

View File

@@ -0,0 +1,38 @@
black_structures.png
size: 192,192
format: RGBA8888
filter: Nearest,Nearest
repeat: none
water
xy: 0, 0
size: 48, 64
muck
xy: 48,0
size:48,64
tree
xy: 96,0
size: 48,64
tree2
xy:144,0
size:48,64
dead_tree
xy: 0,64
size: 48,64
dead_tree2
xy: 48,64
size: 48,64
tree3
xy: 96,64
size: 48,64
tree4
xy:144,64
size:48,64
rock
xy: 0, 128
size: 48, 64
rock2
xy: 48, 128
size: 48,64
dead_tree3
xy:96,128
size:48,64

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -0,0 +1,34 @@
blue_structures.png
size: 192,192
format: RGBA8888
filter: Nearest,Nearest
repeat: none
water
xy: 0, 0
size: 48, 64
tree
xy: 48,0
size:48,64
tree2
xy: 96,0
size: 48,64
rock
xy: 0,64
size: 48,64
rock2
xy: 48,64
size: 48,64
pineapple
xy: 96,64
size: 48,64
rock3
xy: 0, 128
size: 48, 64
rock4
xy: 48, 128
size: 48,64
dune
xy:96,128
size:48,64
dune2
xy:144,128

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -0,0 +1,29 @@
colorless_structures.png
size: 144,192
format: RGBA8888
filter: Nearest,Nearest
repeat: none
hole
xy: 0, 0
size: 48, 64
crater
xy: 48,0
size:48,64
tree
xy: 96,0
size: 48,64
tree2
xy: 0,64
size: 48,64
tree3
xy: 48,64
size: 48,64
tree4
xy: 96,64
size: 48,64
rock
xy: 0, 128
size: 48, 64
mountain
xy: 48, 128
size: 48,64

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -0,0 +1,41 @@
green_structures.png
size: 192,192
format: RGBA8888
filter: Nearest,Nearest
repeat: none
water
xy: 0, 0
size: 48, 64
tree
xy: 48,0
size:48,64
tree2
xy: 96,0
size: 48,64
vine
xy: 144,0
size: 48,64
tree3
xy: 0,64
size: 48,64
tree4
xy: 48,64
size: 48,64
tree5
xy: 96,64
size: 48,64
tree6
xy: 144, 64
size: 48,64
rock
xy: 0, 128
size: 48, 64
mountain
xy: 48, 128
size: 48,64
plant
xy:96,128
size:48,64
bush
xy:144,128
size:48,64

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

Before

Width:  |  Height:  |  Size: 508 B

After

Width:  |  Height:  |  Size: 508 B

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 508 B

After

Width:  |  Height:  |  Size: 508 B

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

View File

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

View File

@@ -0,0 +1,32 @@
red_structures.png
size: 144,192
format: RGBA8888
filter: Nearest,Nearest
repeat: none
lava
xy: 0, 0
size: 48, 64
tree
xy: 48,0
size:48,64
tree2
xy: 96,0
size: 48,64
dead_tree
xy: 0,64
size: 48,64
dead_tree2
xy: 48,64
size: 48,64
tree3
xy: 96,64
size: 48,64
rock
xy: 0, 128
size: 48, 64
mountain
xy: 48, 128
size: 48,64
tree4
xy: 96,128
size: 48,64

Some files were not shown because too many files have changed in this diff Show More