Skip to content

Commit

Permalink
Merge branch 'master' into master2
Browse files Browse the repository at this point in the history
  • Loading branch information
kevlahnota committed Oct 29, 2024
2 parents 57ccf5d + de4b35c commit 12407a6
Show file tree
Hide file tree
Showing 15 changed files with 61 additions and 59 deletions.
14 changes: 6 additions & 8 deletions forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ protected boolean canPlayAI(Player ai, SpellAbility sa) {
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(source, damage, sa);

if (damage.equals("X") || source.getSVar("X").equals("Count$xPaid") || sourceName.equals("Crater's Claws")) {
if (sa.getSVar("X").equals("Count$xPaid") || sa.getSVar(damage).equals("Count$xPaid") || sourceName.equals("Crater's Claws")) {
if (damage.equals("X") || source.getSVar("X").equals("Count$xPaid")) {
if (sa.getSVar("X").equals("Count$xPaid") || sa.getSVar(damage).equals("Count$xPaid")) {
dmg = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());

// Try not to waste spells like Blaze or Fireball on early targets, try to do more damage with them if possible
Expand All @@ -106,7 +106,7 @@ protected boolean canPlayAI(Player ai, SpellAbility sa) {
if (MyRandom.percentTrue(holdChance)) {
int threshold = aic.getIntProperty(AiProps.HOLD_X_DAMAGE_SPELLS_THRESHOLD);
boolean inDanger = ComputerUtil.aiLifeInDanger(ai, false, 0);
boolean isLethal = sa.getTargetRestrictions().canTgtPlayer() && dmg >= ai.getWeakestOpponent().getLife() && !ai.getWeakestOpponent().cantLoseForZeroOrLessLife();
boolean isLethal = sa.usesTargeting() && sa.getTargetRestrictions().canTgtPlayer() && dmg >= ai.getWeakestOpponent().getLife() && !ai.getWeakestOpponent().cantLoseForZeroOrLessLife();
if (dmg < threshold && ai.getGame().getPhaseHandler().getTurn() / 2 < threshold && !inDanger && !isLethal) {
return false;
}
Expand All @@ -116,7 +116,7 @@ protected boolean canPlayAI(Player ai, SpellAbility sa) {
// Set PayX here to maximum value. It will be adjusted later depending on the target.
sa.setXManaCostPaid(dmg);
} else if (sa.getSVar(damage).contains("InYourHand") && source.isInZone(ZoneType.Hand)) {
dmg = AbilityUtils.calculateAmount(source, damage, sa) - 1; // the card will be spent casting the spell, so actual damage is 1 less
dmg -= - 1; // the card will be spent casting the spell, so actual damage is 1 less
} else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) {
// cards that deal damage by the number of cards in target player's hand, e.g. Sudden Impact
if (sa.getTargetRestrictions().canTgtPlayer()) {
Expand Down Expand Up @@ -260,11 +260,9 @@ protected boolean canPlayAI(Player ai, SpellAbility sa) {
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
aic.reserveManaSourcesForNextSpell(chainDmg.getKey(), sa);
}
} else {
} else if (!damageTargetAI(ai, sa, dmg, false)) {
// simple targeting when there is no spell chaining plan
if (!damageTargetAI(ai, sa, dmg, false)) {
return false;
}
return false;
}

if ((damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) ||
Expand Down
6 changes: 5 additions & 1 deletion forge-core/src/main/java/forge/card/DeckHints.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,11 @@ private Iterable<PaperCard> getCardsForFilter(Iterable<PaperCard> cardList, Type
Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.name(StringOp.EQUALS, p), PaperCard::getRules));
break;
case TYPE:
Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.joinedType(StringOp.CONTAINS_IC, p), PaperCard::getRules));
Predicate<CardRules> typePred = CardRulesPredicates.joinedType(StringOp.CONTAINS_IC, p);
if (CardType.isACreatureType(p)) {
typePred = Predicates.or(CardRulesPredicates.hasKeyword("Changeling"), typePred);
}
Iterables.addAll(cards, getMatchingItems(cardList, typePred, PaperCard::getRules));
break;
case NONE:
case ABILITY: // already done above
Expand Down
56 changes: 26 additions & 30 deletions forge-game/src/main/java/forge/game/GameAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,6 @@ private Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer

copied = new CardCopyService(c).copyCard(false);

// CR 707.12 casting of a card copy
if (zoneTo.is(ZoneType.Stack) && c.isRealToken()) {
copied.setCopiedPermanent(c.getCopiedPermanent());
//TODO: Feels like this should fit here and seems to work but it'll take a fair bit more testing to be sure.
//copied.setGamePieceType(GamePieceType.COPIED_SPELL);
}

copied.setGameTimestamp(c.getGameTimestamp());

if (zoneTo.is(ZoneType.Stack)) {
Expand All @@ -267,6 +260,13 @@ private Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer
copied.setExiledBy(c.getExiledBy());
copied.setDrawnThisTurn(c.getDrawnThisTurn());

// CR 707.12 casting of a card copy
if (c.isRealToken()) {
copied.setCopiedPermanent(c.getCopiedPermanent());
//TODO: Feels like this should fit here and seems to work but it'll take a fair bit more testing to be sure.
//copied.setGamePieceType(GamePieceType.COPIED_SPELL);
}

if (c.isTransformed()) {
copied.incrementTransformedTimestamp();
}
Expand All @@ -277,6 +277,7 @@ private Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer

// CR 112.2 A spell’s controller is, by default, the player who put it on the stack.
copied.setController(cause.getActivatingPlayer(), 0);

KeywordInterface kw = cause.getKeyword();
if (kw != null) {
copied.addKeywordForStaticAbility(kw);
Expand All @@ -290,25 +291,24 @@ private Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer
copied.setBackSide(false);
}

copied.setUnearthed(c.isUnearthed());

// 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.") &&
!(zoneTo.is(ZoneType.Hand) || zoneTo.is(ZoneType.Library))) {
copied.setCounters(Maps.newHashMap(lastKnownInfo.getCounters()));
}
}

// perpetual stuff
if (c.hasIntensity()) {
copied.setIntensity(c.getIntensity(false));
}
if (c.isSpecialized()) {
copied.setState(c.getCurrentStateName(), false);
}
if (c.hasPerpetual()) {
copied.setPerpetual(c);
// perpetual stuff
if (c.hasIntensity()) {
copied.setIntensity(c.getIntensity(false));
}
if (c.isSpecialized()) {
copied.setState(c.getCurrentStateName(), false);
}
if (c.hasPerpetual()) {
copied.setPerpetual(c);
}
}

// ensure that any leftover keyword/type changes are cleared in the state view
copied.updateStateForView();

Expand Down Expand Up @@ -780,8 +780,6 @@ private Card moveTo(final Zone zoneTo, Card c, Integer position, SpellAbility ca
final Zone zoneFrom = game.getZoneOf(c);
// String prevName = prev != null ? prev.getZoneName() : "";

// Card lastKnownInfo = c;

// Handle the case that one component of a merged permanent got take to the subgame
if (zoneTo.is(ZoneType.Subgame) && (c.hasMergedCard() || c.isMerged())) {
c.moveMergedToSubgame(cause);
Expand Down Expand Up @@ -913,7 +911,7 @@ public final Card moveToVariantDeck(Card c, ZoneType zone, int deckPosition, Spe
}
return changeZone(game.getZoneOf(c), deck, c, deckPosition, cause, params);
}

public final Card moveToJunkyard(Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
final PlayerZone junkyard = c.getOwner().getZone(ZoneType.Junkyard);
return moveTo(junkyard, c, cause, params);
Expand All @@ -931,7 +929,6 @@ public final Card exile(final Card c, SpellAbility cause, Map<AbilityKey, Object
final PlayerZone removed = c.getOwner().getZone(ZoneType.Exile);
final Card copied = moveTo(removed, c, cause, params);

// Run triggers
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c);
runParams.put(AbilityKey.Cause, cause);
if (origin != null) { // is generally null when adding via dev mode
Expand Down Expand Up @@ -1264,18 +1261,19 @@ public boolean checkStateEffects(final boolean runEvents, final Set<Card> affect
AbilityKey.addCardZoneTableParams(mapParams, table);

for (final Player p : game.getPlayers()) {
for (final ZoneType zt : ZoneType.values()) {
if (zt == ZoneType.Command)
p.checkKeywordCard();
p.checkKeywordCard();

for (final ZoneType zt : ZoneType.values()) {
if (zt == ZoneType.Battlefield) {
continue;
}
for (final Card c : p.getCardsIn(zt).threadSafeIterable()) {
checkAgain |= stateBasedAction704_5d(c);
// Dungeon Card won't affect other cards, so don't need to set checkAgain
stateBasedAction_Dungeon(c);
stateBasedAction_Scheme(c);
if (zt == ZoneType.Command) {
stateBasedAction_Scheme(c);
}
}
}
}
Expand Down Expand Up @@ -1557,7 +1555,7 @@ private void stateBasedAction_Scheme(Card c) {
return;
}
if (!game.getStack().hasSourceOnStack(c, null)) {
moveTo(ZoneType.SchemeDeck, c, null, AbilityKey.newMap());
moveTo(ZoneType.SchemeDeck, c, -1, null, AbilityKey.newMap());
}
}

Expand Down Expand Up @@ -1676,7 +1674,6 @@ public void checkGameOverCondition() {
FCollectionView<Player> allPlayers = game.getPlayers();
for (Player p : allPlayers) {
if (p.checkLoseCondition()) { // this will set appropriate outcomes
// Run triggers
if (losers == null) {
losers = Lists.newArrayListWithCapacity(3);
}
Expand Down Expand Up @@ -1898,7 +1895,6 @@ public final CardCollection sacrifice(final Iterable<Card> list, final SpellAbil
}
}
for (Map.Entry<Player, Collection<Card>> e : lki.asMap().entrySet()) {
// Run triggers
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(e.getKey());
runParams.put(AbilityKey.Cards, new CardCollection(e.getValue()));
runParams.put(AbilityKey.Cause, source);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,6 @@ else if (!sa.hasParam("NoLooking")) {
}
}


for (Card c : movedCards) {
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
AbilityKey.addCardZoneTableParams(moveParams, zoneMovements);
Expand Down
10 changes: 7 additions & 3 deletions forge-game/src/main/java/forge/game/phase/Untap.java
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,10 @@ private static boolean optionalUntap(final Card c) {
}

public static void doPhasing(final Player turn) {
Game game = turn.getGame();

// Needs to include phased out cards
final List<Card> list = CardLists.filter(turn.getGame().getCardsIncludePhasingIn(ZoneType.Battlefield),
final List<Card> list = CardLists.filter(game.getCardsIncludePhasingIn(ZoneType.Battlefield),
c -> (c.isPhasedOut(turn) && c.isDirectlyPhasedOut())
|| (c.hasKeyword(Keyword.PHASING) && c.getController().equals(turn))
);
Expand Down Expand Up @@ -299,11 +301,13 @@ public static void doPhasing(final Player turn) {
if (!phasedOut.isEmpty()) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Cards, phasedOut);
turn.getGame().getTriggerHandler().runTrigger(TriggerType.PhaseOutAll, runParams, false);
game.getTriggerHandler().runTrigger(TriggerType.PhaseOutAll, runParams, false);
}
if (!toPhase.isEmpty()) {
// refresh statics for phased in permanents (e.g. so King of the Oathbreakers sees Changeling)
game.getAction().checkStaticAbilities();
// collect now before some zone change during Untap resets triggers
turn.getGame().getTriggerHandler().collectTriggerForWaiting();
game.getTriggerHandler().collectTriggerForWaiting();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -694,8 +694,7 @@ public void runReplaceDamage(final boolean isCombat, final CardDamageMap damageM

// Determine if need to divide shield among affected entity and
// determine if the prevent next N damage shield is large enough to replace all damage
Map<String, String> mapParams = chosenRE.getMapParams();
if ((mapParams.containsKey("PreventionEffect") && mapParams.get("PreventionEffect").equals("NextN"))
if ((chosenRE.hasParam("PreventionEffect") && chosenRE.getParam("PreventionEffect").equals("NextN"))
|| apiType == ApiType.ReplaceSplitDamage) {
if (apiType == ApiType.ReplaceDamage) {
shieldAmount = AbilityUtils.calculateAmount(effectSA.getHostCard(), effectSA.getParamOrDefault("Amount", "1"), effectSA);
Expand Down
4 changes: 3 additions & 1 deletion forge-game/src/main/java/forge/game/zone/MagicStack.java
Original file line number Diff line number Diff line change
Expand Up @@ -625,9 +625,11 @@ public final void resolveStack() {
}

game.fireEvent(new GameEventSpellResolved(sa, thisHasFizzled));
finishResolving(sa, thisHasFizzled);

game.getAction().checkStaticAbilities();

finishResolving(sa, thisHasFizzled);

game.copyLastState();
if (isEmpty() && !hasSimultaneousStackEntries()) {
// assuming that if the stack is empty, no reason to hold on to old LKI data (everything is a new object)
Expand Down
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/a/arcums_weathervane.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ Name:Arcum's Weathervane
ManaCost:2
Types:Artifact
A:AB$ Animate | Cost$ 2 T | ValidTgts$ Land.Snow | TgtPrompt$ Select target snow land | RemoveTypes$ Snow | Duration$ Permanent | SpellDescription$ Target snow land is no longer snow.
A:AB$ Animate | Cost$ 2 T | ValidTgts$ Land.nonSnow | TgtPrompt$ Select target nonsnow land | Types$ Snow | Duration$ Permanent | SpellDescription$ Target nonsnow basic land becomes snow.
A:AB$ Animate | Cost$ 2 T | ValidTgts$ Land.nonSnow+Basic | TgtPrompt$ Select target nonsnow basic land | Types$ Snow | Duration$ Permanent | SpellDescription$ Target nonsnow basic land becomes snow.
AI:RemoveDeck:Random
Oracle:{2}, {T}: Target snow land is no longer snow.\n{2}, {T}: Target nonsnow basic land becomes snow.
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/g/gate_colossus.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Name:Gate Colossus
ManaCost:8
Types:Artifact Creature Construct
PT:8/8
S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ CARDNAME costs {1} less to cast for each Gate you control.
S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ This spell costs {1} less to cast for each Gate you control.
SVar:X:Count$Valid Gate.YouCtrl
S:Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.powerLE2 | Description$ CARDNAME can't be blocked by creatures with power 2 or less.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Gate.YouCtrl | OptionalDecider$ You | TriggerZones$ Graveyard | Execute$ TrigChange | TriggerDescription$ Whenever a Gate you control enters, you may put CARDNAME from your graveyard on top of your library.
Expand Down
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/g/graveyard_shovel.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Name:Graveyard Shovel
ManaCost:2
Types:Artifact
A:AB$ ChangeZone | Cost$ 2 T | ValidTgts$ Player | DefinedPlayer$ Targeted | TgtPrompt$ Select target player | Origin$ Graveyard | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | Hidden$ True | Chooser$ Targeted | Mandatory$ True | SubAbility$ DBGainLife | ForgetOtherTargets$ True | RememberChanged$ True | IsCurse$ True | StackDescription$ Target player exiles a card from their graveyard. If it's a creature card, you gain 2 life. | SpellDescription$ Target player exiles a card from their graveyard. If it's a creature card, you gain 2 life.
A:AB$ ChangeZone | Cost$ 2 T | ValidTgts$ Player | DefinedPlayer$ Targeted | TgtPrompt$ Select target player | Origin$ Graveyard | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | Hidden$ True | Chooser$ Targeted | Mandatory$ True | SubAbility$ DBGainLife | RememberChanged$ True | IsCurse$ True | StackDescription$ SpellDescription | SpellDescription$ Target player exiles a card from their graveyard. If it's a creature card, you gain 2 life.
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 2 | ConditionDefined$ Remembered | ConditionPresent$ Creature | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
Oracle:{2}, {T}: Target player exiles a card from their graveyard. If it's a creature card, you gain 2 life.
6 changes: 3 additions & 3 deletions forge-gui/res/cardsfolder/j/jinnie_fay_jetmirs_second.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ Name:Jinnie Fay, Jetmir's Second
ManaCost:RG G GW
Types:Legendary Creature Elf Druid
PT:3/3
R:Event$ CreateToken | ActiveZones$ Battlefield | ValidToken$ Card.YouCtrl | ReplaceWith$ GenericChoice | Optional$ True | Description$ If you would create one or more tokens, you may instead create that many 2/2 green Cat creature tokens with haste or that many 3/1 green Dog creature tokens with vigilance.
R:Event$ CreateToken | ActiveZones$ Battlefield | ValidPlayer$ You | ReplaceWith$ GenericChoice | Optional$ True | Description$ If you would create one or more tokens, you may instead create that many 2/2 green Cat creature tokens with haste or that many 3/1 green Dog creature tokens with vigilance.
SVar:GenericChoice:DB$ GenericChoice | Choices$ Cat,Dog
SVar:Cat:DB$ ReplaceToken | Type$ ReplaceToken | ValidCard$ Card.YouCtrl | TokenScript$ g_2_2_cat_haste | SpellDescription$ Create that many 2/2 green Cat creature tokens with haste.
SVar:Dog:DB$ ReplaceToken | Type$ ReplaceToken | ValidCard$ Card.YouCtrl | TokenScript$ g_3_1_dog_vigilance | SpellDescription$ Create that many 3/1 green Dog creature tokens with vigilance.
SVar:Cat:DB$ ReplaceToken | Type$ ReplaceToken | TokenScript$ g_2_2_cat_haste | SpellDescription$ Create that many 2/2 green Cat creature tokens with haste.
SVar:Dog:DB$ ReplaceToken | Type$ ReplaceToken | TokenScript$ g_3_1_dog_vigilance | SpellDescription$ Create that many 3/1 green Dog creature tokens with vigilance.
AI:RemoveDeck:Random
DeckHas:Type$Cat|Dog
DeckNeeds:Ability$Token
Expand Down
Loading

0 comments on commit 12407a6

Please sign in to comment.