From f700b9d43b973074b5fa9fb0c6ff13d030e05d0c Mon Sep 17 00:00:00 2001 From: Tim Mocny Date: Wed, 30 Dec 2020 02:35:25 +0000 Subject: [PATCH] Butcher Orgg!!! --- .../main/java/forge/game/card/CardView.java | 3 + .../main/java/forge/game/combat/Combat.java | 56 +++++++++++++++---- .../forge/trackable/TrackableProperty.java | 1 + .../screens/match/VAssignCombatDamage.java | 19 +++++-- .../match/views/VAssignCombatDamage.java | 13 ++++- forge-gui/res/cardsfolder/b/butcher_orgg.txt | 7 +++ forge-gui/res/languages/de-DE.properties | 1 + forge-gui/res/languages/en-US.properties | 1 + forge-gui/res/languages/es-ES.properties | 1 + forge-gui/res/languages/it-IT.properties | 1 + forge-gui/res/languages/zh-CN.properties | 1 + .../forge/player/PlayerControllerHuman.java | 4 +- 12 files changed, 87 insertions(+), 21 deletions(-) create mode 100644 forge-gui/res/cardsfolder/b/butcher_orgg.txt diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index c848700e2a6..2a3fb86f417 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -1133,6 +1133,7 @@ public class CardView extends GameEntityView { public boolean hasDeathtouch() { return get(TrackableProperty.HasDeathtouch); } public boolean hasDevoid() { return get(TrackableProperty.HasDevoid); } public boolean hasDefender() { return get(TrackableProperty.HasDefender); } + public boolean hasDivideDamage() { return get(TrackableProperty.HasDivideDamage); } public boolean hasDoubleStrike() { return get(TrackableProperty.HasDoubleStrike); } public boolean hasFirstStrike() { return get(TrackableProperty.HasFirstStrike); } public boolean hasFlying() { return get(TrackableProperty.HasFlying); } @@ -1173,6 +1174,8 @@ public class CardView extends GameEntityView { set(TrackableProperty.HasDeathtouch, c.hasKeyword(Keyword.DEATHTOUCH, state)); set(TrackableProperty.HasDevoid, c.hasKeyword(Keyword.DEVOID, state)); set(TrackableProperty.HasDefender, c.hasKeyword(Keyword.DEFENDER, state)); + set(TrackableProperty.HasDivideDamage, c.hasKeyword("You may assign CARDNAME's combat damage divided as " + + "you choose among defending player and/or any number of creatures they control.")); set(TrackableProperty.HasDoubleStrike, c.hasKeyword(Keyword.DOUBLE_STRIKE, state)); set(TrackableProperty.HasFirstStrike, c.hasKeyword(Keyword.FIRST_STRIKE, state)); set(TrackableProperty.HasFlying, c.hasKeyword(Keyword.FLYING, state)); diff --git a/forge-game/src/main/java/forge/game/combat/Combat.java b/forge-game/src/main/java/forge/game/combat/Combat.java index 4c85ccd5cac..992fce3b3bf 100644 --- a/forge-game/src/main/java/forge/game/combat/Combat.java +++ b/forge-game/src/main/java/forge/game/combat/Combat.java @@ -20,21 +20,18 @@ package forge.game.combat; import com.google.common.base.Function; import com.google.common.collect.*; import forge.game.Game; -import forge.game.GameEntity; -import forge.game.GameEntityCounterTable; -import forge.game.GameLogEntryType; -import forge.game.GameObjectMap; +import forge.game.*; import forge.game.ability.AbilityKey; -import forge.game.card.Card; -import forge.game.card.CardCollection; -import forge.game.card.CardCollectionView; -import forge.game.card.CardDamageMap; +import forge.game.card.*; import forge.game.keyword.Keyword; import forge.game.player.Player; import forge.game.spellability.SpellAbilityStackInstance; import forge.game.trigger.TriggerType; +import forge.game.zone.ZoneType; +import forge.util.CardTranslation; import forge.util.collect.FCollection; import forge.util.collect.FCollectionView; +import forge.util.Localizer; import org.apache.commons.lang3.tuple.Pair; import java.util.*; @@ -64,6 +61,7 @@ public class Combat { private Map blockersOrderedForDamageAssignment = Maps.newHashMap(); private Map lkiCache = Maps.newHashMap(); private CardDamageMap dealtDamageTo = new CardDamageMap(); + private boolean dividedToPlayer = false; // List holds creatures who have dealt 1st strike damage to disallow them deal damage on regular basis (unless they have double-strike KW) private CardCollection combatantsThatDealtFirstStrikeDamage = new CardCollection(); @@ -373,7 +371,7 @@ public class Combat { blocker.updateBlockingForView(); } - // remove blocked from specific attacker + // remove blocker from specific attacker public final void removeBlockAssignment(final Card attacker, final Card blocker) { AttackingBand band = getBandOfAttackerNotNull(attacker); Collection cc = blockedBands.get(band); @@ -400,6 +398,15 @@ public class Combat { return result; } + public final CardCollection getDefendersCreatures() { + CardCollection result = new CardCollection(); + for (Card attacker : getAttackers()) { + CardCollection cc = getDefenderPlayerByAttacker(attacker).getCreaturesInPlay(); + result.addAll(cc); + } + return result; + } + public final CardCollection getBlockers(final AttackingBand band) { Collection blockers = blockedBands.get(band); return blockers == null ? new CardCollection() : new CardCollection(blockers); @@ -716,8 +723,26 @@ public class Combat { continue; } + boolean divideCombatDamageAsChoose = (getDefendersCreatures().size() > 0 && + attacker.hasKeyword("You may assign CARDNAME's combat damage divided as you choose among " + + "defending player and/or any number of creatures they control.") + && attacker.getController().getController().confirmAction(null, null, + Localizer.getInstance().getMessage("lblAssignCombatDamageAsChoose", + CardTranslation.getTranslatedName(attacker.getName())))); boolean trampler = attacker.hasKeyword(Keyword.TRAMPLE); orderedBlockers = blockersOrderedForDamageAssignment.get(attacker); + if (divideCombatDamageAsChoose) { + if (orderedBlockers == null || orderedBlockers.isEmpty()) { + orderedBlockers = getDefendersCreatures(); + } + else { + for (Card c : getDefendersCreatures()) { + if (!orderedBlockers.contains(c)) { + orderedBlockers.add(c); + } + } + } + } assignedDamage = true; // If the Attacker is unblocked, or it's a trampler and has 0 blockers, deal damage to defender if (orderedBlockers == null || orderedBlockers.isEmpty()) { @@ -730,6 +755,10 @@ public class Combat { Player assigningPlayer = getAttackingPlayer(); // Defensive Formation is very similar to Banding with Blockers // It allows the defending player to assign damage instead of the attacking player + if (defender instanceof Card && divideCombatDamageAsChoose) { + defender = getDefenderPlayerByAttacker(attacker); + dividedToPlayer = true; + } if (defender instanceof Player && defender.hasKeyword("You assign combat damage of each creature attacking you.")) { assigningPlayer = (Player)defender; } @@ -737,7 +766,8 @@ public class Combat { assigningPlayer = orderedBlockers.get(0).getController(); } - Map map = assigningPlayer.getController().assignCombatDamage(attacker, orderedBlockers, damageDealt, defender, getAttackingPlayer() != assigningPlayer); + Map map = assigningPlayer.getController().assignCombatDamage(attacker, orderedBlockers, + damageDealt, defender, divideCombatDamageAsChoose || getAttackingPlayer() != assigningPlayer); for (Entry dt : map.entrySet()) { if (dt.getKey() == null) { if (dt.getValue() > 0) @@ -767,7 +797,7 @@ public class Combat { private final void addDefendingDamage(final int n, final Card source) { final GameEntity ge = getDefenderByAttacker(source); - if (ge instanceof Card) { + if (ge instanceof Card && !dividedToPlayer) { final Card planeswalker = (Card) ge; planeswalker.addAssignedDamage(n, source); return; @@ -805,6 +835,9 @@ public class Combat { // This function handles both Regular and First Strike combat assignment for (final Entry entry : defendingDamageMap.entrySet()) { GameEntity defender = getDefenderByAttacker(entry.getKey()); + if (dividedToPlayer) { + defender = getDefenderPlayerByAttacker(entry.getKey()); + } if (defender instanceof Player) { // player defender.addCombatDamage(entry.getValue(), entry.getKey(), dealtDamageTo, preventMap, counterTable); } @@ -819,6 +852,7 @@ public class Combat { combatants.addAll(getAttackers()); combatants.addAll(getAllBlockers()); combatants.addAll(getDefendingPlaneswalkers()); + combatants.addAll(getDefendersCreatures()); for (final Card c : combatants) { // if no assigned damage to resolve, move to next diff --git a/forge-game/src/main/java/forge/trackable/TrackableProperty.java b/forge-game/src/main/java/forge/trackable/TrackableProperty.java index 2bc1d95c063..9b46a396fd5 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableProperty.java +++ b/forge-game/src/main/java/forge/trackable/TrackableProperty.java @@ -100,6 +100,7 @@ public enum TrackableProperty { HasDeathtouch(TrackableTypes.BooleanType), HasDevoid(TrackableTypes.BooleanType), HasDefender(TrackableTypes.BooleanType), + HasDivideDamage(TrackableTypes.BooleanType), HasDoubleStrike(TrackableTypes.BooleanType), HasFirstStrike(TrackableTypes.BooleanType), HasFlying(TrackableTypes.BooleanType), diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/VAssignCombatDamage.java b/forge-gui-desktop/src/main/java/forge/screens/match/VAssignCombatDamage.java index 673e4950c89..4cebdcc3491 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/VAssignCombatDamage.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/VAssignCombatDamage.java @@ -74,6 +74,7 @@ public class VAssignCombatDamage { private final int totalDamageToAssign; private boolean attackerHasDeathtouch = false; + private boolean attackerHasDivideDamage = false; private boolean attackerHasTrample = false; private boolean attackerHasInfect = false; private boolean overrideCombatantOrder = false; @@ -152,6 +153,7 @@ public class VAssignCombatDamage { attackerHasDeathtouch = attacker.getCurrentState().hasDeathtouch(); attackerHasInfect = attacker.getCurrentState().hasInfect(); attackerHasTrample = defender != null && attacker.getCurrentState().hasTrample(); + attackerHasDivideDamage = attacker.getCurrentState().hasDivideDamage(); overrideCombatantOrder = overrideOrder; // Top-level UI stuff @@ -173,7 +175,7 @@ public class VAssignCombatDamage { // Defenders area final JPanel pnlDefenders = new JPanel(); pnlDefenders.setOpaque(false); - int cols = attackerHasTrample ? blockers.size() + 1 : blockers.size(); + int cols = ((attackerHasTrample) || (attackerHasDivideDamage && overrideCombatantOrder)) ? blockers.size() + 1 : blockers.size(); final String wrap = "wrap " + cols; pnlDefenders.setLayout(new MigLayout("insets 0, gap 0, ax center, " + wrap)); @@ -187,7 +189,7 @@ public class VAssignCombatDamage { addPanelForDefender(pnlDefenders, c); } - if (attackerHasTrample) { + if ((attackerHasTrample) || (attackerHasDivideDamage && overrideCombatantOrder)) { final DamageTarget dt = new DamageTarget(null, new FLabel.Builder().text("0").fontSize(18).fontAlign(SwingConstants.CENTER).build()); damage.put(null, dt); defenders.add(dt); @@ -275,10 +277,12 @@ public class VAssignCombatDamage { source = null; // If trying to assign to the defender, follow the normal assignment rules - // No need to check for "active" creature assignee when overiding combatant order - if ((source == null || source == defender || !overrideCombatantOrder) && isAdding && - !VAssignCombatDamage.this.canAssignTo(source)) { - return; + // No need to check for "active" creature assignee when overriding combatant order + if (!attackerHasDivideDamage) { // Creatures with this can assign to defender + if ((source == null || source == defender || !overrideCombatantOrder) && isAdding && + !VAssignCombatDamage.this.canAssignTo(source)) { + return; + } } // If lethal damage has already been assigned just act like it's 0. @@ -316,6 +320,9 @@ public class VAssignCombatDamage { } private void checkDamageQueue() { + if (overrideCombatantOrder && attackerHasDivideDamage) { + return; + } // Clear out any Damage that shouldn't be assigned to other combatants boolean hasAliveEnemy = false; for(DamageTarget dt : defenders) { diff --git a/forge-gui-mobile/src/forge/screens/match/views/VAssignCombatDamage.java b/forge-gui-mobile/src/forge/screens/match/views/VAssignCombatDamage.java index 6b709930750..f85881795bd 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VAssignCombatDamage.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VAssignCombatDamage.java @@ -61,6 +61,7 @@ public class VAssignCombatDamage extends FDialog { private final int totalDamageToAssign; private boolean attackerHasDeathtouch = false; + private boolean attackerHasDivideDamage = false; private boolean attackerHasTrample = false; private boolean attackerHasInfect = false; private boolean overrideCombatantOrder = false; @@ -102,6 +103,7 @@ public class VAssignCombatDamage extends FDialog { totalDamageToAssign = damage0; defender = defender0; attackerHasDeathtouch = attacker.getCurrentState().hasDeathtouch(); + attackerHasDivideDamage = attacker.getCurrentState().hasDivideDamage(); attackerHasInfect = attacker.getCurrentState().hasInfect(); attackerHasTrample = defender != null && attacker.getCurrentState().hasTrample(); overrideCombatantOrder = overrideOrder; @@ -166,7 +168,7 @@ public class VAssignCombatDamage extends FDialog { addDamageTarget(c); } - if (attackerHasTrample) { + if (attackerHasTrample || (attackerHasDivideDamage && overrideCombatantOrder)) { //add damage target for target of attack that trample damage will go through to addDamageTarget(null); } @@ -298,8 +300,10 @@ public class VAssignCombatDamage extends FDialog { // If trying to assign to the defender, follow the normal assignment rules // No need to check for "active" creature assignee when overiding combatant order - if ((source == null || source == defender || !overrideCombatantOrder) && isAdding && !canAssignTo(source)) { - return; + if (!attackerHasDivideDamage) { // Creatures with this can assign to defender + if ((source == null || source == defender || !overrideCombatantOrder) && isAdding && !canAssignTo(source)) { + return; + } } // If lethal damage has already been assigned just act like it's 0. @@ -330,6 +334,9 @@ public class VAssignCombatDamage extends FDialog { } private void checkDamageQueue() { + if (overrideCombatantOrder && attackerHasDivideDamage) { + return; + } // Clear out any Damage that shouldn't be assigned to other combatants boolean hasAliveEnemy = false; for (DamageTarget dt : defenders) { diff --git a/forge-gui/res/cardsfolder/b/butcher_orgg.txt b/forge-gui/res/cardsfolder/b/butcher_orgg.txt new file mode 100644 index 00000000000..120e3ed6c58 --- /dev/null +++ b/forge-gui/res/cardsfolder/b/butcher_orgg.txt @@ -0,0 +1,7 @@ +Name:Butcher Orgg +ManaCost:4 R R R +Types:Creature Orgg +PT:6/6 +K:You may assign CARDNAME's combat damage divided as you choose among defending player and/or any number of creatures they control. +AI:RemoveDeck:All +Oracle:You may assign Butcher Orgg’s combat damage divided as you choose among defending player and/or any number of creatures they control. diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 45a2f6bfd0d..b3227352818 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -1170,6 +1170,7 @@ lblDraw=Ziehen lblTooFewCardsMainDeck=Zu wenig Karten in deinem Deck (mindestens {0}). Bitte passe dein Deck an. lblTooManyCardsSideboard=Zu viele Karten in deinem Deck (maximal {0}). Bitte passe dein Deck an. lblAssignCombatDamageWerentBlocked=Möchtest du den Kampfschaden deklarieren, als wäre nicht geblockt worden? +lblAssignCombatDamageAsChoose=Do you want to divide {0}''s combat damage as you choose? lblChosenCards=Wähle Karten lblAttacker=Angreifer lblTriggeredby=Ausgelöst durch diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index c4cca0dd335..d58cacccd0c 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -1170,6 +1170,7 @@ lblDraw=Draw lblTooFewCardsMainDeck=Too few cards in your main deck (minimum {0}), please make modifications to your deck again. lblTooManyCardsSideboard=Too many cards in your sideboard (maximum {0}), please make modifications to your deck again. lblAssignCombatDamageWerentBlocked=Do you want to assign its combat damage as though it weren''t blocked? +lblAssignCombatDamageAsChoose=Do you want to divide {0}''s combat damage as you choose? lblChosenCards=Chosen Cards lblAttacker=Attacker lblTriggeredby=Triggered by diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index adf0243c33e..69636b23831 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -1170,6 +1170,7 @@ lblDraw=Ceder lblTooFewCardsMainDeck=Muy pocas cartas en tu mazo principal (mínimo {0}), por favor realiza modificaciones a tu mazo de nuevo. lblTooManyCardsSideboard=Demasiadas cartas en tu banquillo (máximo {0}), por favor realiza modificaciones a tu mazo de nuevo. lblAssignCombatDamageWerentBlocked=¿Quieres asignar su daño de combate como si no estuviera bloqueado? +lblAssignCombatDamageAsChoose=¿Quieres dividir el daño de combate del {0} como elijas? lblChosenCards=Cartas elegidas lblAttacker=Atacante lblTriggeredby=Activado por diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index f4476649095..f76b760c73a 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -1170,6 +1170,7 @@ lblDraw=Disegnare lblTooFewCardsMainDeck=Troppe carte nel tuo mazzo principale (minimo %s), per favore apporta nuovamente modifiche al tuo mazzo. lblTooManyCardsSideboard=Troppe carte nel tuo sideboard (massimo %s), modifica nuovamente il tuo mazzo. lblAssignCombatDamageWerentBlocked=Vuoi assegnare il suo danno da combattimento come se non fosse bloccato? +lblAssignCombatDamageAsChoose=Vuoi suddividere come preferisci il danno da combattimento dell''{0}? lblChosenCards=Carte scelte lblAttacker=aggressore lblTriggeredby=Innescato da diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 0441ac02e3c..b05f7adcdeb 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -1170,6 +1170,7 @@ lblDraw=后手 lblTooFewCardsMainDeck=主牌中卡牌数过少(最少为{0}),请重新修改套牌。 lblTooManyCardsSideboard=备牌中卡牌数过多(最多为{0}),请重新修改套牌。 lblAssignCombatDamageWerentBlocked=是否要像没有被阻挡一样分配战斗伤害? +lblAssignCombatDamageAsChoose=你想要按你所选的分配方式对{0}造成战斗伤害吗? lblChosenCards=选择牌 lblAttacker=进攻者 lblTriggeredby=触发者 diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 6ee56cc6811..e2d31e40556 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -286,7 +286,9 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont if (defender != null && assignDamageAsIfNotBlocked(attacker)) { map.put(null, damageDealt); } else { - if ((attacker.hasKeyword(Keyword.TRAMPLE) && defender != null) || (blockers.size() > 1)) { + if ((attacker.hasKeyword(Keyword.TRAMPLE) && defender != null) || (blockers.size() > 1) + || (attacker.hasKeyword("You may assign CARDNAME's combat damage divided as you choose among defending" + + " player and/or any number of creatures they control.")) && overrideOrder && blockers.size() >0) { GameEntityViewMap gameCacheBlockers = GameEntityView.getMap(blockers); final CardView vAttacker = CardView.get(attacker); final GameEntityView vDefender = GameEntityView.get(defender);