Merge branch 'master' into master2

This commit is contained in:
Anthony Calosa
2024-10-25 12:31:41 +08:00
11 changed files with 78 additions and 25 deletions

View File

@@ -436,6 +436,13 @@ public abstract class GameState {
}
}
if (!c.getUnlockedRooms().isEmpty()) {
for (CardStateName stateName : c.getUnlockedRooms()) {
newText.append("|UnlockedRoom:");
newText.append(stateName.name());
}
}
cardTexts.put(zoneType, newText.toString());
}
@@ -1401,6 +1408,8 @@ public abstract class GameState {
c.setGamePieceType(GamePieceType.TOKEN);
} else if (info.startsWith("ClassLevel:")) {
c.setClassLevel(Integer.parseInt(info.substring(info.indexOf(':') + 1)));
} else if (info.startsWith("UnlockedRoom:")) {
c.unlockRoom(c.getController(), CardStateName.smartValueOf(info.substring(info.indexOf(':') + 1)));
}
}

View File

@@ -160,6 +160,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
return SpecialCardAi.MazesEnd.consider(aiPlayer, sa);
} else if (aiLogic.equals("Pongify")) {
return sa.isTargetNumberValid(); // Pre-targeted in checkAiLogic
} else if (aiLogic.equals("ReturnCastable")) {
return !sa.getHostCard().getExiledCards().isEmpty()
&& ComputerUtilMana.canPayManaCost(sa.getHostCard().getExiledCards().getFirst().getFirstSpellAbility(), aiPlayer, 0, false);
}
}
if (sa.isHidden()) {

View File

@@ -1,25 +1,12 @@
package forge.ai.ability;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import com.google.common.collect.Lists;
import forge.ai.*;
import forge.game.Game;
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.card.CardPredicates.Presets;
import forge.game.card.CounterEnumType;
import forge.game.combat.Combat;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
@@ -30,6 +17,10 @@ import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class ChooseCardAi extends SpellAbilityAi {
/**
@@ -58,11 +49,15 @@ public class ChooseCardAi extends SpellAbilityAi {
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
final Card host = sa.getHostCard();
final Game game = ai.getGame();
ZoneType choiceZone = ZoneType.Battlefield;
List<ZoneType> choiceZone;
if (sa.hasParam("ChoiceZone")) {
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
choiceZone = ZoneType.listValueOf(sa.getParam("ChoiceZone"));
} else {
choiceZone = Lists.newArrayList(ZoneType.Battlefield);
}
CardCollectionView choices = game.getCardsIn(choiceZone);
if (sa.hasParam("Choices")) {
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host, sa);
}
@@ -129,6 +124,13 @@ public class ChooseCardAi extends SpellAbilityAi {
ownChoices = CardLists.filter(choices, CardPredicates.isControlledByAnyOf(ai.getAllies()));
}
return !ownChoices.isEmpty();
} else if (aiLogic.equals("GoodCreature")) {
for (Card choice : choices) {
if (choice.isCreature() && ComputerUtilCard.evaluateCreature(choice) >= 250) {
return true;
}
}
return false;
}
return true;
}

View File

@@ -81,6 +81,13 @@ public class DrawAi extends SpellAbilityAi {
if (!canLoot(ai, sa)) {
return false;
}
if (ComputerUtilCost.isSacrificeSelfCost(sa.getPayCosts())) {
// Canopy lands and other cards that sacrifice themselves to draw cards
return ai.getCardsIn(ZoneType.Hand).isEmpty()
|| (sa.getHostCard().isLand() && ai.getLandsInPlay().size() >= 5); // TODO: make this configurable in the AI profile
}
return true;
}

View File

@@ -42,6 +42,9 @@ public class LifeGainAi extends SpellAbilityAi {
if (!lifeCritical) {
// return super.willPayCosts(ai, sa, cost, source);
if ("CriticalOnly".equals(sa.getParam("AILogic"))) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa, false)) {
return false;
}

View File

@@ -4,7 +4,7 @@ Types:Legendary Creature Elf Warrior
PT:3/3
S:Mode$ Continuous | Affected$ Card.TopLibrary+YouCtrl | AffectedZone$ Library | MayLookAt$ You | Description$ You may look at the top card of your library any time.
S:Mode$ Continuous | Affected$ Creature.TopLibrary+YouCtrl+nonLand | AffectedZone$ Library | MayPlay$ True | Description$ You may cast creature spells from the top of your library.
A:AB$ ChooseCard | Cost$ G T tapXType<2/Creature> | ChoiceZone$ Hand,Library | PlayerTurn$ True | Reveal$ True | Choices$ Card.TopLibrary+YouOwn,Card.YouOwn+inZoneHand | SubAbility$ DBChangeZone | ChoiceTitle$ Reveal a card from your hand or the top of your library | SpellDescription$ Reveal a card from your hand or the top of your library. If you reveal a creature card this way, put it onto the battlefield. Activate only during your turn.
A:AB$ ChooseCard | Cost$ G T tapXType<2/Creature> | ChoiceZone$ Hand,Library | PlayerTurn$ True | Reveal$ True | Choices$ Card.TopLibrary+YouOwn,Card.YouOwn+inZoneHand | SubAbility$ DBChangeZone | ChoiceTitle$ Reveal a card from your hand or the top of your library | AILogic$ GoodCreature | SpellDescription$ Reveal a card from your hand or the top of your library. If you reveal a creature card this way, put it onto the battlefield. Activate only during your turn.
SVar:DBChangeZone:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Library,Hand | ConditionDefined$ ChosenCard | ConditionPresent$ Creature | Destination$ Battlefield | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True
Oracle:You may look at the top of your library at any time.\nYou may cast creature spells from the top of your library.\n{G}, {T}, Tap two untapped creatures you control: Reveal a card from your hand or the top of your library. If you reveal a creature card this way, put it onto the battlefield. Activate only during your turn.

View File

@@ -4,7 +4,7 @@ Types:Land
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | OptionalDecider$ You | Execute$ TrigExile | TriggerDescription$ Imprint — When CARDNAME enters, you may exile a colorless card with mana value 7 or greater from your hand.
SVar:TrigExile:DB$ ChangeZone | Origin$ Hand | Destination$ Exile | ChangeType$ Card.Colorless+cmcGE7 | ChangeNum$ 1
A:AB$ Mana | Cost$ T | Produced$ C | Amount$ X | SpellDescription$ Add {C}. If a card is exiled with CARDNAME, add {C}{C} instead.
A:AB$ ChangeZone | Cost$ T | Defined$ ExiledWith | Origin$ Exile | Destination$ Hand | SpellDescription$ Return the exiled card to its owner's hand.
A:AB$ ChangeZone | Cost$ T | Defined$ ExiledWith | Origin$ Exile | Destination$ Hand | AILogic$ ReturnCastable | SpellDescription$ Return the exiled card to its owner's hand.
SVar:X:Count$Compare Y GE1.2.1
SVar:Y:Count$ValidExile Card.ExiledWithSource
Oracle:Imprint — When Ugin's Labyrinth enters, you may exile a colorless card with mana value 7 or greater from your hand.\n{T}: Add {C}. If a card is exiled with Ugin's Labyrinth, add {C}{C} instead.\n{T}: Return the exiled card to its owner's hand.

View File

@@ -1,6 +1,6 @@
Name:Zuran Orb
ManaCost:0
Types:Artifact
A:AB$ GainLife | Cost$ Sac<1/Land> | LifeAmount$ 2 | SpellDescription$ You gain 2 life.
A:AB$ GainLife | Cost$ Sac<1/Land> | LifeAmount$ 2 | AILogic$ CriticalOnly | SpellDescription$ You gain 2 life.
SVar:NonStackingEffect:True
Oracle:Sacrifice a land: You gain 2 life.

View File

@@ -0,0 +1,17 @@
[metadata]
Name:Possibility Storm - Duskmourn: House of Horror #03
URL:https://i0.wp.com/www.possibilitystorm.com/wp-content/uploads/2024/10/latest-scaled.jpg?ssl=1
Goal:Win
Turns:1
Difficulty:Uncommon
Description:Win this turn. Smoky Lounge is unlocked, and you start the puzzle with its ability on the stack. (Misty Salon is locked.) Ensure your solution satisfies all possible blocking decisions. Good luck!
[state]
turn=1
activeplayer=p0
activephase=DRAW
activephaseadvance=MAIN1
p0life=20
p0hand=Painter's Studio // Defaced Gallery;Song of Totentanz;Ghostly Dancers;Dollmaker's Shop // Porcelain Gallery
p0battlefield=Marina Vendrell;T:everywhere;T:everywhere;T:everywhere;T:everywhere;T:everywhere;T:everywhere;Smoky Lounge // Misty Salon|UnlockedRoom:LeftSplit
p1life=12
p1battlefield=Miasma Demon;Miasma Demon

View File

@@ -68,14 +68,23 @@
<file src="forge.sh" targetdir="$INSTALL_PATH/" override="true">
<additionaldata key="permission.file" value="775"/>
</file>
<file src="forge.command" targetdir="$INSTALL_PATH/" override="true">
<additionaldata key="permission.file" value="775"/>
</file>
<file src="forge-adventure.sh" targetdir="$INSTALL_PATH/" override="true">
<additionaldata key="permission.file" value="775"/>
</file>
<file src="forge-adventure.command" targetdir="$INSTALL_PATH/" override="true">
<additionaldata key="permission.file" value="775"/>
</file>
<file src="adventure-editor.sh" targetdir="$INSTALL_PATH/" override="true">
<additionaldata key="permission.file" value="775"/>
</file>
<file src="adventure-editor.command" targetdir="$INSTALL_PATH/" override="true">
<additionaldata key="permission.file" value="775"/>
</file>
<executable stage="never" failure="ignore" keep="true">
<fileset targetdir="$INSTALL_PATH/" includes="forge.sh,forge-adventure.sh,adventure-editor.sh"/>
<fileset targetdir="$INSTALL_PATH/" includes="forge.sh,forge.command,forge-adventure.sh,forge-adventure.command,adventure-editor.sh,adventure-editor.command"/>
</executable>
</pack>
</packs>

View File

@@ -206,13 +206,16 @@
<chmod file="${project.build.directory}/${project.build.finalName}/forge-adventure.exe" perm="a+rx" />
<chmod file="${project.build.directory}/${project.build.finalName}/adventure-editor.exe" perm="a+rx" />
<copy todir="${basedir}/target">
<fileset dir="${project.build.directory}/../../forge-gui-desktop/target" includes="forge.sh" />
<fileset dir="${project.build.directory}/../../forge-gui-mobile-dev/target" includes="forge-adventure.sh" />
<fileset dir="${project.build.directory}/../../adventure-editor/target" includes="adventure-editor.sh" />
<fileset dir="${project.build.directory}/../../forge-gui-desktop/target" includes="forge.sh,forge.command" />
<fileset dir="${project.build.directory}/../../forge-gui-mobile-dev/target" includes="forge-adventure.sh,forge-adventure.command" />
<fileset dir="${project.build.directory}/../../adventure-editor/target" includes="adventure-editor.sh,adventure-editor.command" />
</copy>
<chmod file="${basedir}/target/forge.sh" perm="a+rx" />
<chmod file="${basedir}/target/forge.command" perm="a+rx" />
<chmod file="${basedir}/target/forge-adventure.sh" perm="a+rx" />
<chmod file="${basedir}/target/forge-adventure.command" perm="a+rx" />
<chmod file="${basedir}/target/adventure-editor.sh" perm="a+rx" />
<chmod file="${basedir}/target/adventure-editor.command" perm="a+rx" />
<tar destfile="${basedir}/target/${project.build.finalName}.tar.bz2" compression="bzip2">
<tarfileset filemode="755" dir="${project.build.directory}/${project.build.finalName}">
<include name="forge.sh" />