mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 12:18:00 +00:00
Merge branch 'add_camouflage' into 'master'
Add Camouflage See merge request core-developers/forge!4268
This commit is contained in:
@@ -34,6 +34,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.BidLife, BidLifeAi.class)
|
.put(ApiType.BidLife, BidLifeAi.class)
|
||||||
.put(ApiType.Bond, BondAi.class)
|
.put(ApiType.Bond, BondAi.class)
|
||||||
.put(ApiType.Branch, AlwaysPlayAi.class)
|
.put(ApiType.Branch, AlwaysPlayAi.class)
|
||||||
|
.put(ApiType.Camouflage, ChooseCardAi.class)
|
||||||
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
|
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
|
||||||
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
||||||
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public enum ApiType {
|
|||||||
Block (BlockEffect.class),
|
Block (BlockEffect.class),
|
||||||
Bond (BondEffect.class),
|
Bond (BondEffect.class),
|
||||||
Branch (BranchEffect.class),
|
Branch (BranchEffect.class),
|
||||||
|
Camouflage (CamouflageEffect.class),
|
||||||
ChangeCombatants (ChangeCombatantsEffect.class),
|
ChangeCombatants (ChangeCombatantsEffect.class),
|
||||||
ChangeTargets (ChangeTargetsEffect.class),
|
ChangeTargets (ChangeTargetsEffect.class),
|
||||||
ChangeText (ChangeTextEffect.class),
|
ChangeText (ChangeTextEffect.class),
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package forge.game.ability.effects;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.ability.SpellAbilityEffect;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardCollection;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.combat.Combat;
|
||||||
|
import forge.game.combat.CombatUtil;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.util.Localizer;
|
||||||
|
|
||||||
|
public class CamouflageEffect extends SpellAbilityEffect {
|
||||||
|
|
||||||
|
private void randomizeBlockers(SpellAbility sa, Combat combat, Player declarer, Player defender, List<Card> attackers, List<CardCollection> blockerPiles) {
|
||||||
|
CardLists.shuffle(attackers);
|
||||||
|
for (int i = 0; i < attackers.size(); i++) {
|
||||||
|
final Card attacker = attackers.get(i);
|
||||||
|
CardCollection blockers = blockerPiles.get(i);
|
||||||
|
|
||||||
|
// Remove all illegal blockers first
|
||||||
|
for (int j = blockers.size() - 1; j >= 0; j--) {
|
||||||
|
final Card blocker = blockers.get(j);
|
||||||
|
if (!CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||||
|
blockers.remove(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.") &&
|
||||||
|
blockers.size() < defender.getCreaturesInPlay().size() ||
|
||||||
|
blockers.size() < CombatUtil.needsBlockers(attacker)) {
|
||||||
|
// If not enough remaining creatures to block, don't add them as blocker
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attacker.hasKeyword("CantBeBlockedByAmount GT1") && blockers.size() > 1) {
|
||||||
|
// If no more than one creature can block, order the player to choose one to block
|
||||||
|
Card chosen = declarer.getController().chooseCardsForEffect(blockers, sa,
|
||||||
|
Localizer.getInstance().getMessage("lblChooseBlockerForAttacker", attacker.toString()), 1, 1, false, null).get(0);
|
||||||
|
combat.addBlocker(attacker, chosen);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all remaning blockers normally
|
||||||
|
for (final Card blocker : blockers) {
|
||||||
|
combat.addBlocker(attacker, blocker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resolve(SpellAbility sa) {
|
||||||
|
Card hostCard = sa.getHostCard();
|
||||||
|
Player declarer = getDefinedPlayersOrTargeted(sa).get(0);
|
||||||
|
Player defender = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("Defender"), sa).get(0);
|
||||||
|
Combat combat = hostCard.getGame().getCombat();
|
||||||
|
List<Card> attackers = combat.getAttackers();
|
||||||
|
List<CardCollection> blockerPiles = new ArrayList<>();
|
||||||
|
|
||||||
|
if (declarer.isAI()) {
|
||||||
|
// For AI player, just let it declare blockers normally, then randomize it later.
|
||||||
|
declarer.getController().declareBlockers(defender, combat);
|
||||||
|
// Remove all blockers first
|
||||||
|
for (final Card attacker : attackers) {
|
||||||
|
CardCollection blockers = combat.getBlockers(attacker);
|
||||||
|
blockerPiles.add(blockers);
|
||||||
|
for (final Card blocker : blockers) {
|
||||||
|
combat.removeFromCombat(blocker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // Human player
|
||||||
|
CardCollection pool = new CardCollection(defender.getCreaturesInPlay());
|
||||||
|
// remove all blockers that can't block
|
||||||
|
for (final Card blocker : pool) {
|
||||||
|
if (!CombatUtil.canBlock(blocker)) {
|
||||||
|
pool.remove(blocker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<Integer> blockedSoFar = new ArrayList<>(Collections.nCopies(pool.size(), 0));
|
||||||
|
|
||||||
|
for (int i = 0; i < attackers.size(); i++) {
|
||||||
|
int size = pool.size();
|
||||||
|
CardCollection blockers = new CardCollection(declarer.getController().chooseCardsForEffect(
|
||||||
|
pool, sa, Localizer.getInstance().getMessage("lblChooseBlockersForPile", String.valueOf(i + 1)), 0, size, false, null));
|
||||||
|
blockerPiles.add(blockers);
|
||||||
|
// Remove chosen creatures, unless it can block additional attackers
|
||||||
|
for (final Card blocker : blockers) {
|
||||||
|
int index = pool.indexOf(blocker);
|
||||||
|
Integer blockedCount = blockedSoFar.get(index) + 1;
|
||||||
|
if (!blocker.canBlockAny() && blocker.canBlockAdditional() < blockedCount) {
|
||||||
|
pool.remove(index);
|
||||||
|
blockedSoFar.remove(index);
|
||||||
|
} else {
|
||||||
|
blockedSoFar.set(index, blockedCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
randomizeBlockers(sa, combat, declarer, defender, attackers, blockerPiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -653,7 +653,14 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
}
|
}
|
||||||
if (combat.isPlayerAttacked(p)) {
|
if (combat.isPlayerAttacked(p)) {
|
||||||
if (CombatUtil.canBlock(p, combat)) {
|
if (CombatUtil.canBlock(p, combat)) {
|
||||||
whoDeclaresBlockers.getController().declareBlockers(p, combat);
|
// Replacement effects (for Camouflage)
|
||||||
|
final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(p);
|
||||||
|
repRunParams.put(AbilityKey.Player, whoDeclaresBlockers);
|
||||||
|
ReplacementResult repres = game.getReplacementHandler().run(ReplacementType.DeclareBlocker, repRunParams);
|
||||||
|
if (repres == ReplacementResult.NotReplaced) {
|
||||||
|
// If not replaced, run normal declare blockers
|
||||||
|
whoDeclaresBlockers.getController().declareBlockers(p, combat);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else { continue; }
|
else { continue; }
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package forge.game.replacement;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import forge.game.ability.AbilityKey;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
public class ReplaceDeclareBlocker extends ReplacementEffect {
|
||||||
|
|
||||||
|
public ReplaceDeclareBlocker(final Map<String, String> mapParams, final Card host, final boolean intrinsic) {
|
||||||
|
super(mapParams, host, intrinsic);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReplace(Map<AbilityKey, Object> runParams) {
|
||||||
|
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
|
||||||
|
sa.setReplacingObject(AbilityKey.DefendingPlayer, runParams.get(AbilityKey.Affected));
|
||||||
|
// Here the Player is the one who would declare blockers (may be changed by some Card's effect)
|
||||||
|
sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Player));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ public enum ReplacementType {
|
|||||||
CreateToken(ReplaceToken.class),
|
CreateToken(ReplaceToken.class),
|
||||||
DamageDone(ReplaceDamage.class),
|
DamageDone(ReplaceDamage.class),
|
||||||
DealtDamage(ReplaceDealtDamage.class),
|
DealtDamage(ReplaceDealtDamage.class),
|
||||||
|
DeclareBlocker(ReplaceDeclareBlocker.class),
|
||||||
Destroy(ReplaceDestroy.class),
|
Destroy(ReplaceDestroy.class),
|
||||||
Discard(ReplaceDiscard.class),
|
Discard(ReplaceDiscard.class),
|
||||||
Draw(ReplaceDraw.class),
|
Draw(ReplaceDraw.class),
|
||||||
|
|||||||
7
forge-gui/res/cardsfolder/c/camouflage.txt
Normal file
7
forge-gui/res/cardsfolder/c/camouflage.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Name:Camouflage
|
||||||
|
ManaCost:G
|
||||||
|
Types:Instant
|
||||||
|
A:SP$ Effect | Cost$ G | ReplacementEffects$ RDeclareBlocker | ActivationPhases$ Declare Attackers | PlayerTurn$ True | AILogic$ Evasion | SpellDescription$ Cast this spell only during your declare attackers step. This turn, instead of declaring blockers, each defending player chooses any number of creatures they control and divides them into a number of piles equal to the number of attacking creatures for whom that player is the defending player. Creatures those players control that can block additional creatures may likewise be put into additional piles. Assign each pile to a different one of those attacking creatures at random. Each creature in a pile that can block the creature that pile is assigned to does so. (Piles can be empty.)
|
||||||
|
SVar:RDeclareBlocker:Event$ DeclareBlocker | ValidPlayer$ Opponent | ReplaceWith$ DBCamouflage | Description$ This turn, instead of declaring blockers, each defending player chooses any number of creatures they control and divides them into a number of piles equal to the number of attacking creatures for whom that player is the defending player. Creatures those players control that can block additional creatures may likewise be put into additional piles. Assign each pile to a different one of those attacking creatures at random. Each creature in a pile that can block the creature that pile is assigned to does so. (Piles can be empty.)
|
||||||
|
SVar:DBCamouflage:DB$ Camouflage | Defined$ ReplacedPlayer | Defender$ ReplacedDefendingPlayer | AILogic$ BestBlocker
|
||||||
|
Oracle:Cast this spell only during your declare attackers step.\nThis turn, instead of declaring blockers, each defending player chooses any number of creatures they control and divides them into a number of piles equal to the number of attacking creatures for whom that player is the defending player. Creatures those players control that can block additional creatures may likewise be put into additional piles. Assign each pile to a different one of those attacking creatures at random. Each creature in a pile that can block the creature that pile is assigned to does so. (Piles can be empty.)
|
||||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=Möchtest du überbieten? Aktuelles Gebot:
|
|||||||
lblTopBidWithValueLife=hat mit {0} Leben überboten
|
lblTopBidWithValueLife=hat mit {0} Leben überboten
|
||||||
#BondEffect.java
|
#BondEffect.java
|
||||||
lblSelectACardPair=Wähle Karte zum Verbinden
|
lblSelectACardPair=Wähle Karte zum Verbinden
|
||||||
|
#CamouflageEffect.java
|
||||||
|
lblChooseBlockerForAttacker=Choose a creature to block {0}
|
||||||
|
lblChooseBlockersForPile=Choose creatures to put in pile {0} (can be empty)
|
||||||
#ChangeCombatantsEffect.java
|
#ChangeCombatantsEffect.java
|
||||||
lblChooseDefenderToAttackWithCard=Welchen Verteidiger mit {0} angreifen?
|
lblChooseDefenderToAttackWithCard=Welchen Verteidiger mit {0} angreifen?
|
||||||
#ChangeTargetsEffect.java
|
#ChangeTargetsEffect.java
|
||||||
|
|||||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=Do you want to top bid? Current Bid \=
|
|||||||
lblTopBidWithValueLife=topped bid with {0} life
|
lblTopBidWithValueLife=topped bid with {0} life
|
||||||
#BondEffect.java
|
#BondEffect.java
|
||||||
lblSelectACardPair=Select a card to pair with
|
lblSelectACardPair=Select a card to pair with
|
||||||
|
#CamouflageEffect.java
|
||||||
|
lblChooseBlockerForAttacker=Choose a creature to block {0}
|
||||||
|
lblChooseBlockersForPile=Choose creatures to put in pile {0} (can be empty)
|
||||||
#ChangeCombatantsEffect.java
|
#ChangeCombatantsEffect.java
|
||||||
lblChooseDefenderToAttackWithCard=Choose which defender to attack with {0}
|
lblChooseDefenderToAttackWithCard=Choose which defender to attack with {0}
|
||||||
#ChangeTargetsEffect.java
|
#ChangeTargetsEffect.java
|
||||||
|
|||||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=¿Quieres hacer una puja máxima? Puja actual \=
|
|||||||
lblTopBidWithValueLife=puja más alta con {0} de vida
|
lblTopBidWithValueLife=puja más alta con {0} de vida
|
||||||
#BondEffect.java
|
#BondEffect.java
|
||||||
lblSelectACardPair=Selecciona una carta para emparejarla con
|
lblSelectACardPair=Selecciona una carta para emparejarla con
|
||||||
|
#CamouflageEffect.java
|
||||||
|
lblChooseBlockerForAttacker=Choose a creature to block {0}
|
||||||
|
lblChooseBlockersForPile=Choose creatures to put in pile {0} (can be empty)
|
||||||
#ChangeCombatantsEffect.java
|
#ChangeCombatantsEffect.java
|
||||||
lblChooseDefenderToAttackWithCard=Elige con qué defensor atacar con {0}
|
lblChooseDefenderToAttackWithCard=Elige con qué defensor atacar con {0}
|
||||||
#ChangeTargetsEffect.java
|
#ChangeTargetsEffect.java
|
||||||
|
|||||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=Do you want to top bid? Current Bid \=
|
|||||||
lblTopBidWithValueLife=topped bid with {0} life
|
lblTopBidWithValueLife=topped bid with {0} life
|
||||||
#BondEffect.java
|
#BondEffect.java
|
||||||
lblSelectACardPair=Select a card to pair with
|
lblSelectACardPair=Select a card to pair with
|
||||||
|
#CamouflageEffect.java
|
||||||
|
lblChooseBlockerForAttacker=Choose a creature to block {0}
|
||||||
|
lblChooseBlockersForPile=Choose creatures to put in pile {0} (can be empty)
|
||||||
#ChangeCombatantsEffect.java
|
#ChangeCombatantsEffect.java
|
||||||
lblChooseDefenderToAttackWithCard=Choose which defender to attack with {0}
|
lblChooseDefenderToAttackWithCard=Choose which defender to attack with {0}
|
||||||
#ChangeTargetsEffect.java
|
#ChangeTargetsEffect.java
|
||||||
|
|||||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=競りの点数を上げますか? 現在の点数 \=
|
|||||||
lblTopBidWithValueLife={0}点のライフで競りの点数をつけた
|
lblTopBidWithValueLife={0}点のライフで競りの点数をつけた
|
||||||
#BondEffect.java
|
#BondEffect.java
|
||||||
lblSelectACardPair=組にしたいカードを選ぶ
|
lblSelectACardPair=組にしたいカードを選ぶ
|
||||||
|
#CamouflageEffect.java
|
||||||
|
lblChooseBlockerForAttacker={0}をブロックするクリーチャーを選ぶ
|
||||||
|
lblChooseBlockersForPile={0}番の束に入れるクリーチャーを選ぶ(空にできる)
|
||||||
#ChangeCombatantsEffect.java
|
#ChangeCombatantsEffect.java
|
||||||
lblChooseDefenderToAttackWithCard={0}が攻撃する対象を選ぶ
|
lblChooseDefenderToAttackWithCard={0}が攻撃する対象を選ぶ
|
||||||
#ChangeTargetsEffect.java
|
#ChangeTargetsEffect.java
|
||||||
|
|||||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=你想要喊更高的价? 现在价钱 \=
|
|||||||
lblTopBidWithValueLife=最高喊价为{0}生命
|
lblTopBidWithValueLife=最高喊价为{0}生命
|
||||||
#BondEffect.java
|
#BondEffect.java
|
||||||
lblSelectACardPair=选择要组成搭档的牌
|
lblSelectACardPair=选择要组成搭档的牌
|
||||||
|
#CamouflageEffect.java
|
||||||
|
lblChooseBlockerForAttacker=Choose a creature to block {0}
|
||||||
|
lblChooseBlockersForPile=Choose creatures to put in pile {0} (can be empty)
|
||||||
#ChangeCombatantsEffect.java
|
#ChangeCombatantsEffect.java
|
||||||
lblChooseDefenderToAttackWith=选择守军进行进攻
|
lblChooseDefenderToAttackWith=选择守军进行进攻
|
||||||
#ChangeTargetsEffect.java
|
#ChangeTargetsEffect.java
|
||||||
|
|||||||
Reference in New Issue
Block a user