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()); cardTexts.put(zoneType, newText.toString());
} }
@@ -1401,6 +1408,8 @@ public abstract class GameState {
c.setGamePieceType(GamePieceType.TOKEN); c.setGamePieceType(GamePieceType.TOKEN);
} else if (info.startsWith("ClassLevel:")) { } else if (info.startsWith("ClassLevel:")) {
c.setClassLevel(Integer.parseInt(info.substring(info.indexOf(':') + 1))); 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); return SpecialCardAi.MazesEnd.consider(aiPlayer, sa);
} else if (aiLogic.equals("Pongify")) { } else if (aiLogic.equals("Pongify")) {
return sa.isTargetNumberValid(); // Pre-targeted in checkAiLogic 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()) { if (sa.isHidden()) {

View File

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

View File

@@ -81,6 +81,13 @@ public class DrawAi extends SpellAbilityAi {
if (!canLoot(ai, sa)) { if (!canLoot(ai, sa)) {
return false; 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; return true;
} }

View File

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

View File

@@ -4,7 +4,7 @@ Types:Legendary Creature Elf Warrior
PT:3/3 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$ 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. 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:DBChangeZone:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Library,Hand | ConditionDefined$ ChosenCard | ConditionPresent$ Creature | Destination$ Battlefield | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True 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. 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. 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 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$ 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:X:Count$Compare Y GE1.2.1
SVar:Y:Count$ValidExile Card.ExiledWithSource 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. 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 Name:Zuran Orb
ManaCost:0 ManaCost:0
Types:Artifact 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 SVar:NonStackingEffect:True
Oracle:Sacrifice a land: You gain 2 life. 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"> <file src="forge.sh" targetdir="$INSTALL_PATH/" override="true">
<additionaldata key="permission.file" value="775"/> <additionaldata key="permission.file" value="775"/>
</file> </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"> <file src="forge-adventure.sh" targetdir="$INSTALL_PATH/" override="true">
<additionaldata key="permission.file" value="775"/> <additionaldata key="permission.file" value="775"/>
</file> </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"> <file src="adventure-editor.sh" targetdir="$INSTALL_PATH/" override="true">
<additionaldata key="permission.file" value="775"/> <additionaldata key="permission.file" value="775"/>
</file> </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"> <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> </executable>
</pack> </pack>
</packs> </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}/forge-adventure.exe" perm="a+rx" />
<chmod file="${project.build.directory}/${project.build.finalName}/adventure-editor.exe" perm="a+rx" /> <chmod file="${project.build.directory}/${project.build.finalName}/adventure-editor.exe" perm="a+rx" />
<copy todir="${basedir}/target"> <copy todir="${basedir}/target">
<fileset dir="${project.build.directory}/../../forge-gui-desktop/target" includes="forge.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" /> <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" /> <fileset dir="${project.build.directory}/../../adventure-editor/target" includes="adventure-editor.sh,adventure-editor.command" />
</copy> </copy>
<chmod file="${basedir}/target/forge.sh" perm="a+rx" /> <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.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.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"> <tar destfile="${basedir}/target/${project.build.finalName}.tar.bz2" compression="bzip2">
<tarfileset filemode="755" dir="${project.build.directory}/${project.build.finalName}"> <tarfileset filemode="755" dir="${project.build.directory}/${project.build.finalName}">
<include name="forge.sh" /> <include name="forge.sh" />