Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/jjayers99/forge-updates i…
Browse files Browse the repository at this point in the history
…nto master-local
  • Loading branch information
jjayers99 committed Jan 4, 2024
2 parents 42231c7 + 70fcbac commit 0d1ca3a
Show file tree
Hide file tree
Showing 20 changed files with 45 additions and 89 deletions.
3 changes: 1 addition & 2 deletions forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
Original file line number Diff line number Diff line change
Expand Up @@ -344,12 +344,11 @@ private static boolean hiddenOriginCanPlayAI(final Player ai, final SpellAbility
Iterable<Player> pDefined = Lists.newArrayList(source.getController());
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && tgt.canTgtPlayer()) {
sa.resetTargets();
boolean isCurse = sa.isCurse();
if (isCurse && sa.canTarget(opponent)) {
sa.resetTargets();
sa.getTargets().add(opponent);
} else if (!isCurse && sa.canTarget(ai)) {
sa.resetTargets();
sa.getTargets().add(ai);
}
if (!sa.isTargetNumberValid()) {
Expand Down
29 changes: 12 additions & 17 deletions forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java
Original file line number Diff line number Diff line change
Expand Up @@ -611,46 +611,41 @@ public void run() {
}
}

protected static boolean addToCombat(Card c, Player controller, SpellAbility sa, String attackingParam, String blockingParam) {
protected static boolean addToCombat(Card c, SpellAbility sa, String attackingParam, String blockingParam) {
final Card host = sa.getHostCard();
final Game game = controller.getGame();
final Game game = host.getGame();
if (!c.isCreature() || !game.getPhaseHandler().inCombat()) {
return false;
}
boolean combatChanged = false;
final Combat combat = game.getCombat();

if (sa.hasParam(attackingParam) && combat.getAttackingPlayer().equals(controller)) {
if (sa.hasParam(attackingParam) && combat.getAttackingPlayer().equals(c.getController())) {
String attacking = sa.getParam(attackingParam);

GameEntity defender = null;
FCollection<GameEntity> defs = null;
// important to update defenders here, maybe some PW got removed
combat.initConstraints();
if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
PlayerCollection defendingPlayers = AbilityUtils.getDefinedPlayers(sa.hasParam("ForEach") ? c : host, attacking, sa);
defs = new FCollection<>(defendingPlayers);
defs.addAll(Iterables.filter(combat.getDefendingPlaneswalkers(), CardPredicates.isControlledByAnyOf(defendingPlayers)));
} else if ("True".equalsIgnoreCase(attacking)) {
if ("True".equalsIgnoreCase(attacking)) {
defs = (FCollection<GameEntity>) combat.getDefenders();
} else {
defs = AbilityUtils.getDefinedEntities(host, attacking, sa);
defs = AbilityUtils.getDefinedEntities(sa.hasParam("ForEach") ? c : host, attacking.split(","), sa);
}

if (defs != null) {
Map<String, Object> params = Maps.newHashMap();
params.put("Attacker", c);
Player chooser;
if (sa.hasParam("Chooser")) {
chooser = Iterables.getFirst(AbilityUtils.getDefinedPlayers(host, sa.getParam("Chooser"), sa), null);
} else {
chooser = controller;
}
defender = chooser.getController().chooseSingleEntityForEffect(defs, sa,
defender = sa.getActivatingPlayer().getController().chooseSingleEntityForEffect(defs, sa,
Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(c.getName())), false, params);
}

if (defender != null) {
final GameEntity originalDefender = combat.getDefenderByAttacker(c);
if (defender != null &&
(originalDefender == null || !originalDefender.equals(defender))) {
// we might be reselecting
combat.removeFromCombat(c);

combat.addAttacker(c, defender);
combat.getBandOfAttacker(c).setBlocked(false);
combatChanged = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
package forge.game.ability.effects;

import java.util.Map;

import com.google.common.collect.Maps;

import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.combat.AttackingBand;
import forge.game.combat.Combat;
import forge.game.event.GameEventCombatChanged;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
import forge.util.collect.FCollection;

public class ChangeCombatantsEffect extends SpellAbilityEffect {

Expand All @@ -36,33 +29,24 @@ protected String getStackDescription(SpellAbility sa) {
@Override
public void resolve(SpellAbility sa) {
boolean isCombatChanged = false;
final boolean isOptional = sa.hasParam("Optional");
final Player activator = sa.getActivatingPlayer();
final Game game = activator.getGame();

// TODO: may expand this effect for defined blocker (False Orders, General Jarkeld, Sorrow's Path, Ydwen Efreet)
for (final Card c : getTargetCards(sa)) {
String cardString = CardTranslation.getTranslatedName(c.getName()) + " (" + c.getId() + ")";
boolean isOptional = sa.hasParam("Optional");
if (isOptional && !activator.getController().confirmAction(sa, null,
Localizer.getInstance().getMessage("lblChangeCombatantOption", cardString), null)) {
continue;
}

final Combat combat = game.getCombat();
final GameEntity originalDefender = combat.getDefenderByAttacker(c);
final FCollection<GameEntity> defs = new FCollection<>(sa.hasParam("PlayerOnly") ? combat.getDefendingPlayers() : combat.getDefenders());
final GameEntity originalDefender = game.getCombat().getDefenderByAttacker(c);

String title = Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", cardString);
Map<String, Object> params = Maps.newHashMap();
params.put("Attacker", c);
if (addToCombat(c, sa, "Attacking", "Blocking")) {
isCombatChanged = true;
GameEntity defender = game.getCombat().getDefenderByAttacker(c);

final GameEntity defender = activator.getController().chooseSingleEntityForEffect(defs, sa, title, false, params);
if (originalDefender != null && !originalDefender.equals(defender)) {
AttackingBand ab = combat.getBandOfAttacker(c);
if (ab != null) {
combat.unregisterAttacker(c, ab);
ab.removeAttacker(c);
}
combat.addAttacker(c, defender);
// retarget triggers to the new defender (e.g. Ulamog, Ceaseless Hunger + Portal Mage)
for (SpellAbilityStackInstance si : game.getStack()) {
if (si.isTrigger() && c.equals(si.getSourceCard())
Expand All @@ -75,7 +59,6 @@ public void resolve(SpellAbility sa) {
}
}
}
isCombatChanged = true;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ private void changeKnownOriginResolve(final SpellAbility sa) {
if (sa.hasParam("LeaveBattlefield")) {
addLeaveBattlefieldReplacement(movedCard, sa, sa.getParam("LeaveBattlefield"));
}
if (addToCombat(movedCard, movedCard.getController(), sa, "Attacking", "Blocking")) {
if (addToCombat(movedCard, sa, "Attacking", "Blocking")) {
combatChanged = true;
}
if (sa.isNinjutsu()) {
Expand Down Expand Up @@ -1369,7 +1369,7 @@ else if (c.isAura()) { // When it should enter the battlefield attached to an il
}
}

if (addToCombat(c, c.getController(), sa, "Attacking", "Blocking")) {
if (addToCombat(c, sa, "Attacking", "Blocking")) {
combatChanged = true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.event.GameEventCombatChanged;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
Expand Down Expand Up @@ -149,7 +148,6 @@ public void resolve(SpellAbility sa) {
return;
}

boolean combatChanged = false;
CardCollection untapped = new CardCollection();
for (Card tgtC : tgtCards) {
if (!tgtC.isInPlay() || !tgtC.canBeControlledBy(newController)) {
Expand All @@ -176,25 +174,10 @@ public void resolve(SpellAbility sa) {
if (tgtC.untap(true)) untapped.add(tgtC);
}

final List<String> kws = Lists.newArrayList();
final List<String> hiddenKws = Lists.newArrayList();
if (null != keywords) {
for (final String kw : keywords) {
if (kw.startsWith("HIDDEN")) {
hiddenKws.add(kw.substring(7));
} else {
kws.add(kw);
}
}
}

if (!kws.isEmpty()) {
tgtC.addChangedCardKeywords(kws, Lists.newArrayList(), false, tStamp, 0);
if (keywords != null) {
tgtC.addChangedCardKeywords(keywords, Lists.newArrayList(), false, tStamp, 0);
game.fireEvent(new GameEventCardStatsChanged(tgtC));
}
if (!hiddenKws.isEmpty()) {
tgtC.addHiddenExtrinsicKeywords(tStamp, 0, hiddenKws);
}

if (remember && !source.isRemembered(tgtC)) {
source.addRemembered(tgtC);
Expand Down Expand Up @@ -244,31 +227,22 @@ public void resolve(SpellAbility sa) {

@Override
public void run() {
tgtC.removeHiddenExtrinsicKeywords(tStamp, 0);
tgtC.removeChangedCardKeywords(tStamp, 0);
}
};
game.getEndOfTurn().addUntil(untilKeywordEOT);
}

game.getAction().controllerChangeZoneCorrection(tgtC);

if (addToCombat(tgtC, tgtC.getController(), sa, "Attacking", "Blocking")) {
combatChanged = true;
}
} // end foreach target

if (!untapped.isEmpty()) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
final Map<Player, CardCollection> map = Maps.newHashMap();
map.put(activator, untapped);
runParams.put(AbilityKey.Map, map);
game.getTriggerHandler().runTrigger(TriggerType.UntapAll, runParams, false);
}

if (combatChanged) {
game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged());
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ else if (!sa.hasParam("NoLooking")) {
}
c = game.getAction().moveTo(c.getController().getZone(destZone1), c, sa, moveParams);
if (destZone1.equals(ZoneType.Battlefield)) {
if (addToCombat(c, c.getController(), sa, "Attacking", "Blocking")) {
if (addToCombat(c, sa, "Attacking", "Blocking")) {
combatChanged = true;
}
} else if (destZone1.equals(ZoneType.Exile)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ public void resolve(SpellAbility sa) {
c.setTapped(true);
}
m = game.getAction().moveTo(c.getController().getZone(foundDest), c, sa, moveParams);
if (addToCombat(c, c.getController(), sa, "Attacking", "Blocking")) {
if (addToCombat(c, sa, "Attacking", "Blocking")) {
combatChanged = true;
}
} else if (sa.hasParam("NoMoveFound")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public void resolve(SpellAbility sa) {
PlayerZoneBattlefield bf = (PlayerZoneBattlefield)controller.getZone(ZoneType.Battlefield);
bf.addToMelded(secondary);
Card movedCard = game.getAction().changeZone(primary.getZone(), bf, primary, 0, sa);
if (addToCombat(movedCard, movedCard.getController(), sa, "Attacking", "Blocking")) {
if (addToCombat(movedCard, sa, "Attacking", "Blocking")) {
game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ protected TokenCreateTable makeTokenTable(TokenCreateTable tokenTable, final boo
addSelfTrigger(sa, sa.getParam("AtEOTTrig"), moved);
}

if (addToCombat(moved, tok.getController(), sa, "TokenAttacking", "TokenBlocking")) {
if (addToCombat(moved, sa, "TokenAttacking", "TokenBlocking")) {
combatChanged.setTrue();
}

Expand Down
4 changes: 2 additions & 2 deletions forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -1566,8 +1566,8 @@ public static void addTriggerAbility(final KeywordInterface inst, final Card car
final String actualTrigger = "Mode$ Attacks | ValidCard$ Card.Self | Secondary$ True"
+ " | TriggerDescription$ Myriad (" + inst.getReminderText() + ")";

final String copyStr = "DB$ CopyPermanent | Defined$ Self | TokenTapped$ True | Optional$ True | TokenAttacking$ Remembered"
+ "| ForEach$ OppNonDefendingPlayer | ChoosePlayerOrPlaneswalker$ True | AtEOT$ ExileCombat | CleanupForEach$ True";
final String copyStr = "DB$ CopyPermanent | Defined$ Self | TokenTapped$ True | Optional$ True | TokenAttacking$ Player.IsRemembered,Valid Planeswalker.ControlledBy Remembered"
+ "| ForEach$ OppNonDefendingPlayer | AtEOT$ ExileCombat | CleanupForEach$ True";

final SpellAbility copySA = AbilityFactory.getAbility(copyStr, card);
copySA.setIntrinsic(intrinsic);
Expand Down
4 changes: 4 additions & 0 deletions forge-game/src/main/java/forge/game/card/CardProperty.java
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ public static boolean cardHasProperty(Card card, String property, Player sourceC
if (!lp.contains(card.getProtectingPlayer())) {
return false;
}
} else if (property.equals("Defending")) {
if (game.getCombat() == null || !game.getCombat().getAttackersAndDefenders().values().contains(card)) {
return false;
}
} else if (property.startsWith("DefendingPlayer")) {
Player p = property.endsWith("Ctrl") ? controller : card.getOwner();
if (!game.getPhaseHandler().inCombat()) {
Expand Down
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/a/adeline_resplendent_cathar.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$
SVar:X:Count$Valid Creature.YouCtrl
T:Mode$ AttackersDeclared | AttackingPlayer$ You | Execute$ DBRepeat | TriggerZones$ Battlefield | TriggerDescription$ Whenever you attack, for each opponent, create a 1/1 white Human creature token that's tapped and attacking that player or a planeswalker they control.
SVar:DBRepeat:DB$ RepeatEach | RepeatPlayers$ Opponent | ChangeZoneTable$ True | RepeatSubAbility$ DBToken
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_human | TokenTapped$ True | TokenAttacking$ Remembered | ChoosePlayerOrPlaneswalker$ True | TokenOwner$ You
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_human | TokenTapped$ True | TokenAttacking$ Player.IsRemembered,Valid Planeswalker.ControlledBy Remembered | TokenOwner$ You
DeckHas:Ability$Token
Oracle:Vigilance\nAdeline, Resplendent Cathar's power is equal to the number of creatures you control.\nWhenever you attack, for each opponent, create a 1/1 white Human creature token that's tapped and attacking that player or a planeswalker they control.
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/c/capricopian.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ PT:0/0
K:etbCounter:P1P1:X
SVar:X:Count$xPaid
A:AB$ PutCounter | Cost$ 2 | Activator$ Player | IsPresent$ Card.Self+attackingYou | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBReselect | ActivationPhases$ Declare Attackers | AILogic$ AlwaysWithNoTgt | SpellDescription$ Put a +1/+1 counter on CARDNAME, then you may reselect which player CARDNAME is attacking. Only the player CARDNAME is attacking may activate this ability and only during the declare attackers step. (It can't attack its controller.)
SVar:DBReselect:DB$ ChangeCombatants | Defined$ Self | AILogic$ WeakestOppExceptCtrl | PlayerOnly$ True
SVar:DBReselect:DB$ ChangeCombatants | Defined$ Self | AILogic$ WeakestOppExceptCtrl | Attacking$ Player.OpponentOf CardController
DeckHas:Ability$Counters
Oracle:Capricopian enters the battlefield with X +1/+1 counters on it.\n{2}: Put a +1/+1 counter on Capricopian, then you may reselect which player Capricopian is attacking. Only the player Capricopian is attacking may activate this ability and only during the declare attackers step. (It can't attack its controller.)
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/h/hans_eriksson.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ManaCost:2 R G
Types:Legendary Creature Human Scout
PT:1/4
T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigDig | TriggerDescription$ Whenever CARDNAME attacks, reveal the top card of your library. If it's a creature card, put it onto the battlefield tapped and attacking defending player or a planeswalker they control. Otherwise, put that card into your hand. When you put a creature card onto the battlefield this way, it fights CARDNAME.
SVar:TrigDig:DB$ Dig | DigNum$ 1 | ChangeNum$ All | Optional$ True | Reveal$ True | ChangeValid$ Creature | DestinationZone$ Battlefield | DestinationZone2$ Hand | Tapped$ True | Attacking$ DefendingPlayer | ChoosePlayerOrPlaneswalker$ True | RememberChanged$ True | SubAbility$ DBImmediateTriggerCheck
SVar:TrigDig:DB$ Dig | DigNum$ 1 | ChangeNum$ All | Optional$ True | Reveal$ True | ChangeValid$ Creature | DestinationZone$ Battlefield | DestinationZone2$ Hand | Tapped$ True | Attacking$ DefendingPlayer,Valid Planeswalker.ControlledBy DefendingPlayer | RememberChanged$ True | SubAbility$ DBImmediateTriggerCheck
SVar:DBImmediateTriggerCheck:DB$ ImmediateTrigger | Execute$ DBFight | ConditionDefined$ Remembered | ConditionPresent$ Creature | ConditionCompare$ GE1 | TriggerDescription$ When you put a creature card onto the battlefield this way, it fights Hans Eriksson.
SVar:DBFight:DB$ Fight | Defined$ Remembered | ExtraDefined$ Self | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Colors:white,red,green
Types:Enchantment Saga
K:Chapter:4:DBToken,DBMana,DBSearch,DBPumpAll
SVar:DBToken:DB$ Token | TokenOwner$ You | TokenScript$ g_3_3_dinosaur | TokenAmount$ 2 | SpellDescription$ Create two 3/3 green Dinosaur creature tokens.
SVar:DBMana:DB$ Animate | Defined$ Self | staticAbilities$ Static | SpellDescription$ CARDNAME gains "Creatures you control have '{T}: Add {R}, {G}, or {W}.'"
SVar:DBMana:DB$ Animate | Defined$ Self | staticAbilities$ Static | Duration$ Permanenent | SpellDescription$ CARDNAME gains "Creatures you control have '{T}: Add {R}, {G}, or {W}.'"
SVar:Static:Mode$ Continuous | EffectZone$ Battlefield | Affected$ Creature.YouCtrl | AddAbility$ Mana | Description$ Creatures you control have '{T}: Add {R}, {G}, or {W}.
SVar:Mana:AB$ Mana | Cost$ T | Produced$ Combo R G W | Amount$ 1 | SpellDescription$ Add {R}, {G}, or {W}.
SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card.Dinosaur | ChangeNum$ 1 | SpellDescription$ Search your library for a Dinosaur card, reveal it, put it into your hand, then shuffle.
Expand Down
Loading

0 comments on commit 0d1ca3a

Please sign in to comment.