From 812e5793c6493315ad97bde3fc5cea39616b6bfc Mon Sep 17 00:00:00 2001 From: magenoxx Date: Fri, 4 May 2012 12:40:13 +0400 Subject: [PATCH 01/18] optimizations --- Mage.Server/src/main/java/mage/server/User.java | 13 +++++++------ .../src/main/java/mage/server/UserManager.java | 9 +++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Mage.Server/src/main/java/mage/server/User.java b/Mage.Server/src/main/java/mage/server/User.java index 5a4d75726f..5a6bf71727 100644 --- a/Mage.Server/src/main/java/mage/server/User.java +++ b/Mage.Server/src/main/java/mage/server/User.java @@ -27,11 +27,6 @@ */ package mage.server; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.UUID; import mage.cards.decks.Deck; import mage.game.Table; import mage.interfaces.callback.ClientCallback; @@ -43,6 +38,12 @@ import mage.server.tournament.TournamentSession; import mage.view.TableClientMessage; import org.apache.log4j.Logger; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; + /** * * @author BetaSteward_at_googlemail.com @@ -178,7 +179,7 @@ public class User { } public boolean isExpired(Date expired) { - return userState == UserState.Disconnected && lastActivity.before(expired); + return /*userState == UserState.Disconnected && */ lastActivity.before(expired); } private void reconnect() { diff --git a/Mage.Server/src/main/java/mage/server/UserManager.java b/Mage.Server/src/main/java/mage/server/UserManager.java index 5776ebbe3c..7c8ce4a38f 100644 --- a/Mage.Server/src/main/java/mage/server/UserManager.java +++ b/Mage.Server/src/main/java/mage/server/UserManager.java @@ -27,6 +27,9 @@ */ package mage.server; +import mage.view.ChatMessage.MessageColor; +import org.apache.log4j.Logger; + import java.util.Calendar; import java.util.Collection; import java.util.UUID; @@ -35,9 +38,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import mage.view.ChatMessage.MessageColor; -import org.apache.log4j.Logger; - /** * * manages users - if a user is disconnected and 10 minutes have passed with no @@ -127,9 +127,10 @@ public class UserManager { private void checkExpired() { Calendar expired = Calendar.getInstance(); - expired.add(Calendar.MINUTE, -10) ; + expired.add(Calendar.MINUTE, -1) ; for (User user: users.values()) { if (user.isExpired(expired.getTime())) { + logger.info("user session expired " + user.getId()); user.kill(); users.remove(user.getId()); } From 0b0f1c095a76df7b853669b562aabfdc22dd27a6 Mon Sep 17 00:00:00 2001 From: magenoxx Date: Fri, 4 May 2012 17:21:49 +0400 Subject: [PATCH 02/18] [AVR] Midnight Duelist --- .../sets/avacynrestored/MidnightDuelist.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/MidnightDuelist.java diff --git a/Mage.Sets/src/mage/sets/avacynrestored/MidnightDuelist.java b/Mage.Sets/src/mage/sets/avacynrestored/MidnightDuelist.java new file mode 100644 index 0000000000..574259d599 --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/MidnightDuelist.java @@ -0,0 +1,77 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import mage.Constants.CardType; +import mage.Constants.Rarity; +import mage.MageInt; +import mage.abilities.keyword.ProtectionAbility; +import mage.cards.CardImpl; +import mage.filter.Filter; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author noxx + + */ +public class MidnightDuelist extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("Vampires"); + + static { + filter.getSubtype().add("Vampire"); + filter.setScopeSubtype(Filter.ComparisonScope.Any); + } + + public MidnightDuelist(UUID ownerId) { + super(ownerId, 27, "Midnight Duelist", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{W}"); + this.expansionSetCode = "AVR"; + this.subtype.add("Human"); + this.subtype.add("Soldier"); + + this.color.setWhite(true); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Protection from Vampires + this.addAbility(new ProtectionAbility(filter)); + } + + public MidnightDuelist(final MidnightDuelist card) { + super(card); + } + + @Override + public MidnightDuelist copy() { + return new MidnightDuelist(this); + } +} From a16e3041af1d1453019bb5d270b1a299044a8e0d Mon Sep 17 00:00:00 2001 From: magenoxx Date: Fri, 4 May 2012 17:35:22 +0400 Subject: [PATCH 03/18] [AVR] Cursebreak --- .../mage/sets/avacynrestored/Cursebreak.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/Cursebreak.java diff --git a/Mage.Sets/src/mage/sets/avacynrestored/Cursebreak.java b/Mage.Sets/src/mage/sets/avacynrestored/Cursebreak.java new file mode 100644 index 0000000000..26a7ba2cc2 --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/Cursebreak.java @@ -0,0 +1,73 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import mage.Constants.CardType; +import mage.Constants.Rarity; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.cards.CardImpl; +import mage.filter.Filter; +import mage.filter.FilterPermanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author noxx + */ +public class Cursebreak extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("enchantment"); + + static { + filter.getCardType().add(CardType.ENCHANTMENT); + filter.setScopeCardType(Filter.ComparisonScope.Any); + } + + public Cursebreak(UUID ownerId) { + super(ownerId, 14, "Cursebreak", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{1}{W}"); + this.expansionSetCode = "AVR"; + + this.color.setWhite(true); + + // Destroy target enchantment. You gain 2 life. + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addEffect(new GainLifeEffect(2)); + } + + public Cursebreak(final Cursebreak card) { + super(card); + } + + @Override + public Cursebreak copy() { + return new Cursebreak(this); + } +} From a17f8e605978c2a5717404400e1fe3167b14bdfc Mon Sep 17 00:00:00 2001 From: magenoxx Date: Fri, 4 May 2012 18:42:37 +0400 Subject: [PATCH 04/18] two minute timeout --- Mage.Server/src/main/java/mage/server/UserManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Server/src/main/java/mage/server/UserManager.java b/Mage.Server/src/main/java/mage/server/UserManager.java index 7c8ce4a38f..3a2474411f 100644 --- a/Mage.Server/src/main/java/mage/server/UserManager.java +++ b/Mage.Server/src/main/java/mage/server/UserManager.java @@ -127,7 +127,7 @@ public class UserManager { private void checkExpired() { Calendar expired = Calendar.getInstance(); - expired.add(Calendar.MINUTE, -1) ; + expired.add(Calendar.MINUTE, -2) ; for (User user: users.values()) { if (user.isExpired(expired.getTime())) { logger.info("user session expired " + user.getId()); From 1c0b807a02ae4bb4163fff01523c0905a2c728cb Mon Sep 17 00:00:00 2001 From: magenoxx Date: Fri, 4 May 2012 22:49:04 +0400 Subject: [PATCH 05/18] [load] one place server ping --- .../src/main/java/mage/client/MageFrame.java | 12 ++++++++++++ Mage.Common/src/mage/interfaces/MageServer.java | 2 ++ Mage.Common/src/mage/remote/Session.java | 2 ++ Mage.Common/src/mage/remote/SessionImpl.java | 15 +++++++++++++++ .../main/java/mage/server/MageServerImpl.java | 5 +++++ .../main/java/mage/server/SessionManager.java | 17 +++++++++++------ Mage.Server/src/main/java/mage/server/User.java | 4 ++++ .../src/main/java/mage/server/UserManager.java | 12 ++++++++++-- 8 files changed, 61 insertions(+), 8 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index cfadab795a..3d488bf525 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -85,6 +85,9 @@ import java.io.IOException; import java.io.InputStream; import java.util.*; import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.prefs.Preferences; /** @@ -116,6 +119,8 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { private static Map tournaments = new HashMap(); private static MageUI ui = new MageUI(); + private static ScheduledExecutorService pingTaskExecutor = Executors.newSingleThreadScheduledExecutor(); + /** * @return the session */ @@ -193,6 +198,13 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { desktopPane.add(errorDialog, JLayeredPane.POPUP_LAYER); ui.addComponent(MageComponents.DESKTOP_PANE, desktopPane); + pingTaskExecutor.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + session.ping(); + } + }, 60, 60, TimeUnit.SECONDS); + try { tablesPane = new TablesPane(); desktopPane.add(tablesPane, javax.swing.JLayeredPane.DEFAULT_LAYER); diff --git a/Mage.Common/src/mage/interfaces/MageServer.java b/Mage.Common/src/mage/interfaces/MageServer.java index dcdc922888..96b421ae0a 100644 --- a/Mage.Common/src/mage/interfaces/MageServer.java +++ b/Mage.Common/src/mage/interfaces/MageServer.java @@ -51,6 +51,8 @@ public interface MageServer { public boolean setUserData(String userName, String sessionId, UserDataView userDataView) throws MageException; public ServerState getServerState() throws MageException; + + public boolean ping(String sessionId) throws MageException; //table methods public TableView createTable(String sessionId, UUID roomId, MatchOptions matchOptions) throws MageException; diff --git a/Mage.Common/src/mage/remote/Session.java b/Mage.Common/src/mage/remote/Session.java index a708e075dd..1a1030ad77 100644 --- a/Mage.Common/src/mage/remote/Session.java +++ b/Mage.Common/src/mage/remote/Session.java @@ -57,6 +57,8 @@ public interface Session { boolean isConnected(); + boolean ping(); + String[] getPlayerTypes(); List getGameTypes(); diff --git a/Mage.Common/src/mage/remote/SessionImpl.java b/Mage.Common/src/mage/remote/SessionImpl.java index 18634df832..ce088048d9 100644 --- a/Mage.Common/src/mage/remote/SessionImpl.java +++ b/Mage.Common/src/mage/remote/SessionImpl.java @@ -1090,6 +1090,21 @@ public class SessionImpl implements Session { public void setEmbeddedMageServerAction(Action embeddedMageServerAction) { this.embeddedMageServerAction = embeddedMageServerAction; } + + @Override + public boolean ping() { + try { + if (isConnected()) { + server.ping(sessionId); + } + return true; + } catch (MageException ex) { + handleMageException(ex); + } catch (Throwable t) { + handleThrowable(t); + } + return false; + } } class MageAuthenticator extends Authenticator { diff --git a/Mage.Server/src/main/java/mage/server/MageServerImpl.java b/Mage.Server/src/main/java/mage/server/MageServerImpl.java index f1283001eb..9d67fc2369 100644 --- a/Mage.Server/src/main/java/mage/server/MageServerImpl.java +++ b/Mage.Server/src/main/java/mage/server/MageServerImpl.java @@ -248,6 +248,11 @@ public class MageServerImpl implements MageServer { } return null; } + + @Override + public boolean ping(String sessionId) { + return SessionManager.getInstance().extendUserSession(sessionId); + } @Override public void deregisterClient(final String sessionId) throws MageException { diff --git a/Mage.Server/src/main/java/mage/server/SessionManager.java b/Mage.Server/src/main/java/mage/server/SessionManager.java index f8b076e6cb..00a7d04ccb 100644 --- a/Mage.Server/src/main/java/mage/server/SessionManager.java +++ b/Mage.Server/src/main/java/mage/server/SessionManager.java @@ -28,19 +28,17 @@ package mage.server; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import mage.MageException; -import mage.players.net.UserData; -import mage.players.net.UserGroup; import mage.server.services.LogKeys; -import mage.server.services.LogService; import mage.server.services.impl.LogServiceImpl; import mage.view.UserDataView; import org.apache.log4j.Logger; import org.jboss.remoting.callback.InvokerCallbackHandler; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + /** * * @author BetaSteward_at_googlemail.com @@ -148,4 +146,11 @@ public class SessionManager { } return null; } + + public boolean extendUserSession(String sessionId) { + if (sessions.containsKey(sessionId)) { + return UserManager.getInstance().extendUserSession(sessions.get(sessionId).getUserId()); + } + return false; + } } diff --git a/Mage.Server/src/main/java/mage/server/User.java b/Mage.Server/src/main/java/mage/server/User.java index 5a6bf71727..f653f98305 100644 --- a/Mage.Server/src/main/java/mage/server/User.java +++ b/Mage.Server/src/main/java/mage/server/User.java @@ -177,6 +177,10 @@ public class User { lastActivity = new Date(); GameManager.getInstance().sendPlayerInteger(gameId, userId, data); } + + public void updateLastActivity() { + lastActivity = new Date(); + } public boolean isExpired(Date expired) { return /*userState == UserState.Disconnected && */ lastActivity.before(expired); diff --git a/Mage.Server/src/main/java/mage/server/UserManager.java b/Mage.Server/src/main/java/mage/server/UserManager.java index 3a2474411f..8ff58004e0 100644 --- a/Mage.Server/src/main/java/mage/server/UserManager.java +++ b/Mage.Server/src/main/java/mage/server/UserManager.java @@ -124,13 +124,21 @@ public class UserManager { users.remove(userId); } } + + public boolean extendUserSession(UUID userId) { + if (users.containsKey(userId)) { + users.get(userId).updateLastActivity(); + return true; + } + return false; + } private void checkExpired() { Calendar expired = Calendar.getInstance(); - expired.add(Calendar.MINUTE, -2) ; + expired.add(Calendar.MINUTE, -3) ; for (User user: users.values()) { if (user.isExpired(expired.getTime())) { - logger.info("user session expired " + user.getId()); + logger.info(user.getName() + " session expired " + user.getId()); user.kill(); users.remove(user.getId()); } From 6d7b5a403da722a253de3bfb2c35b829a9da144a Mon Sep 17 00:00:00 2001 From: magenoxx Date: Sat, 5 May 2012 08:29:15 +0400 Subject: [PATCH 06/18] npe fix appearing on server --- Mage.Server/src/main/java/mage/server/Session.java | 2 +- .../src/main/java/mage/server/UserManager.java | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Mage.Server/src/main/java/mage/server/Session.java b/Mage.Server/src/main/java/mage/server/Session.java index 0a025f3d73..3d0bbad25c 100644 --- a/Mage.Server/src/main/java/mage/server/Session.java +++ b/Mage.Server/src/main/java/mage/server/Session.java @@ -157,7 +157,7 @@ public class Session { call.setMessageId(messageId++); callbackHandler.handleCallbackOneway(new Callback(call)); } catch (HandleCallbackException ex) { - logger.fatal("Session fireCallback error", ex); + logger.fatal("Session fireCallback error: " + ex.getMessage(), ex); disconnect(); } } diff --git a/Mage.Server/src/main/java/mage/server/UserManager.java b/Mage.Server/src/main/java/mage/server/UserManager.java index 8ff58004e0..c5de8f16c7 100644 --- a/Mage.Server/src/main/java/mage/server/UserManager.java +++ b/Mage.Server/src/main/java/mage/server/UserManager.java @@ -100,12 +100,14 @@ public class UserManager { } public void disconnect(UUID userId) { - ChatManager.getInstance().removeUser(userId); - if (users.containsKey(userId)) { - logger.info("user disconnected " + userId); - users.get(userId).setSessionId(""); - ChatManager.getInstance().broadcast(userId, "has lost connection", MessageColor.BLACK); - } + if (userId != null) { + ChatManager.getInstance().removeUser(userId); + if (users.containsKey(userId)) { + logger.info("user disconnected " + userId); + users.get(userId).setSessionId(""); + ChatManager.getInstance().broadcast(userId, "has lost connection", MessageColor.BLACK); + } + } } public boolean isAdmin(UUID userId) { From fa1e064b6b998c4cad7933727fa14f688e3fbb98 Mon Sep 17 00:00:00 2001 From: magenoxx Date: Sat, 5 May 2012 14:13:46 +0400 Subject: [PATCH 07/18] [mad ai] removed dynamic aggression calculation for now. doesn't work correctly --- .../src/mage/player/ai/ComputerPlayer6.java | 8 ++------ Mage.Server/plugins/mage-player-ai-ma.jar | Bin 58681 -> 62790 bytes 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java index 7eaa221dc2..987d64d055 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java @@ -994,7 +994,7 @@ public class ComputerPlayer6 extends ComputerPlayer implements } int aggressionRate = 5; - aggressionRate = getAggressionRate(oppScore, ourScore, outNumber, score, doAttack, turnsUntilDeathByUnblockable, doUnblockableAttack, aggressionRate); + //aggressionRate = getAggressionRate(oppScore, ourScore, outNumber, score, doAttack, turnsUntilDeathByUnblockable, doUnblockableAttack, aggressionRate); System.out.println("AI aggression = " + String.valueOf(aggressionRate)); @@ -1019,7 +1019,7 @@ public class ComputerPlayer6 extends ComputerPlayer implements boolean shouldAttack = shouldAttack(game, attackingPlayer.getId(), defenderId, attacker, possibleBlockers, aggressionRate); - if (shouldAttack && (totalFirstStrikeBlockPower < attacker.getToughness().getValue() || (aggressionRate == 5)) ) { + if (aggressionRate == 5 || shouldAttack && (totalFirstStrikeBlockPower < attacker.getToughness().getValue()) ) { finalAttackers.add(attacker); } } @@ -1048,10 +1048,6 @@ public class ComputerPlayer6 extends ComputerPlayer implements return false; } - if (aggressionRate == 5) { - return true; - } - for (Permanent defender : blockers) { if (defender.canBlock(attacker.getId(), game)) { numberOfPossibleBlockers += 1; diff --git a/Mage.Server/plugins/mage-player-ai-ma.jar b/Mage.Server/plugins/mage-player-ai-ma.jar index a878f31c038f8952dd6ce5797c304d129e653a3b..165d70e9fb69b17a233724dc123e152061d368b8 100644 GIT binary patch delta 27450 zcmV)bK&ijE$^*vg0}W710|W{H00000?^>mi4I~ZjTBSfNlj-GV0001ekv?c9iIqi( zB~XzRU9XgEknv`E2Jyy*yj;9o08mQ<1PTBE0002)TBVWEAOr4NrIGX}1MXU-lO6#o z1MXU-lS=_D1MXU-lY0R;1MXU-lePgp1MXU-llB2G1MXU-vnT@P0uAn3r9j#(h1gaC z004WFz`qugFa<^g3om>Lvw8(B0txO~r9cm*Ho+;Aum@#-+eAOWr11fehP2ylplLz~ zA;A?_NQ19~c;8U$7C|-1exB!dbk6ZP@3H;#<^6{MX3@ywIEp!p;Nu*nPZ*~pOf$@| ze9H0}%L$gxSx&N)SWdBg!7|J8CCgV5PD?l=aJ*>z;70aF&1x4nt#zlU27V{9{HRlG zY*-z~h@7^6KHLCX=?S9N_y?{2O+p=qso)G|^HY|FcB zg)Z}UW6Jqa;BfW2b;BxomcL%o1EXtiEV_=@(!wwZ&kGD1k!62RYw3-lEehm~pc~rG zqRVsaSA)%VH*&&;XZ_@ab0>K=T7l-nLDS)Gp45hx_9sjppk>`36~-t$PP3yk;ryz{7XJ96fR<#qWnN2X`4R^K;<>YiC? zENfbSgQ~MvFDVFHu5Wq0^QRXgU2ZB@1cn+-O|91!6mzwvRCGFyM%^%Xwa5$FQbk{V z-6k-)SZNyOHm;VHnzE$P;lIUsO)tkohRd2-RhrtWVw#G&tThcP-kY|CYJIyY%VW%y zCOuP9tD2&((5CuVbWeH<)%Oq5>G~f?FZVD**q@+8m`N~8C?%L9%qEy8EbQn#z`Uco%)F!jAagF^A?AF- z`J$;_%941N827lKW&!&D&$l2vpG#ZN1sZWTtfo}xruAA$y;cnLmuIO zXWS&;!ms#^{1$HGck(;<6L-n);SnB_KfyEnN8X2nRK`rNn7?3xbc>!&CB`8fCY_4Y zT%5ExnQ>~xDT>opoNmSGPMq$==}DZP$LR>&DkiQ-IEvakME{>c8~l)_N?{Tok*9Ht z^lQ3DsBMI)kfjoz!-e^SuK-X>0|W{a000080Pb3)K!nw36S_zM08pHh5rh?!Fa-~P zE@NzAb91%52Vhji_Bec|+`XI4O-NyZg#B!C785=w#;5fpJr77|G_n^3H% z*sy^eR75PXpaMbwv0%eqo?_Q$*Qd{3k(TeAxn)Z>fWQCyeB|EgbLPzHGk4!h2OoU` z00wKHc-XW2Po4ke0nYctftUZS^SvH_(9Fy}dhDmi0eT#yM{Aq_!7wG52Ok_q5Q4Ij z9-4=(6fPW1=<#fd&~@RaR&n(3#EW=gP^$!wNE96?n-s^M6UmfH@rYE>(IdQ~6E*Hk zWnDbbU-&57m9pL9MVd&D7u`hR8fH93^ZrN=;z$P-7o#h`fjOAL04qiOOXZgC9d^WEZDdJl~k!$g5IaiK1T$HORU zKEf?Vy2Wuc=J6g;Bu2%H(IQ{-QqM_ z(Hsv{2%lS=PN4d!?_7G6d!SZS=psPxd9(rZ=~3wxRUUDMSl|)WqJ|pQ#$kgxnxQ8> z>Z$#iRMtSi1}UYvMI*fv+@gtgaiLo*qWof-dWl<{MSadD(9ZFQbH#aols#X;OsS>R z^a7gdLdsqw_Ii4NTP!0mE*9D!pJC<_YJDj^F7v>x;&P8zPH4SC7gy4-S9!$M;u>A7 z(8Wp`=-PO=MO^2Bo5l6<;s$XeWoJ?9CTe{%WmkE`YHg zeCZZn(c-_>#W(TryLgXq^dDV(OP#-?$A9VZJ;{L|;>C~RCwlx$*Ga>$e@`yd+Z#U8azA>{;?5ETL0^p!Wv`X+uCOJKXE~STc;CrZ8)1}M9 zQurQS>K@jS3R9(9mvJ7}nF>7~=A(DKhjpX3;bH0YPVlfE^iK4!p7ieEVZG^{7C+X1L&RVVcGQV=wZ3^_Ien~5(1|aQoig=k1o1@^m*72s_yDx`FsxoZ^GRz zPIzS+qFJU>syn56P(_9=d&bFLvbTpFC;L$QzLf1p+5VKxq^1K9Z8D2e+4RWq$Xq#) z9(f*llpGW<2g{>%IV7Hy%40~fjHOh*E=g#~p;#h^QR@Oi=28^OvXF)zPBV?5$4DCg zIBJ|l?T)8^R1u{{>2kD(olaOSk|fJyu}6-TC&bHfvV^kZJ#vDaNKGfXiUuxQii!k~ zTV<(RmZ1=klil(}kDMZ>dgL@o0!*Gn-DbGu$u!B#I5|t6;*qDy*;IEL4K&9iPnUk2 z0m(zorPiudISM9OL3II-oG0gdWTmWf%QM_^fm>F8yJd}A)>8L6YEn;+GhN4n`Jd}} zx0QJEXET8eARBZUBxE$|vdP1)lZAweg-Gdgky|cy^LMCniJR>r(2CshERt#`yXDz# z{vNeI$1Trw%k!vqC`msO81j5t)>5~;fM&Rm9v4yZG8*<`dR(H*OWpD^4|`BvPLJif zyu!nOHp(mMaTPtTcFSuBcPrd-CB3hu$8~Oby<6VkmN(LTH=&G_H+$qNxmuUEc-T&& zxm!K#X?owL%QXyufl*Rh8)z6^?GFY6K?bhsy7}`14Geq}>l)_g`Ro1Vm4Q4g9CAk9 zL{(MDK-SmQRWs-}@eKb$e_m5#Rdrr@U2S=PQ$s_bwlS}GaiF}Zv94iipkZNEc>vqH zYy6AH1nL_r8L$#FTUu8^(@}C-WmWA02D-nzv8t{X3p<64I}z&w6%(t1jW|x+)T;Tl z{>G+;0E44qHX1&$#y>xh=bu|uUDa3>2<8>3)RMxH*j;b*H_Q(-;tWXXAqKbxp zhvlbFFBxNwP+nPwOTrQQ#BN^gubrPawXvb9cE0N1DzB@V>u)49;% z!bXOLxvAxKv@+RHSBE_^%pUdC{w0BiJbzW*)T)}MYCqwKkT)1Rb{bnUv3Smml4;}S zl$1`LHf8$gX(eT)QyHYRg@!@GXvA)RqrbNCB!6`i&J!1G!ugpS-YO2l( zG~n7swpm*gBsKoLQ2V@T4S_(Joi$fIysEaUailKSBC4k#5P`CJCAIT#*4lEM_}I+0 z;F(j{5C;21_bMzIa1x?sG_IUM2i)*LX;aPIK*Kcu-0A==xUSq^eUiVSit<)}p=_+I z3dTSrBEG(Cn~20af84Ymy~sH#RL=$(1E;R$e;S&f@8wb5U` zV3NOH!HC8#PF`MDzXS;|bHL#Nb=Nmk)ioeNGf2%0BdHVcQ8-{04mMw{Bq_QMXU49z zjb%-Z2ti{Y1bC=TG{q~7m-`!kkUo;lwv-9xjm9Sq$gBD==or?i1VQy9;}fUW2O4Vp zwMf(qdW6m66z5I0Q)*Refh84@oT-3d;0;GhFwaB=^{T6{uQR7$L0#T~7CssoXx{2_ z9U9dP1WJ^cc%lt4r46m3kz|i4CKz;SPk`W>nu37}wS73@ED}=@G`FOG-ln_E7&wV3 zAJCS^72_b~#Mpr%TL8w*MYgo0fe2RB;O3#+Dh(`dEV7`Mjo1S1b8K6KyoOS=XcP+M zHnbQmGfGNI2A~Ai=h$*dEc7E0Yvq+y)fL1Ciodi9Q&?;M>iSAQ7S9bd;;VxtrzShQ zr2rjGeNjzf1B7yFz~4}RURgYEUVxBa9FF`T;Vw^M(8lV3Xmkfu$6#LgFeFg|)_^_Q z>)na!J~Uf3x;_PWaEo5uc)v{XrIu)7 zvA8WEf^Qu4(q^20K!w?YRJ)rv%cK#Hzq;BH4Oy{ub1Z`gT{^Vxm7Fw{^I>GZGK7e} z;~eGAAm^Xxeg-2BH^Cv`Ra4_yCzB~k41va0T`-`_yOCp1Z=uM-B^;j_t(>hY3;ULZ zmN#0x+iVHxNVGInC8p}^VY{?9stJc`?TtAD*>4KaX%_!~)*KE(M~MatW3al?uI*td z)^ap8AlX1|hIiK0b_QHmu&$|r);0Zq25=-`aYQvIPx9BI?x@I&UQ&5o5G5np0RB8% zhf>qgSlnj$VQuX;!I)FJYpiOr&<{UIp|ZZHbquwNqIO4N;1H9hbCA0g8?$LLSv#_ugz2Ch=oKl;T8r$ z)`&!tyk3|0pbD%Alvn#30;8(y$`>e~MxSdYNt`y4-ZZj6&zs z!*%?}^Pnz~&AJ?dw#mGzYSiW^A4=}{DCn1^(A z_YrE9LoDGRcZde?NOmKHo#`fWOn)H3p%`;H1`5rh5E1oi>!7qnPG}6`qgbr=e*rkj z;*+9(7$jZf>;|hMj`GuR)6k=yQrA>VMrBnnY?Y%M0#;xq+1k4qRgINnr`9!$LC2(P zV_l=adTdn#x;5zhEHL?Ha@``e*}=-Xrs@iFJ~Cfb0_n=Ompm1Ld4bvri(~Cf1YWC1 zhU?^!t3@Q2BaFBc=9p$}YDC!>wHy>8_Hbi=>l##Gr&DABJ{7E}F3rt#RE3$PU`|b4 zZQfXwv$24^oahd@sg^ItaO1qu__cuV+@{6FqZyPRURlIc9!DzH3QI^kC71haM+K$?(5_rE zfyjgOM3oZGZOm}hb!t8X7AnB3`Cd`fIUvf=#Y}h8l5{tq8i*s%{w*ReBs!-3uYoHucaN#+kP zuT~*&1}7bQ?l!UY=$P-w!gc`jOjYjNScRUBQQZa-$OijT-iLLu4oa|&e`&Bc*uN0* zp$0__4gMu&{ciTQ!QQ3EXY_c5y=t)6*{iy|-(as{hJQf=d?_C=*oRbq_dff;V1LN< zhI~*ygp7CSbzx8Tiy+%Idz9?VPJ; zC|u-p*+UEaTkb`F{6=q}bHX?Ru~$eq9YH&{5BItuP*b-sP?0gOp{^#wITgd|{YYgz zjqNe`NBm<$9^hZLCG05qNGx&CU>~uM4fX{3s+VZ6IMJPdiH21{vC5${a19L*6({T$@3E#-YV)pFu=ALk#v3ds)|xF|>T` zSX~=xXv4GuqA@aU+l0m$^gW`p0A&AgGRkPAQ-%y|VQ9lOF&J!JE2WSwE?{m9dWiOMO;jz&2yowL$zXP zlL$5~=wC>lRF8JIG*@k@!9HhiGsrsfGY&&5mERyq#<0l@t>QGCfYj2S1Jbb#01& zp-rWdY5WI+&*zm?T!T_jo33kUoc20=+?BzA_8XlLKr^|j+-_@VGqjU+ZKk2k(oRAA zHU(+vCnIKb?NmdXO?u@t5;JqO({*`|q4~ABx>jyz6=(rz0r@yOs!`$A>GcZ&f%>UP zhK4pzLp9nts==5B|01fkHFQ{{wjp4Dx;c|jm#DC9iG;V%Y`BN~|lMeh*KWCzpR*0h7}>VUtt$=dcah&VcEsKKk{<_HsiGlYE- z8P+6SqA92rZA^2d8Vx0PomP)_H(z3CXHx2XN;MD@hXD~-NJgjfNQ)_{Ye7S6(iW0j zSY&96HSBX1X0@}ma}4cV?L33uh~h>&pJdK8e1*ZU=PPt=si9qU1MO{8wJjyzsyXg6!C3~jY`i>}>jXt!x=bnSLSTdUn+XzR2)b?q*M zjhOgK?QS$rH4P2jqLPeIyiLi#^*)U}5U?O|<$u5CnnM%!fY*ZAv(_K3Dwm-ial7TUkQQF3ZQ zV9BDoh6*HriX})Mww0x|Xd4V|tG3P1w#(0T?NNedhps(FZ6z~Xv}H({H`x(tf_W}u2RDyx z!H;TXQB`9lsqTQ%-3-RJ-&4(1ZtWdSYC>pEokImx!HlNxIO)*#2Md}3N9u(w8c?rg zs4$YbN(Pzjbgivxr0#PA8P*Asp*^ADX71!a8QN2%a-QZlMaY7Gh{%(nJ);cQLj|S` z?OCGf=g4|Z&alo36fi^`hPF#_QFyr`8!{MVE&8^G_B_pzWX8-aI2%-T#=>9*8KN(s z-seA|rV4Yrki}m#w3oD(4eb>TJ(t(C*NG)?BqKZsgc23a%XwAvQDqOTr>zVOEXVyE zNZ0-bHZE2%jvowv?F}N$WucpZVgQWjR*)c?+~-StG$ON zkON^s0WARkLs4j%GHM^KYR5D|&;Jkhr=h*CeV}U}8rnzN$7q@z($=V|sYhXh!moOI zeOc%h#!-jwRD5PrRd|>5LplhDcOVAq+P`hB*8W9E6q1pD;Y0Ly+g^Ud!nW52kTl!5 zVQj0ZHun+GJdJf0+}0pD16&)*pYfWam1K6 zgI(m_(3yfS{7&KMx@zJlx-O*-;1PG$olZB!Tg_zW%;;dTswlK=*T`Zl4F($RyH26A zj?~OHVO<1&I(c4IgL5%Tsb{pf`b+nKcVnF$!i$W?J6GFNV?c*Ht8P43*P0Yps-zR8%o9;c zQ4rOC95lw&sif#KlfSHF&qE%-o7<=(m65|)YEWEw5SsmTDER&w7N1Gn|UJc!so`~{X>wn%M3Xqme6j; zsVJtzV9YI9= z`=XuWJnQmJ=XR=0su}I9KT$ZIy1+c8mjthp$(=yvN0tgLSMPP2ze6mo{ z%59(z8!9SH1~Vhz!LYAK+KZ(VuXR6vbPLCRAkOF>fi^2vULUBgR`rDu&U$n$LReo{ zZ+;JeI=8WbcGdh~z!3^KKunonF;sj7>dVoUbwMR;I@p(E905_(mNpMZt8K({k623# zyHju5c5UD@&z;5%l^PV< z;kqNbTWvCwGg4w^*g)!Y1}Ceoj4SrmRgbC)ljrS_Xvwyo&> z;Xh5JOJHM)M^7x8Qaq=BXxg-*(G!ZNpy23wC?_zlU0 zN$Ck;cp_4@MyU87K|0`2fS?R)2q5W{A0gaG4jst{TIjD*sw;GVKkpEsq3M{UxL#dD zT(H7h+g%lOLq$|xQ)YYNX^OKD^<^OQ&{m_9bVvPtJa+1G3G8}*fWTVqj zg^CPBHaG1W7h+;UV2Qfi9Dbn1;OKvDHIX==>#1fi`Ur&ZteBqz9P(CDTRd4|6Q>1( zLG1~cb_Ydi&_+Ukgt2C7h|a?Vh&P;y!Y+Y!urBqMIo}-5_{X~+Gw2e-W9SfdD<27< z(qCIq9VlMxuU4PycQXBbON54b{l{fSTuwJnnPTF(P2|a%Tz#An^3KxQGdeJZt^2ie z;xop&MDKX(WZ;x2p-j3wtL>=m^mJl(G3F=AMDN01MVCr{yR{7m*xwk>vQ(|f;YlHN zKR-}n%9`*zgTeold}#)6tQb~d+p~LnonaqCI7rHEA1!U0$FNi!9M&}<^+pXo@DMm} z+uovtX?0EWD{BK35RJDz6vYpYF=~BPf##c)D73E-MAw+w(lxoPWNKL{ZdmO8mOkth zdvNkg57AG5eQmo4*|en53hpyl`9HrOe;8sog3k&P?NC~vVN8|!Bw_NP$%BwK?chu3 zbD9WZJPbHaOeUc!XvutY_&)7yJSns=Ce3W0L?T)iwm%{+kZ#W(!H5wpt&mLkkxj$@ zdf$#@V~EJdI3F0rR|Ufy9Cg<$q!B2Tae{T<-?Ev1ipPYj61N&l{g8qYHmt=;Aq%D7 zVPc1l^V+A43Y-<|^KJTlgGS|tb;*paa!#TE4TbfG#+dpDfl&txemjuR2Dfd0ti}~s zT&0|^7;NklP21IpHI!A3?&^w*SLN-47WIAwsAPEE-$2I8b4_{FBx1Bn@y>!{L7_DKXN2 zfu7)H&jN$LAQ4v6g<=5cO1C=kE$LFedcS79U$@?Guzvw#Z(^FWw=ln(z3t@R!Th^n z`S&pYKKnrR`_StD5&Kxp^KbSEw)<4IdkFu3K8xtrm2S^D`7a;~(_gZ0@cADsc@zXl ze3xcr=WGR*wFCIftsu65#H5Bv*A~!2Np~n2mzAznZUIkLdNRwwM10mJFt$KKRtqF< zRC9fc&n)oZGXO4_3dt}H(qKCDhf`oM%!6StA4WkXlt2|sf-~56s=b1S{g-{Oe%-5o zl>I=fQ>h=>Pry~`XT*R|>-fc5#~+xdx^uF!w?c=1w2bV{;GuQkJ826f;VXF;BvLlH z1yZ&^a@J;$n3FA#Is?->ruFU6kpt!2m~)#sTD4HC&W8ki)_{hqPQq1p!&UdgRS$w% zTy-53K|PFvGqJV-=0FfzHp1D^q%hEb$y|qnfpgfem}2ZVOm_g*ik-O5e#d`U!07IR zg@U6ACHRip0p6KeTcOhiXBS+mVu>2xFdJFpGxitTV-CQ6K-}Zptany+3v|xj4qecA z*$loG=$cLM?a+4YbOtTZy#;y8=uY%`6ZSX1}mupRm`eDq_m6Z%t~+LO%HxSa^_0D5G}_2z&v zhfOty-3r-M-Ha5`w$D(o9U|gpcpg^4i*O6P54XbSh@NlZcK8AAfS=$_ zXob6&8}4QupqZt@eas6FFdwXE-QXeC10H5QVPlAFSZ|wbF!eXvYmrSyNZbe2+CCWV z(zG6e;Xh470kV(nH@9AN!>xBCwZvuTY=>M9rJG>j4#-0mIVxpP3k*ho6dg@lKBNVX zX@UH(+GDBK?6e(*Qk%gk!^|EwkrYr_AwF`q!|{MjE#rN+Kq|(V2XFfld=nK&N3lOr*w0g^khhOj7Wu87W&z zWB4K@Blmp zyOEUtf+tx5>}37nDV7aSvlHPNRsqkl^WiymIqYJ$!wc*_c#%DS2`{5YdX4=EuX8uN zp$K;#YKYyC!49y4pu-a|jI|;qyKszUoN*31D}>w3G9pl$z%iu=oKo~n8j`=8GxjUj zPz+wiF5oUqNo1DOP+EQhnqq_@pts@_I>}G&)_I)HJ?dY)&QWshg`xXlG$!{$xy}=M z>;>;$99PmeXeOh7@G2ZrNA{XU!eMnManVPl}rk6%@j49N@ zndT&7kQSKXYypzY$y7CS4IGn=nOVdniaC5ZbFZ)wPq8>;s7*-NJ1ci5oQe{0_6+QL z8j*}93ADKFFo(fvSdse_oW2?^H;epC4Qml_BTYA#rYpyP=`<==dWwe452&m8+mnSeGihi`5VZK7&DX>t)RcUrAeKO4^Oewjs)^MZF2|7TOKF zp$ohNUEy8m1Mfi|3d*_g0oH#A=fFoOa6g8%@G0C6pTQ$2K)1jb@B(~^J-@=5ui?M& z4N~ZTP-K38%d+4*HU|F7CcqD@6n;V~`Tt zfA_=1I`7hPKcpk&?Sn;s1Lu2@$RvILXsPCLl*dkgTS>_+SPdKLT|H?V)Sx9(JCo($ zHhN60RksD|H^G@H4J{DNqN40=(1>;o;~8 z?W9~EVa|*~dlk^w=){ts8%stSPk}NtJf^XZuz+=fRje}_BVEuK=?d>4H~kys{m(21 z_OVb1ecC^}~3`A@<_>!lik3w9*9IjwtJCg+3e#p~#_rk(`FrB2iq_3|mSvv-Q z`7R!NaHRE%x5FhwRhOcPP@u5_7nWVN9WF(& zn&E~`a0RY9;V#NtmgRG&$qKA%fh+SpKF!&?ps9K*OjIMzIl4%#OpoDuO9&G|H_p zP|b=V$i^a-o&aZ~2)U4zpk^Eo*P!IPk(I$3HW}_<({L}R!~N_eG$?1l7Ird!>_BPw zG@Fh3`ZV|#n*+PqTzHq2!>6dzzhVKDmh*70>1Tqg)n3=*E)9p9c~A6DY{lIe&dMF*<9R=qB=&KTcS*W~+v*ZjEEhrOwiAYh|JjZg8t|bb?cMk5+4J~lv zq#TQK(sC@qxv3QO;?1kTjq3B}4b82eu?-IHQ>ZUH6ZxV6da)p6v4xI2>IMeS|V28jKD)`!vEv@VX~gDi@lhx!FQM^=RTcNLL;`|6!=3n}KO z;MP@4%ks&caBB1%HtKTodNsd`U|9cGD7FOETjvBtL9(2a=fC6vtgBEo(W% zHxD>`)4#xtP42|nIO=g1mEBt4$?|z<)_7k$j_-5(JVexY`{ERoRCf<)A*A>;YaUcX z1%{$%!=h-T1)6>H-Yz6DdR{@mdyN(^tu2%%G z3^(l}SgF)d9Qt+_^TD{;9&~ok77Z3SSH#S|mq1AZ(Y ziksgP{z5Vug2awQ;4n&&!ze`#qZBDIit3LYjifcqGAjClXvKlu%E0Z;l#yGY^Fo~u zw=CR^f0KPhpR!wjN^mZa#SAci2W*(R12$6VVAEE3Wa1{+j9O(22U6|v7jJ@=7TCHI zwr!Y1V(d{=EZHbNcg$#ptVua7@VK2zqui5rE|GFinK`iyp5B1W;)6^WfV)fYLUbYM zU4nKwy=NfBndV<9q#}4XK@wX7KDHJHuse|D*1=$QC(^}#gHXusLQcOMY55))i}@1P z4CC3o=s(?u>gqw5!R|)``T;nVtw(d>VYO2u5yx4O$dBX4BNm5)2UA6u8n05LFm3vwznC}W3 z=tb)0a?A&>Ykk@p=xnLJJ0XD#^OuO_$A)!!89QMUn)Ve75OdbBIigjtjzgO&SlK{B z!iIa5%o@8YG*-5)a9_)JVdt<8uiJ}$!(MdCzifnmys2D^P%L47%3J2}yB(?LPI>KtG`<;Eti`Zw52rNOHoHV}+Sy?EZt;2)R z(JI@AWh#YiYNx)mQ^XIBXFtXkcbKxo9i}XQy8u(BT`-uZC=sZ!-(j*f9x>y1h~E$M zQ1W%&2cw3!LK(O$X~;+(BA0Sx%FyLdp19fjcXQOx4eS z!${)`+I6>0Rw-YqtnFT}pqk5-hK@7!H`_xgrN5^acocXYBfwkH%-+s1)jFzha=^b* zkoY{?;cM!4sxN*!d?RqE`zeK*HOm*DjcT5nZiWA>B75~Zs(OjciUHJ7#T0sk54Rot zYoJDIz2D6+ZGw28C*2z7zXe9vD!&hZss0~mg`Us~af}}s%*gT?>9WEry&9S631NMI zBIZeOcutM)n9QZ#1h2LZj`1esYd(YQ9BTSAnIJYB`+T~}I?fr|>|~}VkY#^q$g*F9 zL&>|K9EXzsY^Zed$Wdw0Sig|2w2ggb73roSlBwk!zZ9RGuc6^1opa`E|Bj0?3#Qq=jC%#3|^IsT`lAfJfM$=#TF~Ir2A}NPE#Q*av^I{qQ$Cz!F$1>&}4< z;GB)%g3aZUoxwG>guB>vs3g~aaW}h<$FT>whi&BXY#TS&b3B24h#dVnPi5cnj_e2C zh5gQbY!7<*`*}Amcp6XQ>AWBB!w2%dXhQWvHQk?2KshrFO{P<@w4CShd1!jo@FBb& zTbzYP)rEX0znB;D%lL4969nxE20eDQog;Gn&u9H-HhO+SX zLype7?1xcUnRfs-l6*^l9-5mR*NXnrA^IV}y^>Mjoy=zfSI!cOX|h8pj_{k3IHkFk zztA<;5;wn-Z$cvP4*|`sUxY$;iE7cOph7qm2Jq7qipi=?hd4e9%OxT&Lm`n&&n(AH z%CdvHkfjj89Ym-SL5a*Mwp9I}DoXD-e7Uy6 z_ZO{d4@tGonE0C#iI~`Hg`&% zqt!4KihC#zumgghM|8sIH%OdwOi9`h{8{Q)aSp*6pOu}HyA}4WN0|2$T*YSPeJ=;&pjbM%5H zYA#A_>D*%H!Gx!Q@(6hg%lT3hRFsbL*_V0nd2a3j)Rmr4pQbQR7e{Y zolI2ZS*WN*iZo@@U)Y+EJv(PR;}L8y&SnE92mw!jF^Mh{n&8`+hGKw)S>UNmR|Y(D zx3D%-+u8Y3sdmHFxR3%#+|9tRohA zTUaN%NoUM=QB8*1O?+6?HLOWD%%`a)KD#J?9gDiFB9~p%1B)`K2!6ARdSX#8RrIM{ z)EkTXsG?WxqP|$vPZd3CL(m_KGQ%JkfcY%du7>g1mML9i2JW3F#jhCpci+(!2X?eQ=MWt5n6NqXF@G#hfO+%?v+a(5 z!&XC?bMU^272IRWhpnuz8TyA+40kFv#*?$hW~gd^@!8M`1gE3|{1q z!~6UR_=xX>@A*^k6MqI;`Lm4kT`YxvKhM1UMb?+U#4`CSY$$(~74p~F1pWp)h5w74 z#@}MKd^bCfzs)Y-@3NKrJ$60+01e6y*=GI`dy{{R2IRj{4S&Wp{yFc+zu?{ZmuMb- zg%ai)K8k3hY;;b}Ha2EEE9OWn-(_WQWn)Wop9i^-k|z|%tZXIy z$C(mPsRXK1g(YO#SVg&F8yio5#oO3~0xfh{SKt!)Ithh|+u0;$MTXtvVk&8R2BnlV zEmi&|D^u4RwzJ9D%tHkyQsor#D|f*#a@wZ1N5Vmbr&K56;Fcd>hmC>O_d~+>Z`!8O|^%jj)3{^kjnpnKKxI#0sn%5d=KRDeJE=8ql!L%fGYYROb`sF z2@bP`fYXEv%7q)MMI0;^@o=^z+Rz12)TR^z z8JKETeuXpZl{l-K3(KszFlAEW)!l*+mW?FOa0*;s^@%4)1Tcm))H(~q=Qra1P>_ySi> z^fBKc=eS}#lBx?ylxPYmYMQ{>B((XgV>G49Uv6%_-zPUj>T=N?;zSSVFEXH5^n@9r z7x+bQI79STBycJMSd2E{N+ba{y6V$uQF~ZfYF;UgNkgHHrkdN?oXk zAr30|wL}bRLo<|rxl^My&uXQ%QMGcmu^VUy$O4+74!F{Fo`8uVe*+Jh0@vZ+^)_zd zR~&*Bc`InlW77mnL(pDTA8n~rv57}i?f3CC9_TBFEDeB~g%a$%4d2qVQH$KdOa ztg_O=Nv?wv=kNlG9Gnz6I4N>)GL+vCg_9x&Cq)iUu$6;!;SnVi^e+O z0ECCBNEIiMLYqOJpOS?d+h8H5G$-1J>g)JW{ZUAg(Reb6$lMTW-pvhRrXP{9I0if- zA5z7!;1vasE()Pg3`eRO0i|LjoG6ZSFjb7sFH@K*M1gjJI@9b6IgXt;)xp_R3uhiE zLDQOWmX6|oP6-u-DZ1=u*1cob5vHumerPdz5kCjW!H0#b{je80aHT3A?u2*I7VcXVd%IIulb$XDT2Cus#UOYFiC@b|5U7NIJR& z%gjhvW`)5bzp^hWwsSSKKxWfEl6KQbO`c*ph?XXQ^l3;Fr(&Is#E%}LwvEj;HJ-X= zk>4h~3cZ~0>i7aruFqqtFfz4IlhEAy!NzTDj?HgzA|J^iUgR4)*y(7b`3n-vdxAc7 z_Cj|6*<5piL|>vc!Awe76Lj!(*v`rsY-1JMS%6SK)E9?k<|!)H&7&dauK};u6Uv^R z?^zaq&$4|Twz0}p;96#|=GHqqvZ`T;Ue6jxGA~|U>h)Y&H!RTxHA&a&k5g`j``gENBLj=O>yA512ze{uLlEh(YiNm^o zi~J0NoM!In>uJsGr<65wFPY!lXL;^@e0{7I{V8R&=lLBv(0J?J-1@e+_bN#3 zl17y#;Fp#UL&$IGl9qTA?!JxfE@^ecx??jdi&Y3=hC9D!=qgt#tER|$&uy&6xx5u# zTiaq@tqnWy%D1Vp3y2 zUq6Ra`Z?(6@9Q7Ydlhu{_SLA+L_+21`F)#lxB5rSx(ZUfeM60?P3xUm+2;LRRDykc zy0_<6c4mG*Z%_B_IAp(7kmgmeH>e)#P*?V%T~XxL8}~aQ97Dkv7{ZNF>*(!&>+Oh? z-p)Gu_$LWTg*pkTZz*449F1+P`Ok?nW%-6L>-(i>d|xw!UEBR9s z5Q|`~Sd7;0S+H503tPof*eNc6XT*iDOI!pmii_b*aS3DMGL|YXXX#=&%Mw?zVd5$_ zTwKG7#R@h-+`uM_8`*Sm6Pqi4Rt>^8BMJuL2E+r^!1m$;j~E$(3- ziU-)IVmX|;>rwE^wS_W@I>u;fd=;nsh_lllJzM@_w?CwF#mhhmV3ytbkJGZrOx>cJF z9qUQnl%06fMlcs@+e>%;`G>d%t3HlM@J&zDrucca8GieK)p-dil^2LURK&RPR+T5@ z@oVF|H>Wsd7gXo15U2h!GdDQZ(VT@yN+-%is>Gcc=+_*ns^-+_ zEkjVpVZx)LGv%0WqPSqRS09_5<_*dBw*J)z}G3KT6xLbSkfKPdf#QKT(`h zwe$k#HU~U+zLM@^kmhB$!aS*6UMI9`TOPg^shgc8`MQnhk%rR=7kPH(2(- z@^)&SrqSoMd}c_s=xVkH1sTDh<*kroA+*4b)@pdsJJ%19zix_Uzcg>3KM2e9c87h1 zYo|~}XkiFCL%em97G<>Bz`O{;0!sTm7#^hp{o`CY*SwEoW5{jSN!a6kr{|(zHDAlaE>*B?3%sSLU<9zge$(|6K}F@i^pXxFopGDVIdnTIJbhL?MxovzCD(}rYJ8?^vH+XG z^;sM~s3M9fYDr0fCc5WQ&)xSO^FU?+!qILttpPGc?AM*fGtHdWqX=s;Hw3pL9~iz; z-k(SbJV9Lkv1uCjsg`6%(OJ5f?V6>xwDqK`n4Mvtg3B5^XG(D;y#Uiv&~* zL{4%F8uGqrMiFQ{Kk^Cc*hD7Ke!1hegy=3%J3UDZrKlk$2jm~P2B;()NWTYO%6`y- z!_3?_C6A}EHVQ+H^&Dij8e~jjx>PNsW$ZdWDn){2ont@C2l?FK2|#9{?7CenyN$7xrZJ|=tyY%b`wP4pP`WzM z2bQIx&Mp=1+{Jed>g)OIu}dWFO|2WOC={#m)fEXp&5#{s9Rb3+CLZ3|4p+5g63iDYObNOqWW&c~@OTAOE-FM*LuD-@ff08Fnm%#l=;i zJgi$}p*mydtUVN1aX!Z!^z3rUB=SZT=L<=5oUet9aCC0xG(d)DVuwgx2cGJD+i3+p za4vq^-h#CtDzvK|NUnq!SVbyW1+&i%MFyZv!B#iZR}+sZ^_i$5ryJljjD`HEQN|{v zw^I7$k3MwN+7nlsm|*`}{eadf$XA86r1eVlM>gR~6FKG0YdSE~$9?-?AKu&c(&`jH9yNqr^$1XaW5@0UyQ2e`6LHNPxa$8!`|%DID($aKP8 zFqq48-YB3puUc(nSz2m*AKMDYK6OZj0&jZqyV>L;UIT3$4(T#1ahrWi@L!!vBDtiJ}8F-)Q?yw<}%n5xWD@db9rf=7DwV7Y7As+VBPFalrq zs-u=It{phI*3~M6s8AEhVA9ciU;;ut*g)INzb9BB2W(A126$7<}t*72J zjToP*1*mja0|dNyN8)Ze%&Uk-<8QipFT5J$grP8p5z}5>$PN6+J_bbF_C^QsxuD;5 z10H~%YDpF$k`T=HLWaU?T%PC%%Y6c&XYJF)39?0^`nrK$z$5qVkGD;IC94Ilv^5F z=G@rp-wFrX_LE%)6%|>LdFl}I4E+A(DSr$bN_t8Z>zrej9a8B>BT}YX`9Ip#d`&2R?%SY!6rs{-R<>FpcIyD* z<%330hnSC8?iIc}l-V|VmYr6>BwN>0PZt;dUeRhxTM;+?xN%-?(!=DLx99QL3@e;w z?o_2e3$JET8^0p^Ao=CK?Rz1EM>&DB$WV`g!XAjpsfXIdhvl=k9+)~jk+stU%%R8f z*$4kZy+NuC;bw4-?D|y{>_yF-7?+7^~4RUNu?BK_up9k8T|YJ1Y49Exzhr2+quQlXAX|rQwow zEtpZofYBYV2%Rv#&3kE0h~4FfoFyzMJv%ng99Wc@muXY-DP8`PrJq8yp%1g`(Qo93ga$ z^gX0}4iwd4a=5;YeQS;TQ0I62y)j-00Hh277qCCenNcLARFzDYT7Z<^CQ!$W_C4&s zfqGDaa&tSYIb|>Oz(GrzLOYJmS!CM*%n)0T@iJ2&UfMLb$2eT!H9SXD1SF! z2nDwhUq9R*c{8Yg^semOb?q7PUqR0PhFp3BI`f8wg@Q_h2X(gp3Auz(?el>E7WPjl zvy_i4%HTcZJyhP4Gm_{Y_xS zU_ifcsA8M{lGoyT{wDGhV~Jlx0#yL`m-uOW_5F7Gkoaj4FY(j7*Ac6S>Rn+l{=|uT zm9&Ek3E2c?3;z-)ied>KM7f1fZPyg@H}u*GVxq1MM4karrK{#@1^&&WuHE>rejj`t zX&nv}R5tT}=wktUbx=LjcH%|oIY9!PjiLjvuS8d)XlNuvm2pMIk%-YhQbNb+Y{WJa zmCKoIj=+VN*_K$>3LoHls99UK5aoq~sHF;<}2m5|8JMA&4DlIXJpEB^S+ zKET{7b3){PFjLd`HfXYiv7+MRsnp~{7+1ycd=%;){gtY@7u`=1j%$bg$1bU7nf9xx ziamLXKCe%r`gajr?K6984_pyXQ;N0|K;*1??z^-MlTbYUyVi`=ED^kMihae4J5!Oz zfTx23mwjM6>Yk9>nj*!xvbpfvOfk2QMlen$5tk2YlB})^0rbsz=dOI8(^E29kEZ5N z3sm>vkp|!=_a?);UqvOZ!im&6WO3nk28VU{h2=5C%&Cxu(1Tq$6we*J3x?*o%=2X( zVlzy~m&{!ATSjA@s`GfIwi>;3OdZk+&OJnG-(Lb-IuuE^8#ynf81;@W zQj+}nKLJD+84>x;d*?x|rgkBk^~I*vV7olvw+M-@y?OJ#B$LRj*ZG2$*l_SBEDoMvf1ilwJZ zmv0m?*c=Ol_xv)W`LU>1_6i}H4nR^^FJPW)Z(U`cHc__9T|V*3BZFM_(Wv|ImX|aP z2pt7K%Re4xe>KxR#=#$vmBg^tmCKiJbns5>t5JsNEq+hVkDi0o**%*LZ51B1eCEx_ zr)jUGHPapeZ#pKJL=&ZVPJ8J;h?ueisBqUqdU1cVa}wzW{$Qn;a_}GdP!LA5f0SPHA zYi8C=(57=xLqOTv{1I84Q_2JP3KcPbNyM%2iFJ7@(mb`SC$c(c!oYnbI<9J?VapTz z#ypRGk_f(_>%@?lHTS-W|}z-_=Z*Qn6N-C{n2)CSFjel3E8WX`)mgs2s;3S5iX z6maWOjm>HjTk!o7z90D|Hy`gn`|DUx0_*6vO)-AxX4=3V%y7`aNV4Ua zv88bpd-Jb|X3RGF%pG{}5I4oxOoB;mrono`io10B1ygB`iG4r$>j&&gl=R0)92do1 z_LuvPiEXF9;o8pLdZe+gGxNg_PJ@j+vJ`GE`>~a!+Qmk6yCm3jjA*{}9dv&XwCUTC zAT)1L-?g^dYf00Lk~b+DB?FR9ziJWS%RR>+<9>qNl)7En_laMsL3yiC&Kb6m|8p06 zhvx1(xE;6~vz9P(4;~JuMX`oAatgW?=qgz zxR`_dTF+MEK-o{0xjc*7CbVyYiJZW5=94TSBVD?y?L#8pV0JfAvBId>i0CL$qrw(a z8FPd+^G~^{ucwJXbxL*yUi%~l1_V2G9u#Edd_+syDFVR4RFT|nLPRW?YJqcN(V@OM zi*Ruv4_1XuGkzUT!t!%Iy}AWWf3a$H?Vv$S z`jYweNo1ez%$Z)WQN>a1&*@ho^x@J$O&91n$qp2IibNs6dcB0~7=Ob-pMvybl2h6_PhY!j~3drG6wH_qKB0N6nbIUK5Yp<`?wgy*H(Ra>ZXlI1u z=9J#hWnYg2$8WdJ-jkAUkjQp8*JFo;W2L}lBXO1m1_U?(=(Sk=7G0Urr}v3vhx+U_ zmK(VjPER7uTq=(9y4y(K1{W}AQ%_-K;3K#R4$JU$&C!v-4Qe&my-UV!(a~52G*OTt{nHQ5XidG&P5g zt>=l!lqCWJ=pmFWHOcF>=cShUWL0;#jD$;d$GSVYS~k>YslLxL=9b!LNVB!Jv^85< zT%4Q*VuVTCiLS2!@=xuyc1WP`aty1dIJ(6K85`R{!&+*o>LWXPFeZc1sTXeU#Ghg++0t3mpjOKSE7>_N1GMi=r zmyyF_(*F7tH0>CpRjLV;$WylJ&0`#|tajuTAd)YS&Z1FZ?t5gY!CTE_qT6Y8BrNj% zQvt42v;)d)!4yVKmgOw|8QtLPp<4LI_bnT#{1@SW3lxgN|4Org9hsp=@12>t-T+<)$pw+=tH}C`mrJ`pLFM<)_UtA?KUS^ z=IQJ}MHsmy5yV+Q*H2{-4`4RP{&4UhUs$T>rZqTIM{`Fo^Tisi;Fk+fhvkadrz(~x zkcLaqRc9CN{k8{x)QDJ!#X-Z{cvIp@3k>iYN#G#M{P@VlcRL;}iD)+DRoGFcHYe9> zwksOoFp(-HYbHE131g8NP~$@|0j_yPP@F%!Z`?#QCSb%(<3F|)Tb#jW)X<%1P*7G4CF*}Q;wQCimYD3-H+SbVkg zeRRILW7?gE#e#dBOI+xA*QqjoB*$3i=5MHR^eb}`(JLouTGB*y#u_e7KCNWUc;8Tg zb%Z#W!d3*cVpIq6o3^&jxli{hGCW7 zTBN8PAX8TAR@YSik;*IC;ns2{_z-r|t`h!Tl0)3s<{REg`B7yazo0kndcD{w&Yd;l zT#syNGzvRIcE$3PHS);3gZgqAu&OGpsrv+#^3{or(N*ni&G;jBD=taBqCoZ8kw6ub z;4Z-y3J=q+-zGlR0kYpIzIq^T>Z1yEsdX5sNCCT8 zfqfRKNS8CbduxZX(t$by*#vk*GoC5K6i!ixORVtXg5hz&e6N&tYk-BDPs*xzfcBK9 zpQ4;1iF;s|JMXQ&FBZNZCa`yd6FI3Zh{abM6exmBj;9%TGtn2fQO{5cItWo@+tZV9 z=?>#SZEYk$iQL`RwWftXUPckA(ixiuOLLQ>Z0-t>Q$zv&C_cwLW75I-;J}h zYD@VRm?$z)=ypfKfELT@Yjn09k7PO?-i%R7_%$^Rg-sa>|?*Pkf7)X@({RRK$*|bedO?T6yg#KhC#cn+f?S_ zQWUeGqPjkkv+Jo)AvMOacz1KXCXWLW50)u9XMb1X@JJ;hPuS>Zfrf6*0nnHV(_`!8 z?|`IF2=()r0U&Q*D~kL@#KkX2u&EK0}n!I^@?wz8JRsC%O{)YyUiEl z^g#|Pg1O)G2b@z@4_TpD9Zt>!0T8D-_ z_%DqYqNSXtqusO^(GOi*0k=1YrX-$D{At(@MR1H@SHP4N{ySmT%$rAtxA2#aLW!L?pegUS6}SMIiv5E zW#x8M=E^INTg_79ni)cT| zo)|9Od;B&AsPup!*n{v1mZq;Y5S47aXryNRLk3Km)aI%7=w9U+vrD|U0EwdwBFYID z9x%T-(Q>po+A^-~efOC0<+qDP<5VGBmlL$*Mozok(T){+cGYo*-N1BI2I|jz8b!{* zql!7!sDi0`n&XodK6Psy2<`BTPh=l&M7$g7AupeXHE8?x{=6%)BQPur6YxFsJ)v=L zJ2gcMs_-clc`IhP7nBD%{F87luOYG+a1}xKnPD40KUy>Cy?&OTAgeq6>To0#87F5`jc=F?8c_OeZ1Z_NfP$Vs zwJ1XZgtmir<;5C#2%CxwD40z}(gLX4V)~T@9ymSW7m`9xNn-HZ;O!+9$-kG`A^S;HR)DQM|x4 z^QqGs?L%ivYbV{68LmR?a|LZBLa{fDM=F&rQ_gh)mUZzo%&xnb7w} z+|UBrc(v85#O>+XwAfc%LbG|P(Q&uVZPlDe!L4GN0r&2i9*gbHd4@Q(S?NuzkO?@dGLia2|X z%u-||E`8jl1uQ`(0ZMexl~O-sHc4p8_9uP@Sn)0DMAl_bgsaV%h07MWC4TjyJa;lr zpY09g;UYwjp~_LNmG+QWu>jTl(#_g&s{NRf;5@JE$Q(0?jU$rtb4xUKi4yS#N~okmMVbIS97ES>>bcLVfxh7tD9UVDb~ zWgj&oiH-~FE_!%@W&#cAdS#%E8Ghg=%Oy5E&vak_vU}~eiDBG25sHzLSzVX#yzay{ ziDnuz@<(T!56}0;x<8KQ8M2hl{G1$f9Zn}&d8Ko46h-A5SW(MGn8_nzEW>40V5KB* z?vSJlN!Nf&>-eF{Eqh6attLY5^-EbRK?(KYTTBn`4mT_0Z$I`l@c650!5IS06ib~@ z*N|n4ZEgGU4CydA_RTt-H6e2} zlk(uw+gg-D8a|raU{?&~Kx<@f0;@MM2J#>zW9D{z?Ck6DW!`}c^;aP;n_kLa%L z;i_?}C=r>&wA5mYyyg9#Xf*_ZiW$KcHUcco)(SGxyw~YxZLRbzjgqOUaP_Wd<*xyh zKBgTEutLGjL73#Ee7~PEN+`FZcZtS+#isCD7$>wZQH{}$I-UINy$$rapL1=-Ly*2F zKZ$u#gG9xMCy38JOJ-oAoTi!yrMtzua@5SG&vM>-MannB<#*Upj()EK%=m42pgmQj z78i@}nen~e#&~Vg`Sfewk92AHwW=1CW5MJTOg)*3@%|h>)8N2DQaLHdXI04u-`wL? zBF-@ZLHmzlgqdw4`Lb2@v@}&FuO1%GX`2-6+P$nTIvKW6hPHJ3KAzVmvv5ug+cwFZ zhApCV($dW5if!eelu6HtlAE7V{L>v#v*Z$P0hy6(An$jev>9fQ**q3d45f<=>%+$5 z_b-`;{xpnn@8_r|)fLYy`>`rN5*bm|*PpE3G?f@Dt7+d(xOTB5Hxd0F=sW3J#oXZX zPcHzEc7jEl_vqsV`InYBC7IWb>%t{-K4AxBa<`t9_TMjA*>WlS8EcGVD#=2_;KBZT z@fcA^A^vQj`}w#3Ttx=NwZQ$yTLH3KAbHgep}fqX|Ec~*n6W?ty$NbwAbFuKERcwx zLH}#AwKDt$_{wT%&iY?UqJJ{{*9LJ>{Lh93bu7jQ4VnnSd;9-WAo~{|>W_l?9|ekk z)ap;MvKC2Ta{q{E{*UM!BHD%qVQmsH{ckpZ9YqD%3O*%}flCSx1x4}iY@ncWeF#8` zODvRs{4)Pu@&hX1LQ1Ug&delh&N3xhyG z(fq?OJSxO6B?z#L^U}y}%Oo(+Y7mG5RJV-(0*yi-Y0Vc1Wd-*IqFfO;X)FW87EAvh zUFq*J-Ye67br7=;q%tByWFJ4MR!zWMc1i1 zV;7*EQ=ssQGN52EK>vHDYrPZU$>67Iy#;OEqiSvbxdIdUEorQihJ(9OLe?#0V`bbEAx{{Q~}IwsB#f&S0jQvS~*V*~#J{Wof|I56FRZFgYK|Jsef zeE->dV21zsn@K^AL7@Ht!EBsbp=2a*BQQt?U-)J>a5@m^|GC5I9C?2wS3{ZsGFT)1 z1EdZ?iQui;w*;3$kc5M1>JlIfAn*GM*Xe?+lfHQ8Z_jcLUH+JSoKJrs0?!mBjys{b zU=5CQN#`@lOJ$g67`>$&v!tQY<~Qe;jipq6A-pt~>j1_0Z0;^Ezo0)PvJV8|p9Kw9+)`8&x&?Otj1^o0c_QXJBQW-=weq z3`wWyc-vHtY5_w{pznGJuQYSxmy;?=s;d}u->fGNEcTuzDq>>YK6{-i9lIaRz)xR3 zn8eA>>(=%;z^(8#|JGauv_Pm?)axrFwdw9Yc%LdTh<0tO)C6u-B>4Lwv|@h52vNeyL5ApINwM`Nr|5GIxeS zz&WF?878+ch==n6h8Q}e`a%uvao^~+$8L{2?kI1Pc85uMDF8thukk&Ikd-l+`b>Z^ zuyYK)T6BwTF#xQrCiRP=%r*%zW62NE;U)*L`Sq%kBD!9 zQ1(}1xzr&teiDU4tn)S3c*QL!6=E53{&3hwNCV*i6&l+ADisRoe>KrlZh!N}4Fog` zNCqB2Pe!~#1kjt<8o9XC@phh?k0 zReC~_dw6HW?}|0EQ8b?sJ)ca6r2zzs+4Pu;zMr3gUzjY0U7AofRYH@woE zp|(fi@Waht@bqUPPMcS8F53+ls>+a4-BCIO(mG^0B)ZR@7pPFF=#pfRl$T_Y6mBt) zn}XahJg#NPBqKt8Ss?R58A&rSip#&8o_p!9zFx^bbrgNqnKWP5{_mmc=o{8D$RG-D zfVU{aVs?}t+DwW}d7YnCrN8PG!;ANO06hc}7HH^Yyh0;mL>hstR2ZZ^MkYaGs3rPF z);h$G@Q6@mhqL{NgBF>{EZTa$T_stt@)(cs^oYLrK=sXu!CwjHq z^5{o5Ct$m8VL9CtsQ&2Vkm6`ICujtWbY*93^7LXnBn`4Cno+fsnv#Q+kiW<&-C@pa z6j7IFWc6z!@R70+Kb|A8#;Nq`fF(>=j>v3k4_M%aF;aVU8@>bf#<-*{HmC4#w<8Ya z=iIE0>0#y9*euJZhB?hiLL?8H0|LZL>C{WU8JS&pg4N(YT)rAizuV^q{-@sg;rHKv z{Shw}cI61z6Z*3GY#II9JvAx(2=5wf-|e3pbl>eC8+e2E*cU}p`g4=;0a=zW4a1dh zLWsD74rF`i+dQbR?Dt58Z$?vm!krS8M(Gp;GDz{!l)P71_hiq2sHpDBS61}=!n%2s z&Aj66$CREuWQM48Dz6PvedKzIsI`&HEKkzJ)Cdl#CSS46c2*%_nVw*_1HFqc0=bMh zVXYp8PDIO6MC!MQlb;N3fPlmof8lm{%0~>;sf(PiP;lS!hz5Z=#h-Fw15`2JVpud| z_~qByIeVgY{*_S=GS8Q@1D{-m;%|Yo0|~uP(b`+2EHslZS)0$IXalRzU8=X-s-FU@ zpOPa6xjj69&pI|0CgVnV*P5NRbrPb;QV*34E$O$_Y_Lz zULrweh1Bq{`oYEo0)accUoz`PA}}Lkl#TW0pp#mk;sl}bUR_mf%@ZWHC*>Lh&7-ed zUz6B3Zw;@n(;w3n)d9iPl~r{nELN{Zhf0R}LVm?m4RsxrRV27~n~TS&w2x_=3!7k; z&%x3_W`UQrYQO1mz{lkUCX?Z)d1FaiPFnHRj96e6IUK8jmR~^|1WmW-3=j6!=W#M< zp?40Yk$6GVn8q&B6ALG;eR(mZ#>cNOufEX0DnY*i!YLZnPIT~Mrw;X7SoQZyr%-vN z;Faw!B z#UQRx7D{?5`b;L5_ZlabXc-xEMoBAp_+W%Xr0(awtoMz+DhORl;^lx-?5e-%EO*PR zE09&7Wv~kg8)j@JKK#JDbak{f)x9b< zvyQt&?Ak$=^af%$_y~M8#T+hNB^CsaxdKvxj{cHD??M62rS^s?1>8EiN441(a8T8@ zUuIuLf>a13$oFpW&)<8hYAiueT@dr3fi#lU%x6A|0LumkOQ`~w%mTpJU>W7WjyVg# zP>i?1MiO-&H#jLyXoshk-3|=dK+H@qXw@1xXsZyJB}64QGMO7K1$gG8W0Q1ivV5am z2Qxx#C4*gs1k}Zs(ZH~uF}1!?&kUyMyCnOEGI0tAf=W&TBn0Cb9#aLD&@=HNYWeD0 z@Jb&JfXo`gk7U)U>alE-tCgm8@ZiC6PF0XmGFnuIYF-o~wyL(enl0UsSgx#qJZ1`O zR#2|2=zdbLKr}L%k{d~7bAkRs>LtTCNT|IH4Cc1VfOxDP3GnhO1aGYwC$Jnh-nBkrODPI9 zQ6g$D8r95H($&q0MA6hSoF;Ev4i~-prGr-Si;`dQ^Y|A>XzR3wTKh!!3hdYO>$&A* zW#ya|wcz-orfTv!aAD5=U+`%RbT!pi=r^&%2}I{_2OYj3mUk zo}Dmg|u&7TQtDL+&nZ+C-8TZvpo8 zYC|ZkJ8bA$c_Dq@^b>YUU#_k-Z(2a)mkMm^tMHiX2ab1?pLHVh*OkP^^Pu|Dz8B|> znC$C&`oYf(?{L*kS)xw18#3G$lA8*aQcMDF<6(76WH_Q^q2Z;Rdd}hnsKskalAzjG z3fRUPnc+{pZ!`MbK+aJJRUlY9#t6Z;|^208y)$RpL)sUZt8?OfqA9Dex{$&9k}=yks{{1iLcHys8)M z>Uu~`+=1p0GGOAhvuO&QvW z=(X4ppo4RzZ6XuJ_m`pgsFDU`y}}eizf~{v#0Jcp`1pazC2ioH^npb?RwRNej#q|? zH0~E!UX28b5|`7SNt_Ln{Q6dEb#CFaWZ<44{W~B;#`W%AD6>HV*hqqI9X$W$)-Kz2 zzm~U@y9zgTA7jD*7=L-CtX3WsZ8#B*SD|4~t<}RUCn%x)dQ_4{;*pqP9IhdYjZ}@> zNtziGwEF`_Tin_yf%+lEo5rIf6^(jU82U~?yBt{U8heAf|JNh#!JPIdTqyn(G^+Zt zO0ok_PrWoq8P2@D-@V#W!xqilRR}l0%bY6MI!IXIG{Z;)0A(6%m#S|YjoCHA?%y}K z+O&mqzmE_)Sl|%4;SbrpWEh)bTXtmb9zAGp)^6FouG}evp)lV?9!0Hb|}vqurxPbS#?uogi`O z_+s$VILJQ~!Z4QDlM)u6RLtfTLp(kqOctT3jI)JQp&T^9hdLYd=_?YKdI-mr7l2I@ z4aX%!P(2wLMth=Bz{)w8kbKk#_xkA?%qPo5N7?%eptw(HHgR}lG;vFrqFrRxrzfB# zzi1jsEOV?XBA>jZL^P^7^6#Jc_akytWM9_9r>n?Z0t*}0bVBnno*Y4ZoKLs&dV&kI z#UmA0p!x#Kg`b<-_k*lRp5*ZeoCJwQ(TyWSm}TvoZz-_?NsW6X16abZ)AH=kjQqMu z@GxF2z&mT_LV}qyC%ZQB{gcu;-1~^U&`T}gQv^FkLNs!JBjnl0rv&i}3{-jX+0nw=EzXn18vS#6xdxa`^}h`(8@mQ_Bw#*| z>Gk{*PDK6jQ3usM#+NLuf@xjcy?)T%w0c1a(B$g_M!a*{@fB8+YRQ*pJW&9L-2P&VG2PB1t=et?nF zw^Fguxo3m;LL?qBo2WjLmeaSJu!&&xX+iBUUTE%h%pH7b{0i$yi#X%<>ZRbpeSj$H z|Czv*YReUy{3Tb%ekAu`F7k)uGGZtZK;@qsSVO4!rfg5%k0l*-elmGjJ~Q0+qsu zEqicn5m*v&jZ7?_L-i^vEb~v%mI1G0;-T_3t!V4=Pc;7f;G82U){PAQ2JsR372_`L z>Hy9NTu&V^o0G*w2#!vFT|Z3;h>I&A5V5Cp`lY_GLsVbM=1D(EwKube>ctdn#3VzN zm{(r2!iyH>+PDeB`6vAFcW$u1v(I(zLM@O`3RWPK$W6j*+owHGjU=i%4sWnU+aG5y zu46@FQIb>Yw}Qd>eJTt0q8yH=r+MeCReRF~v4hkUVYWv7t0}jy1g#?Fb4@JE( zS1iIOx?*!35s4A`ApfP=%%Cn&aAml+I^X#;eLGjy(+h&7QQP(+)?O2w@pl?fHgiN5 z*B?zR6JCX8jsDkOTgdq1T##y#2E zR!rwROvrJi+?hyY1t^WCVVzo?O+k;;U5zC}{ErV&Jd0^WxVJ()Bs1f%Mg@{#?GhbY z@RfpCtGTDd&-H}-35CU*lhLrx80XTIkDLrn5U3wzX6{r8ZBZ>bf1`=|qct+jKBgnU zaeLS&LiB^rTQD&IkRpKH12P)>nQ1x);@>nl{Wh8PIhxQvwF~#w{V9^XGhzRxk_^Ch zj{oGDx^*hj3fq2UN*-zvqX z$LHxrNm$b*(8qS8P(lV8POX58cmV+mn=a_&NkC7`u@_SUq^MFdv*}7%b0j`T`Aa!Y z<2*6JX-H@nCu8?ZwijVkars$6g_;LfqfQz4qf5s?J*L~ak>l2(X;kfBTuHO&>gnpi zYj9*uM!1e8HYv?&fc;W(MW__%f_@86c@usXITIR5ct4(6>lt$NI*&-!d3$Y<^%!0O z4K1mrH$FQq%SO?rYfyl2FxET7<}1vfdrOH5k`< zn03uir>o^l6fBaBJ0{p);?e?GbK_kYT&g*?U8Ai7VCkk3Y%bhG?*bI=#zoQy10Z_|_LKs~mij@-V$*4X7KIHT#H<4*g5*jcl8PkS%WgQ^8Q5ylNoQuXO3UsYX9A5tZbbFcrldFx0^x0$?)oL5S_;eEb zZ5=#O@boTXxAvs0`$5*W|gDM|AuK2N#u^nFtT4|vk zU7B<_`XW{&yaI-IEnt{ZugEaM)0)g?c_$eTP>Fj3rn_u@H0Y!rL)aM&Cs!jVr4}nG zbUWDzCfv!Ls;@u{c=h|o-(OxVn42ZwGK_3gO~I%s#ViyBCWTpZ_Z+;uUp%+E7m#tE zob#T`#De1I)p(z)shKG2Mkev4WaCvdIHOfDQ^vZOZBw_+9Wl^sweLhjK4P(zFh7a| zkfdO#iJ`;e#mF^}>!DuHbCrIm-Vr56Nx0EPu^pYdi7G^E)YSGfu#|n#az!IzDjIA$ z{_rOKHe;ix?5<$UIc4&>rhh+6u6`Vev^1gyV_F z*ReB|&S>eNx;|+MaZ=`acvesEu5;=Ewr#YxM{5!UOnVKMKnja4xU=LVG0@w~>}*zr zs>@04Gbu0N4efdVIE0t%m>VKCqCFMw;Y;N41mFLBo5^jrM*){E9!wQ)v@sFt4=aI) zp{0^q1x?BP%?1bX!nF+1@R_%LnUT4>8CxyNUT-T<;hXiH%Q()};lp!m;TiJ*R^_HM zFf~BPkXY3T@i*)f--OfV7MZ!V&xb)CxeBR2XR5o|Z{%yb$Ls7^Ge=nFHUKSI4ZF4~ zn3s)4AY!mJEDBJempyG+`|=8lGL2W{1mTG(m6aE+nj3eCfxB{l~R0y1zIc(9kxI)L1)`mYe33CN$^u7 zP4M>FUh12YgA^L&V?={&H!z}~`9e8+kv)|(&BWfXxF2g);nP{&5DW>g*U)0UIChO0 z?Idvd8I*VuBoY!TJ?S)nc9F}(2)XzKntqyQGWNwa;O{Ce!F}19H{tBw2%uJGGmEV* zj;a1Sb(5-wI^Rm%KCTPZMs7fFDN)$Zzx@Rc@xck|2LS9-X-0@6G3NjNQ(R=nBZ6%s(^iLKJ zG8Ko{pE!lqm+&hM{jB5n5w|Q_2Fg{dCOo`;d})=qbmv@4x8^z>@$Jc!O?a*VFP&%R zedTT)NhNldwkr`AHrm__JnL(dbJuvwP-(>PX}F`EqkFMK3QTzhZADHO^Zky)sIua{ zvp;zZ>yreG?#`8f6#=O?kit9M(})FY+a8AiX*xt!lH1r^_raJs89f@aorgjxwXIrqm2-RBbsh@nJSzAP;lt3YYB>4^E zq1dB^Ox?oLz5r#teMqJ25rDZ-T98NU;CmxY*(yD$&^8x ztn!o;*X8}^VA0@Xk;*uh3fDQRo}T+x0J>?himoO&R8e=fdn_)KQ%*kpd9gttGuHQQ zEd>f>1?vi0I9Ukwoe*e^+VmlRob6vRNt+g}!IaAVT~&BHnLjOD)G*=dAf!PmD#NCP z#0tMjB7Z#q5{Yi^It10!Dj#8MxPG#*4#JENIFay8{!o$d(LSuCXuL(IeN-*SW3HsS zU~w`x#kb~tX>IGW@;m=yY*y!$;8WQQSi@v_$Yywg5e~Mw%~t}{%b6Jd-2OTXmw28M zK1)Le{B53eE9h97;FKXGVf+Ph(k!>FPP_K5QF6fruwEy2`$&gONlnN>{u+uekZB>| zNnVi=>K&W(R@?`>6d@zo^!&>a@2`7RUs-|xT^JThOw$tTZk|Jx(TdD-m5a(%{NBiH z-*-(&_1AvQnzF0no*^4a^M--LZmII(?@VUgsLx$KE2S8X_8F|z6}RCXCfT61w>cQl z&Uq~)z@MCNMP-+5^{@J6ciIj_OzkkQMZ3uxf_u42AMA82@^N>gFQQKUm|eq|gX!NY zw;PkcCjD|%yP2=;T-LfCT`C?^hy#W*4rW_SJArJ7K|qnaNi|n^HPf5WzBlejTY|=` zp;~|07lyR>a^xm1jz0r1?WY(l@yTh&?0wDtyL`O*3Hl zjBtb*A;SiRIbo&@X;Va)UX5T>8z9vH;P6|M1R#o?;Z`2M{zCbnbxd6g!$GyikJtEp z2AWy(@X`42xPEGEP5gl&*o5@wu?v=emHGoOr7CM5lwy~V(~3@(W{O%eH`o*ARg2B- z0w_p|Bj2H2tP9xGZeI^5_MFDTJXAW}6AX(FAJEs19D* zLkkV0KjFuN%b6n>$1_nd>?3W!z#CzEA^RSN5$CYNfC)pDzRPYv{e5C?7fzp@qvBW{ z2Eo>E=bX){H?Vi2UuY1#_lQ&0Rf zx}`W{G3^CGxkt3t2mKk-1fbUR|BQShKmWG>js^%2ca}uQa+(?VH2PN?x~jKjN+{Np zLONQ81;Ql@!MP6_TB}$jHR8mh0?(C@$=I_}#_-rxab3Gll%0r|M6C9zhx;6h0-mE} zN5xMo=0ja&6-JBs;g$DX$?jP09OuVC`5=QF`P&SD(g#BqpfCr`JFphrsjo(|!uMf3 z;p&7%bs-gopYLdkPV_pE`GeIWGP3s|MVAyDX<7!f%ZBZ0NVn&d_vO8`PsKlpnEQNUS&~s_G*0x^sO)}DnJ4nDIU^th|xHZpC$0k-~k+X_; zt*`KN2gwg?+IO4~P*p}l!*1Mu%4NPhmU%-jPawv4cc|q3@VC;p+uC0G{w&!Ujz%}8BiTnb>o1a*DfeH+{MIe5 zTuRz+uCQLf0UyeddtPx+c@~j-^|ZA%ai2II5-sbH|0eoty@!x~VVxuQuLwg)k6*); z!=5h#N@j_Hm}Zq1NvhL=yRx&4`mp)Cy4(ivisj_;j2V|_41tQxihXbwoujmL1GKP~ zYHBa&#bTY%HhaRLayxn&`!F+v&0Yp=HEos=*Enb$V6ahj1&l_V26xIOzIyevhF z8(*$Se1eBlEFW1SD@Gg_Vozh_!NHQ|C(hynQaV+@27bln*5}hj=E7$)Y}Z8kAlJ zFrQ-CX(Jhx*r5rK0LeFm`Ntv+_(PKd&yZ_j=wTVVl+@UV zX#OjJAI1sXr6WizN0!p@I-EE5OH;7{(fnGBAn}P}E2;G`@fO7iOhUWA0GpnystZak z#20hg!(?Z4))9J&uGBbndsCK+ywdu?tv;?y-Cww#)eE-+cmJKv5&NcV7+lBP~LcKj7qYuai0T6-e4K^;OxC4QvC^6*JcDEh}crbWBLD`EM&+{M&61mTt)X{Ic(jFaU2q!j4) zIH8|a@H=cTuRl~iRLgCbFK7bPV||rCzclGg8w*I|y#odj8URSn_*<6sWbw}bSis<{ zqR}F6&=LOyJjfA=!qW}dXA{nC*yl3YBQU|wK8CSZ=n6<>uhIuo)$cXSJqvaf;-zN*MuSdry$k!3M7PH&f1 zL_(lP@yHhN-+}=dErl_mAU2^C3Z*7TGn46-S^da8y8-IEW+l5*{R=09IuyjtsYIKg z4^rLxdqtb*denWGkh919(=J;RpSCqZLeKBcxKJY4PheS2=Q1 z*%9mDuzMwCDgmHXMoQIthBgxa?v=t6gTYjgB9Gn=c8~a(PP{v(&n4kN_OTvJ8M()% z5LE|W-<`$uV6J%`2P^DmMADTKW?lQ}X)7UZZ4itWvw6!1r5p6CoJUD-2h?R<&r*8NW;e$43jrd^iQ(qp47}! z(Tqw4@wMqDFpU)GNw@pJjT)mXB<~K`JkpX+W0`*lk8zQORmK(*;qPr+pSr-hw2N+h z?gXQ6yhgxhNQBV*A<>oabe|!xaUD*hcn!>|qqPb+4er@p#JN6M^O!x%e5xJIcDi_n zljBH0`n6oYu><$98ZL1?^%@DDOogo1PR=*H#Kk$a1A7q7F`a~>X(E=d4?0|Vpma=M za~6mlY8yq*?alkjD`EdjG3g-o;8{6;x`HF3I z?dRAhF6B^~O2)J}#-G|}TBAp_Py$lX>Qust^ zPx_coF>{<1l6mmhpZTlJ=d-Vhg+zysJ6Axm!|`cHHkmFU{b1;Z0yBy1bE0LbmrIf! zm%0RFZEVw&n*tb!Hm?0WnA^LVrUZhM_K6LnYEZFW?24-f_-=mq#U5F zz_nbX^=HIZ^>Nva$G17^YQ?7VoQs);*{UFl`YAG}4%^SVK;F84caF&}NVL8@oo(LZ zO?-sDAF=wI&@w#l4#6po5PN3w0jJm0D@)cTG4l_x@s+{|FFszL$|Dbjil!yF zN$oIH`Wg{Wc-OZqMa1lwetlU~oC_d~ohS1gf1;_M%66lEu<{6BF-&C=z@f(QFtH{~ zG5ZXAF1@v`p`K`tBfOyxuVjbogTWr~3(fmse=*)0s9cG_BEu`}x2eybL(2kB!RSu*wMFI?A$wG7Lnz+5@!UEL zYjoXlQ0-yTj>riTj&Qb;)y*EaPZVXtUoVs_X8euz9@LLO5A*&<*0yjDqYp0)*5a;v z`R!@QdsqIDzsIDW0{(Ef^YMT~&{x^KNrgSFo*0Wq7~9&D@Oy6lnEa{mN0+z#PY{2y z{*3q|jl1Gkc;5W_LvUYE!coGb!?$7Is3Zf8B4V~EIo4R#wjgZjBG-X0zS@~C(d{pd zE7Xw`$jo{%Y!eAaq zh1N*Oym-H*1PA^K6F!g4@-wUt@ zCx>MAs!x`#9{m0Kn(-%5f>J%GhoO0QXton z2j@u<6BZegbA4o`Hj#X_F0`G12eUF#bCbLAU+hFP{-WxNnIDoX32hN08)<7GWFkXy zW{|2EJ{OA@1rKXPW@6P^(?%1;g@?vP>7`Ik(Tpd>*wPQMbOu&}x`I{Tkg zY^RB(Sm8~nrj`HJz<}dJgX^EW*@XkxPuPW`Bf(}xgD6HwF&866Pb6JN( zgI4KL(uh&gL^%3D-tSb5-~`|)?w5>k1VB&jS&)HG_uK(d8weZs=nS%lzGXVb5XRB|OaMw?6+#$DIpM^Gbh`^B`IoB?O6k}#q~WxcQi)q z{V?)-YeWDy-SPxxrMIRXbmeVHYzo{2IITS*v%ugKa0F!xPD!l-MPfTuS0<%Ii$Y^o zfw3exfRTgn%V&=HSn;}H(JX{45(t3_iXH{)V0I?#C{T36fJ$Z=oM#zFBiML~Vqs^k z<|VX^bW1JrtuNct3CAFjKMUM(JCRzA5ak`a=m3B_oX&d~ia!kVAkB@+ZQZ!9`R&b@ zmav>%fY41Lar4tRnr;c%{z0Amu1NhHiW<_6{;-A`Es`_7HEzqf zac$llj8@$s>vq4 z7^%0IOJQdRdXtquqY43k>G+%shp*WX$D!jRG+n^b;zz(6VhD`d`0grJ6fbmp9E{#P zG9Qe1Xe>hpk>MqH;EQpHC$mOE;+y1bpMFVNhJbMivlDdef^n0Sy`aEJICsN>^#gEG z-uMm}-TfzEwNm+wFJZ2bjt#yN&tePOo;+K2Rfaar#vEKhj$wEn=|K0xk+ek1wL z6Ncr#7B2*eBcH}MqkV(P94a&h`f^O~%{L@|YN-$8Ta4dhu0wx{xuXCwSMD^{Ge6bb z5dgV*v%7|C_w5S>zS(xiw+(Br-2h9$zH$F!LTAjM2#*uO$3LH_dD8-7Uk_Z&yXie4 zfYI{%pQ6QA{QTkmQ_6cnycfBnQC}2+I5G?mG7N(~T~>y@LJZQsn4`forHHE^nQ0lS)( zY+0;YDvf)ClxlAA0pH319KvRZ*mufUE@@_@wj#@&E|KD zn&EocrXQ2Wz^8@easL3&H_c{1{~*#g&(VFq;_{6U`WHvlS+66W01(TY02Q~+9@$`_ ztYifWy(T>?r`%bCJlO!~5NLdqsHZa4UFR84e>B-TP11gVq?xX(IUusA`Wj~7)Dl|G ztiDjw7uh+Lh+_|!GfiRmaa^k)KtbY?6?wJLjWu{)*yhEVQ|x(3$;N-m!lXI?`Hmt# zm@5R_{&@VcQ&MYMX(cK1ENsb;Tzkx*N(Om2@)?J{m1($yy%cL=K(+b-HAhWhL~TV| z)1o$%D;cF8ESG-u6|ncU0J@ZdF+uLK;r|@KF7TCI-}pD9>04(Z<9xxqdrYCpdbW@2 zdM=dgPf!G^JZ5PV#$B@9&MXB{%Ne&&>oWvHzVZ9w%arT5erC$;Tx9PF?uD*~tgx~h zX(WmqvDhry`X;m;|0Ryx`NJu$&Z^IGnWY|mmq^eVaIlyH6#%8zFzyVVe-RQR`a22? zs#VMq^gRL0bs{)Xy}+Ll1uroaH_52ZWLuTzy?4{epb8z@F*Um`f*5$}O_&72t>AoW zyHY*e;#qmT!kcS+9aI-`0Yo`MK=2lne`IDK0E(CmV)m+Ge~95fN*|<|8oecy z*fIOk<>Jwx1+bJ5H>^FbmGpHh{`Zv>${YO8GNGV!R= zv@pu9H9TotYC ztTBJXJ$*+!qlw_7a5N+Al`DXtZ0NWCL|XzH(Td4dtYg`8h3Ot~1p)Hv_f$LP_+Hxh z01y-90bt`s&;k@RSA@{=Qnp5gTLqXp0A5fNgU8yDe@K}9!~uiWrPwGF6MDM(g>;lX zFYIL6mRsVtJ-u==Wd=e~DReJ=NhE5coXCL$;$my?EC*Yjw>eXhnkV`@*ZzvsTb^$H zxT=wX7+(;dgqradLPCI*&NycSd7&n{?qERo?yNVBUj7;SY+?SYgx_DDcB0lLr5Ca8!P7_67z9`t zlYbJ|nd^#!>|P2;hZ=EP5vW+s$Gmy zSBo1kq^__A&9p`mq4A#^jnEjgRsCcaBzIuT1?Ujz)C{8bqn|{zTH5VGSO6m290NN* z=`lQpzKRx=JY9&#nc=0C#4=%SsVEtlvFJ}^R;)~i-zv6mG%f(iq#W#GV6yii--W2& zYra)B5F*+I7HAKs_k00a(qy zS?f2P!|-Q{bQ%(EK-b!j6HSGQ^@w!yPJ@tj9d839tf(5VO19imrZsRvdaGQ6OFBP{S=GQcPwSW>ln!MyLAm-Q6s zYxXRv+sE}a&vg@Q9T|HDQOt0*jrzP>i$czRywN*J)$4`bJP-udgsnWL$vJZgeCvyN z;9L04%z|mkBXiAR5^h|TUc)r1Y=#h8IBYF#-?BENDm>rqu>hrgQ47b~6UF5hRbyqn zS{K*vMQZ;J?IS5VfYu~kYQfWEd=0FX527QA(-UwE!c6aBT+!=E#QGqHM{zA#`Xl8( zbR4TZ*9KLd>I?gtVRZ|X#O8T{sABe>DHf3Ur(HmU%d%Ke}luX0~1Zy{)iM^_2w8D zaS>@`0|YpW$0dzqb?y7A8r}1Iak))-{!#Hmu5Eevk>?F_%4@C%9%uC50Y(Q3Xf8e^ z{@NUa#AVPIQvH=ix@{CD(=;{6OUs0Q{>eNW9w%=z)JFNnpy1_Vx_)`TdW)x}0pEr7=L)KV z)AM!t-9pnLn@J}f(nC*%Q zRtW|KWB`}k0EwPlbA#9n8ru!_Kec@GV4@4if7u)MI-+Qj7pPm{tLbIn}Poa^hfjm3pGvjfc!U1|5W||0jF70kpJFi5fZld z1qld9n)<)52zdDaRK}y)u)aBJsXzIqNe;|!n`O81y9}FTayZgn6r~Gg)L_3U?B0RE zHtUn6xUWqqW@NR49>fJzQ#j}l!Bd+ign{L7FF-CxDryc$Iw~%^g6f|eFS~A{ID~{#3@7ofHTC+YrWG20)MSS_h|(TuB_tM>juoW4OFE@Rx>Q)Y7DQ6`7vArEzt8_Y z*R|K4bI(1`%slhV%(>>Aa}UFRI39a*Tu={h?hz5(!*(Sz{fd0Hu?J0iZG=d0Kfce{ zoaML(?dm^HCk9G8xEg->1)DVBa=$3)#>eC_ldf|cl>fx9<+oJ8*3 z(Vl`apviH5>;-CRy{CfWIycO{ByWATHw$_l3M?l4Fhf1VILEjw>42!9Ajb{C4>=Am ztM<$=w3iGXE_~SqHuq{EyK6_o7){|v+&}SL$#B+F6Ph#vMOTIpD6#7E;LTmnAnfY|)HkDeMx=o%4 z*4e(9`T z0;~jhmOFj1+zRtUOcq{pXi|C%k{O{THqW*d;I z>CVFbmYY(QUMic9N?kO+ZbSa|x!Il4{MB*EKy2y=rAp<7yHy@?OISGVD<~7F}p`B%eVONbE$Px zF;aXI1BydluG7`$YzyjYv{J{HbhJ(H+%QC6*_L?_9-e z2`$X{9G2sPnwBV?m(FOA?6fS6_a{ZeSlJ=B&0pJER#De_+0rkzJnkT8R4_0Ee#BTk zfWz$u)Y&GeiA0PyE4PQZ8f}Xnuzs9dzNpqBBVjgW}0;h|rb)0YigW&_UIqfEQ(?~LE z^`BS@h3VOff5{f(6T64GlJ?iLG(XZ{q@os!`&J!FLAnCCV%e zV_q**9HwZd-I}p!DPGV0I^f-{fxYn~J;yH(Ydiye~VyJE3G+c1T?R~1_)B6lY$yRBReii9` zHp?sC-}y1}zIE$!r{+b8_gRVNi#XI|cfpA&M&Y^!lHy4ePSY$Y&}qwlU9sWV1Lt3~ zkP4(seQ#{I^Yq0`Xpo(O(F}WD(8qc50uQH>o*6p;%b9PyNIp#;uNaxpevg;JVxQb{njTzbw>#fr#L+=z~KG5XbOYQz)IWW)m_ncXL=pNr*k zs#h+lS*ABCp`r&)v)n#GMb=`#$fxi*tNxzDR<;p~em10okqi7~(^qB<6Y!{M|J!9@ zl8pvo;;bl=IO)2;(JwRumV%u#$+IScG!{eb7S8>-%VY34%l=}w6z}fsnz>sZhHeQ^ zkA6v&U$bsebB_IfZY|J^m@@_Kg3~Z)Mm(NAf2ED{LOH<2pp2Ya^uti2JuSPf>#{W_ z9ijvo;KuT)JM|P)7LM^8u)+mcWS=X0J-T2^tsYYxZRxTZ9dl+Tu@@! z8>`p2f%`r8p!3G@h4!2YeJb$4bRe+fTwS4dev`$rBm3@4&734^HknQxWX}|9?97xR zfAN?QncwXDcPyW31MVLM|1gnoi6cHyDiQ>es^9jh23qRUP4zKwA(hOGjM)@d+q-1@ z2oHQ_rfOW-H(0)q$h7Sv44zhTSFq-WgdVL8-?J%{ZVR??fAO>rE3(!4$9&t^4gH1OdP|rrafHQRjVq>#vJ|x)ln4=)5p&XBQ#Pq3GFb}J?c_x!_h|HH03dsQ>i1is%QYP>1mpK)C zhA)i)iEM6NAIUzC+=vPB;g1ue`$07w4#$uGdZr;slS8P~R_)U#uFSX_dXFScaXGtskp@VegPglkaw zdxizdGTf7Imvi!fQ{1??EpKsC?D-f4&Rs~`_KbU>l)4?MdH|9hL8p0iehfTQK>^lhU|}| zeN~9KA%Wo0)W{vL<2bVAuv98*;A4G2oVS~AvPeBnt+(5`zghL-ul6yn7tpEHr)`o0 z6q1XpM;6i;FwH?#z{HS!t?T)$#<0^p{@Js~@JheT%km_Hr^CV*3y+!;cptIS$|`lM zhUM&bcl|u5`fke?@S}T=bt;wb{MFJXZ*+H1^iWq_qeC0(ZB?GB-)k;6;CfxRsaNb)8RI?KpswOr;Fj&r#^ zV^TLeHZjaD%hy6=TCBf|+fvA!Rm~avpqdIN+-+&GWMT`8*sfhaB*d1Kd`9T1|*X}724pONGVLC9oLidcf1vcK&F z*Cl9bB&D1J4iy{I!#;~0g7!O5fMrvwq@_27TelEisv%3|mq+R8VXC!Rc!7H^y-1*c z%2Mz?GuhBk9FarQqjzLE9Yk>h+b_twyorX5sZD9_;YsGv;1jP};^L;+v*-}3vd3my6~Nap%thc^L9 z*Y~(992#s;)x2(==#WAEn^9zU#LpAj_NltyrEg^R|kAD89 zg*j&s|9tpx8G%VQt>; zc)CwDHa}+^+}Uxp7^!I$p_+D?XR3~x0WTsZ!f~FZ*T;G{2D3P1b&?Zj*?6-v= z*PM)JAl@^UIfrjqEAH4SLzB}hRa>8?Dj@J(kZ=jmf%2NXFyiqWangARIcY-jOlIsgQZ|JGT_nWRNLY<=RHX1Xt%eoEFH1zBP{ zW?O>THWq03fDmlPB+NgvH!*@9kT5U5GJH*^5awf7hteA8y{MSHy+Cl;IAxBQY)72z zns1-KZb-LYAxUZp9n5gaYK_X+E6wN$X=8Wi%gzvCq_WoMlq3lrw)(w_QyT5}?g-Fq znODuduOapDKA>d%u^hO^GZiIJQAwiHAY3w#>={O&_oX#H-EVP#sg8G+f`{MW8`0@m zN@7UH!QdXj-4AB_5_?>!@65)WTH5ZOyGXUviBUwP(9I~YmvGkH2#&ABsyf%>|9I>f zNUXZAg?<@Ln2d(8yGsMXX_PjfBX~pXH^4&8`_S%KLUuMVgkw4RVzr0ih^rLBIaTp*FHWmoP3cJLhfdL0(KyZwTN{19>@UWngS$B=(Mx{<;p#iOH zA*C<#jYl-fZ96&J`nfrg*-CAf%kpJ6c+N2{*ikn0TY+KNHan@QFLo39CXag9-pFoc zTbSR$VNCg65#Ra>69*S|CizSff7dvS8mmx%Tadw{s58o1 z4lqrjCtZ81$!Nh<(V(P3ZRcwpTgMBQjf=o7fw%&b1?pe0j*MX27xs9~?prCX;9mc_ zt@%a;Lb~aPNdcpu&w^mv1c2dWKfaabFAX*&0jpH3oN2T1c35-S-O9A_sZ-`kyrl>;H<^Lf&f3|XP4`JDN$up+v%?M*Vr7}x2y z4K^7EW|(fa=FROrrPS5}$sOZB?d7QYI2MDn^jU$OlQq=goL)%m=v6Iva!NyEW(i*J7bYt)8C zT6iU3c{R|kk{RbS}q{keMAWF z8|-!sY*&NUvuEHs%$thT@cIQV@iLD_#wouw&wiHrfEHno#X%sHDvaX^J)bJ|fdOTb z#zaBKuBd#n+be%l_pnin=at^7OcZ5$C$}AiX3Qu*QVlDez!a_JDt1yS-}6RsMYE{d zz8b$3yj)#3C~yahQ#+dS*INY{tubi(OeT~~S58>2`$G}I*h~CRe!GR;>ulfXi?pUZ zk%=a2<3BRJ$7cxhBhENKeIPI#))Jr!6gF`W(9ts<-V9kIX@RLsD^*v zSJG$IPK+!F8DyaxXLk7p^1$6{=B&jf`;Dxr%&%bY``@Q~YnRrEy^@mcA_*r>a>gOg zR@Bb+n0_ z>L+)qmAaeAs~E`LyE|iIR48_=dmH`ViOqib0G|)1VD_}J*8BE!ca$SXzJhs%fBy0f z`-5BuOFI6;i1E_7*iPMio~q5;6a%WLCOr-DR7)`N5CT!4E`HRH?BlgrSyJwce|j48(*M*qSqk<^I9VX z+VBH;2|*)1nU|z{b6SDDq)kLtq3OSHrU#TQYGQTTS|;b2N|RW^-QwxY@NgGc!FDoz z2~_zC4xuHG-lv`mF}^CeuSU%{y?*Yjh+}E?*GR6E+qn}c@U9D}y<&q4mr87XL6S`zkf;}+y<3jGI0 zpP;wX|8rb%TwuTk_x0E;*#KkaW58N5?h^gcOxOiFVI3nDllC9{T09dbwF$-m!;Ck1 zn6HYsQk~UB4{0Ct#{1pBOF;i;`(xq2ZV@oUK5tT96R&k>(Np;Z&i^N-;awA%wkWSF z{a$7TI&Z8y+F_Ki zi*1f;A9C)1F$n?KNDMQ~c8BsBdX0uo0@2W2^s@H1WB+pC2s-Im2-*M(%pr~j_IQu* zx&oLBVj|4wHi@ApN8vvzQ2p+jT$Vt2MZPwlvJ1xKh>T4UzecV?3u6zAi4}t;LSPbm zkn2{Op&`mxnCnM+7<`ZJx{$SE1KI#|caftV$M#1dU*gcjTd=*ozq(67S{1BC4+5>- zN3Xtr5R($nL^hZT>MwT~qQICu6=;YS7KOTgNCGQDL-5*bD7cFhX7`ulV)wzAq7AS& zgA6dPUVQHV^~zN|@8BgExu9#7@vlHSYC>na1&iLNV7?+QDjQqgK@*?+i7;X1Lxlze7*ch{f`L{=RX%$!!BSPz37QD|9dU`hlL(EpRY*Vu#TU9wfD~%@PCQS UQ`bbVHDVa=At~ Date: Sat, 5 May 2012 15:38:37 +0300 Subject: [PATCH 08/18] [AVR] 5 cards --- .../sets/avacynrestored/EssenceHarvest.java | 109 ++++++++++++++ .../sets/avacynrestored/GraveExchange.java | 112 +++++++++++++++ .../mage/sets/avacynrestored/KillingWave.java | 136 ++++++++++++++++++ .../mage/sets/avacynrestored/MentalAgony.java | 64 +++++++++ .../avacynrestored/RevengeOfTheHunted.java | 116 +++++++++++++++ 5 files changed, 537 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/EssenceHarvest.java create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/GraveExchange.java create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/KillingWave.java create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/MentalAgony.java create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/RevengeOfTheHunted.java diff --git a/Mage.Sets/src/mage/sets/avacynrestored/EssenceHarvest.java b/Mage.Sets/src/mage/sets/avacynrestored/EssenceHarvest.java new file mode 100644 index 0000000000..e8d9a22e30 --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/EssenceHarvest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import java.util.List; +import java.util.UUID; +import mage.Constants.CardType; +import mage.Constants.Outcome; +import mage.Constants.Rarity; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPlayer; + +/** + * + * @author North + */ +public class EssenceHarvest extends CardImpl { + + public EssenceHarvest(UUID ownerId) { + super(ownerId, 100, "Essence Harvest", Rarity.COMMON, new CardType[]{CardType.SORCERY}, "{2}{B}"); + this.expansionSetCode = "AVR"; + + this.color.setBlack(true); + + // Target player loses X life and you gain X life, where X is the greatest power among creatures you control. + this.getSpellAbility().addEffect(new EssenceHarvestEffect()); + this.getSpellAbility().addTarget(new TargetPlayer()); + } + + public EssenceHarvest(final EssenceHarvest card) { + super(card); + } + + @Override + public EssenceHarvest copy() { + return new EssenceHarvest(this); + } +} + +class EssenceHarvestEffect extends OneShotEffect { + + public EssenceHarvestEffect() { + super(Outcome.Damage); + this.staticText = "Target player loses X life and you gain X life, where X is the greatest power among creatures you control"; + } + + public EssenceHarvestEffect(final EssenceHarvestEffect effect) { + super(effect); + } + + @Override + public EssenceHarvestEffect copy() { + return new EssenceHarvestEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Player targetPlayer = game.getPlayer(source.getFirstTarget()); + if (player != null && targetPlayer != null) { + List creatures = game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), player.getId()); + int amount = 0; + for (Permanent creature : creatures) { + int power = creature.getPower().getValue(); + if (amount < power) { + amount = power; + } + } + + if (amount > 0) { + targetPlayer.loseLife(amount, game); + player.gainLife(amount, game); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/avacynrestored/GraveExchange.java b/Mage.Sets/src/mage/sets/avacynrestored/GraveExchange.java new file mode 100644 index 0000000000..73372f5db3 --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/GraveExchange.java @@ -0,0 +1,112 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import java.util.UUID; +import mage.Constants.CardType; +import mage.Constants.Outcome; +import mage.Constants.Rarity; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.cards.CardImpl; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetPlayer; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.common.TargetControlledPermanent; + +/** + * + * @author North + */ +public class GraveExchange extends CardImpl { + + private static final FilterCreatureCard filter = new FilterCreatureCard("creature card from your graveyard"); + + public GraveExchange(UUID ownerId) { + super(ownerId, 105, "Grave Exchange", Rarity.COMMON, new CardType[]{CardType.SORCERY}, "{4}{B}{B}"); + this.expansionSetCode = "AVR"; + + this.color.setBlack(true); + + // Return target creature card from your graveyard to your hand. + this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(filter)); + // Target player sacrifices a creature. + this.getSpellAbility().addEffect(new GraveExchangeEffect()); + this.getSpellAbility().addTarget(new TargetPlayer()); + } + + public GraveExchange(final GraveExchange card) { + super(card); + } + + @Override + public GraveExchange copy() { + return new GraveExchange(this); + } +} + +class GraveExchangeEffect extends OneShotEffect { + + public GraveExchangeEffect() { + super(Outcome.Sacrifice); + this.staticText = "Target player sacrifices a creature"; + } + + public GraveExchangeEffect(final GraveExchangeEffect effect) { + super(effect); + } + + @Override + public GraveExchangeEffect copy() { + return new GraveExchangeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getTargets().get(1).getFirstTarget()); + if (player == null) { + return false; + } + + Target target = new TargetControlledPermanent(new FilterControlledCreaturePermanent()); + if (target.canChoose(player.getId(), game) && player.choose(Outcome.Sacrifice, target, source.getSourceId(), game)) { + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent != null) { + return permanent.sacrifice(source.getSourceId(), game); + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/avacynrestored/KillingWave.java b/Mage.Sets/src/mage/sets/avacynrestored/KillingWave.java new file mode 100644 index 0000000000..fc6eebdf93 --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/KillingWave.java @@ -0,0 +1,136 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; +import mage.Constants.CardType; +import mage.Constants.Outcome; +import mage.Constants.Rarity; +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author North + */ +public class KillingWave extends CardImpl { + + public KillingWave(UUID ownerId) { + super(ownerId, 111, "Killing Wave", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{X}{B}"); + this.expansionSetCode = "AVR"; + + this.color.setBlack(true); + + // For each creature, its controller sacrifices it unless he or she pays X life. + this.getSpellAbility().addEffect(new KillingWaveEffect()); + } + + public KillingWave(final KillingWave card) { + super(card); + } + + @Override + public KillingWave copy() { + return new KillingWave(this); + } +} + +class KillingWaveEffect extends OneShotEffect { + + public KillingWaveEffect() { + super(Outcome.Sacrifice); + this.staticText = "For each creature, its controller sacrifices it unless he or she pays X life"; + } + + public KillingWaveEffect(final KillingWaveEffect effect) { + super(effect); + } + + @Override + public KillingWaveEffect copy() { + return new KillingWaveEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + int amount = (new ManacostVariableValue()).calculate(game, source); + if (amount > 0) { + LinkedList sacrifices = new LinkedList(); + HashMap lifePaidAmounts = new HashMap(); + + FilterCreaturePermanent filter = new FilterCreaturePermanent(); + for (UUID playerId : controller.getInRange()) { + Player player = game.getPlayer(playerId); + List creatures = game.getBattlefield().getAllActivePermanents(filter, playerId); + + int lifePaid = 0; + int playerLife = player.getLife(); + for (Permanent creature : creatures) { + String message = "Pay " + amount + " life? If you don't, " + creature.getName() + " will be sacrificed."; + if (playerLife - amount - lifePaid >= 0 && player != null && player.chooseUse(Outcome.Neutral, message, game)) { + game.informPlayers(player.getName() + " pays " + amount + " life. He will not sacrifice " + creature.getName()); + lifePaid += amount; + } else { + game.informPlayers(player.getName() + " will sacrifice " + creature.getName()); + sacrifices.add(creature); + } + } + lifePaidAmounts.put(playerId, lifePaid); + } + + for (UUID playerId : controller.getInRange()) { + int lifePaid = lifePaidAmounts.get(playerId); + if (lifePaid > 0) { + Player player = game.getPlayer(playerId); + if (player != null) { + player.loseLife(lifePaid, game); + } + } + } + + for (Permanent creature : sacrifices) { + creature.sacrifice(source.getId(), game); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/avacynrestored/MentalAgony.java b/Mage.Sets/src/mage/sets/avacynrestored/MentalAgony.java new file mode 100644 index 0000000000..07028d1244 --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/MentalAgony.java @@ -0,0 +1,64 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import java.util.UUID; +import mage.Constants.CardType; +import mage.Constants.Rarity; +import mage.abilities.effects.common.DiscardTargetEffect; +import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.cards.CardImpl; +import mage.target.TargetPlayer; + +/** + * + * @author North + */ +public class MentalAgony extends CardImpl { + + public MentalAgony(UUID ownerId) { + super(ownerId, 114, "Mental Agony", Rarity.COMMON, new CardType[]{CardType.SORCERY}, "{3}{B}"); + this.expansionSetCode = "AVR"; + + this.color.setBlack(true); + + // Target player discards two cards and loses 2 life. + this.getSpellAbility().addEffect(new DiscardTargetEffect(2)); + this.getSpellAbility().addEffect(new LoseLifeTargetEffect(2)); + this.getSpellAbility().addTarget(new TargetPlayer()); + } + + public MentalAgony(final MentalAgony card) { + super(card); + } + + @Override + public MentalAgony copy() { + return new MentalAgony(this); + } +} diff --git a/Mage.Sets/src/mage/sets/avacynrestored/RevengeOfTheHunted.java b/Mage.Sets/src/mage/sets/avacynrestored/RevengeOfTheHunted.java new file mode 100644 index 0000000000..11b7d736b8 --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/RevengeOfTheHunted.java @@ -0,0 +1,116 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import java.util.UUID; +import mage.Constants.CardType; +import mage.Constants.Duration; +import mage.Constants.Rarity; +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.RequirementEffect; +import mage.abilities.effects.common.continious.BoostTargetEffect; +import mage.abilities.effects.common.continious.GainAbilityTargetEffect; +import mage.abilities.keyword.MiracleAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author North + */ +public class RevengeOfTheHunted extends CardImpl { + + public RevengeOfTheHunted(UUID ownerId) { + super(ownerId, 191, "Revenge of the Hunted", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{4}{G}{G}"); + this.expansionSetCode = "AVR"; + + this.color.setGreen(true); + + // Until end of turn, target creature gets +6/+6 and gains trample, and all creatures able to block it this turn do so. + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addEffect(new BoostTargetEffect(6, 6, Duration.EndOfTurn)); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance(), Duration.EndOfTurn)); + this.getSpellAbility().addEffect(new RevengeOfTheHuntedEffect()); + + this.addAbility(new MiracleAbility(new ManaCostsImpl("{G}"))); + } + + public RevengeOfTheHunted(final RevengeOfTheHunted card) { + super(card); + } + + @Override + public RevengeOfTheHunted copy() { + return new RevengeOfTheHunted(this); + } +} + +class RevengeOfTheHuntedEffect extends RequirementEffect { + + public RevengeOfTheHuntedEffect() { + this(Duration.EndOfTurn); + } + + public RevengeOfTheHuntedEffect(Duration duration) { + super(duration); + staticText = "All creatures able to block it this turn do so"; + } + + public RevengeOfTheHuntedEffect(final RevengeOfTheHuntedEffect effect) { + super(effect); + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + return true; + } + + @Override + public boolean mustAttack(Game game) { + return false; + } + + @Override + public boolean mustBlock(Game game) { + return true; + } + + @Override + public UUID mustBlockAttacker(Ability source, Game game) { + return source.getFirstTarget(); + } + + @Override + public RevengeOfTheHuntedEffect copy() { + return new RevengeOfTheHuntedEffect(this); + } +} From 9d0a902817d5c73881beafc9214ec85da722fb25 Mon Sep 17 00:00:00 2001 From: magenoxx Date: Sat, 5 May 2012 17:58:55 +0400 Subject: [PATCH 09/18] Support for multi X in variable cost --- .../abilities/costs/mana/ManaCostsImpl.java | 18 +++++++++++++++--- .../abilities/costs/mana/VariableManaCost.java | 4 ++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Mage/src/mage/abilities/costs/mana/ManaCostsImpl.java b/Mage/src/mage/abilities/costs/mana/ManaCostsImpl.java index ea7129b1ca..a860c1bff5 100644 --- a/Mage/src/mage/abilities/costs/mana/ManaCostsImpl.java +++ b/Mage/src/mage/abilities/costs/mana/ManaCostsImpl.java @@ -28,7 +28,6 @@ package mage.abilities.costs.mana; -import java.util.*; import mage.Constants.ColoredManaSymbol; import mage.Mana; import mage.abilities.Ability; @@ -40,6 +39,8 @@ import mage.players.ManaPool; import mage.players.Player; import mage.target.Targets; +import java.util.*; + /** * @author BetaSteward_at_googlemail.com */ @@ -252,6 +253,7 @@ public class ManaCostsImpl extends ArrayList implements M if (mana == null || mana.length() == 0) return; String[] symbols = mana.split("^\\{|\\}\\{|\\}$"); + int modifierForX = 0; for (String symbol : symbols) { if (symbol.length() > 0) { if (symbol.length() == 1 || isNumeric(symbol)) { @@ -260,8 +262,18 @@ public class ManaCostsImpl extends ArrayList implements M } else { if (!symbol.equals("X")) this.add((T) new ColoredManaCost(ColoredManaSymbol.lookup(symbol.charAt(0)))); - else - this.add((T) new VariableManaCost()); + else { + // check X wasn't added before + if (modifierForX == 0) { + // count X occurence + for (String s : symbols) { + if (s.equals("X")) { + modifierForX++; + } + } + this.add((T) new VariableManaCost(modifierForX)); + } + } //TODO: handle multiple {X} and/or {Y} symbols } } else { diff --git a/Mage/src/mage/abilities/costs/mana/VariableManaCost.java b/Mage/src/mage/abilities/costs/mana/VariableManaCost.java index 0296169831..cf82980bf9 100644 --- a/Mage/src/mage/abilities/costs/mana/VariableManaCost.java +++ b/Mage/src/mage/abilities/costs/mana/VariableManaCost.java @@ -46,12 +46,12 @@ public class VariableManaCost extends ManaCostImpl implements public VariableManaCost() { this(1); - this.cost = new Mana(); - options.add(new Mana()); } public VariableManaCost(int multiplier) { this.multiplier = multiplier; + this.cost = new Mana(); + options.add(new Mana()); } public VariableManaCost(VariableManaCost manaCost) { From 788ee74e32521e8d4c07a93686f070a51ee2bd2c Mon Sep 17 00:00:00 2001 From: magenoxx Date: Sat, 5 May 2012 17:59:25 +0400 Subject: [PATCH 10/18] card plugin fix for null rule in tooltip --- .../main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java b/Mage.Client/src/main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java index 4623eca8f3..66456d0581 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java @@ -149,7 +149,7 @@ public class CardInfoPaneImpl extends JEditorPane implements CardInfoPane { legal = legal.replace("\r\n", "
"); legal += "
"; for (String ruling : rulings) { - if (!ruling.replace(".", "").trim().isEmpty()) { + if (ruling != null && !ruling.replace(".", "").trim().isEmpty()) { legal += "

"; legal += ruling; legal += "

"; From bd4c8e74740a44a745fb09f1bba94d62b6ed119a Mon Sep 17 00:00:00 2001 From: magenoxx Date: Sat, 5 May 2012 18:07:57 +0400 Subject: [PATCH 11/18] Extrace 'Take extra turn' to common effect --- .../turn/AddExtraTurnControllerEffect.java | 65 +++++++++++++++++++ .../common/turn/AddExtraTurnTargetEffect.java | 63 ++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 Mage/src/mage/abilities/effects/common/turn/AddExtraTurnControllerEffect.java create mode 100644 Mage/src/mage/abilities/effects/common/turn/AddExtraTurnTargetEffect.java diff --git a/Mage/src/mage/abilities/effects/common/turn/AddExtraTurnControllerEffect.java b/Mage/src/mage/abilities/effects/common/turn/AddExtraTurnControllerEffect.java new file mode 100644 index 0000000000..90f8b9c6bb --- /dev/null +++ b/Mage/src/mage/abilities/effects/common/turn/AddExtraTurnControllerEffect.java @@ -0,0 +1,65 @@ +/* +* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are +* permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this list of +* conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, this list +* of conditions and the following disclaimer in the documentation and/or other materials +* provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR +* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* The views and conclusions contained in the software and documentation are those of the +* authors and should not be interpreted as representing official policies, either expressed +* or implied, of BetaSteward_at_googlemail.com. +*/ +package mage.abilities.effects.common.turn; + +import mage.Constants; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.game.Game; +import mage.game.turn.TurnMod; +import mage.players.Player; + +/** + * @author noxx + */ +public class AddExtraTurnControllerEffect extends OneShotEffect { + + public AddExtraTurnControllerEffect() { + super(Constants.Outcome.ExtraTurn); + staticText = "Target player takes an extra turn after this one"; + } + + public AddExtraTurnControllerEffect(final AddExtraTurnControllerEffect effect) { + super(effect); + } + + @Override + public AddExtraTurnControllerEffect copy() { + return new AddExtraTurnControllerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player != null) { + game.getState().getTurnMods().add(new TurnMod(player.getId(), false)); + } + return true; + } + +} \ No newline at end of file diff --git a/Mage/src/mage/abilities/effects/common/turn/AddExtraTurnTargetEffect.java b/Mage/src/mage/abilities/effects/common/turn/AddExtraTurnTargetEffect.java new file mode 100644 index 0000000000..bf21242823 --- /dev/null +++ b/Mage/src/mage/abilities/effects/common/turn/AddExtraTurnTargetEffect.java @@ -0,0 +1,63 @@ +/* +* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are +* permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this list of +* conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, this list +* of conditions and the following disclaimer in the documentation and/or other materials +* provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR +* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* The views and conclusions contained in the software and documentation are those of the +* authors and should not be interpreted as representing official policies, either expressed +* or implied, of BetaSteward_at_googlemail.com. +*/ +package mage.abilities.effects.common.turn; + +import mage.Constants; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.game.Game; +import mage.game.turn.TurnMod; + +/** + * @author noxx + */ +public class AddExtraTurnTargetEffect extends OneShotEffect { + + public AddExtraTurnTargetEffect() { + super(Constants.Outcome.ExtraTurn); + staticText = "Target player takes an extra turn after this one"; + } + + public AddExtraTurnTargetEffect(final AddExtraTurnTargetEffect effect) { + super(effect); + } + + @Override + public AddExtraTurnTargetEffect copy() { + return new AddExtraTurnTargetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (source.getFirstTarget() != null) { + game.getState().getTurnMods().add(new TurnMod(source.getFirstTarget(), false)); + } + return true; + } + +} \ No newline at end of file From 92e5a771165cc5e0a74e13385cd2149c26a31a8a Mon Sep 17 00:00:00 2001 From: magenoxx Date: Sat, 5 May 2012 18:08:39 +0400 Subject: [PATCH 12/18] Some fixes for Miracle keyword. Added displaying spell in the stack --- .../mage/watchers/common/MiracleWatcher.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/Mage/src/mage/watchers/common/MiracleWatcher.java b/Mage/src/mage/watchers/common/MiracleWatcher.java index 4dbb6f0b55..b3355656a1 100644 --- a/Mage/src/mage/watchers/common/MiracleWatcher.java +++ b/Mage/src/mage/watchers/common/MiracleWatcher.java @@ -37,6 +37,7 @@ import mage.abilities.keyword.MiracleAbility; import mage.cards.Card; import mage.game.Game; import mage.game.events.GameEvent; +import mage.game.stack.StackAbility; import mage.players.Player; import mage.watchers.WatcherImpl; @@ -91,12 +92,25 @@ public class MiracleWatcher extends WatcherImpl { for (Ability ability : card.getAbilities()) { if (ability instanceof MiracleAbility) { Player controller = game.getPlayer(ability.getControllerId()); + // FIXME: I don't like that I need to call it manually + // it's the place for bugs + game.getContinuousEffects().costModification(ability, game); ManaCosts manaCostsToPay = ability.getManaCostsToPay(); - if (controller != null && controller.chooseUse(Constants.Outcome.Benefit, "Use Miracle " + manaCostsToPay.getText() + "?", game)) { - if (manaCostsToPay.pay(ability, game, ability.getSourceId(), ability.getControllerId(), false)) { - controller.cast(card.getSpellAbility(), game, true); + if (controller != null) { + game.getStack().add(new StackAbility(ability, controller.getId())); + if (controller.chooseUse(Constants.Outcome.Benefit, "Use Miracle " + manaCostsToPay.getText() + "?", game)) { + game.getStack().poll(); + ManaCosts costRef = card.getSpellAbility().getManaCostsToPay(); + // replace with the new cost + costRef.clear(); + for (ManaCost manaCost : manaCostsToPay) { + costRef.add(manaCost); + } + controller.cast(card.getSpellAbility(), game, false); + break; + } else { + game.getStack().poll(); } - break; } } } From b82f291ca9fbe3403f129e3015ab52f8e4cac569 Mon Sep 17 00:00:00 2001 From: magenoxx Date: Sat, 5 May 2012 18:09:06 +0400 Subject: [PATCH 13/18] [AVR] 5 cards with Miracle --- .../sets/avacynrestored/BanishingStroke.java | 79 +++++++++++++ .../avacynrestored/BonfireOfTheDamned.java | 111 ++++++++++++++++++ .../sets/avacynrestored/EntreatTheAngels.java | 69 +++++++++++ .../sets/avacynrestored/TemporalMastery.java | 67 +++++++++++ .../mage/sets/avacynrestored/Vanishment.java | 68 +++++++++++ 5 files changed, 394 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/BanishingStroke.java create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/BonfireOfTheDamned.java create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/EntreatTheAngels.java create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/TemporalMastery.java create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/Vanishment.java diff --git a/Mage.Sets/src/mage/sets/avacynrestored/BanishingStroke.java b/Mage.Sets/src/mage/sets/avacynrestored/BanishingStroke.java new file mode 100644 index 0000000000..d7da7d182c --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/BanishingStroke.java @@ -0,0 +1,79 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import mage.Constants.CardType; +import mage.Constants.Rarity; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.PutOnLibraryTargetEffect; +import mage.abilities.keyword.MiracleAbility; +import mage.cards.CardImpl; +import mage.filter.Filter; +import mage.filter.FilterPermanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * + * @author noxx + */ +public class BanishingStroke extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("artifact, creature, or enchantment"); + + static { + filter.getCardType().add(CardType.ARTIFACT); + filter.getCardType().add(CardType.CREATURE); + filter.getCardType().add(CardType.ENCHANTMENT); + filter.setScopeCardType(Filter.ComparisonScope.Any); + } + + public BanishingStroke(UUID ownerId) { + super(ownerId, 7, "Banishing Stroke", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{5}{W}"); + this.expansionSetCode = "AVR"; + + this.color.setWhite(true); + + // Put target artifact, creature, or enchantment on the bottom of its owner's library. + this.getSpellAbility().addEffect(new PutOnLibraryTargetEffect(false)); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + + // Miracle {W} + this.addAbility(new MiracleAbility(new ManaCostsImpl("{W}"))); + } + + public BanishingStroke(final BanishingStroke card) { + super(card); + } + + @Override + public BanishingStroke copy() { + return new BanishingStroke(this); + } +} diff --git a/Mage.Sets/src/mage/sets/avacynrestored/BonfireOfTheDamned.java b/Mage.Sets/src/mage/sets/avacynrestored/BonfireOfTheDamned.java new file mode 100644 index 0000000000..2b6309e026 --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/BonfireOfTheDamned.java @@ -0,0 +1,111 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import mage.Constants; +import mage.Constants.CardType; +import mage.Constants.Rarity; +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.MiracleAbility; +import mage.cards.CardImpl; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * + * @author noxx + */ +public class BonfireOfTheDamned extends CardImpl { + + public BonfireOfTheDamned(UUID ownerId) { + super(ownerId, 129, "Bonfire of the Damned", Rarity.MYTHIC, new CardType[]{CardType.SORCERY}, "{X}{X}{R}"); + this.expansionSetCode = "AVR"; + + this.color.setRed(true); + + // Bonfire of the Damned deals X damage to target player and each creature he or she controls. + this.getSpellAbility().addEffect(new BonfireOfTheDamnedEffect()); + this.getSpellAbility().addTarget(new TargetPlayer()); + + // Miracle {X}{R} + this.addAbility(new MiracleAbility(new ManaCostsImpl("{X}{R}"))); + } + + public BonfireOfTheDamned(final BonfireOfTheDamned card) { + super(card); + } + + @Override + public BonfireOfTheDamned copy() { + return new BonfireOfTheDamned(this); + } +} + +class BonfireOfTheDamnedEffect extends OneShotEffect { + + private static FilterPermanent filter = new FilterCreaturePermanent(); + + public BonfireOfTheDamnedEffect() { + super(Constants.Outcome.Damage); + staticText = "Bonfire of the Damned deals X damage to target player and each creature he or she controls"; + } + + public BonfireOfTheDamnedEffect(final BonfireOfTheDamnedEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getFirstTarget()); + if (player != null) { + int damage = source.getManaCostsToPay().getX(); + if (damage > 0) { + player.damage(damage, source.getId(), game, false, true); + for (Permanent perm: game.getBattlefield().getAllActivePermanents(filter, player.getId())) { + perm.damage(damage, source.getId(), game, true, false); + } + return true; + } + } + return false; + } + + @Override + public BonfireOfTheDamnedEffect copy() { + return new BonfireOfTheDamnedEffect(this); + } + +} diff --git a/Mage.Sets/src/mage/sets/avacynrestored/EntreatTheAngels.java b/Mage.Sets/src/mage/sets/avacynrestored/EntreatTheAngels.java new file mode 100644 index 0000000000..cef22db0d4 --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/EntreatTheAngels.java @@ -0,0 +1,69 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import mage.Constants.CardType; +import mage.Constants.Rarity; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.MiracleAbility; +import mage.cards.CardImpl; +import mage.game.permanent.token.AngelToken; + +import java.util.UUID; + +/** + * + * @author noxx + + */ +public class EntreatTheAngels extends CardImpl { + + public EntreatTheAngels(UUID ownerId) { + super(ownerId, 20, "Entreat the Angels", Rarity.MYTHIC, new CardType[]{CardType.SORCERY}, "{X}{X}{W}{W}{W}"); + this.expansionSetCode = "AVR"; + + this.color.setWhite(true); + + // Put X 4/4 white Angel creature tokens with flying onto the battlefield. + this.getSpellAbility().addEffect(new CreateTokenEffect(new AngelToken(), new ManacostVariableValue())); + + // Miracle {X}{W}{W} + this.addAbility(new MiracleAbility(new ManaCostsImpl("{X}{W}{W}"))); + } + + public EntreatTheAngels(final EntreatTheAngels card) { + super(card); + } + + @Override + public EntreatTheAngels copy() { + return new EntreatTheAngels(this); + } +} diff --git a/Mage.Sets/src/mage/sets/avacynrestored/TemporalMastery.java b/Mage.Sets/src/mage/sets/avacynrestored/TemporalMastery.java new file mode 100644 index 0000000000..dec1cba3a2 --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/TemporalMastery.java @@ -0,0 +1,67 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import mage.Constants.CardType; +import mage.Constants.Rarity; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ExileSpellEffect; +import mage.abilities.effects.common.turn.AddExtraTurnControllerEffect; +import mage.abilities.keyword.MiracleAbility; +import mage.cards.CardImpl; + +import java.util.UUID; + +/** + * @author noxx + */ +public class TemporalMastery extends CardImpl { + + public TemporalMastery(UUID ownerId) { + super(ownerId, 81, "Temporal Mastery", Rarity.MYTHIC, new CardType[]{CardType.SORCERY}, "{5}{U}{U}"); + this.expansionSetCode = "AVR"; + + this.color.setBlue(true); + + // Take an extra turn after this one. Exile Temporal Mastery. + this.getSpellAbility().addEffect(new AddExtraTurnControllerEffect()); + this.getSpellAbility().addEffect(ExileSpellEffect.getInstance()); + + // Miracle {1}{U} + this.addAbility(new MiracleAbility(new ManaCostsImpl("{1}{U}"))); + } + + public TemporalMastery(final TemporalMastery card) { + super(card); + } + + @Override + public TemporalMastery copy() { + return new TemporalMastery(this); + } +} diff --git a/Mage.Sets/src/mage/sets/avacynrestored/Vanishment.java b/Mage.Sets/src/mage/sets/avacynrestored/Vanishment.java new file mode 100644 index 0000000000..af3661282d --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/Vanishment.java @@ -0,0 +1,68 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import mage.Constants.CardType; +import mage.Constants.Rarity; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.PutOnLibraryTargetEffect; +import mage.abilities.keyword.MiracleAbility; +import mage.cards.CardImpl; +import mage.target.common.TargetNonlandPermanent; + +import java.util.UUID; + +/** + * + * @author noxx + */ +public class Vanishment extends CardImpl { + + public Vanishment(UUID ownerId) { + super(ownerId, 82, "Vanishment", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{4}{U}"); + this.expansionSetCode = "AVR"; + + this.color.setBlue(true); + + // Put target nonland permanent on top of its owner's library. + this.getSpellAbility().addEffect(new PutOnLibraryTargetEffect(true)); + this.getSpellAbility().addTarget(new TargetNonlandPermanent()); + + // Miracle {U} + this.addAbility(new MiracleAbility(new ManaCostsImpl("{U}"))); + } + + public Vanishment(final Vanishment card) { + super(card); + } + + @Override + public Vanishment copy() { + return new Vanishment(this); + } +} From b05519078a436cfd8130afbc0459cc61ac56f602 Mon Sep 17 00:00:00 2001 From: magenoxx Date: Sat, 5 May 2012 18:46:26 +0400 Subject: [PATCH 14/18] [AVR] 5 cards --- .../mage/sets/avacynrestored/Fettergeist.java | 121 ++++++++++++++++++ .../mage/sets/avacynrestored/MistRaven.java | 74 +++++++++++ .../sets/avacynrestored/ScrapskinDrake.java | 72 +++++++++++ .../sets/avacynrestored/SpectralPrison.java | 119 +++++++++++++++++ .../mage/sets/avacynrestored/SpiritAway.java | 85 ++++++++++++ 5 files changed, 471 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/Fettergeist.java create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/MistRaven.java create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/ScrapskinDrake.java create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/SpectralPrison.java create mode 100644 Mage.Sets/src/mage/sets/avacynrestored/SpiritAway.java diff --git a/Mage.Sets/src/mage/sets/avacynrestored/Fettergeist.java b/Mage.Sets/src/mage/sets/avacynrestored/Fettergeist.java new file mode 100644 index 0000000000..0961cbac39 --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/Fettergeist.java @@ -0,0 +1,121 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import mage.Constants; +import mage.Constants.CardType; +import mage.Constants.Rarity; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +import java.util.UUID; + +/** + * + * @author noxx + */ +public class Fettergeist extends CardImpl { + + public Fettergeist(UUID ownerId) { + super(ownerId, 52, "Fettergeist", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{2}{U}"); + this.expansionSetCode = "AVR"; + this.subtype.add("Spirit"); + + this.color.setBlue(true); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + this.addAbility(FlyingAbility.getInstance()); + + // At the beginning of your upkeep, sacrifice Fettergeist unless you pay {1} for each other creature you control. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new FettergeistUnlessPaysEffect(), Constants.TargetController.YOU, false)); + + } + + public Fettergeist(final Fettergeist card) { + super(card); + } + + @Override + public Fettergeist copy() { + return new Fettergeist(this); + } +} + +class FettergeistUnlessPaysEffect extends OneShotEffect { + + private static FilterCreaturePermanent filter = new FilterCreaturePermanent(); + + public FettergeistUnlessPaysEffect() { + super(Constants.Outcome.Sacrifice); + staticText = "sacrifice {this} unless you pay {1} for each other creature you control."; + } + + public FettergeistUnlessPaysEffect(final FettergeistUnlessPaysEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getSourceId()); + if (player != null && permanent != null) { + PermanentsOnBattlefieldCount amount = new PermanentsOnBattlefieldCount(filter, 1); + int count = amount.calculate(game, source); + if (count == 0) { + return true; + } + if (player.chooseUse(Constants.Outcome.Benefit, "Pay " + count + "?", game)) { + GenericManaCost cost = new GenericManaCost(count); + if (cost.pay(source, game, source.getId(), source.getControllerId(), false)) { + return true; + } + } + permanent.sacrifice(source.getSourceId(), game); + return true; + } + return false; + } + + @Override + public FettergeistUnlessPaysEffect copy() { + return new FettergeistUnlessPaysEffect(this); + } + +} + diff --git a/Mage.Sets/src/mage/sets/avacynrestored/MistRaven.java b/Mage.Sets/src/mage/sets/avacynrestored/MistRaven.java new file mode 100644 index 0000000000..97a32495b8 --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/MistRaven.java @@ -0,0 +1,74 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import mage.Constants.CardType; +import mage.Constants.Rarity; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author noxx + + */ +public class MistRaven extends CardImpl { + + public MistRaven(UUID ownerId) { + super(ownerId, 67, "Mist Raven", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{2}{U}{U}"); + this.expansionSetCode = "AVR"; + this.subtype.add("Bird"); + + this.color.setBlue(true); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + this.addAbility(FlyingAbility.getInstance()); + + // When Mist Raven enters the battlefield, return target creature to its owner's hand. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandTargetEffect()); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + public MistRaven(final MistRaven card) { + super(card); + } + + @Override + public MistRaven copy() { + return new MistRaven(this); + } +} diff --git a/Mage.Sets/src/mage/sets/avacynrestored/ScrapskinDrake.java b/Mage.Sets/src/mage/sets/avacynrestored/ScrapskinDrake.java new file mode 100644 index 0000000000..5af31267ad --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/ScrapskinDrake.java @@ -0,0 +1,72 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import mage.Constants; +import mage.Constants.CardType; +import mage.Constants.Rarity; +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continious.CanBlockOnlyFlyingEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; + +import java.util.UUID; + +/** + * + * @author noxx + + */ +public class ScrapskinDrake extends CardImpl { + + public ScrapskinDrake(UUID ownerId) { + super(ownerId, 73, "Scrapskin Drake", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{2}{U}"); + this.expansionSetCode = "AVR"; + this.subtype.add("Zombie"); + this.subtype.add("Drake"); + + this.color.setBlue(true); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + this.addAbility(FlyingAbility.getInstance()); + + // Scrapskin Drake can block only creatures with flying. + this.addAbility(new SimpleStaticAbility(Constants.Zone.BATTLEFIELD, new CanBlockOnlyFlyingEffect())); + } + + public ScrapskinDrake(final ScrapskinDrake card) { + super(card); + } + + @Override + public ScrapskinDrake copy() { + return new ScrapskinDrake(this); + } +} diff --git a/Mage.Sets/src/mage/sets/avacynrestored/SpectralPrison.java b/Mage.Sets/src/mage/sets/avacynrestored/SpectralPrison.java new file mode 100644 index 0000000000..b67a2fa618 --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/SpectralPrison.java @@ -0,0 +1,119 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import mage.Constants; +import mage.Constants.CardType; +import mage.Constants.Rarity; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.DestroySourceEffect; +import mage.abilities.effects.common.SkipEnchantedUntapEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author noxx + */ +public class SpectralPrison extends CardImpl { + + public SpectralPrison(UUID ownerId) { + super(ownerId, 75, "Spectral Prison", Rarity.COMMON, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}"); + this.expansionSetCode = "AVR"; + this.subtype.add("Aura"); + + this.color.setBlue(true); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Constants.Outcome.Detriment)); + + // Enchanted creature doesn't untap during its controller's untap step. + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + this.addAbility(new SimpleStaticAbility(Constants.Zone.BATTLEFIELD, new SkipEnchantedUntapEffect())); + + // When enchanted creature becomes the target of a spell, sacrifice Spectral Prison. + this.addAbility(new SpectralPrisonAbility()); + } + + public SpectralPrison(final SpectralPrison card) { + super(card); + } + + @Override + public SpectralPrison copy() { + return new SpectralPrison(this); + } +} + +class SpectralPrisonAbility extends TriggeredAbilityImpl { + + public SpectralPrisonAbility() { + super(Constants.Zone.BATTLEFIELD, new DestroySourceEffect()); + } + + public SpectralPrisonAbility(final SpectralPrisonAbility ability) { + super(ability); + } + + @Override + public SpectralPrisonAbility copy() { + return new SpectralPrisonAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.TARGETED) { + Permanent enchantment = game.getPermanent(sourceId); + if (enchantment != null && enchantment.getAttachedTo() != null) { + if (event.getTargetId().equals(enchantment.getAttachedTo())) { + return true; + } + } + } + return false; + } + + @Override + public String getRule() { + return "When enchanted creature becomes the target of a spell or ability, destroy {this}."; + } + +} diff --git a/Mage.Sets/src/mage/sets/avacynrestored/SpiritAway.java b/Mage.Sets/src/mage/sets/avacynrestored/SpiritAway.java new file mode 100644 index 0000000000..09f2a69d73 --- /dev/null +++ b/Mage.Sets/src/mage/sets/avacynrestored/SpiritAway.java @@ -0,0 +1,85 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.avacynrestored; + +import mage.Constants; +import mage.Constants.CardType; +import mage.Constants.Rarity; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continious.BoostEnchantedEffect; +import mage.abilities.effects.common.continious.ControlEnchantedEffect; +import mage.abilities.effects.common.continious.GainAbilityAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author noxx + + */ +public class SpiritAway extends CardImpl { + + public SpiritAway(UUID ownerId) { + super(ownerId, 76, "Spirit Away", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{5}{U}{U}"); + this.expansionSetCode = "AVR"; + this.subtype.add("Aura"); + + this.color.setBlue(true); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Constants.Outcome.Detriment)); + + // You control enchanted creature. + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + this.addAbility(new SimpleStaticAbility(Constants.Zone.BATTLEFIELD, new ControlEnchantedEffect())); + + // Enchanted creature gets +2/+2 and has flying. + SimpleStaticAbility ability2 = new SimpleStaticAbility(Constants.Zone.BATTLEFIELD, new BoostEnchantedEffect(2, 2, Constants.Duration.WhileOnBattlefield)); + ability2.addEffect(new GainAbilityAttachedEffect(FlyingAbility.getInstance(), Constants.AttachmentType.AURA)); + this.addAbility(ability2); + } + + public SpiritAway(final SpiritAway card) { + super(card); + } + + @Override + public SpiritAway copy() { + return new SpiritAway(this); + } +} From 951e335997e2f5c73db4f6f1f2c6a932d2920177 Mon Sep 17 00:00:00 2001 From: magenoxx Date: Sun, 6 May 2012 11:22:00 +0400 Subject: [PATCH 15/18] [load test] new load pattern --- .../mage/test/load/LoadCallbackClient.java | 74 +++++++++++++++++ .../org/mage/test/load/LoadPhaseManager.java | 82 +++++++++++++++++++ .../java/org/mage/test/load/LoadTest.java | 26 ++++-- .../org/mage/test/load/SimpleMageClient.java | 4 + 4 files changed, 180 insertions(+), 6 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/load/LoadPhaseManager.java diff --git a/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java b/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java index 41f31bd388..0f07cbb193 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java @@ -4,9 +4,14 @@ import mage.interfaces.callback.CallbackClient; import mage.interfaces.callback.ClientCallback; import mage.remote.Session; import mage.utils.CompressUtil; +import mage.view.GameClientMessage; +import mage.view.GameView; +import mage.view.SimpleCardView; import mage.view.TableClientMessage; import org.apache.log4j.Logger; +import java.util.UUID; + /** * @author noxx */ @@ -15,15 +20,58 @@ public class LoadCallbackClient implements CallbackClient { private static final transient Logger log = Logger.getLogger(LoadCallbackClient.class); private Session session; + private UUID gameId; + private UUID playerId; + private boolean gameOver; + + private volatile int controlCount; + + private GameView gameView; @Override public void processCallback(ClientCallback callback) { //TODO + controlCount = 0; log.info(callback.getMethod()); callback.setData(CompressUtil.decompress(callback.getData())); if (callback.getMethod().equals("startGame")) { TableClientMessage message = (TableClientMessage) callback.getData(); + gameId = message.getGameId(); + playerId = message.getPlayerId(); session.joinGame(message.getGameId()); + startControlThread(); + } else if (callback.getMethod().equals("gameInform")) { + GameClientMessage message = (GameClientMessage) callback.getData(); + log.info("Inform: " + message.getMessage()); + gameView = message.getGameView(); + } else if (callback.getMethod().equals("gameInit")) { + + } else if (callback.getMethod().equals("gameTarget")) { + GameClientMessage message = (GameClientMessage) callback.getData(); + log.info("Target: " + message.getMessage()); + if (message.getMessage().equals("Select a starting player")) { + session.sendPlayerUUID(gameId, playerId); + } else if (message.getMessage().equals("Select a card to discard")) { + log.info("hand size: " + gameView.getHand().size()); + SimpleCardView card = gameView.getHand().values().iterator().next(); + session.sendPlayerUUID(gameId, card.getId()); + } + } else if (callback.getMethod().equals("gameAsk")) { + GameClientMessage message = (GameClientMessage) callback.getData(); + log.info("Ask: " + message.getMessage()); + if (message.getMessage().equals("Do you want to take a mulligan?")) { + session.sendPlayerBoolean(gameId, false); + } + } else if (callback.getMethod().equals("gameSelect")) { + GameClientMessage message = (GameClientMessage) callback.getData(); + log.info("Select: " + message.getMessage()); + if (LoadPhaseManager.getInstance().isSkip(message.getGameView(), message.getMessage(), playerId)) { + log.info("Skipped: " + message.getMessage()); + session.sendPlayerBoolean(gameId, false); + } + } else if (callback.getMethod().equals("gameOver")) { + log.info("Game over"); + gameOver = true; } } @@ -31,4 +79,30 @@ public class LoadCallbackClient implements CallbackClient { public void setSession(Session session) { this.session = session; } + + public boolean isGameOver() { + return gameOver; + } + + private void startControlThread() { + new Thread(new Runnable() { + @Override + public void run() { + while (true) { + controlCount++; + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (controlCount > 5) { + log.warn("Game seems freezed. Sending boolean message to server."); + session.sendPlayerBoolean(gameId, false); + controlCount = 0; + } + } + + } + }).start(); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/load/LoadPhaseManager.java b/Mage.Tests/src/test/java/org/mage/test/load/LoadPhaseManager.java new file mode 100644 index 0000000000..8b9470864f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/load/LoadPhaseManager.java @@ -0,0 +1,82 @@ +package org.mage.test.load; + +import mage.view.GameView; +import mage.view.PlayerView; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class LoadPhaseManager { + + private static final LoadPhaseManager fInstance = new LoadPhaseManager(); + + public static String DEFAULT_PLAYER_NAME = "player"; + + public static String PHASE_ON = "on"; + public static String PHASE_OFF = "off"; + public static String UPKEEP_YOU = "upkeepYou"; + public static String DRAW_YOU = "drawYou"; + public static String MAIN_YOU = "mainYou"; + public static String BEFORE_COMBAT_YOU = "beforeCombatYou"; + public static String END_OF_COMBAT_YOU = "endOfCombatYou"; + public static String MAIN_2_YOU = "main2You"; + public static String END_OF_TURN_YOU = "endOfTurnYou"; + + public static String UPKEEP_OTHERS = "upkeepOthers"; + public static String DRAW_OTHERS = "drawOthers"; + public static String MAIN_OTHERS = "mainOthers"; + public static String BEFORE_COMBAT_OTHERS = "beforeCombatOthers"; + public static String END_OF_COMBAT_OTHERS = "endOfCombatOthers"; + public static String MAIN_2_OTHERS = "main2Others"; + public static String END_OF_TURN_OTHERS = "endOfTurnOthers"; + + private static Map mapYou = new HashMap() {{ + put("Upkeep - play instants and activated abilities.", UPKEEP_YOU); + put("Draw - play instants and activated abilities.", DRAW_YOU); + put("Precombat Main - play spells and abilities.", MAIN_YOU); + put("Begin Combat - play instants and activated abilities.", BEFORE_COMBAT_YOU); + put("End Combat - play instants and activated abilities.", END_OF_COMBAT_YOU); + put("Postcombat Main - play spells and abilities.", MAIN_2_YOU); + put("End Turn - play instants and activated abilities.", END_OF_TURN_YOU); + }}; + + private static Map mapOthers = new HashMap() {{ + put("Upkeep - play instants and activated abilities.", UPKEEP_OTHERS); + put("Draw - play instants and activated abilities.", DRAW_OTHERS); + put("Precombat Main - play instants and activated abilities.", MAIN_OTHERS); + put("Begin Combat - play instants and activated abilities.", BEFORE_COMBAT_OTHERS); + put("End Combat - play instants and activated abilities.", END_OF_COMBAT_OTHERS); + put("Postcombat Main - play instants and activated abilities.", MAIN_2_OTHERS); + put("End Turn - play instants and activated abilities.", END_OF_TURN_OTHERS); + }}; + + public static LoadPhaseManager getInstance() { + return fInstance; + } + + public boolean isSkip(GameView gameView, String message, UUID playerId) { + UUID activePlayer = null; + Map map = mapOthers; + for (PlayerView playerView : gameView.getPlayers()) { + if (playerView.isActive()) { + activePlayer = playerView.getPlayerId(); + if (activePlayer.equals(playerId)) { + map = mapYou; + } + } + } + if (activePlayer == null) { + throw new IllegalStateException("No active player found."); + } + for (Map.Entry entry : map.entrySet()) { + if (message.equals(entry.getKey())) { + /*if (message.equals("Precombat Main - play spells and abilities.")) { + return false; + }*/ + return true; + } + } + return false; + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java b/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java index 8e72dea2c3..21fd8ebe3e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java @@ -129,15 +129,14 @@ public class LoadTest { } /** - * Test playing the whole game. - * Player use cheat to add lands, creatures and other cards. - * Then play only lands, one of them plays 1 damage targeting player. + * Tests simple game till the end (game over). + * Players do nothing but skip phases and discard cards at the end. * - * This results in 40 turns of the game. + * This results in a game that lasts until there is no cards in library. */ @Test @Ignore - public void testPlayGame() throws Exception { + public void testSimpleGame() throws Exception { DeckCardLists deckList = createDeck(); for (int i = 0; i < EXECUTION_COUNT_PLAY_GAME; i++) { @@ -182,10 +181,25 @@ public class LoadTest { /*** Start game ***/ session.startGame(roomId, table.getTableId()); - Thread.sleep(100); + while (!mageClient.isGameOver()) { + Thread.sleep(1000); + } } } + /** + * Tests playing the whole game. + * Player use cheat to add lands, creatures and other cards. + * Then play only lands, one of them plays 1 damage targeting player. + * + * This results in 40 turns of the game. + */ + @Test + @Ignore + public void testPlayGame() throws Exception { + //TODO: to be implemented + } + /** * Creates connection to the server. * Server should run independently. diff --git a/Mage.Tests/src/test/java/org/mage/test/load/SimpleMageClient.java b/Mage.Tests/src/test/java/org/mage/test/load/SimpleMageClient.java index b46a2ed987..a21da72c79 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/SimpleMageClient.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/SimpleMageClient.java @@ -66,4 +66,8 @@ public class SimpleMageClient implements MageClient { public void setSession(Session session) { ((LoadCallbackClient)callbackClient).setSession(session); } + + public boolean isGameOver() { + return ((LoadCallbackClient)callbackClient).isGameOver(); + } } From 13776a06291e284d47b7a2cfc9a2cc87aa360a7a Mon Sep 17 00:00:00 2001 From: magenoxx Date: Sun, 6 May 2012 16:12:09 +0400 Subject: [PATCH 16/18] Fixed memory leak --- .../java/mage/client/table/TablesPanel.java | 3 + .../java/mage/server/TableController.java | 10 +- .../java/org/mage/test/load/LoadTest.java | 110 +++++++++++------- Mage/src/mage/game/GameImpl.java | 18 +-- Mage/src/mage/game/turn/Turn.java | 13 ++- 5 files changed, 91 insertions(+), 63 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index 04d48638a1..4931d52e26 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -97,6 +97,9 @@ public class TablesPanel extends javax.swing.JPanel { initComponents(); + // disable replays + chkShowCompleted.setVisible(false); + tableTables.createDefaultColumnsFromModel(); chatPanel.useExtendedView(ChatPanel.VIEW_MODE.NONE); chatPanel.setBorder(null); diff --git a/Mage.Server/src/main/java/mage/server/TableController.java b/Mage.Server/src/main/java/mage/server/TableController.java index 68be1a1c24..f7be561dcf 100644 --- a/Mage.Server/src/main/java/mage/server/TableController.java +++ b/Mage.Server/src/main/java/mage/server/TableController.java @@ -52,7 +52,6 @@ import mage.server.challenge.ChallengeManager; import mage.server.draft.DraftManager; import mage.server.game.*; import mage.server.services.LogKeys; -import mage.server.services.LogService; import mage.server.services.impl.LogServiceImpl; import mage.server.tournament.TournamentFactory; import mage.server.tournament.TournamentManager; @@ -432,9 +431,12 @@ public class TableController { cancelTimeout(); startGame(choosingPlayerId); } -// else { -// GamesRoomManager.getInstance().removeTable(table.getId()); -// } + else { + GamesRoomManager.getInstance().removeTable(table.getId()); + match.getGames().clear(); + match = null; + table = null; + } } catch (GameException ex) { logger.fatal(null, ex); } diff --git a/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java b/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java index 21fd8ebe3e..6a4b1022bf 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java @@ -72,7 +72,7 @@ public class LoadTest { /** * Determines how many times test will be executed in a row. */ - private static final int EXECUTION_COUNT_PLAY_GAME = 1; + private static final int EXECUTION_COUNT_PLAY_GAME = 100; /** * Tests connecting with two players, creating game and starting it. @@ -128,63 +128,83 @@ public class LoadTest { } } + /** + * Tests 10 simple games played one after another. + */ + @Test + @Ignore + public void testSimpleGame() throws Exception { + final DeckCardLists deckList = createDeck(); + + for (int i = 0; i < EXECUTION_COUNT_PLAY_GAME; i++) { + final int j = i; + Thread t = new Thread(new Runnable() { + @Override + public void run() { + try { + testSimpleGame0(deckList, j); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + t.start(); + t.join(); + } + } + /** * Tests simple game till the end (game over). * Players do nothing but skip phases and discard cards at the end. * * This results in a game that lasts until there is no cards in library. */ - @Test - @Ignore - public void testSimpleGame() throws Exception { - DeckCardLists deckList = createDeck(); + private boolean testSimpleGame0(DeckCardLists deckList, int i) throws InterruptedException { + Connection connection = createConnection(TEST_USER_NAME + i); - for (int i = 0; i < EXECUTION_COUNT_PLAY_GAME; i++) { - Connection connection = createConnection(TEST_USER_NAME + i); + SimpleMageClient mageClient = new SimpleMageClient(); + Session session = new SessionImpl(mageClient); - SimpleMageClient mageClient = new SimpleMageClient(); - Session session = new SessionImpl(mageClient); + session.connect(connection); - session.connect(connection); + mageClient.setSession(session); + UUID roomId = session.getMainRoomId(); - mageClient.setSession(session); - UUID roomId = session.getMainRoomId(); + GameTypeView gameTypeView = session.getGameTypes().get(0); + log.info("Game type view: " + gameTypeView.getName()); + MatchOptions options = createGameOptions(gameTypeView, session); - GameTypeView gameTypeView = session.getGameTypes().get(0); - log.info("Game type view: " + gameTypeView.getName()); - MatchOptions options = createGameOptions(gameTypeView, session); + TableView table = session.createTable(roomId, options); - TableView table = session.createTable(roomId, options); - - if (!session.joinTable(roomId, table.getTableId(), TEST_USER_NAME + i, "Human", 1, deckList)) { - log.error("Error while joining table"); - Assert.assertTrue("Error while joining table", false); - return; - } - - /*** Connect with a second player ***/ - Connection connection2 = createConnection(TEST_USER_NAME_2 + i); - SimpleMageClient mageClient2 = new SimpleMageClient(); - Session session2 = new SessionImpl(mageClient2); - session2.connect(connection2); - - mageClient2.setSession(session2); - UUID roomId2 = session2.getMainRoomId(); - - // connect to the table with the same deck - if (!session2.joinTable(roomId2, table.getTableId(), TEST_USER_NAME_2 + i, "Human", 1, deckList)) { - log.error("Error while joining table"); - Assert.assertTrue("Error while joining table", false); - return; - } - - /*** Start game ***/ - session.startGame(roomId, table.getTableId()); - - while (!mageClient.isGameOver()) { - Thread.sleep(1000); - } + if (!session.joinTable(roomId, table.getTableId(), TEST_USER_NAME + i, "Human", 1, deckList)) { + log.error("Error while joining table"); + Assert.assertTrue("Error while joining table", false); + return true; } + + /*** Connect with a second player ***/ + Connection connection2 = createConnection(TEST_USER_NAME_2 + i); + SimpleMageClient mageClient2 = new SimpleMageClient(); + Session session2 = new SessionImpl(mageClient2); + session2.connect(connection2); + + mageClient2.setSession(session2); + UUID roomId2 = session2.getMainRoomId(); + + // connect to the table with the same deck + if (!session2.joinTable(roomId2, table.getTableId(), TEST_USER_NAME_2 + i, "Human", 1, deckList)) { + log.error("Error while joining table"); + Assert.assertTrue("Error while joining table", false); + return true; + } + + /*** Start game ***/ + session.startGame(roomId, table.getTableId()); + + while (!mageClient.isGameOver()) { + Thread.sleep(1000); + } + return false; } /** diff --git a/Mage/src/mage/game/GameImpl.java b/Mage/src/mage/game/GameImpl.java index 13e57de941..2aea6c4135 100644 --- a/Mage/src/mage/game/GameImpl.java +++ b/Mage/src/mage/game/GameImpl.java @@ -386,7 +386,7 @@ public abstract class GameImpl> implements Game, Serializa scorePlayer = state.getPlayers().values().iterator().next(); init(choosingPlayerId, options); play(startingPlayerId); - saveState(); + //saveState(); } @Override @@ -448,7 +448,7 @@ public abstract class GameImpl> implements Game, Serializa player.beginTurn(this); } fireInformEvent("game has started"); - saveState(); + //saveState(); //20091005 - 103.1 if (!gameOptions.skipInitShuffling) { //don't shuffle in test mode for card injection on top of player's libraries @@ -476,7 +476,7 @@ public abstract class GameImpl> implements Game, Serializa return; } - saveState(); + //saveState(); //20091005 - 103.3 for (UUID playerId: state.getPlayerList(startingPlayerId)) { @@ -496,7 +496,7 @@ public abstract class GameImpl> implements Game, Serializa mulligan(player.getId()); } fireInformEvent(player.getName() + " keeps hand"); - saveState(); + //saveState(); } for (UUID playerId : state.getPlayerList(startingPlayerId)) { @@ -627,8 +627,8 @@ public abstract class GameImpl> implements Game, Serializa Player player; while (!isPaused() && !isGameOver()) { try { - if (bookmark == 0) - bookmark = bookmarkState(); + //if (bookmark == 0) + //bookmark = bookmarkState(); player = getPlayer(state.getPlayerList().get()); state.setPriorityPlayerId(player.getId()); while (!player.isPassed() && !player.hasLost() && !player.hasLeft() && !isPaused() && !isGameOver()) { @@ -654,7 +654,7 @@ public abstract class GameImpl> implements Game, Serializa state.getRevealed().reset(); break; } else { - removeBookmark(bookmark); + //removeBookmark(bookmark); return; } } @@ -662,13 +662,13 @@ public abstract class GameImpl> implements Game, Serializa catch (Exception ex) { logger.fatal("Game exception ", ex); this.fireErrorEvent("Game exception occurred: ", ex); - restoreState(bookmark); + //restoreState(bookmark); bookmark = 0; continue; } state.getPlayerList().getNext(); } - removeBookmark(bookmark); + //removeBookmark(bookmark); bookmark = 0; } } catch (Exception ex) { diff --git a/Mage/src/mage/game/turn/Turn.java b/Mage/src/mage/game/turn/Turn.java index 21166ae6b7..12ef1da10e 100644 --- a/Mage/src/mage/game/turn/Turn.java +++ b/Mage/src/mage/game/turn/Turn.java @@ -28,15 +28,16 @@ package mage.game.turn; +import mage.Constants.PhaseStep; +import mage.Constants.TurnPhase; +import mage.game.Game; +import mage.players.Player; + import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.UUID; -import mage.Constants.PhaseStep; -import mage.Constants.TurnPhase; -import mage.game.Game; -import mage.players.Player; /** * @@ -121,7 +122,9 @@ public class Turn implements Serializable { if (phase.play(game, activePlayerId)) { //20091005 - 500.4/703.4n game.emptyManaPools(); - game.saveState(); + + //game.saveState(); + //20091005 - 500.8 playExtraPhases(game, phase.getType()); } From 074faf09ec13d487f36f544c1ac9d36c77f3dd90 Mon Sep 17 00:00:00 2001 From: magenoxx Date: Sun, 6 May 2012 23:16:55 +0400 Subject: [PATCH 17/18] GlimmerpointStag effect outcome fix --- .../src/mage/sets/scarsofmirrodin/GlimmerpointStag.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/sets/scarsofmirrodin/GlimmerpointStag.java b/Mage.Sets/src/mage/sets/scarsofmirrodin/GlimmerpointStag.java index 12eecb5d15..5bafc06682 100644 --- a/Mage.Sets/src/mage/sets/scarsofmirrodin/GlimmerpointStag.java +++ b/Mage.Sets/src/mage/sets/scarsofmirrodin/GlimmerpointStag.java @@ -27,7 +27,6 @@ */ package mage.sets.scarsofmirrodin; -import java.util.UUID; import mage.Constants.CardType; import mage.Constants.Outcome; import mage.Constants.Rarity; @@ -45,6 +44,8 @@ import mage.game.permanent.Permanent; import mage.target.Target; import mage.target.TargetPermanent; +import java.util.UUID; + /** * * @author maurer.it_at_gmail.com @@ -83,7 +84,7 @@ class GlimmerpointStagEffect extends OneShotEffect { private static final String effectText = "exile another target permanent. Return that card to the battlefield under its owner's control at the beginning of the next end step"; GlimmerpointStagEffect ( ) { - super(Outcome.Benefit); + super(Outcome.Detriment); staticText = effectText; } From 836b9aaf25bebe85b83e0ce695580352bd1d66db Mon Sep 17 00:00:00 2001 From: magenoxx Date: Sun, 6 May 2012 23:17:57 +0400 Subject: [PATCH 18/18] Added looking at card and revealing it for Miracle keyword --- Mage/src/mage/watchers/common/MiracleWatcher.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Mage/src/mage/watchers/common/MiracleWatcher.java b/Mage/src/mage/watchers/common/MiracleWatcher.java index b3355656a1..ba843d4ec3 100644 --- a/Mage/src/mage/watchers/common/MiracleWatcher.java +++ b/Mage/src/mage/watchers/common/MiracleWatcher.java @@ -35,6 +35,8 @@ import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; import mage.abilities.keyword.MiracleAbility; import mage.cards.Card; +import mage.cards.Cards; +import mage.cards.CardsImpl; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.stack.StackAbility; @@ -98,8 +100,12 @@ public class MiracleWatcher extends WatcherImpl { ManaCosts manaCostsToPay = ability.getManaCostsToPay(); if (controller != null) { game.getStack().add(new StackAbility(ability, controller.getId())); + Cards cards = new CardsImpl(Constants.Zone.PICK); + cards.add(card); + controller.lookAtCards("Miracle", cards, game); if (controller.chooseUse(Constants.Outcome.Benefit, "Use Miracle " + manaCostsToPay.getText() + "?", game)) { game.getStack().poll(); + controller.revealCards("Miracle", cards, game); ManaCosts costRef = card.getSpellAbility().getManaCostsToPay(); // replace with the new cost costRef.clear();