Skip to content

Commit

Permalink
PIP: Radiation mechanic implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Northmoc committed Nov 24, 2023
1 parent 2436d6e commit e4d45cc
Show file tree
Hide file tree
Showing 27 changed files with 321 additions and 115 deletions.
1 change: 1 addition & 0 deletions forge-ai/src/main/java/forge/ai/SpellApiToAi.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ public enum SpellApiToAi {
.put(ApiType.PumpAll, PumpAllAi.class)
.put(ApiType.PutCounter, CountersPutAi.class)
.put(ApiType.PutCounterAll, CountersPutAllAi.class)
.put(ApiType.Radiation, AlwaysPlayAi.class)
.put(ApiType.RearrangeTopOfLibrary, RearrangeTopOfLibraryAi.class)
.put(ApiType.Regenerate, RegenerateAi.class)
.put(ApiType.RegenerateAll, RegenerateAllAi.class)
Expand Down
16 changes: 16 additions & 0 deletions forge-game/src/main/java/forge/game/GameLogFormatter.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import forge.LobbyPlayer;
import forge.game.card.Card;
import forge.game.card.CounterEnumType;
import forge.game.event.*;
import forge.game.event.GameEventCardDamaged.DamageType;
import forge.game.player.Player;
Expand Down Expand Up @@ -224,6 +225,21 @@ public GameLogEntry visit(GameEventPlayerPoisoned ev) {
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
}

@Override
public GameLogEntry visit(GameEventPlayerRadiation ev) {
String message;
final int change = ev.change;
String radCtr = CounterEnumType.RAD.getName().toLowerCase() + " " +
Localizer.getInstance().getMessage("lblCounter").toLowerCase();
if (change >= 0) message = localizer.getMessage("lblLogPlayerRadiation",
ev.receiver.toString(), Lang.nounWithNumeralExceptOne(String.valueOf(change), radCtr),
ev.source.toString());
else message = localizer.getMessage("lblLogPlayerRadRemove",
ev.receiver.toString(), Lang.nounWithNumeralExceptOne(String.valueOf(Math.abs(change)), radCtr),
ev.source.toString());
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
}

@Override
public GameLogEntry visit(final GameEventAttackersDeclared ev) {
final StringBuilder sb = new StringBuilder();
Expand Down
1 change: 1 addition & 0 deletions forge-game/src/main/java/forge/game/ability/ApiType.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ public enum ApiType {
PumpAll (PumpAllEffect.class),
PutCounter (CountersPutEffect.class),
PutCounterAll (CountersPutAllEffect.class),
Radiation (RadiationEffect.class),
RearrangeTopOfLibrary (RearrangeTopOfLibraryEffect.class),
Regenerate (RegenerateEffect.class),
RegenerateAll (RegenerateAllEffect.class),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package forge.game.ability.effects;

import java.util.Map;

import forge.game.Game;
import forge.game.GameLogEntryType;
import forge.game.ability.AbilityKey;
Expand All @@ -16,7 +14,8 @@
import forge.game.zone.ZoneType;
import forge.util.Lang;
import forge.util.Localizer;
import forge.util.TextUtil;

import java.util.Map;

public class MillEffect extends SpellAbilityEffect {
@Override
Expand Down Expand Up @@ -47,8 +46,10 @@ public void resolve(SpellAbility sa) {
continue;
}

String toZoneStr = destination.equals(ZoneType.Graveyard) ? "" : " " +
Localizer.getInstance().getMessage("lblMilledToZone", destination.getTranslatedName());
if (sa.hasParam("Optional")) {
final String prompt = TextUtil.concatWithSpace(Localizer.getInstance().getMessage("lblDoYouWantPutLibraryCardsTo", destination.getTranslatedName()));
final String prompt = Localizer.getInstance().getMessage("lblDoYouWantPutLibraryCardsTo") + toZoneStr;
// CR 701.13b
if (numCards > p.getZone(ZoneType.Library).size() || !p.getController().confirmAction(sa, null, prompt, null)) {
continue;
Expand All @@ -59,11 +60,11 @@ public void resolve(SpellAbility sa) {
// graveyard to figure out which ones were milled.
if (!facedown && reveal) { // do not reveal when exiling face down
if (showRevealDialog) {
game.getAction().reveal(milled, p, false);
final String message = Localizer.getInstance().getMessage("lblMilledCards") + toZoneStr;
game.getAction().reveal(milled, p, false, message);
}
StringBuilder sb = new StringBuilder();
sb.append(p).append(" milled ").append(milled).append(" to ").append(destination);
p.getGame().getGameLog().add(GameLogEntryType.ZONE_CHANGE, sb.toString());
p.getGame().getGameLog().add(GameLogEntryType.ZONE_CHANGE, p + " milled " +
Lang.joinHomogenous(milled) + toZoneStr + ".");
}
if (destination.equals(ZoneType.Exile)) {
for (final Card c : milled) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package forge.game.ability.effects;

import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CounterEnumType;
import forge.game.event.GameEventPlayerRadiation;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;

import java.util.Map;

public class RadiationEffect extends SpellAbilityEffect {

@Override
public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard();
final Game game = host.getGame();
final int toAdd = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Add", "0"), sa);
final int toRem = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Remove", "0"), sa);
final Map<Player, Integer> list = Maps.newHashMap();

GameEntityCounterTable table = new GameEntityCounterTable();

for (final Player p : getTargetPlayers(sa)) {
if (!p.isInGame()) continue;

list.put(p, p.getCounters(CounterEnumType.RAD));
if (toAdd >= 1) p.addRadCounters(toAdd, host, table);
else if (toRem >= 1) p.removeRadCounters(toRem, host);
}
table.replaceCounterEffect(game, sa, true);
for (final Player p : list.keySet()) {
int oldCount = list.get(p);
int newCount = p.getCounters(CounterEnumType.RAD);
if (newCount > 0 && !p.hasRadiationEffect()) p.createRadiationEffect(host.getSetCode());
if (oldCount < newCount) game.fireEvent(new GameEventPlayerRadiation(p, host, newCount - oldCount));
}
}
}
2 changes: 2 additions & 0 deletions forge-game/src/main/java/forge/game/card/CounterEnumType.java
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,8 @@ public enum CounterEnumType {

POISON("POISN"),

RAD("RAD"),

TICKET("TICKET"),

// Keyword Counters
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package forge.game.event;

import forge.game.card.Card;
import forge.game.player.Player;

public class GameEventPlayerRadiation extends GameEvent {
public final Player receiver;
public final Card source;
public final int change;

public GameEventPlayerRadiation(Player recv, Card src, int chng) {
receiver = recv;
source = src;
change = chng;
}

@Override
public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public interface IGameEventVisitor<T> {
T visit(GameEventPlayerDamaged gameEventPlayerDamaged);
T visit(GameEventPlayerCounters event);
T visit(GameEventPlayerPoisoned event);
T visit(GameEventPlayerRadiation event);
T visit(GameEventPlayerPriority event);
T visit(GameEventPlayerShardsChanged event);
T visit(GameEventPlayerStatsChanged event);
Expand Down Expand Up @@ -90,6 +91,7 @@ class Base<T> implements IGameEventVisitor<T>{
public T visit(GameEventPlayerControl event) { return null; }
public T visit(GameEventPlayerCounters event) { return null; }
public T visit(GameEventPlayerPoisoned event) { return null; }
public T visit(GameEventPlayerRadiation event) { return null; }
public T visit(GameEventPlayerPriority event) { return null; }
public T visit(GameEventPlayerShardsChanged event) { return null; }
public T visit(GameEventPlayerStatsChanged event) { return null; }
Expand Down
75 changes: 46 additions & 29 deletions forge-game/src/main/java/forge/game/phase/PhaseHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,22 @@
*/
package forge.game.phase;

import java.util.*;

import com.google.common.collect.*;
import org.apache.commons.lang3.time.StopWatch;

import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.GameStage;
import forge.game.GameType;
import forge.game.GlobalRuleChange;
import com.google.common.base.Predicates;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import forge.game.*;
import forge.game.ability.AbilityKey;
import forge.game.ability.effects.AddTurnEffect;
import forge.game.ability.effects.SkipPhaseEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardUtil;
import forge.game.card.*;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardZoneTable;
import forge.game.card.CounterEnumType;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.CostEnlist;
import forge.game.cost.CostExert;
import forge.game.event.GameEventAttackersDeclared;
import forge.game.event.GameEventBlockersDeclared;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.event.GameEventCombatChanged;
import forge.game.event.GameEventCombatEnded;
import forge.game.event.GameEventGameRestarted;
import forge.game.event.GameEventPlayerPriority;
import forge.game.event.GameEventPlayerStatsChanged;
import forge.game.event.GameEventTokenStateUpdate;
import forge.game.event.GameEventTurnBegan;
import forge.game.event.GameEventTurnEnded;
import forge.game.event.GameEventTurnPhase;
import forge.game.event.*;
import forge.game.player.Player;
import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
Expand All @@ -64,9 +43,14 @@
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.util.CollectionSuppliers;
import forge.util.Lang;
import forge.util.Localizer;
import forge.util.TextUtil;
import forge.util.maps.HashMapOfLists;
import forge.util.maps.MapOfLists;
import org.apache.commons.lang3.time.StopWatch;

import java.util.*;


/**
Expand Down Expand Up @@ -299,6 +283,9 @@ private final void onPhaseBegin() {
if (playerTurn.isArchenemy()) {
playerTurn.setSchemeInMotion();
}
if (playerTurn.hasRadiationEffect()) {
handleRadiation();
}
GameEntityCounterTable table = new GameEntityCounterTable();
// all Saga get Lore counter at the begin of pre combat
for (Card c : playerTurn.getCardsIn(ZoneType.Battlefield)) {
Expand Down Expand Up @@ -541,6 +528,36 @@ private void onPhaseEnd() {
}
}

private void handleRadiation() {
int numRad = playerTurn.getCounters(CounterEnumType.RAD);
if (numRad == 0) playerTurn.removeRadiationEffect();
else {
final CardZoneTable table = new CardZoneTable();
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, game.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, game.getLastStateGraveyard());
final SpellAbility sa = new SpellAbility.EmptySa(playerTurn.getRadiationEffect(), playerTurn);
final CardCollectionView milled = playerTurn.mill(numRad, ZoneType.Graveyard, sa,
table, moveParams);
game.getAction().reveal(milled, playerTurn, false,
Localizer.getInstance().getMessage("lblMilledCards", playerTurn));
game.getGameLog().add(GameLogEntryType.ZONE_CHANGE, playerTurn + " milled " +
Lang.joinHomogenous(milled) + ".");
table.triggerChangesZoneAll(game, sa);
int n = CardLists.filter(milled, Predicates.not(CardPredicates.Presets.LANDS)).size();
final Map<Player, Integer> lossMap = Maps.newHashMap();
final int lost = playerTurn.loseLife(n, false, false);
if (lost > 0) {
lossMap.put(playerTurn, lost);
}
if (!lossMap.isEmpty()) { // Run triggers if any player actually lost life
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPIMap(lossMap);
game.getTriggerHandler().runTrigger(TriggerType.LifeLostAll, runParams, false);
}
playerTurn.removeRadCounters(n, playerTurn.getRadiationEffect());
}
}

private void declareAttackersTurnBasedAction() {
final Player whoDeclares = playerDeclaresAttackers == null || playerDeclaresAttackers.hasLost()
? playerTurn
Expand Down
Loading

0 comments on commit e4d45cc

Please sign in to comment.