From d92cacae7f56034dc78fba73fe1473939c24e82e Mon Sep 17 00:00:00 2001 From: Agetian Date: Thu, 24 Oct 2024 12:31:23 +0300 Subject: [PATCH 1/3] Various improvements to AI-related logic (Canopy lands, Ugin's Labyrinth, Zuran Orb, Pyrite Spellbomb, Eladamri) (#6418) * - Improve AI for Canopy lands. - Improve AI for Ugin's Labyrinth. * - Pyrite Spellbomb: also don't sac immediately. * - Improve AI for Eladamri, Korvecdal * - Improve AI decision for Zuran Orb. * - Cleaner implementation for Eladamri AI - More generic implementation for SacToDraw AI that doesn't need a logic parameter * - Fix imports * - Cleaner implementation for Eladamri AI * - Suggested code tweak --- .../java/forge/ai/ability/ChangeZoneAi.java | 3 ++ .../java/forge/ai/ability/ChooseCardAi.java | 38 ++++++++++--------- .../main/java/forge/ai/ability/DrawAi.java | 7 ++++ .../java/forge/ai/ability/LifeGainAi.java | 3 ++ .../res/cardsfolder/e/eladamri_korvecdal.txt | 2 +- .../res/cardsfolder/u/ugins_labyrinth.txt | 2 +- forge-gui/res/cardsfolder/z/zuran_orb.txt | 2 +- 7 files changed, 36 insertions(+), 21 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index 22b754f3dc3..83bbd6b2cc7 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -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()) { diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java index 10c50ac98e2..721c183f635 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java @@ -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 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; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java index 453f45ac6f5..9de4d7302fd 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -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; } diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java index 1185df31edd..0b510d251e8 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java @@ -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; } diff --git a/forge-gui/res/cardsfolder/e/eladamri_korvecdal.txt b/forge-gui/res/cardsfolder/e/eladamri_korvecdal.txt index 2138799ecb0..fee1da6d5c8 100644 --- a/forge-gui/res/cardsfolder/e/eladamri_korvecdal.txt +++ b/forge-gui/res/cardsfolder/e/eladamri_korvecdal.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/u/ugins_labyrinth.txt b/forge-gui/res/cardsfolder/u/ugins_labyrinth.txt index 9af78cf952d..155efa7b80a 100644 --- a/forge-gui/res/cardsfolder/u/ugins_labyrinth.txt +++ b/forge-gui/res/cardsfolder/u/ugins_labyrinth.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/z/zuran_orb.txt b/forge-gui/res/cardsfolder/z/zuran_orb.txt index 9be1520a4cf..8d40776dbc4 100644 --- a/forge-gui/res/cardsfolder/z/zuran_orb.txt +++ b/forge-gui/res/cardsfolder/z/zuran_orb.txt @@ -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. From a6ccbe0f1d594b3db947b6983cda6dab1c2a4afc Mon Sep 17 00:00:00 2001 From: Agetian Date: Thu, 24 Oct 2024 20:01:43 +0300 Subject: [PATCH 2/3] - Add GameState support for unlocked doors. (#6426) - Add puzzle PS_DSK3. --- forge-ai/src/main/java/forge/ai/GameState.java | 9 +++++++++ forge-gui/res/puzzle/PS_DSK3.pzl | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 forge-gui/res/puzzle/PS_DSK3.pzl diff --git a/forge-ai/src/main/java/forge/ai/GameState.java b/forge-ai/src/main/java/forge/ai/GameState.java index a0afe54be58..4ee9eae9879 100644 --- a/forge-ai/src/main/java/forge/ai/GameState.java +++ b/forge-ai/src/main/java/forge/ai/GameState.java @@ -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))); } } diff --git a/forge-gui/res/puzzle/PS_DSK3.pzl b/forge-gui/res/puzzle/PS_DSK3.pzl new file mode 100644 index 00000000000..75f977e0c5c --- /dev/null +++ b/forge-gui/res/puzzle/PS_DSK3.pzl @@ -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 From 2585ad1cf67cf4a0993c8c203ad3e3a97e94b24c Mon Sep 17 00:00:00 2001 From: wcoldren Date: Thu, 24 Oct 2024 11:13:46 -0400 Subject: [PATCH 3/3] fix: add executable permissions for .command files during installation - Update install.xml to set 775 permissions for .command files in Script pack - Add .command files to target directory copy tasks in pom.xml - Allow macOS users to run Forge apps without manual chmod This change ensures .command files receive proper executable permissions during installation, matching the behavior of .sh files on Unix/Linux. --- forge-installer/libs/install.xml | 11 ++++++++++- forge-installer/pom.xml | 9 ++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/forge-installer/libs/install.xml b/forge-installer/libs/install.xml index e1aa18b1312..175b8d0e17f 100644 --- a/forge-installer/libs/install.xml +++ b/forge-installer/libs/install.xml @@ -68,14 +68,23 @@ + + + + + + + + + - + diff --git a/forge-installer/pom.xml b/forge-installer/pom.xml index 40604fff99b..f81742660c8 100644 --- a/forge-installer/pom.xml +++ b/forge-installer/pom.xml @@ -206,13 +206,16 @@ - - - + + + + + +