Merge branch 'add_camouflage' into 'master'

Add Camouflage

See merge request core-developers/forge!4268
This commit is contained in:
Michael Kamensky
2021-03-26 10:57:03 +00:00
13 changed files with 173 additions and 1 deletions

View File

@@ -30,6 +30,7 @@ public enum ApiType {
Block (BlockEffect.class),
Bond (BondEffect.class),
Branch (BranchEffect.class),
Camouflage (CamouflageEffect.class),
ChangeCombatants (ChangeCombatantsEffect.class),
ChangeTargets (ChangeTargetsEffect.class),
ChangeText (ChangeTextEffect.class),

View File

@@ -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);
}
}

View File

@@ -653,7 +653,14 @@ public class PhaseHandler implements java.io.Serializable {
}
if (combat.isPlayerAttacked(p)) {
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; }

View File

@@ -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));
}
}

View File

@@ -21,6 +21,7 @@ public enum ReplacementType {
CreateToken(ReplaceToken.class),
DamageDone(ReplaceDamage.class),
DealtDamage(ReplaceDealtDamage.class),
DeclareBlocker(ReplaceDeclareBlocker.class),
Destroy(ReplaceDestroy.class),
Discard(ReplaceDiscard.class),
Draw(ReplaceDraw.class),