From bf60039ee73def81d970116c0ec145af826bd59d Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sat, 4 Apr 2020 14:17:47 +0000 Subject: [PATCH 01/19] GameActionUtil: fix CyclingForZero --- .../main/java/forge/game/GameActionUtil.java | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index 6d9ed807964..be1b30a9199 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -31,6 +31,7 @@ import forge.game.ability.ApiType; import forge.game.card.*; import forge.game.card.CardPlayOption.PayManaCost; import forge.game.cost.Cost; +import forge.game.keyword.Keyword; import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import forge.game.player.PlayerController; @@ -218,23 +219,34 @@ public final class GameActionUtil { } } - if (!sa.isBasicSpell()) { - return alternatives; - } - + // below are for some special cases of activated abilities if (sa.isCycling() && activator.hasKeyword("CyclingForZero")) { - // set the cost to this directly to buypass non mana cost - final SpellAbility newSA = sa.copyWithDefinedCost("Discard<1/CARDNAME>"); - newSA.setActivatingPlayer(activator); - newSA.setBasicSpell(false); - newSA.getMapParams().put("CostDesc", ManaCostParser.parse("0")); - // makes new SpellDescription - final StringBuilder sb = new StringBuilder(); - sb.append(newSA.getCostDescription()); - sb.append(newSA.getParam("SpellDescription")); - newSA.setDescription(sb.toString()); - alternatives.add(newSA); + for (final KeywordInterface inst : source.getKeywords()) { + // need to find the correct Keyword from which this Ability is from + if (!inst.getAbilities().contains(sa)) { + continue; + } + + // set the cost to this directly to buypass non mana cost + final SpellAbility newSA = sa.copyWithDefinedCost("Discard<1/CARDNAME>"); + newSA.setActivatingPlayer(activator); + newSA.getMapParams().put("CostDesc", ManaCostParser.parse("0")); + + // need to build a new Keyword to get better Reminder Text + String data[] = inst.getOriginal().split(":"); + data[1] = "0"; + KeywordInterface newKi = Keyword.getInstance(StringUtils.join(data, ":")); + + // makes new SpellDescription + final StringBuilder sb = new StringBuilder(); + sb.append(newSA.getCostDescription()); + sb.append("(").append(newKi.getReminderText()).append(")"); + newSA.setDescription(sb.toString()); + + alternatives.add(newSA); + break; + } } if (sa.hasParam("Equip") && activator.hasKeyword("EquipInstantSpeed")) { From 9f1b93373d4d50eebd3b5153a774584b4dabf9c7 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sun, 5 Apr 2020 07:02:13 +0800 Subject: [PATCH 02/19] Refactor check permission without supportv4 dependency --- forge-gui-android/pom.xml | 7 ------- forge-gui-android/src/forge/app/Main.java | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/forge-gui-android/pom.xml b/forge-gui-android/pom.xml index 194d8786a2c..a556f6c9698 100644 --- a/forge-gui-android/pom.xml +++ b/forge-gui-android/pom.xml @@ -104,13 +104,6 @@ gdx-backend-android 1.9.10 - - com.android.support - support-v4 - 23.1.1 - system - ${pom.basedir}/libs/android-support-v4.jar - diff --git a/forge-gui-android/src/forge/app/Main.java b/forge-gui-android/src/forge/app/Main.java index 3d3fbb5135a..bba163343bf 100644 --- a/forge-gui-android/src/forge/app/Main.java +++ b/forge-gui-android/src/forge/app/Main.java @@ -25,7 +25,6 @@ import android.os.Bundle; import android.os.Environment; import android.os.PowerManager; import android.provider.Settings; -import android.support.v4.content.ContextCompat; import android.text.SpannableString; import android.text.style.StyleSpan; import android.view.Gravity; @@ -145,10 +144,18 @@ public class Main extends AndroidApplication { super.onBackPressed(); } private boolean checkPermission() { - int result = ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE); - if (result == PackageManager.PERMISSION_GRANTED) { - return true; - } else { + int pid = android.os.Process.myPid(); + int uid = android.os.Process.myUid(); + try { + int result = this.getBaseContext().checkPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, pid, uid); + //we only need the result above atm, we can free the dependency from android-support-v4 :) + //int result = ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE); + if (result == PackageManager.PERMISSION_GRANTED) { + return true; + } else { + return false; + } + } catch (NullPointerException e) { return false; } } From c714187eeea9307019b1413baa464022e49d4407 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sun, 5 Apr 2020 07:07:30 +0800 Subject: [PATCH 03/19] Refactor Object Decoder/Encoder Prevent crash on netplay --- .../java/forge/net/CObjectInputStream.java | 55 ++++++++++++++++++ .../java/forge/net/CObjectOutputStream.java | 31 ++++++++++ .../forge/net/CompatibleObjectDecoder.java | 46 +++++++++++++++ .../forge/net/CompatibleObjectEncoder.java | 37 ++++++++++++ .../java/forge/net/CustomObjectDecoder.java | 58 ------------------- .../java/forge/net/CustomObjectEncoder.java | 56 ------------------ .../java/forge/net/GameProtocolHandler.java | 18 ++++-- .../main/java/forge/net/ProtocolMethod.java | 32 ++++------ .../java/forge/net/client/FGameClient.java | 8 +-- .../java/forge/net/server/FServerManager.java | 8 +-- 10 files changed, 201 insertions(+), 148 deletions(-) create mode 100644 forge-gui/src/main/java/forge/net/CObjectInputStream.java create mode 100644 forge-gui/src/main/java/forge/net/CObjectOutputStream.java create mode 100644 forge-gui/src/main/java/forge/net/CompatibleObjectDecoder.java create mode 100644 forge-gui/src/main/java/forge/net/CompatibleObjectEncoder.java delete mode 100644 forge-gui/src/main/java/forge/net/CustomObjectDecoder.java delete mode 100644 forge-gui/src/main/java/forge/net/CustomObjectEncoder.java diff --git a/forge-gui/src/main/java/forge/net/CObjectInputStream.java b/forge-gui/src/main/java/forge/net/CObjectInputStream.java new file mode 100644 index 00000000000..b6d53c311ad --- /dev/null +++ b/forge-gui/src/main/java/forge/net/CObjectInputStream.java @@ -0,0 +1,55 @@ +package forge.net; + +import io.netty.handler.codec.serialization.ClassResolver; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; +import java.io.StreamCorruptedException; + +public class CObjectInputStream extends ObjectInputStream { + private final ClassResolver classResolver; + + CObjectInputStream(InputStream in, ClassResolver classResolver) throws IOException { + super(in); + this.classResolver = classResolver; + } + + protected void readStreamHeader() throws IOException { + int version = this.readByte() & 255; + if (version != 5) { + throw new StreamCorruptedException("Unsupported version: " + version); + } + } + + protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { + int type = this.read(); + if (type < 0) { + throw new EOFException(); + } else { + switch(type) { + case 0: + return super.readClassDescriptor(); + case 1: + String className = this.readUTF(); + Class clazz = this.classResolver.resolve(className); + return ObjectStreamClass.lookupAny(clazz); + default: + throw new StreamCorruptedException("Unexpected class descriptor type: " + type); + } + } + } + + protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + Class clazz; + try { + clazz = this.classResolver.resolve(desc.getName()); + } catch (ClassNotFoundException var4) { + clazz = super.resolveClass(desc); + } + + return clazz; + } +} diff --git a/forge-gui/src/main/java/forge/net/CObjectOutputStream.java b/forge-gui/src/main/java/forge/net/CObjectOutputStream.java new file mode 100644 index 00000000000..b086ad87827 --- /dev/null +++ b/forge-gui/src/main/java/forge/net/CObjectOutputStream.java @@ -0,0 +1,31 @@ +package forge.net; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.io.OutputStream; + +public class CObjectOutputStream extends ObjectOutputStream { + static final int TYPE_FAT_DESCRIPTOR = 0; + static final int TYPE_THIN_DESCRIPTOR = 1; + + CObjectOutputStream(OutputStream out) throws IOException { + super(out); + } + + protected void writeStreamHeader() throws IOException { + this.writeByte(5); + } + + protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException { + Class clazz = desc.forClass(); + if (!clazz.isPrimitive() && !clazz.isArray() && !clazz.isInterface() && desc.getSerialVersionUID() != 0L) { + this.write(1); + this.writeUTF(desc.getName()); + } else { + this.write(0); + super.writeClassDescriptor(desc); + } + + } +} diff --git a/forge-gui/src/main/java/forge/net/CompatibleObjectDecoder.java b/forge-gui/src/main/java/forge/net/CompatibleObjectDecoder.java new file mode 100644 index 00000000000..ff836965c88 --- /dev/null +++ b/forge-gui/src/main/java/forge/net/CompatibleObjectDecoder.java @@ -0,0 +1,46 @@ +package forge.net; + +import forge.GuiBase; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.serialization.ClassResolver; + +import java.io.ObjectInputStream; +import java.io.StreamCorruptedException; + +public class CompatibleObjectDecoder extends LengthFieldBasedFrameDecoder { + private final ClassResolver classResolver; + + public CompatibleObjectDecoder(ClassResolver classResolver) { + this(1048576, classResolver); + } + + public CompatibleObjectDecoder(int maxObjectSize, ClassResolver classResolver) { + super(maxObjectSize, 0, 4, 0, 4); + this.classResolver = classResolver; + } + + protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + ByteBuf frame = (ByteBuf)super.decode(ctx, in); + if (frame == null) { + return null; + } else { + ObjectInputStream ois = GuiBase.hasPropertyConfig() ? + new ObjectInputStream(new ByteBufInputStream(frame, true)): + new CObjectInputStream(new ByteBufInputStream(frame, true),this.classResolver); + + Object var5 = null; + try { + var5 = ois.readObject(); + } catch (StreamCorruptedException e) { + e.printStackTrace(); + } finally { + ois.close(); + } + + return var5; + } + } +} diff --git a/forge-gui/src/main/java/forge/net/CompatibleObjectEncoder.java b/forge-gui/src/main/java/forge/net/CompatibleObjectEncoder.java new file mode 100644 index 00000000000..a1060ed1a30 --- /dev/null +++ b/forge-gui/src/main/java/forge/net/CompatibleObjectEncoder.java @@ -0,0 +1,37 @@ +package forge.net; + +import forge.GuiBase; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +import java.io.ObjectOutputStream; +import java.io.Serializable; + +public class CompatibleObjectEncoder extends MessageToByteEncoder { + private static final byte[] LENGTH_PLACEHOLDER = new byte[4]; + + @Override + protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) throws Exception { + int startIdx = out.writerIndex(); + ByteBufOutputStream bout = new ByteBufOutputStream(out); + ObjectOutputStream oout = null; + + try { + bout.write(LENGTH_PLACEHOLDER); + oout = GuiBase.hasPropertyConfig() ? new ObjectOutputStream(bout) : new CObjectOutputStream(bout); + oout.writeObject(msg); + oout.flush(); + } finally { + if (oout != null) { + oout.close(); + } else { + bout.close(); + } + } + + int endIdx = out.writerIndex(); + out.setInt(startIdx, endIdx - startIdx - 4); + } +} diff --git a/forge-gui/src/main/java/forge/net/CustomObjectDecoder.java b/forge-gui/src/main/java/forge/net/CustomObjectDecoder.java deleted file mode 100644 index 47d898769d4..00000000000 --- a/forge-gui/src/main/java/forge/net/CustomObjectDecoder.java +++ /dev/null @@ -1,58 +0,0 @@ -package forge.net; - -import forge.GuiBase; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufInputStream; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.LengthFieldBasedFrameDecoder; -import io.netty.handler.codec.serialization.ClassResolver; -import org.mapdb.elsa.ElsaObjectInputStream; - -import java.io.ObjectInputStream; - -public class CustomObjectDecoder extends LengthFieldBasedFrameDecoder { - private final ClassResolver classResolver; - - public CustomObjectDecoder(ClassResolver classResolver) { - this(1048576, classResolver); - } - - public CustomObjectDecoder(int maxObjectSize, ClassResolver classResolver) { - super(maxObjectSize, 0, 4, 0, 4); - this.classResolver = classResolver; - } - - protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { - ByteBuf frame = (ByteBuf) super.decode(ctx, in); - if (frame == null) { - return null; - } else { - if (GuiBase.hasPropertyConfig()){ - ElsaObjectInputStream ois = new ElsaObjectInputStream(new ByteBufInputStream(frame, true)); - - Object var5; - try { - var5 = ois.readObject(); - } finally { - ois.close(); - } - - return var5; - } - else { - ObjectInputStream ois = new ObjectInputStream(new ByteBufInputStream(frame, true)); - - Object var5; - try { - var5 = ois.readObject(); - } finally { - ois.close(); - } - - return var5; - } - } - } - - public static int maxObjectsize = 10000000; //10megabyte??? -} diff --git a/forge-gui/src/main/java/forge/net/CustomObjectEncoder.java b/forge-gui/src/main/java/forge/net/CustomObjectEncoder.java deleted file mode 100644 index 58dc82a58fb..00000000000 --- a/forge-gui/src/main/java/forge/net/CustomObjectEncoder.java +++ /dev/null @@ -1,56 +0,0 @@ -package forge.net; - -import forge.GuiBase; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufOutputStream; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToByteEncoder; -import org.mapdb.elsa.ElsaObjectOutputStream; - -import java.io.ObjectOutputStream; -import java.io.Serializable; - -public class CustomObjectEncoder extends MessageToByteEncoder { - private static final byte[] LENGTH_PLACEHOLDER = new byte[4]; - - public CustomObjectEncoder() { - } - - protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) throws Exception { - int startIdx = out.writerIndex(); - ByteBufOutputStream bout = new ByteBufOutputStream(out); - - if (GuiBase.hasPropertyConfig()){ - ElsaObjectOutputStream oout = null; - try { - bout.write(LENGTH_PLACEHOLDER); - oout = new ElsaObjectOutputStream(bout); - oout.writeObject(msg); - oout.flush(); - } finally { - if (oout != null) { - oout.close(); - } else { - bout.close(); - } - } - } else { - ObjectOutputStream oout = null; - try { - bout.write(LENGTH_PLACEHOLDER); - oout = new ObjectOutputStream(bout); - oout.writeObject(msg); - oout.flush(); - } finally { - if (oout != null) { - oout.close(); - } else { - bout.close(); - } - } - } - - int endIdx = out.writerIndex(); - out.setInt(startIdx, endIdx - startIdx - 4); - } -} diff --git a/forge-gui/src/main/java/forge/net/GameProtocolHandler.java b/forge-gui/src/main/java/forge/net/GameProtocolHandler.java index face68c68ac..d22fc8bdf33 100644 --- a/forge-gui/src/main/java/forge/net/GameProtocolHandler.java +++ b/forge-gui/src/main/java/forge/net/GameProtocolHandler.java @@ -1,8 +1,10 @@ package forge.net; import forge.FThreads; +import forge.assets.FSkinProp; import forge.net.event.GuiGameEvent; import forge.net.event.ReplyEvent; +import forge.util.gui.SOptionPane; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; @@ -25,6 +27,7 @@ public abstract class GameProtocolHandler extends ChannelInboundHandlerAdapte @Override public final void channelRead(final ChannelHandlerContext ctx, final Object msg) { + final String[] catchedError = {""}; System.out.println("Received: " + msg); if (msg instanceof ReplyEvent) { final ReplyEvent event = (ReplyEvent) msg; @@ -36,7 +39,9 @@ public abstract class GameProtocolHandler extends ChannelInboundHandlerAdapte final Method method = protocolMethod.getMethod(); if (method == null) { - throw new IllegalStateException(String.format("Method %s not found", protocolMethod.name())); + //throw new IllegalStateException(String.format("Method %s not found", protocolMethod.name())); + catchedError[0] += String.format("IllegalStateException: Method %s not found (GameProtocolHandler.java Line 43)\n", protocolMethod.name()); + System.err.println(String.format("Method %s not found", protocolMethod.name())); } final Object[] args = event.getObjects(); @@ -56,7 +61,9 @@ public abstract class GameProtocolHandler extends ChannelInboundHandlerAdapte } catch (final IllegalAccessException | IllegalArgumentException e) { System.err.println(String.format("Unknown protocol method %s with %d args", methodName, args == null ? 0 : args.length)); } catch (final InvocationTargetException e) { - throw new RuntimeException(e.getTargetException()); + //throw new RuntimeException(e.getTargetException()); + catchedError[0] += (String.format("RuntimeException: %s (GameProtocolHandler.java Line 65)\n", e.getTargetException().toString())); + System.err.println(e.getTargetException().toString()); } } else { Serializable reply = null; @@ -70,8 +77,11 @@ public abstract class GameProtocolHandler extends ChannelInboundHandlerAdapte } } catch (final IllegalAccessException | IllegalArgumentException e) { System.err.println(String.format("Unknown protocol method %s with %d args, replying with null", methodName, args == null ? 0 : args.length)); - } catch (final InvocationTargetException e) { - throw new RuntimeException(e.getTargetException()); + } catch (final NullPointerException | InvocationTargetException e) { + //throw new RuntimeException(e.getTargetException()); + catchedError[0] += e.toString(); + SOptionPane.showMessageDialog(catchedError[0], "Error", FSkinProp.ICO_WARNING); + System.err.println(e.toString()); } getRemote(ctx).send(new ReplyEvent(event.getId(), reply)); } diff --git a/forge-gui/src/main/java/forge/net/ProtocolMethod.java b/forge-gui/src/main/java/forge/net/ProtocolMethod.java index 642e79dec5c..f34d35fdb8e 100644 --- a/forge-gui/src/main/java/forge/net/ProtocolMethod.java +++ b/forge-gui/src/main/java/forge/net/ProtocolMethod.java @@ -1,7 +1,6 @@ package forge.net; import com.google.common.base.Function; -import forge.GuiBase; import forge.assets.FSkinProp; import forge.deck.CardPool; import forge.game.GameEntityView; @@ -18,12 +17,9 @@ import forge.player.PlayerZoneUpdates; import forge.trackable.TrackableCollection; import forge.util.ITriggerEvent; import forge.util.ReflectionUtil; -import org.apache.commons.lang3.SerializationUtils; -import java.io.Serializable; import java.lang.reflect.Method; import java.util.Collection; -import java.util.ConcurrentModificationException; import java.util.List; import java.util.Map; @@ -159,24 +155,15 @@ public enum ProtocolMethod { } public void checkArgs(final Object[] args) { - if (GuiBase.hasPropertyConfig()) - return; //uses custom serializer for Android 8+.. for (int iArg = 0; iArg < args.length; iArg++) { - Object arg = null; - Class type = null; - try { - arg = args[iArg]; - if (this.args.length > iArg) - type = this.args[iArg]; + final Object arg = args[iArg]; + final Class type = this.args[iArg]; + if (!ReflectionUtil.isInstance(arg, type)) { + //throw new InternalError(String.format("Protocol method %s: illegal argument (%d) of type %s, %s expected", name(), iArg, arg.getClass().getName(), type.getName())); + System.err.println(String.format("InternalError: Protocol method %s: illegal argument (%d) of type %s, %s expected (ProtocolMethod.java Line 163)", name(), iArg, arg.getClass().getName(), type.getName())); } - catch (ArrayIndexOutOfBoundsException ex){ ex.printStackTrace(); } - catch(ConcurrentModificationException ex) { ex.printStackTrace(); } - if (arg != null) - if (type != null) - if (!ReflectionUtil.isInstance(arg, type)) { - throw new InternalError(String.format("Protocol method %s: illegal argument (%d) of type %s, %s expected", name(), iArg, arg.getClass().getName(), type.getName())); - } - if (arg != null) { + //this should be handled via decoder or it will process them twice + /*if (arg != null) { // attempt to Serialize each argument, this will throw an exception if it can't. try { byte[] serialized = SerializationUtils.serialize((Serializable) arg); @@ -189,7 +176,7 @@ public enum ProtocolMethod { // can't seem to avoid this from periodically happening ex.printStackTrace(); } - } + }*/ } } @@ -199,7 +186,8 @@ public enum ProtocolMethod { return; } if (!ReflectionUtil.isInstance(value, returnType)) { - throw new IllegalStateException(String.format("Protocol method %s: illegal return object type %s returned by client, expected %s", name(), value.getClass().getName(), getReturnType().getName())); + //throw new IllegalStateException(String.format("Protocol method %s: illegal return object type %s returned by client, expected %s", name(), value.getClass().getName(), getReturnType().getName())); + System.err.println(String.format("IllegalStateException: Protocol method %s: illegal return object type %s returned by client, expected %s (ProtocolMethod.java Line 190)", name(), value.getClass().getName(), getReturnType().getName())); } } } diff --git a/forge-gui/src/main/java/forge/net/client/FGameClient.java b/forge-gui/src/main/java/forge/net/client/FGameClient.java index 44a1a7d79d5..740d78a5c08 100644 --- a/forge-gui/src/main/java/forge/net/client/FGameClient.java +++ b/forge-gui/src/main/java/forge/net/client/FGameClient.java @@ -1,5 +1,7 @@ package forge.net.client; +import forge.net.CompatibleObjectDecoder; +import forge.net.CompatibleObjectEncoder; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -26,8 +28,6 @@ import forge.net.event.IdentifiableNetEvent; import forge.net.event.LobbyUpdateEvent; import forge.net.event.MessageEvent; import forge.net.event.NetEvent; -import io.netty.handler.codec.serialization.ObjectDecoder; -import io.netty.handler.codec.serialization.ObjectEncoder; public class FGameClient implements IToServer { @@ -58,8 +58,8 @@ public class FGameClient implements IToServer { public void initChannel(final SocketChannel ch) throws Exception { final ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast( - new ObjectEncoder(), - new ObjectDecoder(9766*1024, ClassResolvers.cacheDisabled(null)), + new CompatibleObjectEncoder(), + new CompatibleObjectDecoder(9766*1024, ClassResolvers.cacheDisabled(null)), new MessageHandler(), new LobbyUpdateHandler(), new GameClientHandler(FGameClient.this)); diff --git a/forge-gui/src/main/java/forge/net/server/FServerManager.java b/forge-gui/src/main/java/forge/net/server/FServerManager.java index 68e4f7d1bbd..1e0ad247eb4 100644 --- a/forge-gui/src/main/java/forge/net/server/FServerManager.java +++ b/forge-gui/src/main/java/forge/net/server/FServerManager.java @@ -6,6 +6,8 @@ import forge.interfaces.IGuiGame; import forge.interfaces.ILobbyListener; import forge.match.LobbySlot; import forge.match.LobbySlotType; +import forge.net.CompatibleObjectDecoder; +import forge.net.CompatibleObjectEncoder; import forge.net.event.LobbyUpdateEvent; import forge.net.event.LoginEvent; import forge.net.event.LogoutEvent; @@ -24,8 +26,6 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.serialization.ClassResolvers; -import io.netty.handler.codec.serialization.ObjectDecoder; -import io.netty.handler.codec.serialization.ObjectEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; @@ -99,8 +99,8 @@ public final class FServerManager { public final void initChannel(final SocketChannel ch) throws Exception { final ChannelPipeline p = ch.pipeline(); p.addLast( - new ObjectEncoder(), - new ObjectDecoder(9766*1024, ClassResolvers.cacheDisabled(null)), + new CompatibleObjectEncoder(), + new CompatibleObjectDecoder(9766*1024, ClassResolvers.cacheDisabled(null)), new MessageHandler(), new RegisterClientHandler(), new LobbyInputHandler(), From 52307c02d0b8bd34b1d682c853e3867321ea2620 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sun, 5 Apr 2020 07:14:03 +0800 Subject: [PATCH 04/19] Add option to turn on Experimental Network Compatibility the user can try to turn on this option to test compatibility, defaults to OFF --- forge-gui-android/src/forge/app/Main.java | 2 +- .../src/main/java/forge/control/FControl.java | 4 ++-- .../screens/home/settings/CSubmenuPreferences.java | 2 +- .../screens/home/settings/VSubmenuPreferences.java | 10 +++++----- forge-gui-ios/src/forge/ios/Main.java | 2 +- forge-gui-mobile-dev/src/forge/app/Main.java | 2 +- .../src/forge/screens/settings/SettingsPage.java | 8 ++++---- .../main/java/forge/properties/ForgePreferences.java | 2 +- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/forge-gui-android/src/forge/app/Main.java b/forge-gui-android/src/forge/app/Main.java index bba163343bf..39f0b892b5e 100644 --- a/forge-gui-android/src/forge/app/Main.java +++ b/forge-gui-android/src/forge/app/Main.java @@ -225,7 +225,7 @@ public class Main extends AndroidApplication { } ForgePreferences prefs = FModel.getPreferences(); - boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_USE_ELSA); + boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_NETPLAY_COMPAT); initialize(Forge.getApp(new AndroidClipboard(), adapter, assetsDir, propertyConfig)); } diff --git a/forge-gui-desktop/src/main/java/forge/control/FControl.java b/forge-gui-desktop/src/main/java/forge/control/FControl.java index 2b29279d4e7..99b9316813d 100644 --- a/forge-gui-desktop/src/main/java/forge/control/FControl.java +++ b/forge-gui-desktop/src/main/java/forge/control/FControl.java @@ -220,8 +220,8 @@ public enum FControl implements KeyEventDispatcher { final ForgePreferences prefs = FModel.getPreferences(); - //set ElsaSerializer from preference - boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_USE_ELSA); + //set ExperimentalNetworkOption from preference + boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_NETPLAY_COMPAT); GuiBase.enablePropertyConfig(propertyConfig); closeAction = CloseAction.valueOf(prefs.getPref(FPref.UI_CLOSE_ACTION)); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java index ef151e4c515..4e175b92487 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java @@ -115,7 +115,7 @@ public enum CSubmenuPreferences implements ICDoc { lstControls.add(Pair.of(view.getCbSingletons(), FPref.DECKGEN_SINGLETONS)); lstControls.add(Pair.of(view.getCbEnableAICheats(), FPref.UI_ENABLE_AI_CHEATS)); lstControls.add(Pair.of(view.getCbEnableUnknownCards(), FPref.UI_LOAD_UNKNOWN_CARDS)); - lstControls.add(Pair.of(view.getCbUseElsa(), FPref.UI_USE_ELSA)); + lstControls.add(Pair.of(view.getCbUseExperimentalNetworkStream(), FPref.UI_NETPLAY_COMPAT)); lstControls.add(Pair.of(view.getCbImageFetcher(), FPref.UI_ENABLE_ONLINE_IMAGE_FETCHER)); lstControls.add(Pair.of(view.getCbDisplayFoil(), FPref.UI_OVERLAY_FOIL_EFFECT)); lstControls.add(Pair.of(view.getCbRandomFoil(), FPref.UI_RANDOM_FOIL)); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java index 797a54208ea..264ae7dc8ab 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java @@ -108,7 +108,7 @@ public enum VSubmenuPreferences implements IVSubmenu { private final JCheckBox cbRemindOnPriority = new OptionsCheckBox(localizer.getMessage("cbRemindOnPriority")); private final JCheckBox cbUseSentry = new OptionsCheckBox(localizer.getMessage("cbUseSentry")); private final JCheckBox cbEnableUnknownCards = new OptionsCheckBox("Enable Unknown Cards"); - private final JCheckBox cbUseElsa = new OptionsCheckBox("Use ELSA Serializer"); + private final JCheckBox cbUseExperimentalNetworkStream = new OptionsCheckBox("Experimental Network Compatibility"); private final Map shortcutFields = new HashMap<>(); @@ -292,8 +292,8 @@ public enum VSubmenuPreferences implements IVSubmenu { pnlPrefs.add(cbEnableUnknownCards, titleConstraints); pnlPrefs.add(new NoteLabel("Enable Unknown Cards to be loaded to Unknown Set. (Requires restart)"), descriptionConstraints); - /*pnlPrefs.add(cbUseElsa, titleConstraints); - pnlPrefs.add(new NoteLabel("Use ELSA Serializer for Network (EXPERIMENTAL Option, Requires restart)"), descriptionConstraints);*/ + pnlPrefs.add(cbUseExperimentalNetworkStream, titleConstraints); + pnlPrefs.add(new NoteLabel("Forge switches to compatible network stream. (If unsure, turn OFF this option)"), descriptionConstraints); // Graphic Options pnlPrefs.add(new SectionLabel(localizer.getMessage("GraphicOptions")), sectionConstraints + ", gaptop 2%"); @@ -594,8 +594,8 @@ public enum VSubmenuPreferences implements IVSubmenu { } /** @return {@link javax.swing.JCheckBox} */ - public JCheckBox getCbUseElsa() { - return cbUseElsa; + public JCheckBox getCbUseExperimentalNetworkStream() { + return cbUseExperimentalNetworkStream; } /** @return {@link javax.swing.JCheckBox} */ diff --git a/forge-gui-ios/src/forge/ios/Main.java b/forge-gui-ios/src/forge/ios/Main.java index 74679a68360..b22e1923bd0 100644 --- a/forge-gui-ios/src/forge/ios/Main.java +++ b/forge-gui-ios/src/forge/ios/Main.java @@ -32,7 +32,7 @@ public class Main extends IOSApplication.Delegate { config.useAccelerometer = false; config.useCompass = false; ForgePreferences prefs = FModel.getPreferences(); - boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_USE_ELSA); + boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_NETPLAY_COMPAT); final ApplicationListener app = Forge.getApp(new IOSClipboard(), new IOSAdapter(), assetsDir, propertyConfig); final IOSApplication iosApp = new IOSApplication(app, config); return iosApp; diff --git a/forge-gui-mobile-dev/src/forge/app/Main.java b/forge-gui-mobile-dev/src/forge/app/Main.java index cbac0f09d8e..014bc13f08f 100644 --- a/forge-gui-mobile-dev/src/forge/app/Main.java +++ b/forge-gui-mobile-dev/src/forge/app/Main.java @@ -95,7 +95,7 @@ public class Main { config.useHDPI = desktopMode; // enable HiDPI on Mac OS ForgePreferences prefs = FModel.getPreferences(); - boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_USE_ELSA); + boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_NETPLAY_COMPAT); new LwjglApplication(Forge.getApp(new LwjglClipboard(), new DesktopAdapter(switchOrientationFile), desktopMode ? desktopModeAssetsDir : assetsDir, propertyConfig), config); } diff --git a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java index 90848bcbb19..c2aac060aa1 100644 --- a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java +++ b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java @@ -236,10 +236,10 @@ public class SettingsPage extends TabPage { "Enable Unknown Cards", "Enable Unknown Cards to be loaded to Unknown Set. (Requires restart)"), 3); - /*lstSettings.addItem(new BooleanSetting(FPref.UI_USE_ELSA, - "Use ELSA Serializer", - "Use ELSA Serializer for Network (EXPERIMENTAL Option, Requires restart)"), - 3);*/ + lstSettings.addItem(new BooleanSetting(FPref.UI_NETPLAY_COMPAT, + "Experimental Network Compatibility", + "Forge switches to compatible network stream. (If unsure, turn OFF this option)"), + 3); //Graphic Options lstSettings.addItem(new BooleanSetting(FPref.UI_ENABLE_ONLINE_IMAGE_FETCHER, diff --git a/forge-gui/src/main/java/forge/properties/ForgePreferences.java b/forge-gui/src/main/java/forge/properties/ForgePreferences.java index c18a316c30a..8ba539ec1ba 100644 --- a/forge-gui/src/main/java/forge/properties/ForgePreferences.java +++ b/forge-gui/src/main/java/forge/properties/ForgePreferences.java @@ -139,7 +139,7 @@ public class ForgePreferences extends PreferencesStore { UI_ENABLE_PRELOAD_EXTENDED_ART("false"), UI_ENABLE_BORDER_MASKING("false"), UI_SHOW_FPS("false"), - UI_USE_ELSA("false"), + UI_NETPLAY_COMPAT("false"), UI_LOAD_UNKNOWN_CARDS("true"), UI_ALLOW_ORDER_GRAVEYARD_WHEN_NEEDED ("Never"), UI_DEFAULT_FONT_SIZE("12"), From 96ce695aae268314765bfdd02abe5898ec2df40b Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sun, 5 Apr 2020 01:15:09 +0000 Subject: [PATCH 05/19] Remove unnecessary lines --- forge-gui-android/src/forge/app/Main.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/forge-gui-android/src/forge/app/Main.java b/forge-gui-android/src/forge/app/Main.java index 39f0b892b5e..a3f9715730e 100644 --- a/forge-gui-android/src/forge/app/Main.java +++ b/forge-gui-android/src/forge/app/Main.java @@ -148,8 +148,6 @@ public class Main extends AndroidApplication { int uid = android.os.Process.myUid(); try { int result = this.getBaseContext().checkPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, pid, uid); - //we only need the result above atm, we can free the dependency from android-support-v4 :) - //int result = ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE); if (result == PackageManager.PERMISSION_GRANTED) { return true; } else { From b466ff38a3046c6ebc3d1a8abe768e693d2fbff9 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sun, 5 Apr 2020 11:41:21 +0800 Subject: [PATCH 06/19] Update SettingsPage --- .../home/settings/VSubmenuPreferences.java | 2 +- .../forge/screens/settings/SettingsPage.java | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java index 264ae7dc8ab..4f8245eb74f 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java @@ -292,7 +292,7 @@ public enum VSubmenuPreferences implements IVSubmenu { pnlPrefs.add(cbEnableUnknownCards, titleConstraints); pnlPrefs.add(new NoteLabel("Enable Unknown Cards to be loaded to Unknown Set. (Requires restart)"), descriptionConstraints); - pnlPrefs.add(cbUseExperimentalNetworkStream, titleConstraints); + pnlPrefs.add(cbUseExperimentalNetworkStream, titleConstraints); pnlPrefs.add(new NoteLabel("Forge switches to compatible network stream. (If unsure, turn OFF this option)"), descriptionConstraints); // Graphic Options diff --git a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java index c2aac060aa1..c823baa07e0 100644 --- a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java +++ b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java @@ -3,6 +3,7 @@ package forge.screens.settings; import com.badlogic.gdx.utils.Align; import forge.Forge; import forge.Graphics; +import forge.GuiBase; import forge.MulliganDefs; import forge.StaticData; import forge.ai.AiProfileUtil; @@ -234,11 +235,34 @@ public class SettingsPage extends TabPage { 3); lstSettings.addItem(new BooleanSetting(FPref.UI_LOAD_UNKNOWN_CARDS, "Enable Unknown Cards", - "Enable Unknown Cards to be loaded to Unknown Set. (Requires restart)"), + "Enable Unknown Cards to be loaded to Unknown Set. (Requires restart)") { + @Override + public void select() { + super.select(); + FOptionPane.showConfirmDialog( + localizer.getMessage("lblRestartForgeDescription"), + localizer.getMessage("lblRestartForge"), + localizer.getMessage("lblRestart"), + localizer.getMessage("lblLater"), new Callback() { + @Override + public void run(Boolean result) { + if (result) { + Forge.restart(true); + } + } + }); + } + }, 3); lstSettings.addItem(new BooleanSetting(FPref.UI_NETPLAY_COMPAT, "Experimental Network Compatibility", - "Forge switches to compatible network stream. (If unsure, turn OFF this option)"), + "Forge switches to compatible network stream. (If unsure, turn OFF this option)") { + @Override + public void select() { + super.select(); + GuiBase.enablePropertyConfig(FModel.getPreferences().getPrefBoolean(FPref.UI_NETPLAY_COMPAT)); + } + }, 3); //Graphic Options From a18f142add0ac8299f4d0db7ebb7372327784c28 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sun, 5 Apr 2020 12:34:56 +0800 Subject: [PATCH 07/19] Update SettingsPage Desktop --- .../screens/home/settings/CSubmenuPreferences.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java index 4e175b92487..2e9e306ec04 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java @@ -95,6 +95,19 @@ public enum CSubmenuPreferences implements ICDoc { } }); + // This updates Experimental Network Option + view.getCbUseExperimentalNetworkStream().addItemListener(new ItemListener() { + @Override + public void itemStateChanged(final ItemEvent arg0) { + if (updating) { return; } + + final boolean toggle = view.getCbUseExperimentalNetworkStream().isSelected(); + GuiBase.enablePropertyConfig(toggle); + prefs.setPref(FPref.UI_NETPLAY_COMPAT, String.valueOf(toggle)); + prefs.save(); + } + }); + lstControls.clear(); // just in case lstControls.add(Pair.of(view.getCbAnte(), FPref.UI_ANTE)); lstControls.add(Pair.of(view.getCbAnteMatchRarity(), FPref.UI_ANTE_MATCH_RARITY)); @@ -255,6 +268,7 @@ public enum CSubmenuPreferences implements ICDoc { setPlayerNameButtonText(); view.getCbDevMode().setSelected(ForgePreferences.DEV_MODE); view.getCbEnableMusic().setSelected(prefs.getPrefBoolean(FPref.UI_ENABLE_MUSIC)); + view.getCbUseExperimentalNetworkStream().setSelected(prefs.getPrefBoolean(FPref.UI_NETPLAY_COMPAT)); for(final Pair kv: lstControls) { kv.getKey().setSelected(prefs.getPrefBoolean(kv.getValue())); From ba3e8de481a53d2c2cccee1e46de6b98d506b067 Mon Sep 17 00:00:00 2001 From: Tim Mocny Date: Sun, 5 Apr 2020 09:02:24 +0000 Subject: [PATCH 08/19] IKO - 4 April --- .../res/cardsfolder/upcoming/back_for_more.txt | 6 ++++++ .../cardsfolder/upcoming/boon_of_the_wish_giver.txt | 6 ++++++ .../res/cardsfolder/upcoming/channeled_force.txt | 7 +++++++ .../upcoming/chevill_bane_of_monsters.txt | 13 +++++++++++++ forge-gui/res/cardsfolder/upcoming/deaths_oasis.txt | 10 ++++++++++ forge-gui/res/cardsfolder/upcoming/dire_tactics.txt | 7 +++++++ .../cardsfolder/upcoming/drannith_magistrate.txt | 6 ++++++ .../res/cardsfolder/upcoming/fiend_artisan.txt | 11 +++++++++++ .../res/cardsfolder/upcoming/flourishing_fox.txt | 8 ++++++++ .../upcoming/general_kudro_of_drannith.txt | 11 +++++++++++ 10 files changed, 85 insertions(+) create mode 100755 forge-gui/res/cardsfolder/upcoming/back_for_more.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/boon_of_the_wish_giver.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/channeled_force.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/chevill_bane_of_monsters.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/deaths_oasis.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/dire_tactics.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/drannith_magistrate.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/fiend_artisan.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/flourishing_fox.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/general_kudro_of_drannith.txt diff --git a/forge-gui/res/cardsfolder/upcoming/back_for_more.txt b/forge-gui/res/cardsfolder/upcoming/back_for_more.txt new file mode 100755 index 00000000000..3ccf437129a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/back_for_more.txt @@ -0,0 +1,6 @@ +Name:Back for More +ManaCost:4 B G +Types:Instant +A:SP$ ChangeZone | Cost$ 4 B G | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose target creature card in your graveyard | ValidTgts$ Creature.YouOwn | SubAbility$ DBFight | StackDescription$ SpellDescription | SpellDescription$ Return target creature card from your graveyard to the battlefield. When you do, it fights up to one target creature you don't control. +SVar:DBFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Choose target creature you don't control | SubAbility$ DBCleanup | StackDescription$ None +Oracle:Return target creature card from your graveyard to the battlefield. When you do, it fights up to one target creature you don't control. (Each deals damage equal to its power to the other.) diff --git a/forge-gui/res/cardsfolder/upcoming/boon_of_the_wish_giver.txt b/forge-gui/res/cardsfolder/upcoming/boon_of_the_wish_giver.txt new file mode 100755 index 00000000000..34f8037b47a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/boon_of_the_wish_giver.txt @@ -0,0 +1,6 @@ +Name:Boon of the Wish-Giver +ManaCost:4 U U +Types:Sorcery +A:SP$ Draw | Cost$ 4 U U | NumCards$ 4 | SpellDescription$ Draw four cards. +K:Cycling:1 +Oracle:Draw four cards.\nCycling {1} ({1}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/upcoming/channeled_force.txt b/forge-gui/res/cardsfolder/upcoming/channeled_force.txt new file mode 100755 index 00000000000..3d6aae1c6bf --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/channeled_force.txt @@ -0,0 +1,7 @@ +Name:Channeled Force +ManaCost:2 U R +Types:Instant +A:SP$ Draw | Cost$ 2 U R Discard | CostDesc$ As an additional cost to cast this spell, discard X cards. | NumCards$ ChosenX | ValidTgts$ Player | TgtPrompt$ Choose a player | References$ X | SubAbility$ DBDamage | SpellDescription$ Target player draws X cards. CARDNAME deals X damage to up to one target creature or planeswalker. +SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature,Planeswalker | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select target creature or planeswalker. | NumDmg$ ChosenX | References$ X +SVar:X:XChoice +Oracle:As an additional cost to cast this spell, discard X cards.\nTarget player draws X cards. Channeled Force deals X damage to up to one target creature or planeswalker. diff --git a/forge-gui/res/cardsfolder/upcoming/chevill_bane_of_monsters.txt b/forge-gui/res/cardsfolder/upcoming/chevill_bane_of_monsters.txt new file mode 100755 index 00000000000..f2a537b3240 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/chevill_bane_of_monsters.txt @@ -0,0 +1,13 @@ +Name:Chevill, Bane of Monsters +ManaCost:B G +Types:Legendary Creature Human Rogue +PT:1/3 +K:Deathtouch +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | IsPresent$ Permanent.OppCtrl+counters_GE1_BOUNTY | PresentCompare$ EQ0 | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ At the beginning of your upkeep, if your opponents control no permanents with bounty counters on them, put a bounty counter on target creature or planeswalker an opponent controls. +SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.OppCtrl,Planeswalker.OppCtrl | TgtPrompt$ Select target creature or planeswalker an opponent controls | CounterType$ BOUNTY | CounterNum$ 1 | IsCurse$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Permanent.OppCtrl+counters_GE1_BOUNTY | TriggerZones$ Battlefield | Execute$ TrigGainLife | TriggerDescription$ Whenever a permanent an opponent controls with a bounty counter on it dies, you gain 3 life and draw a card. +SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 3 | SubAbility$ DBDraw +SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1 +SVar:PlayMain1:TRUE +DeckHints:Ability$Counters +Oracle:Deathtouch\nAt the beginning of your upkeep, if your opponents control no permanents with bounty counters on them, put a bounty counter on target creature or planeswalker an opponent controls.\nWhenever a permanent an opponent controls with a bounty counter on it dies, you gain 3 life and draw card. diff --git a/forge-gui/res/cardsfolder/upcoming/deaths_oasis.txt b/forge-gui/res/cardsfolder/upcoming/deaths_oasis.txt new file mode 100755 index 00000000000..c0ee84512b7 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/deaths_oasis.txt @@ -0,0 +1,10 @@ +Name:Death's Oasis +ManaCost:W B G +Types:Enchantment +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.YouCtrl+nonToken | TriggerZones$ Battlefield | Execute$ TrigMill | TriggerDescription$ Whenever a nontoken creature you control dies, put the top two cards of your library into your graveyard. Then return a creature card with lesser converted mana cost than the creature that died from the graveyard to your hand. +SVar:TrigMill:DB$ Mill | NumCards$ 2 | Defined$ You | SubAbility$ DBReturn +SVar:DBReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ChangeType$ Creature.YouOwn+cmcLTY | References$ Y | Hidden$ True | ChangeNum$ 1 +SVar:Y:TriggeredCard$CardManaCost +A:AB$ GainLife | Cost$ 1 Sac<1/CARDNAME> | LifeAmount$ X | References$ X | SpellDescription$ You gain life equal to the greatest converted mana cost among creatures you control. +SVar:X:Count$HighestCMC_Creature.YouCtrl+inZoneBattlefield +Oracle:Whenever a nontoken creature you control dies, put the top two cards of your library into your graveyard. Then return a creature card with lesser converted mana cost than the creature that died from the graveyard to your hand.\n{1}, Sacrifice Death's Oasis: You gain life equal to the greatest converted mana cost among creatures you control. diff --git a/forge-gui/res/cardsfolder/upcoming/dire_tactics.txt b/forge-gui/res/cardsfolder/upcoming/dire_tactics.txt new file mode 100755 index 00000000000..e9fde96bc74 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/dire_tactics.txt @@ -0,0 +1,7 @@ +Name:Dire Tactics +ManaCost:W B +Types:Instant +A:SP$ ChangeZone | Cost$ W B | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBLoseLife | StackDescription$ SpellDescription | SpellDescription$ Exile target creature. If you don't control a Human, you lose life equal to that creature's toughness. +SVar:DBLoseLife:DB$ LoseLife | ConditionPresent$ Human.YouCtrl | ConditionCompare$ EQ0 | Defined$ You | LifeAmount$ X | References$ X | StackDescription$ None +SVar:X:Targeted$CardToughness +Oracle:Exile target creature. If you don't control a Human, you lose life equal to that creature's toughness. diff --git a/forge-gui/res/cardsfolder/upcoming/drannith_magistrate.txt b/forge-gui/res/cardsfolder/upcoming/drannith_magistrate.txt new file mode 100755 index 00000000000..c4121042a56 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/drannith_magistrate.txt @@ -0,0 +1,6 @@ +Name:Drannith Magistrate +ManaCost:1 W +Types:Creature Human Wizard +PT:1/3 +S:Mode$ CantBeCast | ValidCard$ Card | Caster$ Opponent | Origin$ Library,Graveyard,Exile | Description$ Your opponents can't cast spells from anywhere other than their hands. +Oracle:Your opponents can't cast spells from anywhere other than their hands. diff --git a/forge-gui/res/cardsfolder/upcoming/fiend_artisan.txt b/forge-gui/res/cardsfolder/upcoming/fiend_artisan.txt new file mode 100755 index 00000000000..3300037e039 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fiend_artisan.txt @@ -0,0 +1,11 @@ +Name:Fiend Artisan +ManaCost:B/G B/G +Types:Creature Nightmare +PT:1/1 +S:Mode$ Continuous | Affected$ Card.Self | AddPower$ Y | AddToughness$ Y | Description$ CARDNAME gets +1/+1 for each creature card in your graveyard. +A:AB$ ChangeZone | Cost$ X BG T Sac<1/Creature.Other/another creature> | Origin$ Library | Destination$ Battlefield | ChangeType$ Creature.cmcLEX | ChangeNum$ 1 | References$ X | ChangeNum$ 1 | SorcerySpeed$ True | StackDescription$ SpellDescription | SpellDescription$ Search your library for a creature card with converted mana cost X or less, put it onto the battlefield, then shuffle your library. Activate this ability only any time you could cast a sorcery. +SVar:Y:Count$TypeInYourYard.Creature +SVar:X:Count$xPaid +AI:RemoveDeck:All +DeckHints:Ability$Graveyard +Oracle:Fiend Artisan gets +1/+1 for each creature card in your graveyard.\n{X}{B/G}, {T}, Sacrifice another creature: Search your library for a creature card with converted mana cost X or less, put it onto the battlefield, then shuffle your library. Activate this ability only any time you could cast a sorcery. diff --git a/forge-gui/res/cardsfolder/upcoming/flourishing_fox.txt b/forge-gui/res/cardsfolder/upcoming/flourishing_fox.txt new file mode 100755 index 00000000000..c74da09394e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/flourishing_fox.txt @@ -0,0 +1,8 @@ +Name:Flourishing Fox +ManaCost:W +Types:Creature Fox +PT:1/1 +T:Mode$ Cycled | ValidCard$ Card.Other | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cycle another card, put a +1/+1 counter on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 +K:Cycling:1 +Oracle:Whenever you cycle another card, put a +1/+1 counter on Flourishing Fox.\nCycling {1} ({1}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/upcoming/general_kudro_of_drannith.txt b/forge-gui/res/cardsfolder/upcoming/general_kudro_of_drannith.txt new file mode 100755 index 00000000000..54765c7e001 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/general_kudro_of_drannith.txt @@ -0,0 +1,11 @@ +Name:General Kudro of Drannith +ManaCost:1 W B +Types:Legendary Creature Human Soldier +PT:3/3 +S:Mode$ Continuous | Affected$ Human.Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other Humans you control get +1/+1. +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ Whenever CARDNAME or another Human enters the battlefield under your control, exile target card from an opponent's graveyard. +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Other+Human+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigExile | Secondary$ True | TriggerDescription$ Whenever CARDNAME or another Human enters the battlefield under your control, exile target card from an opponent's graveyard. +SVar:TrigExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in an opponent's graveyard | ValidTgts$ Card.OppOwn +A:AB$ Destroy | Cost$ 2 Sac<2/Human> | ValidTgts$ Creature.powerGE4 | TgtPrompt$ Select target creature with power 4 or greater | SpellDescription$ Destroy target creature with power 4 or greater. +DeckHints:Type$Human +Oracle:Other Humans you control get +1/+1.\nWhenever General Kudro of Drannith or another Human enters the battlefield under your control, exile target card from an opponent's graveyard.\n{2}, Sacrifice two Humans: Destroy target creature with power 4 or greater. From 56dbb45d63bf8d135130b9a9f8a44e3359704a65 Mon Sep 17 00:00:00 2001 From: Tim Mocny Date: Sun, 5 Apr 2020 09:02:26 +0000 Subject: [PATCH 09/19] Little grammar fix --- forge-gui-android/src/forge/app/Main.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui-android/src/forge/app/Main.java b/forge-gui-android/src/forge/app/Main.java index 3d3fbb5135a..604e7e31bbc 100644 --- a/forge-gui-android/src/forge/app/Main.java +++ b/forge-gui-android/src/forge/app/Main.java @@ -77,7 +77,7 @@ public class Main extends AndroidApplication { text.setTypeface(Typeface.SERIF); String title="Forge needs Storage Permission to run properly...\n" + - "Follow this simple steps below:\n\n"; + "Follow these simple steps:\n\n"; String steps = " 1) Tap \"Open App Details\" Button.\n" + " 2) Tap Permissions\n"+ " 3) Turn on the Storage Permission.\n\n"+ From 8eab8424163004d3493b7531bd7efb04004e30ed Mon Sep 17 00:00:00 2001 From: Tim Mocny Date: Sun, 5 Apr 2020 09:03:18 +0000 Subject: [PATCH 10/19] C20 - Jirina & Kalamax --- .../res/cardsfolder/upcoming/C2020/jirina_kudro.txt | 11 +++++++++++ .../upcoming/C2020/kalamax_the_stormsire.txt | 10 ++++++++++ 2 files changed, 21 insertions(+) create mode 100755 forge-gui/res/cardsfolder/upcoming/C2020/jirina_kudro.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/C2020/kalamax_the_stormsire.txt diff --git a/forge-gui/res/cardsfolder/upcoming/C2020/jirina_kudro.txt b/forge-gui/res/cardsfolder/upcoming/C2020/jirina_kudro.txt new file mode 100755 index 00000000000..5f91feb2e00 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/C2020/jirina_kudro.txt @@ -0,0 +1,11 @@ +Name:Jirina Kudro +ManaCost:1 R W B +Types:Legendary Creature Human Soldier +PT:3/3 +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a 1/1 white Human Soldier creature token for each time you've cast a commander from the command zone this game. +SVar:TrigToken:DB$ Token | TokenAmount$ X | TokenScript$ w_1_1_human | TokenOwner$ You | LegacyImage$ w 1 1 human c20 | References$ X +SVar:X:Count$TotalCommanderCastFromCommandZone +S:Mode$ Continuous | Affected$ Human.Other+YouCtrl | AddPower$ 2 | Description$ Other Humans you control get +2/+0. +SVar:PlayMain1:TRUE +DeckHints:Type$Human +Oracle:When Jirina Kudro enters the battlefield, create a 1/1 white Human Soldier creature token for each time you've cast a commander from the command zone this game.\nOther Humans you control get +2/+0. diff --git a/forge-gui/res/cardsfolder/upcoming/C2020/kalamax_the_stormsire.txt b/forge-gui/res/cardsfolder/upcoming/C2020/kalamax_the_stormsire.txt new file mode 100755 index 00000000000..ac646c3d73a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/C2020/kalamax_the_stormsire.txt @@ -0,0 +1,10 @@ +Name:Kalamax, the Stormsire +ManaCost:1 G U R +Types:Legendary Creature Elemental Dinosaur +PT:4/4 +T:Mode$ SpellCast | ValidCard$ Instant | ValidActivatingPlayer$ You | ActivatorThisTurnCast$ EQ1 | NoResolvingCheck$ True | Execute$ TrigCopy | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast your first instant spell each turn, if CARDNAME is tapped, copy that spell. You may choose new targets for the copy. +SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | IsPresent$ Card.Self+tapped | AILogic$ Always +SVar:BuffedBy:Instant +T:Mode$ SpellCopy | ValidCard$ Instant | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you copy an instant spell, put a +1/+1 counter on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 +Oracle:Whenever you cast your first instant spell each turn, if Kalamax, the Stormsire is tapped, copy that spell. You may choose new targets for the copy.\nWhenever you copy an instant spell, put a +1/+1 counter on Kalamax. From 6ce62b73168a79f14575739db69442a36b92153e Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 5 Apr 2020 17:29:21 +0200 Subject: [PATCH 11/19] Keyword Counter: special counters add keywords to cards --- .../src/main/java/forge/game/card/Card.java | 63 +++++++++++++++++++ .../java/forge/game/card/CounterType.java | 53 +++++++++++++++- .../res/cardsfolder/upcoming/fully_grown.txt | 6 ++ 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/fully_grown.txt diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index d89ebcdaaf7..722b532282e 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -129,6 +129,8 @@ public class Card extends GameEntity implements Comparable { private final Multimap cantHaveKeywords = MultimapBuilder.hashKeys().enumSetValues(Keyword.class).build(); + private final Map counterTypeTimestamps = Maps.newEnumMap(CounterType.class); + private final Map canBlockAdditional = Maps.newTreeMap(); private final Set canBlockAny = Sets.newHashSet(); @@ -1305,12 +1307,44 @@ public class Card extends GameEntity implements Comparable { getController().addCounterToPermThisTurn(counterType, addAmount); view.updateCounters(this); } + if (newValue <= 0) { + removeCounterTimestamp(counterType); + } else { + addCounterTimestamp(counterType); + } if (table != null) { table.put(this, counterType, addAmount); } return addAmount; } + public boolean addCounterTimestamp(CounterType counterType) { + return addCounterTimestamp(counterType, true); + } + public boolean addCounterTimestamp(CounterType counterType, boolean updateView) { + if (!counterType.isKeywordCounter()) { + return false; + } + removeCounterTimestamp(counterType); + + long timestamp = game.getNextTimestamp(); + counterTypeTimestamps.put(counterType, timestamp); + addChangedCardKeywords(List.of(counterType.getKeyword().toString()), null, false, false, timestamp, updateView); + return true; + } + + public boolean removeCounterTimestamp(CounterType counterType) { + return removeCounterTimestamp(counterType, true); + } + + public boolean removeCounterTimestamp(CounterType counterType, boolean updateView) { + Long old = counterTypeTimestamps.remove(counterType); + if (old != null) { + removeChangedCardKeywords(old, updateView); + } + return old != null; + } + /** *

* addCountersAddedBy. @@ -1358,6 +1392,10 @@ public class Card extends GameEntity implements Comparable { setCounters(counterName, newValue); view.updateCounters(this); + if (newValue <= 0) { + this.removeCounterTimestamp(counterName); + } + //fire card stats changed event if p/t bonuses or loyalty changed from subtracted counters if (powerBonusBefore != getPowerBonusFromCounters() || toughnessBonusBefore != getToughnessBonusFromCounters() || loyaltyBefore != getCurrentLoyalty()) { getGame().fireEvent(new GameEventCardStatsChanged(this)); @@ -1380,8 +1418,23 @@ public class Card extends GameEntity implements Comparable { @Override public final void setCounters(final Map allCounters) { + boolean changed = false; + for (CounterType ct : counters.keySet()) { + if (removeCounterTimestamp(ct, false)) { + changed = true; + } + } counters = allCounters; view.updateCounters(this); + + for (CounterType ct : counters.keySet()) { + if (addCounterTimestamp(ct, false)) { + changed = true; + } + } + if (changed) { + updateKeywords(); + } } @Override @@ -1389,6 +1442,16 @@ public class Card extends GameEntity implements Comparable { if (counters.isEmpty()) { return; } counters.clear(); view.updateCounters(this); + + boolean changed = false; + for (CounterType ct : counterTypeTimestamps.keySet()) { + if (removeCounterTimestamp(ct, false)) { + changed = true; + } + } + if (changed) { + updateKeywords(); + } } public final String getSVar(final String var) { diff --git a/forge-game/src/main/java/forge/game/card/CounterType.java b/forge-game/src/main/java/forge/game/card/CounterType.java index 5d9fd5970d6..80825fa1f03 100644 --- a/forge-game/src/main/java/forge/game/card/CounterType.java +++ b/forge-game/src/main/java/forge/game/card/CounterType.java @@ -20,6 +20,8 @@ package forge.game.card; import com.google.common.collect.ImmutableList; +import forge.game.keyword.Keyword; + /** * The class Counters. * @@ -309,7 +311,23 @@ public enum CounterType { EXPERIENCE("EXP"), - POISON("POISN"); + POISON("POISN"), + + // Keyword Counters + + FLYING("Flying"), + FIRST_STRIKE("First Strike"), + DOUBLE_STRIKE("Double Strike"), + DEATHTOUCH("Deathtouch"), + HEXPROOF("Hexproof"), + INDESTRUCTIBLE("Indestructible"), + LIFELINK("Lifelink"), + MENACE("Menace"), + REACH("Reach"), + TRAMPLE("Trample"), + VIGILANCE("Vigilance") + + ; private String name, counterOnCardDisplayName; private int red, green, blue; @@ -365,6 +383,39 @@ public enum CounterType { return Enum.valueOf(CounterType.class, replacedName); } + public boolean isKeywordCounter() { + return this.getKeyword() != null; + } + + public Keyword getKeyword() { + switch (this) { + case FLYING: + return Keyword.FLYING; + case FIRST_STRIKE: + return Keyword.FIRST_STRIKE; + case DOUBLE_STRIKE: + return Keyword.DOUBLE_STRIKE; + case DEATHTOUCH: + return Keyword.DEATHTOUCH; + case HEXPROOF: + return Keyword.HEXPROOF; + case INDESTRUCTIBLE: + return Keyword.INDESTRUCTIBLE; + case LIFELINK: + return Keyword.LIFELINK; + case MENACE: + return Keyword.MENACE; + case REACH: + return Keyword.REACH; + case TRAMPLE: + return Keyword.TRAMPLE; + case VIGILANCE: + return Keyword.VIGILANCE; + default: + return null; + } + } + public static final ImmutableList values = ImmutableList.copyOf(values()); } diff --git a/forge-gui/res/cardsfolder/upcoming/fully_grown.txt b/forge-gui/res/cardsfolder/upcoming/fully_grown.txt new file mode 100644 index 00000000000..edfefc1522a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fully_grown.txt @@ -0,0 +1,6 @@ +Name:Fully Grown +ManaCost:2 G +Types:Instant +A:SP$ Pump | Cost$ 2 G | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +3 | NumDef$ +3 | SubAbility$ PutCounter | SpellDescription$ Target creature gets +3/+3 until end of turn. Put a trample counter on it. +SVar:PutCounter:DB$ PutCounter | Defined$ Targeted | CounterType$ TRAMPLE | CounterNum$ 1 +Oracle:Target creature gets +3/+3 until end of turn. Put a trample counter on it. From 3bb42a95c9f9085bf53eafd2aa072bbc0a7b4a5e Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 5 Apr 2020 21:57:32 +0200 Subject: [PATCH 12/19] CounterPutEffect: add Choices with inbuilt ChosenCard Effect --- .../game/ability/effects/CountersPutEffect.java | 13 +++++++++++++ forge-gui/res/cardsfolder/d/dismantle.txt | 14 +++++--------- forge-gui/res/cardsfolder/f/find_finality.txt | 6 ++---- .../res/cardsfolder/h/haphazard_bombardment.txt | 8 +++----- forge-gui/res/cardsfolder/s/settle_the_score.txt | 5 ++--- forge-gui/res/cardsfolder/t/the_elderspell.txt | 5 ++--- 6 files changed, 27 insertions(+), 24 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java index 76c8ed3b6a8..63a8536f1ff 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java @@ -150,6 +150,19 @@ public class CountersPutEffect extends SpellAbilityEffect { CardCollection leastToughness = new CardCollection(Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetDefense)); tgtCards.addAll(pc.chooseCardsForEffect(leastToughness, sa, Localizer.getInstance().getMessage("lblChooseACreatureWithLeastToughness"), 1, 1, false)); tgtObjects.addAll(tgtCards); + } else if (sa.hasParam("Choices")) { + ZoneType choiceZone = ZoneType.Battlefield; + if (sa.hasParam("ChoiceZone")) { + choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone")); + } + CardCollection choices = new CardCollection(game.getCardsIn(choiceZone)); + + int n = sa.hasParam("ChoiceAmount") ? Integer.parseInt(sa.getParam("ChoiceAmount")) : 1; + + choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, card); + + String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseaCard") + " "; + tgtObjects.addAll(new CardCollection(pc.chooseCardsForEffect(choices, sa, title, n, n, !sa.hasParam("ChoiceOptional")))); } else { tgtObjects.addAll(getDefinedOrTargeted(sa, "Defined")); } diff --git a/forge-gui/res/cardsfolder/d/dismantle.txt b/forge-gui/res/cardsfolder/d/dismantle.txt index c39d32dfd26..69a7d4e4fb4 100644 --- a/forge-gui/res/cardsfolder/d/dismantle.txt +++ b/forge-gui/res/cardsfolder/d/dismantle.txt @@ -1,13 +1,9 @@ Name:Dismantle ManaCost:2 R Types:Sorcery -A:SP$ Destroy | Cost$ 2 R | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | RememberTargets$ True | SubAbility$ DBChoice | SpellDescription$ Destroy target artifact. If that artifact had counters on it, put that many +1/+1 counters or charge counters on an artifact you control. -SVar:DBChoice:DB$ GenericChoice | Choices$ DBP1P1,DBCharge | ConditionDefined$ Targeted | ConditionPresent$ Card.HasCounters | ConditionCompare$ GE1 | StackDescription$ put that many +1/+1 counters or charge counters on an artifact you control. -SVar:DBP1P1:DB$ ChooseCard | Choices$ Artifact.YouCtrl | Amount$ 1 | SpellDescription$ +1/+1 | SubAbility$ DBPutP1P1 -SVar:DBPutP1P1:DB$ PutCounter | Defined$ ChosenCard | CounterType$ P1P1 | CounterNum$ X | References$ X | SubAbility$ DBCleanup -SVar:DBCharge:DB$ ChooseCard | Choices$ Artifact.YouCtrl | Amount$ 1 | SpellDescription$ charge | SubAbility$ DBPutCharge -SVar:DBPutCharge:DB$ PutCounter | Defined$ ChosenCard | CounterType$ CHARGE | CounterNum$ X | References$ X | SpellDescription$ Charge | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:RememberedLKI$CardCounters.ALL -SVar:Picture:http://www.wizards.com/global/images/magic/general/dismantle.jpg +A:SP$ Destroy | Cost$ 2 R | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | SubAbility$ DBChoice | SpellDescription$ Destroy target artifact. If that artifact had counters on it, put that many +1/+1 counters or charge counters on an artifact you control. +SVar:DBChoice:DB$ GenericChoice | Choices$ DBPutP1P1,DBPutCharge | ConditionDefined$ Targeted | ConditionPresent$ Card.HasCounters | ConditionCompare$ GE1 | StackDescription$ put that many +1/+1 counters or charge counters on an artifact you control. +SVar:DBPutP1P1:DB$ PutCounter | Choices$ Artifact.YouCtrl | CounterType$ P1P1 | CounterNum$ X | References$ X | SpellDescription$ +1/+1 +SVar:DBPutCharge:DB$ PutCounter | Choices$ Artifact.YouCtrl | CounterType$ CHARGE | CounterNum$ X | References$ X | SpellDescription$ Charge +SVar:X:TargetedLKI$CardCounters.ALL Oracle:Destroy target artifact. If that artifact had counters on it, put that many +1/+1 counters or charge counters on an artifact you control. diff --git a/forge-gui/res/cardsfolder/f/find_finality.txt b/forge-gui/res/cardsfolder/f/find_finality.txt index 2b85d2313fd..a4c41981640 100644 --- a/forge-gui/res/cardsfolder/f/find_finality.txt +++ b/forge-gui/res/cardsfolder/f/find_finality.txt @@ -10,9 +10,7 @@ ALTERNATE Name:Finality ManaCost:4 B G Types:Sorcery -A:SP$ ChooseCard | Cost$ 4 B G | Defined$ You | Amount$ 1 | MinAmount$ 0 | Choices$ Creature.YouCtrl | SubAbility$ DBPutCounter | SpellDescription$ You may put two +1/+1 counters on a creature you control. Then all creatures get -4/-4 until end of turn. -SVar:DBPutCounter:DB$ PutCounter | Defined$ ChosenCard | CounterType$ P1P1 | CounterNum$ 2 | SubAbility$ DBPumpAll -SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Creature | NumAtt$ -4 | NumDef$ -4 | IsCurse$ True | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True +A:SP$ PutCounter | Cost$ 4 B G | Choices$ Creature.YouCtrl | ChoiceOptional$ True | CounterType$ P1P1 | CounterNum$ 2 | SubAbility$ DBPumpAll | StackDescription$ SpellDescription | SpellDescription$ You may put two +1/+1 counters on a creature you control. Then all creatures get -4/-4 until end of turn. +SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Creature | NumAtt$ -4 | NumDef$ -4 | IsCurse$ True DeckHas:Ability$Counters Oracle:You may put two +1/+1 counters on a creature you control. Then all creatures get -4/-4 until end of turn. diff --git a/forge-gui/res/cardsfolder/h/haphazard_bombardment.txt b/forge-gui/res/cardsfolder/h/haphazard_bombardment.txt index d598dd69a28..eccf46ba889 100644 --- a/forge-gui/res/cardsfolder/h/haphazard_bombardment.txt +++ b/forge-gui/res/cardsfolder/h/haphazard_bombardment.txt @@ -1,12 +1,10 @@ Name:Haphazard Bombardment ManaCost:5 R Types:Enchantment -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChoose | TriggerDescription$ When CARDNAME enters the battlefield, choose four nonenchantment permanents you don't control and put an aim counter on each of them. -SVar:TrigChoose:DB$ ChooseCard | Defined$ You | Amount$ 4 | Choices$ Permanent.YouDontCtrl+nonEnchantment | SubAbility$ DBPutCounter | AILogic$ AtLeast1 | Mandatory$ True -SVar:DBPutCounter:DB$ PutCounter | Defined$ ChosenCard | CounterType$ AIM | CounterNum$ 1 | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBPutCounter | TriggerDescription$ When CARDNAME enters the battlefield, choose four nonenchantment permanents you don't control and put an aim counter on each of them. +SVar:DBPutCounter:DB$ PutCounter | Choices$ Permanent.YouDontCtrl+nonEnchantment | ChoiceAmount§ 4 | Defined$ ChosenCard | CounterType$ AIM | CounterNum$ 1 T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | IsPresent$ Permanent.YouDontCtrl+counters_GE1_AIM | PresentCompare$ GE2 | Execute$ TrigDestroy | TriggerDescription$ At the beginning of your end step, if two or more permanents you don't control have an aim counter on them, destroy one of those permanents at random. SVar:TrigDestroy:DB$ ChooseCard | Amount$ 1 | AtRandom$ True | Choices$ Permanent.YouDontCtrl+counters_GE1_AIM | SubAbility$ DBDestroy SVar:DBDestroy:DB$ Destroy | Defined$ ChosenCard | SubAbility$ DBCleanup -SVar:Picture:http://www.wizards.com/global/images/magic/general/haphazard_bombardment.jpg +SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True Oracle:When Haphazard Bombardment enters the battlefield, choose four nonenchantment permanents you don't control and put an aim counter on each of them.\nAt the beginning of your end step, if two or more permanents you don't control have an aim counter on them, destroy one of those permanents at random. diff --git a/forge-gui/res/cardsfolder/s/settle_the_score.txt b/forge-gui/res/cardsfolder/s/settle_the_score.txt index 08d18bef014..4820ed006f0 100644 --- a/forge-gui/res/cardsfolder/s/settle_the_score.txt +++ b/forge-gui/res/cardsfolder/s/settle_the_score.txt @@ -1,7 +1,6 @@ Name:Settle the Score ManaCost:2 B B Types:Sorcery -A:SP$ ChangeZone | Cost$ 2 B B | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBChoice | SpellDescription$ Exile target creature. Put two loyalty counters on a planeswalker you control. -SVar:DBChoice:DB$ ChooseCard | Choices$ Planeswalker.YouCtrl | Amount$ 1 | Mandatory$ True | SubAbility$ DBPutLoyalty -SVar:DBPutLoyalty:DB$ PutCounter | Defined$ ChosenCard | CounterType$ LOYALTY | CounterNum$ 2 +A:SP$ ChangeZone | Cost$ 2 B B | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBPutLoyalty | SpellDescription$ Exile target creature. Put two loyalty counters on a planeswalker you control. +SVar:DBPutLoyalty:DB$ PutCounter | Choices$ Planeswalker.YouCtrl | CounterType$ LOYALTY | CounterNum$ 2 Oracle:Exile target creature. Put two loyalty counters on a planeswalker you control. diff --git a/forge-gui/res/cardsfolder/t/the_elderspell.txt b/forge-gui/res/cardsfolder/t/the_elderspell.txt index ec0e5f85773..2255b8d308f 100644 --- a/forge-gui/res/cardsfolder/t/the_elderspell.txt +++ b/forge-gui/res/cardsfolder/t/the_elderspell.txt @@ -1,9 +1,8 @@ Name:The Elderspell ManaCost:B B Types:Sorcery -A:SP$ Destroy | Cost$ B B | ValidTgts$ Planeswalker | TgtPrompt$ Select target planeswalker | TargetMin$ 0 | TargetMax$ MaxTargets | References$ MaxTargets | SubAbility$ DBChooseCard | RememberDestroyed$ True | SpellDescription$ Destroy any number of target planeswalkers. Choose a planeswalker you control. Put two loyalty counters on it for each planeswalker destroyed this way. -SVar:DBChooseCard:DB$ ChooseCard | Defined$ You | Amount$ 1 | Choices$ Planeswalker.YouCtrl | Mandatory$ True | SubAbility$ DBPutLoyalty -SVar:DBPutLoyalty:DB$ PutCounter | Defined$ ChosenCard | CounterType$ LOYALTY | CounterNum$ X | References$ X | SubAbility$ DBCleanup +A:SP$ Destroy | Cost$ B B | ValidTgts$ Planeswalker | TgtPrompt$ Select target planeswalker | TargetMin$ 0 | TargetMax$ MaxTargets | References$ MaxTargets | SubAbility$ DBPutLoyalty | RememberDestroyed$ True | SpellDescription$ Destroy any number of target planeswalkers. Choose a planeswalker you control. Put two loyalty counters on it for each planeswalker destroyed this way. +SVar:DBPutLoyalty:DB$ PutCounter | Choices$ Planeswalker.YouCtrl | CounterType$ LOYALTY | CounterNum$ X | References$ X | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:MaxTargets:Count$Valid Planeswalker SVar:X:Count$RememberedSize/Twice From 9edaa9561e7dd6a00075fae173d78bcecbdc64f3 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Mon, 6 Apr 2020 05:02:25 +0800 Subject: [PATCH 13/19] Add Disconnect for Mobile --- forge-gui-mobile/src/forge/Forge.java | 2 + .../src/forge/screens/match/MatchScreen.java | 39 ++++++++------ .../screens/online/OnlineLobbyScreen.java | 52 +++++++++++++++++-- .../src/forge/screens/online/OnlineMenu.java | 32 +++++++++++- forge-gui/pom.xml | 5 -- forge-gui/src/main/java/forge/GuiBase.java | 7 +-- .../main/java/forge/net/NetConnectUtil.java | 3 +- .../java/forge/net/client/FGameClient.java | 3 +- .../java/forge/net/server/FServerManager.java | 9 ++++ 9 files changed, 119 insertions(+), 33 deletions(-) diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java index 9c300860bae..0102b16c2b3 100644 --- a/forge-gui-mobile/src/forge/Forge.java +++ b/forge-gui-mobile/src/forge/Forge.java @@ -1,5 +1,6 @@ package forge; +import com.badlogic.gdx.Application; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input.Keys; @@ -87,6 +88,7 @@ public class Forge implements ApplicationListener { //install our error handler ExceptionHandler.registerErrorHandling(); + GuiBase.setIsAndroid(Gdx.app.getType() == Application.ApplicationType.Android); graphics = new Graphics(); splashScreen = new SplashScreen(); frameRate = new FrameRate(); diff --git a/forge-gui-mobile/src/forge/screens/match/MatchScreen.java b/forge-gui-mobile/src/forge/screens/match/MatchScreen.java index bd61f09d006..3ccc8f9f72b 100644 --- a/forge-gui-mobile/src/forge/screens/match/MatchScreen.java +++ b/forge-gui-mobile/src/forge/screens/match/MatchScreen.java @@ -113,23 +113,28 @@ public class MatchScreen extends FScreen { if (MatchController.instance.getLocalPlayerCount() <= 1 || MatchController.instance.hotSeatMode()) { topPlayerPrompt = null; } - else { //show top prompt if multiple human players and not playing in Hot Seat mode - topPlayerPrompt = add(new VPrompt("", "", - new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - getGameController().selectButtonOk(); - } - }, - new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - getGameController().selectButtonCancel(); - } - })); - topPlayerPrompt.setRotate180(true); - topPlayerPanel.setRotate180(true); - getHeader().setRotate90(true); + else { + if (GuiBase.isNetworkplay()) { + topPlayerPrompt = null; + } else { + //show top prompt if multiple human players and not playing in Hot Seat mode and not in network play + topPlayerPrompt = add(new VPrompt("", "", + new FEventHandler() { + @Override + public void handleEvent(FEvent e) { + getGameController().selectButtonOk(); + } + }, + new FEventHandler() { + @Override + public void handleEvent(FEvent e) { + getGameController().selectButtonCancel(); + } + })); + topPlayerPrompt.setRotate180(true); + topPlayerPanel.setRotate180(true); + getHeader().setRotate90(true); + } } gameMenu = new VGameMenu(); diff --git a/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java b/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java index 2d4b650a647..1fc609fdb5e 100644 --- a/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java +++ b/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java @@ -2,6 +2,7 @@ package forge.screens.online; import forge.FThreads; import forge.Forge; +import forge.assets.FSkinProp; import forge.interfaces.ILobbyView; import forge.match.GameLobby; import forge.net.ChatMessage; @@ -13,12 +14,51 @@ import forge.net.client.FGameClient; import forge.screens.LoadingOverlay; import forge.screens.constructed.LobbyScreen; import forge.screens.online.OnlineMenu.OnlineScreen; +import forge.util.gui.SOptionPane; public class OnlineLobbyScreen extends LobbyScreen implements IOnlineLobby { public OnlineLobbyScreen() { super(null, OnlineMenu.getMenu(), new OfflineLobby()); } + private static GameLobby gameLobby; + + public GameLobby getGameLobby() { + return gameLobby; + } + + public static void clearGameLobby() { + gameLobby = null; + } + + public static void setGameLobby(GameLobby gameLobby) { + OnlineLobbyScreen.gameLobby = gameLobby; + } + + private static FGameClient fGameClient; + + public static FGameClient getfGameClient() { + return fGameClient; + } + + public static void closeClient() { + getfGameClient().close(); + fGameClient = null; + } + + public void closeConn(String msg) { + clearGameLobby(); + Forge.back(); + if (msg.length() > 0) { + FThreads.invokeInBackgroundThread(new Runnable() { + @Override + public void run() { + SOptionPane.showMessageDialog(msg, "Error", FSkinProp.ICO_WARNING); + } + }); + } + } + @Override public ILobbyView setLobby(GameLobby lobby0) { initLobby(lobby0); @@ -27,13 +67,13 @@ public class OnlineLobbyScreen extends LobbyScreen implements IOnlineLobby { @Override public void setClient(FGameClient client) { - // TODO Auto-generated method stub - + fGameClient = client; } @Override public void onActivate() { - if (getLobby() instanceof OfflineLobby) { + if (getGameLobby() == null) { + setGameLobby(getLobby()); //prompt to connect to server when offline lobby activated FThreads.invokeInBackgroundThread(new Runnable() { @Override @@ -43,7 +83,7 @@ public class OnlineLobbyScreen extends LobbyScreen implements IOnlineLobby { @Override public void run() { if (url == null) { - Forge.back(); //go back to previous screen if user cancels connection + closeConn(""); //go back to previous screen if user cancels connection return; } @@ -56,6 +96,10 @@ public class OnlineLobbyScreen extends LobbyScreen implements IOnlineLobby { final IOnlineChatInterface chatInterface = (IOnlineChatInterface)OnlineScreen.Chat.getScreen(); if (joinServer) { result = NetConnectUtil.join(url, OnlineLobbyScreen.this, chatInterface); + if (result.getMessage() == "<<_EM_ESOLC_<<") { //this message is returned via netconnectutil on exception + closeConn("Invalid host address (" + url + ") was detected."); + return; + } } else { result = NetConnectUtil.host(OnlineLobbyScreen.this, chatInterface); diff --git a/forge-gui-mobile/src/forge/screens/online/OnlineMenu.java b/forge-gui-mobile/src/forge/screens/online/OnlineMenu.java index f796b3d55d3..7f316541178 100644 --- a/forge-gui-mobile/src/forge/screens/online/OnlineMenu.java +++ b/forge-gui-mobile/src/forge/screens/online/OnlineMenu.java @@ -6,16 +6,20 @@ import forge.assets.FSkinImage; import forge.menu.FMenuItem; import forge.menu.FPopupMenu; import forge.model.FModel; +import forge.net.server.FServerManager; import forge.properties.ForgePreferences; import forge.properties.ForgePreferences.FPref; import forge.screens.FScreen; import forge.toolbox.FEvent; import forge.toolbox.FEvent.FEventHandler; +import forge.toolbox.FOptionPane; +import forge.util.Callback; public class OnlineMenu extends FPopupMenu { public enum OnlineScreen { - Lobby("Lobby", FSkinImage.FAVICON, OnlineLobbyScreen.class), - Chat("Chat", FSkinImage.QUEST_NOTES, OnlineChatScreen.class); + Lobby("Lobby", FSkinImage.QUEST_GEAR, OnlineLobbyScreen.class), + Chat("Chat", FSkinImage.QUEST_NOTES, OnlineChatScreen.class), + Close("Disconnect", FSkinImage.EXILE, null);; private final FMenuItem item; private final Class screenClass; @@ -26,6 +30,30 @@ public class OnlineMenu extends FPopupMenu { item = new FMenuItem(caption0, icon0, new FEventHandler() { @Override public void handleEvent(FEvent e) { + if(screenClass == null) { + FOptionPane.showConfirmDialog( + "Leave lobby? Doing so will shut down all connections and stop hosting.", + "Stop Network", new Callback() { + @Override + public void run(Boolean result) { + if (result) { + if (FServerManager.getInstance() != null) + if(FServerManager.getInstance().isHosting()) { + FServerManager.getInstance().unsetReady(); + FServerManager.getInstance().stopServer(); + } + + if (OnlineLobbyScreen.getfGameClient() != null) + OnlineLobbyScreen.closeClient(); + + Forge.back(); + screen = null; + OnlineLobbyScreen.clearGameLobby(); + } + } + }); + return; + } Forge.back(); //remove current screen from chain open(); setPreferredScreen(OnlineScreen.this); diff --git a/forge-gui/pom.xml b/forge-gui/pom.xml index 98f0a976c45..c4097dc235d 100644 --- a/forge-gui/pom.xml +++ b/forge-gui/pom.xml @@ -77,11 +77,6 @@ slf4j-simple 1.7.22 - - org.mapdb - elsa - 3.0.0-M7 - diff --git a/forge-gui/src/main/java/forge/GuiBase.java b/forge-gui/src/main/java/forge/GuiBase.java index aee6e20dd49..89001f1da97 100644 --- a/forge-gui/src/main/java/forge/GuiBase.java +++ b/forge-gui/src/main/java/forge/GuiBase.java @@ -6,6 +6,7 @@ public class GuiBase { private static IGuiBase guiInterface; private static boolean propertyConfig = true; private static boolean networkplay = false; + private static boolean isAndroidport = false; public static IGuiBase getInterface() { return guiInterface; @@ -13,9 +14,9 @@ public class GuiBase { public static void setInterface(IGuiBase i0) { guiInterface = i0; } - public static void enablePropertyConfig(boolean value) { - propertyConfig = value; - } + public static void enablePropertyConfig(boolean value) { propertyConfig = value; } + public static void setIsAndroid(boolean value) { isAndroidport = value; } + public static boolean isAndroid() { return isAndroidport; } public static boolean isNetworkplay() { return networkplay; } diff --git a/forge-gui/src/main/java/forge/net/NetConnectUtil.java b/forge-gui/src/main/java/forge/net/NetConnectUtil.java index 9de56210721..89ecf568be3 100644 --- a/forge-gui/src/main/java/forge/net/NetConnectUtil.java +++ b/forge-gui/src/main/java/forge/net/NetConnectUtil.java @@ -178,7 +178,8 @@ public class NetConnectUtil { client.connect(hostname, port); } catch (Exception ex) { - return null; + //return a message to close the connection so we will not crash... + return new ChatMessage(null, "<<_EM_ESOLC_<<"); } return new ChatMessage(null, String.format("Connected to %s:%d", hostname, port)); diff --git a/forge-gui/src/main/java/forge/net/client/FGameClient.java b/forge-gui/src/main/java/forge/net/client/FGameClient.java index 740d78a5c08..75547b95677 100644 --- a/forge-gui/src/main/java/forge/net/client/FGameClient.java +++ b/forge-gui/src/main/java/forge/net/client/FGameClient.java @@ -86,7 +86,8 @@ public class FGameClient implements IToServer { } public void close() { - channel.close(); + if (channel != null) + channel.close(); } @Override diff --git a/forge-gui/src/main/java/forge/net/server/FServerManager.java b/forge-gui/src/main/java/forge/net/server/FServerManager.java index 1e0ad247eb4..8e4942ce6b4 100644 --- a/forge-gui/src/main/java/forge/net/server/FServerManager.java +++ b/forge-gui/src/main/java/forge/net/server/FServerManager.java @@ -180,6 +180,15 @@ public final class FServerManager { this.localLobby = lobby; } + public void unsetReady() { + if (this.localLobby != null) { + if (this.localLobby.getSlot(0) != null) { + this.localLobby.getSlot(0).setIsReady(false); + updateLobbyState(); + } + } + } + public boolean isMatchActive() { return this.localLobby != null && this.localLobby.isMatchActive(); } From c82ed43c699599ecef8ecfeb4ad2e75bb95a7e40 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Mon, 6 Apr 2020 05:18:53 +0800 Subject: [PATCH 14/19] Update description --- forge-gui-mobile/src/forge/screens/online/OnlineMenu.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-gui-mobile/src/forge/screens/online/OnlineMenu.java b/forge-gui-mobile/src/forge/screens/online/OnlineMenu.java index 7f316541178..6e0fad9c9a7 100644 --- a/forge-gui-mobile/src/forge/screens/online/OnlineMenu.java +++ b/forge-gui-mobile/src/forge/screens/online/OnlineMenu.java @@ -19,7 +19,7 @@ public class OnlineMenu extends FPopupMenu { public enum OnlineScreen { Lobby("Lobby", FSkinImage.QUEST_GEAR, OnlineLobbyScreen.class), Chat("Chat", FSkinImage.QUEST_NOTES, OnlineChatScreen.class), - Close("Disconnect", FSkinImage.EXILE, null);; + Disconnect("Disconnect", FSkinImage.EXILE, null);; private final FMenuItem item; private final Class screenClass; @@ -33,7 +33,7 @@ public class OnlineMenu extends FPopupMenu { if(screenClass == null) { FOptionPane.showConfirmDialog( "Leave lobby? Doing so will shut down all connections and stop hosting.", - "Stop Network", new Callback() { + "Disconnect", new Callback() { @Override public void run(Boolean result) { if (result) { From 852328888406d6c0d14b70f9868158b3aaac9371 Mon Sep 17 00:00:00 2001 From: Sol Date: Sun, 5 Apr 2020 23:56:37 +0000 Subject: [PATCH 15/19] Revert "Merge branch 'keywordCounter' into 'master'" This reverts merge request !2667 --- .../ability/effects/CountersPutEffect.java | 13 ---- .../src/main/java/forge/game/card/Card.java | 63 ------------------- .../java/forge/game/card/CounterType.java | 53 +--------------- forge-gui/res/cardsfolder/d/dismantle.txt | 14 +++-- forge-gui/res/cardsfolder/f/find_finality.txt | 6 +- .../cardsfolder/h/haphazard_bombardment.txt | 8 ++- .../res/cardsfolder/s/settle_the_score.txt | 5 +- .../res/cardsfolder/t/the_elderspell.txt | 5 +- .../res/cardsfolder/upcoming/fully_grown.txt | 6 -- 9 files changed, 25 insertions(+), 148 deletions(-) delete mode 100644 forge-gui/res/cardsfolder/upcoming/fully_grown.txt diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java index 63a8536f1ff..76c8ed3b6a8 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java @@ -150,19 +150,6 @@ public class CountersPutEffect extends SpellAbilityEffect { CardCollection leastToughness = new CardCollection(Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetDefense)); tgtCards.addAll(pc.chooseCardsForEffect(leastToughness, sa, Localizer.getInstance().getMessage("lblChooseACreatureWithLeastToughness"), 1, 1, false)); tgtObjects.addAll(tgtCards); - } else if (sa.hasParam("Choices")) { - ZoneType choiceZone = ZoneType.Battlefield; - if (sa.hasParam("ChoiceZone")) { - choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone")); - } - CardCollection choices = new CardCollection(game.getCardsIn(choiceZone)); - - int n = sa.hasParam("ChoiceAmount") ? Integer.parseInt(sa.getParam("ChoiceAmount")) : 1; - - choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, card); - - String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseaCard") + " "; - tgtObjects.addAll(new CardCollection(pc.chooseCardsForEffect(choices, sa, title, n, n, !sa.hasParam("ChoiceOptional")))); } else { tgtObjects.addAll(getDefinedOrTargeted(sa, "Defined")); } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 722b532282e..d89ebcdaaf7 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -129,8 +129,6 @@ public class Card extends GameEntity implements Comparable { private final Multimap cantHaveKeywords = MultimapBuilder.hashKeys().enumSetValues(Keyword.class).build(); - private final Map counterTypeTimestamps = Maps.newEnumMap(CounterType.class); - private final Map canBlockAdditional = Maps.newTreeMap(); private final Set canBlockAny = Sets.newHashSet(); @@ -1307,44 +1305,12 @@ public class Card extends GameEntity implements Comparable { getController().addCounterToPermThisTurn(counterType, addAmount); view.updateCounters(this); } - if (newValue <= 0) { - removeCounterTimestamp(counterType); - } else { - addCounterTimestamp(counterType); - } if (table != null) { table.put(this, counterType, addAmount); } return addAmount; } - public boolean addCounterTimestamp(CounterType counterType) { - return addCounterTimestamp(counterType, true); - } - public boolean addCounterTimestamp(CounterType counterType, boolean updateView) { - if (!counterType.isKeywordCounter()) { - return false; - } - removeCounterTimestamp(counterType); - - long timestamp = game.getNextTimestamp(); - counterTypeTimestamps.put(counterType, timestamp); - addChangedCardKeywords(List.of(counterType.getKeyword().toString()), null, false, false, timestamp, updateView); - return true; - } - - public boolean removeCounterTimestamp(CounterType counterType) { - return removeCounterTimestamp(counterType, true); - } - - public boolean removeCounterTimestamp(CounterType counterType, boolean updateView) { - Long old = counterTypeTimestamps.remove(counterType); - if (old != null) { - removeChangedCardKeywords(old, updateView); - } - return old != null; - } - /** *

* addCountersAddedBy. @@ -1392,10 +1358,6 @@ public class Card extends GameEntity implements Comparable { setCounters(counterName, newValue); view.updateCounters(this); - if (newValue <= 0) { - this.removeCounterTimestamp(counterName); - } - //fire card stats changed event if p/t bonuses or loyalty changed from subtracted counters if (powerBonusBefore != getPowerBonusFromCounters() || toughnessBonusBefore != getToughnessBonusFromCounters() || loyaltyBefore != getCurrentLoyalty()) { getGame().fireEvent(new GameEventCardStatsChanged(this)); @@ -1418,23 +1380,8 @@ public class Card extends GameEntity implements Comparable { @Override public final void setCounters(final Map allCounters) { - boolean changed = false; - for (CounterType ct : counters.keySet()) { - if (removeCounterTimestamp(ct, false)) { - changed = true; - } - } counters = allCounters; view.updateCounters(this); - - for (CounterType ct : counters.keySet()) { - if (addCounterTimestamp(ct, false)) { - changed = true; - } - } - if (changed) { - updateKeywords(); - } } @Override @@ -1442,16 +1389,6 @@ public class Card extends GameEntity implements Comparable { if (counters.isEmpty()) { return; } counters.clear(); view.updateCounters(this); - - boolean changed = false; - for (CounterType ct : counterTypeTimestamps.keySet()) { - if (removeCounterTimestamp(ct, false)) { - changed = true; - } - } - if (changed) { - updateKeywords(); - } } public final String getSVar(final String var) { diff --git a/forge-game/src/main/java/forge/game/card/CounterType.java b/forge-game/src/main/java/forge/game/card/CounterType.java index 80825fa1f03..5d9fd5970d6 100644 --- a/forge-game/src/main/java/forge/game/card/CounterType.java +++ b/forge-game/src/main/java/forge/game/card/CounterType.java @@ -20,8 +20,6 @@ package forge.game.card; import com.google.common.collect.ImmutableList; -import forge.game.keyword.Keyword; - /** * The class Counters. * @@ -311,23 +309,7 @@ public enum CounterType { EXPERIENCE("EXP"), - POISON("POISN"), - - // Keyword Counters - - FLYING("Flying"), - FIRST_STRIKE("First Strike"), - DOUBLE_STRIKE("Double Strike"), - DEATHTOUCH("Deathtouch"), - HEXPROOF("Hexproof"), - INDESTRUCTIBLE("Indestructible"), - LIFELINK("Lifelink"), - MENACE("Menace"), - REACH("Reach"), - TRAMPLE("Trample"), - VIGILANCE("Vigilance") - - ; + POISON("POISN"); private String name, counterOnCardDisplayName; private int red, green, blue; @@ -383,39 +365,6 @@ public enum CounterType { return Enum.valueOf(CounterType.class, replacedName); } - public boolean isKeywordCounter() { - return this.getKeyword() != null; - } - - public Keyword getKeyword() { - switch (this) { - case FLYING: - return Keyword.FLYING; - case FIRST_STRIKE: - return Keyword.FIRST_STRIKE; - case DOUBLE_STRIKE: - return Keyword.DOUBLE_STRIKE; - case DEATHTOUCH: - return Keyword.DEATHTOUCH; - case HEXPROOF: - return Keyword.HEXPROOF; - case INDESTRUCTIBLE: - return Keyword.INDESTRUCTIBLE; - case LIFELINK: - return Keyword.LIFELINK; - case MENACE: - return Keyword.MENACE; - case REACH: - return Keyword.REACH; - case TRAMPLE: - return Keyword.TRAMPLE; - case VIGILANCE: - return Keyword.VIGILANCE; - default: - return null; - } - } - public static final ImmutableList values = ImmutableList.copyOf(values()); } diff --git a/forge-gui/res/cardsfolder/d/dismantle.txt b/forge-gui/res/cardsfolder/d/dismantle.txt index 69a7d4e4fb4..c39d32dfd26 100644 --- a/forge-gui/res/cardsfolder/d/dismantle.txt +++ b/forge-gui/res/cardsfolder/d/dismantle.txt @@ -1,9 +1,13 @@ Name:Dismantle ManaCost:2 R Types:Sorcery -A:SP$ Destroy | Cost$ 2 R | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | SubAbility$ DBChoice | SpellDescription$ Destroy target artifact. If that artifact had counters on it, put that many +1/+1 counters or charge counters on an artifact you control. -SVar:DBChoice:DB$ GenericChoice | Choices$ DBPutP1P1,DBPutCharge | ConditionDefined$ Targeted | ConditionPresent$ Card.HasCounters | ConditionCompare$ GE1 | StackDescription$ put that many +1/+1 counters or charge counters on an artifact you control. -SVar:DBPutP1P1:DB$ PutCounter | Choices$ Artifact.YouCtrl | CounterType$ P1P1 | CounterNum$ X | References$ X | SpellDescription$ +1/+1 -SVar:DBPutCharge:DB$ PutCounter | Choices$ Artifact.YouCtrl | CounterType$ CHARGE | CounterNum$ X | References$ X | SpellDescription$ Charge -SVar:X:TargetedLKI$CardCounters.ALL +A:SP$ Destroy | Cost$ 2 R | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | RememberTargets$ True | SubAbility$ DBChoice | SpellDescription$ Destroy target artifact. If that artifact had counters on it, put that many +1/+1 counters or charge counters on an artifact you control. +SVar:DBChoice:DB$ GenericChoice | Choices$ DBP1P1,DBCharge | ConditionDefined$ Targeted | ConditionPresent$ Card.HasCounters | ConditionCompare$ GE1 | StackDescription$ put that many +1/+1 counters or charge counters on an artifact you control. +SVar:DBP1P1:DB$ ChooseCard | Choices$ Artifact.YouCtrl | Amount$ 1 | SpellDescription$ +1/+1 | SubAbility$ DBPutP1P1 +SVar:DBPutP1P1:DB$ PutCounter | Defined$ ChosenCard | CounterType$ P1P1 | CounterNum$ X | References$ X | SubAbility$ DBCleanup +SVar:DBCharge:DB$ ChooseCard | Choices$ Artifact.YouCtrl | Amount$ 1 | SpellDescription$ charge | SubAbility$ DBPutCharge +SVar:DBPutCharge:DB$ PutCounter | Defined$ ChosenCard | CounterType$ CHARGE | CounterNum$ X | References$ X | SpellDescription$ Charge | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:RememberedLKI$CardCounters.ALL +SVar:Picture:http://www.wizards.com/global/images/magic/general/dismantle.jpg Oracle:Destroy target artifact. If that artifact had counters on it, put that many +1/+1 counters or charge counters on an artifact you control. diff --git a/forge-gui/res/cardsfolder/f/find_finality.txt b/forge-gui/res/cardsfolder/f/find_finality.txt index a4c41981640..2b85d2313fd 100644 --- a/forge-gui/res/cardsfolder/f/find_finality.txt +++ b/forge-gui/res/cardsfolder/f/find_finality.txt @@ -10,7 +10,9 @@ ALTERNATE Name:Finality ManaCost:4 B G Types:Sorcery -A:SP$ PutCounter | Cost$ 4 B G | Choices$ Creature.YouCtrl | ChoiceOptional$ True | CounterType$ P1P1 | CounterNum$ 2 | SubAbility$ DBPumpAll | StackDescription$ SpellDescription | SpellDescription$ You may put two +1/+1 counters on a creature you control. Then all creatures get -4/-4 until end of turn. -SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Creature | NumAtt$ -4 | NumDef$ -4 | IsCurse$ True +A:SP$ ChooseCard | Cost$ 4 B G | Defined$ You | Amount$ 1 | MinAmount$ 0 | Choices$ Creature.YouCtrl | SubAbility$ DBPutCounter | SpellDescription$ You may put two +1/+1 counters on a creature you control. Then all creatures get -4/-4 until end of turn. +SVar:DBPutCounter:DB$ PutCounter | Defined$ ChosenCard | CounterType$ P1P1 | CounterNum$ 2 | SubAbility$ DBPumpAll +SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Creature | NumAtt$ -4 | NumDef$ -4 | IsCurse$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True DeckHas:Ability$Counters Oracle:You may put two +1/+1 counters on a creature you control. Then all creatures get -4/-4 until end of turn. diff --git a/forge-gui/res/cardsfolder/h/haphazard_bombardment.txt b/forge-gui/res/cardsfolder/h/haphazard_bombardment.txt index eccf46ba889..d598dd69a28 100644 --- a/forge-gui/res/cardsfolder/h/haphazard_bombardment.txt +++ b/forge-gui/res/cardsfolder/h/haphazard_bombardment.txt @@ -1,10 +1,12 @@ Name:Haphazard Bombardment ManaCost:5 R Types:Enchantment -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBPutCounter | TriggerDescription$ When CARDNAME enters the battlefield, choose four nonenchantment permanents you don't control and put an aim counter on each of them. -SVar:DBPutCounter:DB$ PutCounter | Choices$ Permanent.YouDontCtrl+nonEnchantment | ChoiceAmount§ 4 | Defined$ ChosenCard | CounterType$ AIM | CounterNum$ 1 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChoose | TriggerDescription$ When CARDNAME enters the battlefield, choose four nonenchantment permanents you don't control and put an aim counter on each of them. +SVar:TrigChoose:DB$ ChooseCard | Defined$ You | Amount$ 4 | Choices$ Permanent.YouDontCtrl+nonEnchantment | SubAbility$ DBPutCounter | AILogic$ AtLeast1 | Mandatory$ True +SVar:DBPutCounter:DB$ PutCounter | Defined$ ChosenCard | CounterType$ AIM | CounterNum$ 1 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | IsPresent$ Permanent.YouDontCtrl+counters_GE1_AIM | PresentCompare$ GE2 | Execute$ TrigDestroy | TriggerDescription$ At the beginning of your end step, if two or more permanents you don't control have an aim counter on them, destroy one of those permanents at random. SVar:TrigDestroy:DB$ ChooseCard | Amount$ 1 | AtRandom$ True | Choices$ Permanent.YouDontCtrl+counters_GE1_AIM | SubAbility$ DBDestroy SVar:DBDestroy:DB$ Destroy | Defined$ ChosenCard | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True +SVar:Picture:http://www.wizards.com/global/images/magic/general/haphazard_bombardment.jpg Oracle:When Haphazard Bombardment enters the battlefield, choose four nonenchantment permanents you don't control and put an aim counter on each of them.\nAt the beginning of your end step, if two or more permanents you don't control have an aim counter on them, destroy one of those permanents at random. diff --git a/forge-gui/res/cardsfolder/s/settle_the_score.txt b/forge-gui/res/cardsfolder/s/settle_the_score.txt index 4820ed006f0..08d18bef014 100644 --- a/forge-gui/res/cardsfolder/s/settle_the_score.txt +++ b/forge-gui/res/cardsfolder/s/settle_the_score.txt @@ -1,6 +1,7 @@ Name:Settle the Score ManaCost:2 B B Types:Sorcery -A:SP$ ChangeZone | Cost$ 2 B B | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBPutLoyalty | SpellDescription$ Exile target creature. Put two loyalty counters on a planeswalker you control. -SVar:DBPutLoyalty:DB$ PutCounter | Choices$ Planeswalker.YouCtrl | CounterType$ LOYALTY | CounterNum$ 2 +A:SP$ ChangeZone | Cost$ 2 B B | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBChoice | SpellDescription$ Exile target creature. Put two loyalty counters on a planeswalker you control. +SVar:DBChoice:DB$ ChooseCard | Choices$ Planeswalker.YouCtrl | Amount$ 1 | Mandatory$ True | SubAbility$ DBPutLoyalty +SVar:DBPutLoyalty:DB$ PutCounter | Defined$ ChosenCard | CounterType$ LOYALTY | CounterNum$ 2 Oracle:Exile target creature. Put two loyalty counters on a planeswalker you control. diff --git a/forge-gui/res/cardsfolder/t/the_elderspell.txt b/forge-gui/res/cardsfolder/t/the_elderspell.txt index 2255b8d308f..ec0e5f85773 100644 --- a/forge-gui/res/cardsfolder/t/the_elderspell.txt +++ b/forge-gui/res/cardsfolder/t/the_elderspell.txt @@ -1,8 +1,9 @@ Name:The Elderspell ManaCost:B B Types:Sorcery -A:SP$ Destroy | Cost$ B B | ValidTgts$ Planeswalker | TgtPrompt$ Select target planeswalker | TargetMin$ 0 | TargetMax$ MaxTargets | References$ MaxTargets | SubAbility$ DBPutLoyalty | RememberDestroyed$ True | SpellDescription$ Destroy any number of target planeswalkers. Choose a planeswalker you control. Put two loyalty counters on it for each planeswalker destroyed this way. -SVar:DBPutLoyalty:DB$ PutCounter | Choices$ Planeswalker.YouCtrl | CounterType$ LOYALTY | CounterNum$ X | References$ X | SubAbility$ DBCleanup +A:SP$ Destroy | Cost$ B B | ValidTgts$ Planeswalker | TgtPrompt$ Select target planeswalker | TargetMin$ 0 | TargetMax$ MaxTargets | References$ MaxTargets | SubAbility$ DBChooseCard | RememberDestroyed$ True | SpellDescription$ Destroy any number of target planeswalkers. Choose a planeswalker you control. Put two loyalty counters on it for each planeswalker destroyed this way. +SVar:DBChooseCard:DB$ ChooseCard | Defined$ You | Amount$ 1 | Choices$ Planeswalker.YouCtrl | Mandatory$ True | SubAbility$ DBPutLoyalty +SVar:DBPutLoyalty:DB$ PutCounter | Defined$ ChosenCard | CounterType$ LOYALTY | CounterNum$ X | References$ X | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:MaxTargets:Count$Valid Planeswalker SVar:X:Count$RememberedSize/Twice diff --git a/forge-gui/res/cardsfolder/upcoming/fully_grown.txt b/forge-gui/res/cardsfolder/upcoming/fully_grown.txt deleted file mode 100644 index edfefc1522a..00000000000 --- a/forge-gui/res/cardsfolder/upcoming/fully_grown.txt +++ /dev/null @@ -1,6 +0,0 @@ -Name:Fully Grown -ManaCost:2 G -Types:Instant -A:SP$ Pump | Cost$ 2 G | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +3 | NumDef$ +3 | SubAbility$ PutCounter | SpellDescription$ Target creature gets +3/+3 until end of turn. Put a trample counter on it. -SVar:PutCounter:DB$ PutCounter | Defined$ Targeted | CounterType$ TRAMPLE | CounterNum$ 1 -Oracle:Target creature gets +3/+3 until end of turn. Put a trample counter on it. From aa86f8eae3660509db4a2d53fa80c7247f6fe972 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Mon, 6 Apr 2020 09:21:10 +0800 Subject: [PATCH 16/19] Forge constant for close command message --- .../src/forge/screens/online/OnlineLobbyScreen.java | 3 ++- forge-gui/src/main/java/forge/net/NetConnectUtil.java | 3 ++- forge-gui/src/main/java/forge/properties/ForgeConstants.java | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java b/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java index 1fc609fdb5e..fbd00c54e0f 100644 --- a/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java +++ b/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java @@ -11,6 +11,7 @@ import forge.net.IOnlineLobby; import forge.net.NetConnectUtil; import forge.net.OfflineLobby; import forge.net.client.FGameClient; +import forge.properties.ForgeConstants; import forge.screens.LoadingOverlay; import forge.screens.constructed.LobbyScreen; import forge.screens.online.OnlineMenu.OnlineScreen; @@ -96,7 +97,7 @@ public class OnlineLobbyScreen extends LobbyScreen implements IOnlineLobby { final IOnlineChatInterface chatInterface = (IOnlineChatInterface)OnlineScreen.Chat.getScreen(); if (joinServer) { result = NetConnectUtil.join(url, OnlineLobbyScreen.this, chatInterface); - if (result.getMessage() == "<<_EM_ESOLC_<<") { //this message is returned via netconnectutil on exception + if (result.getMessage() == ForgeConstants.CLOSE_CONN_COMMAND) { //this message is returned via netconnectutil on exception closeConn("Invalid host address (" + url + ") was detected."); return; } diff --git a/forge-gui/src/main/java/forge/net/NetConnectUtil.java b/forge-gui/src/main/java/forge/net/NetConnectUtil.java index 89ecf568be3..8b9e5fc39db 100644 --- a/forge-gui/src/main/java/forge/net/NetConnectUtil.java +++ b/forge-gui/src/main/java/forge/net/NetConnectUtil.java @@ -1,6 +1,7 @@ package forge.net; import forge.match.LobbySlotType; +import forge.properties.ForgeConstants; import org.apache.commons.lang3.StringUtils; import forge.GuiBase; @@ -179,7 +180,7 @@ public class NetConnectUtil { } catch (Exception ex) { //return a message to close the connection so we will not crash... - return new ChatMessage(null, "<<_EM_ESOLC_<<"); + return new ChatMessage(null, ForgeConstants.CLOSE_CONN_COMMAND); } return new ChatMessage(null, String.format("Connected to %s:%d", hostname, port)); diff --git a/forge-gui/src/main/java/forge/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/properties/ForgeConstants.java index 471e6acbbf0..12e9b309a5d 100644 --- a/forge-gui/src/main/java/forge/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/properties/ForgeConstants.java @@ -237,6 +237,7 @@ public final class ForgeConstants { public static final String QUEST_PREFS_FILE = USER_PREFS_DIR + "quest.preferences"; public static final String CONQUEST_PREFS_FILE = USER_PREFS_DIR + "conquest.preferences"; public static final String ITEM_VIEW_PREFS_FILE = USER_PREFS_DIR + "item_view.preferences"; + public static final String CLOSE_CONN_COMMAND = "<<_EM_ESOLC_<<"; // data that has defaults in the program dir but overrides/additions in the user dir private static final String _DEFAULTS_DIR = RES_DIR + "defaults" + PATH_SEPARATOR; From 349640a3a0996b8a973365e2a99252eb0088db47 Mon Sep 17 00:00:00 2001 From: Tim Mocny Date: Mon, 6 Apr 2020 02:48:01 +0000 Subject: [PATCH 17/19] IKO - 5 April --- .../upcoming/generals_enforcer.txt | 9 ++ .../cardsfolder/upcoming/indatha_crystal.txt | 8 ++ .../cardsfolder/upcoming/ketria_crystal.txt | 8 ++ .../cardsfolder/upcoming/migration_path.txt | 6 + .../res/cardsfolder/upcoming/neutralize.txt | 6 + .../cardsfolder/upcoming/phase_dolphin.txt | 7 ++ .../cardsfolder/upcoming/primal_empathy.txt | 14 ++- .../cardsfolder/upcoming/proud_wildbonder.txt | 7 ++ .../cardsfolder/upcoming/raugrin_crystal.txt | 8 ++ .../upcoming/reconnaissance_mission.txt | 7 ++ .../cardsfolder/upcoming/savai_crystal.txt | 8 ++ .../cardsfolder/upcoming/savai_sabertooth.txt | 5 + .../cardsfolder/upcoming/shredded_sails.txt | 8 ++ .../cardsfolder/upcoming/skull_prophet.txt | 9 ++ .../res/editions/Ikoria Lair of Behemoths.txt | 111 ++++++++++++++++++ 15 files changed, 215 insertions(+), 6 deletions(-) create mode 100755 forge-gui/res/cardsfolder/upcoming/generals_enforcer.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/indatha_crystal.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/ketria_crystal.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/migration_path.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/neutralize.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/phase_dolphin.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/proud_wildbonder.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/raugrin_crystal.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/reconnaissance_mission.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/savai_crystal.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/savai_sabertooth.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/shredded_sails.txt create mode 100755 forge-gui/res/cardsfolder/upcoming/skull_prophet.txt create mode 100644 forge-gui/res/editions/Ikoria Lair of Behemoths.txt diff --git a/forge-gui/res/cardsfolder/upcoming/generals_enforcer.txt b/forge-gui/res/cardsfolder/upcoming/generals_enforcer.txt new file mode 100755 index 00000000000..a1a15e1f3d9 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/generals_enforcer.txt @@ -0,0 +1,9 @@ +Name:General's Enforcer +ManaCost:W B +Types:Creature Human Soldier +PT:2/3 +S:Mode$ Continuous | Affected$ Human.YouCtrl+Legendary | AddKeyword$ Indestructible | Description$ Legendary Humans you control have indestructible. +A:AB$ ChangeZone | Cost$ 2 W B | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SubAbility$ DBToken | SpellDescription$ Exile target card from a graveyard. If it was a creature card, create a 1/1 white Human Soldier creature token. +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_human_soldier | TokenOwner$ You | LegacyImage$ w 1 1 human soldier iko | ConditionDefined$ Targeted | ConditionPresent$ Creature | ConditionCompare$ EQ1 | StackDescription$ If it was a creature card, create a 1/1 white Human Soldier creature token. +DeckHints:Type$Human +Oracle:Legendary Humans you control have indestructible.\n{2}{W}{B}: Exile target card from a graveyard. If it was a creature card, create a 1/1 white Human Soldier creature token. diff --git a/forge-gui/res/cardsfolder/upcoming/indatha_crystal.txt b/forge-gui/res/cardsfolder/upcoming/indatha_crystal.txt new file mode 100755 index 00000000000..d2a68855418 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/indatha_crystal.txt @@ -0,0 +1,8 @@ +Name:Indatha Crystal +ManaCost:3 +Types:Artifact +A:AB$ Mana | Cost$ T | Produced$ W | SpellDescription$ Add {W}. +A:AB$ Mana | Cost$ T | Produced$ B | SpellDescription$ Add {B}. +A:AB$ Mana | Cost$ T | Produced$ G | SpellDescription$ Add {G}. +K:Cycling:2 +Oracle:{T}: Add {W}, {B}, or {G}.\nCycling {2} ({2}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/upcoming/ketria_crystal.txt b/forge-gui/res/cardsfolder/upcoming/ketria_crystal.txt new file mode 100755 index 00000000000..f625a617f3c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/ketria_crystal.txt @@ -0,0 +1,8 @@ +Name:Ketria Crystal +ManaCost:3 +Types:Artifact +A:AB$ Mana | Cost$ T | Produced$ G | SpellDescription$ Add {G}. +A:AB$ Mana | Cost$ T | Produced$ U | SpellDescription$ Add {U}. +A:AB$ Mana | Cost$ T | Produced$ R | SpellDescription$ Add {R}. +K:Cycling:2 +Oracle:{T}: Add {G}, {U}, or {R}.\nCycling {2} ({2}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/upcoming/migration_path.txt b/forge-gui/res/cardsfolder/upcoming/migration_path.txt new file mode 100755 index 00000000000..74cbf679d8b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/migration_path.txt @@ -0,0 +1,6 @@ +Name:Migration Path +ManaCost:3 G +Types:Sorcery +A:SP$ ChangeZone | Cost$ 3 G | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 2 | StackDescription$ SpellDescription | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle your library. +K:Cycling:2 +Oracle:Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle your library.\nCycling {2} ({2}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/upcoming/neutralize.txt b/forge-gui/res/cardsfolder/upcoming/neutralize.txt new file mode 100755 index 00000000000..757fe250adf --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/neutralize.txt @@ -0,0 +1,6 @@ +Name:Neutralize +ManaCost:1 U U +Types:Instant +A:SP$ Counter | Cost$ 1 U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | SpellDescription$ Counter target spell. +K:Cycling:2 +Oracle:Counter target spell.\nCycling {2} ({2}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/upcoming/phase_dolphin.txt b/forge-gui/res/cardsfolder/upcoming/phase_dolphin.txt new file mode 100755 index 00000000000..5986f560749 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/phase_dolphin.txt @@ -0,0 +1,7 @@ +Name:Phase Dolphin +ManaCost:2 U +Types:Creature Elemental Whale +PT:1/4 +T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever CARDNAME attacks, another target attacking creature can't be blocked this turn. +SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.attacking+Other | TgtPrompt$ Select another target attacking creature | KW$ HIDDEN Unblockable +Oracle:Whenever Phase Dolphin attacks, another target attacking creature can't be blocked this turn. diff --git a/forge-gui/res/cardsfolder/upcoming/primal_empathy.txt b/forge-gui/res/cardsfolder/upcoming/primal_empathy.txt index 1d2bfd4f62d..4f16e19ecca 100755 --- a/forge-gui/res/cardsfolder/upcoming/primal_empathy.txt +++ b/forge-gui/res/cardsfolder/upcoming/primal_empathy.txt @@ -1,13 +1,15 @@ Name:Primal Empathy ManaCost:1 G U Types:Enchantment -T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ At the beginning of your upkeep, draw a card if you control a creature with the greatest power among creatures on the battlefield. Otherwise, put a +1/+1 counter on a creature you control. -SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 | ConditionPresent$ Creature | ConditionCompare$ GE1 | ConditionPresent$ Creature | ConditionCompare$ GE1 | ConditionCheckSVar$ Z | ConditionSVarCompare$ GEY | References$ Y,Z | RememberDrawn$ True | SubAbility$ DBChoose -SVar:DBChoose:DB$ ChooseCard | Defined$ You | Choices$ Creature.YouCtrl | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ0 | References$ X | SubAbility$ DBPutCounter -SVar:DBPutCounter:DB$ PutCounter | Defined$ ChosenCard | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearChosenCard$ True -SVar:X:Count$RememberedSize +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigBranch | TriggerDescription$ At the beginning of your upkeep, draw a card if you control a creature with the greatest power among creatures on the battlefield. Otherwise, put a +1/+1 counter on a creature you control. +SVar:TrigBranch:DB$ Branch | BranchConditionSVar$ X | References$ X | TrueSubAbility TrigBranch2 | FalseSubAbility$ DBPutCounter +SVar:TrigBranch2:DB$ Branch | BranchConditionSVar$ GreatPow | References$ GreatPow | TrueSubAbility$ DBDraw | FalseSubAbility$ DBPutCounter +SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 +SVar:DBPutCounter:DB$ PutCounter | Choices$ Creature.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 +SVar:GreatPow:Count$Compare Diff GE0.1.0 +SVar:X:Count$Valid Creature.YouCtrl SVar:Y:Count$GreatestPower_Creature.YouDontCtrl SVar:Z:Count$GreatestPower_Creature.YouCtrl +SVar:Diff:SVar$Z/Minus.Y DeckHas:Ability$Counters Oracle:At the beginning of your upkeep, draw a card if you control a creature with the greatest power among creatures on the battlefield. Otherwise, put a +1/+1 counter on a creature you control. diff --git a/forge-gui/res/cardsfolder/upcoming/proud_wildbonder.txt b/forge-gui/res/cardsfolder/upcoming/proud_wildbonder.txt new file mode 100755 index 00000000000..509ba6ab126 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/proud_wildbonder.txt @@ -0,0 +1,7 @@ +Name:Proud Wildbonder +ManaCost:2 R/G R/G +Types:Creature Human Warrior +PT:4/3 +K:Trample +S:Mode$ Continuous | Affected$ Creature.YouCtrl+withTrample | AddKeyword$ You may have CARDNAME assign its combat damage as though it weren't blocked. | Description$ Creatures you control with trample have "You may have this creature assign its combat damage as though it weren't blocked." +Oracle:Trample\nCreatures you control with trample have "You may have this creature assign its combat damage as though it weren't blocked." diff --git a/forge-gui/res/cardsfolder/upcoming/raugrin_crystal.txt b/forge-gui/res/cardsfolder/upcoming/raugrin_crystal.txt new file mode 100755 index 00000000000..a82eb62f2d5 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/raugrin_crystal.txt @@ -0,0 +1,8 @@ +Name:Raugrin Crystal +ManaCost:3 +Types:Artifact +A:AB$ Mana | Cost$ T | Produced$ U | SpellDescription$ Add {U}. +A:AB$ Mana | Cost$ T | Produced$ R | SpellDescription$ Add {R}. +A:AB$ Mana | Cost$ T | Produced$ W | SpellDescription$ Add {W}. +K:Cycling:2 +Oracle:{T}: Add {U}, {R}, or {W}.\nCycling {2} ({2}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/upcoming/reconnaissance_mission.txt b/forge-gui/res/cardsfolder/upcoming/reconnaissance_mission.txt new file mode 100755 index 00000000000..6d23f87cd14 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/reconnaissance_mission.txt @@ -0,0 +1,7 @@ +Name:Reconnaissance Mission +ManaCost:2 U U +Types:Enchantment +T:Mode$ DamageDone | ValidSource$ Creature.YouCtrl | ValidTarget$ Player | CombatDamage$ True | OptionalDecider$ You | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever a creature you control deals combat damage to a player, you may draw a card. +SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 +K:Cycling:2 +Oracle:Whenever a creature you control deals combat damage to a player, you may draw a card.\nCycling {2} ({2}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/upcoming/savai_crystal.txt b/forge-gui/res/cardsfolder/upcoming/savai_crystal.txt new file mode 100755 index 00000000000..761abfc2946 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/savai_crystal.txt @@ -0,0 +1,8 @@ +Name:Savai Crystal +ManaCost:3 +Types:Artifact +A:AB$ Mana | Cost$ T | Produced$ R | SpellDescription$ Add {R}. +A:AB$ Mana | Cost$ T | Produced$ W | SpellDescription$ Add {W}. +A:AB$ Mana | Cost$ T | Produced$ B | SpellDescription$ Add {B}. +K:Cycling:2 +Oracle:{T}: Add {R}, {W}, or {B}.\nCycling {2} ({2}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/upcoming/savai_sabertooth.txt b/forge-gui/res/cardsfolder/upcoming/savai_sabertooth.txt new file mode 100755 index 00000000000..cf1abcd40a7 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/savai_sabertooth.txt @@ -0,0 +1,5 @@ +Name:Savai Sabertooth +ManaCost:1 W +Types:Creature Cat +PT:3/1 +Oracle: diff --git a/forge-gui/res/cardsfolder/upcoming/shredded_sails.txt b/forge-gui/res/cardsfolder/upcoming/shredded_sails.txt new file mode 100755 index 00000000000..9f2b86e1505 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/shredded_sails.txt @@ -0,0 +1,8 @@ +Name:Shredded Sails +ManaCost:1 R +Types:Instant +A:SP$ Charm | Cost$ 1 R | CharmNum$ 1 | Choices$ DestArt,DmgFly +SVar:DestArt:DB$ Destroy | ValidTgts$ Artifact | SpellDescription$ Destroy target artifact. +SVar:DmgFly:DB$ DealDamage | ValidTgts$ Creature.withFlying | NumDmg$ 2 | SpellDescription$ CARDNAME deals 4 damage to target creature with flying. +K:Cycling:2 +Oracle:Choose one —\n• Destroy target artifact.\n• Shredded Sails deals 4 damage to target creature with flying.\nCycling {2} ({2}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/upcoming/skull_prophet.txt b/forge-gui/res/cardsfolder/upcoming/skull_prophet.txt new file mode 100755 index 00000000000..90a2fd343e2 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/skull_prophet.txt @@ -0,0 +1,9 @@ +Name:Skull Prophet +ManaCost:B G +Types:Creature Human Druid +PT:3/1 +A:AB$ Mana | Cost$ T | Produced$ B | SpellDescription$ Add {B}. +A:AB$ Mana | Cost$ T | Produced$ G | SpellDescription$ Add {G}. +A:AB$ Mill | Cost$ T | Defined$ You | NumCards$ 2 | SpellDescription$ Put the top two cards of your library into your graveyard. +DeckHas:Ability$Graveyard +Oracle:{T}: Add {B} or {G}.\n{T}: Put the top two cards of your library into your graveyard. diff --git a/forge-gui/res/editions/Ikoria Lair of Behemoths.txt b/forge-gui/res/editions/Ikoria Lair of Behemoths.txt new file mode 100644 index 00000000000..48e47c77e8c --- /dev/null +++ b/forge-gui/res/editions/Ikoria Lair of Behemoths.txt @@ -0,0 +1,111 @@ +[metadata] +Code=IKO +Date=2020-04-16 +Name=Ikoria: Lair of Behemoths +Type=Expansion + +[cards] +3 C Mysterious Egg +11 R Drannith Magistrate +13 U Flourishing Fox +16 U Huntmaster Liger +24 R Mythos of Snapdax +25 C Pacifism +29 C Savai Sabertooth +34 U Stormwild Capridor +36 U Valiant Rescuer +38 U "Knock Blocked" +41 U Archipelagore +43 U Boon of the Wish-Giver +47 C Dreamtail Heron +49 C Essence Scatter +59 U Neutralize +61 U "What's Kraken" +62 C Phase Dolphin +63 U Pollywog Symbiote +64 U Pouncing Shoreshark +65 U Reconnaissance Mission +67 R Shark Typhoon +69 C Thieving Otter +70 R Voracious Greatshark +71 C Wingfold Pteron +73 U Bastion of Remembrance +75 C Blood Curdle +78 U Call of the Death-Dweller +79 C Cavern Whisperer +80 U Chittering Harvester +84 R Dirge Bat +88 R "Odd the Evens" +89 C Gloom Pangolin +90 U Grimdancer +93 U Insatiable Hemophage +97 R Mythos of Nethroi +101 U "Learning Social Distancing" +104 U Void Beckoner +106 U Zagoth Mamba +111 U "Round 2: Fight!" +112 C Cloudpiercer +113 C Drannith Stinger +114 R Everquill Phoenix +118 U Footfall Crater +125 M Lukka, Coppercoat Outcast +127 R Mythos of Vadrok +133 U "Recycling Monitor" +136 C Shredded Sails +141 R Yidaro, Wandering Monster +144 U Auspicious Starrix +145 U "Breach the Barrier" +146 C Bristling Boar +147 U "Trump Shard" +148 R Colossification +152 C Fertilid +154 C Fully Grown +155 R Gemrazer +156 U Glowstone Recluse +162 R Kogla, the Titan Ape +164 U Migration Path +165 C "Spring Cleaner" +166 U Monstruous Step +167 C Mosscoat Goriak +168 R "Mythos of Brokkos" +174 U Titanoth Rex +175 M Vivien, Monsters' Advocate +176 C "Harmless Houseplant" +177 U Back for More +178 U "BLARGbeast" +179 M Brokkos, Apex of Forever +180 U Channeled Force +181 M Chevill, Bane of Monsters +182 R Death's Oasis +183 U Dire Tactics +187 M General Kudro of Drannith +188 U General's Enforcer +190 M Illuna, Apex of Wishes +193 R Labyrinth Raptor +197 M Nethroi, Apex of Death +200 U Primal Empathy +201 R "Big Boy Forest Crusher" +203 M Rielle, the Everwise +206 U Skull Prophet +209 M Snapdax, Apex of the Hunt +211 U Sprite Dragon +213 U Trumpeting Gnarr +214 M Vadrok, Apex of Thunder +220 M Fiend Artisan +221 R Gyruda, Doom of Depths +227 R Lutri, the Spellchaser +228 R "Obosh, With The Leggies" +229 U Proud Wildbonder +231 R Umori, the Collector +234 R Crystalline Giant +235 U Indatha Crystal +236 U Ketria Crystal +238 U Raugrin Crystal +239 U Savai Crystal +242 U Zagoth Crystal +275 M Zilortha, Strength Incarnate +316 M Luminous Broodmoth +354 R Keruga, the Macrosage + +[tokens] +w_1_1_human_soldier \ No newline at end of file From 0b5c70b4bd68254fc9328fb958bfd5e7323723cc Mon Sep 17 00:00:00 2001 From: BlueWizard <196-BlueWizard@users.noreply.git.cardforge.org> Date: Mon, 6 Apr 2020 02:48:25 +0000 Subject: [PATCH 18/19] Start duel sound + correction in Unstable edition file (3 missing tokens) --- forge-gui/MANUAL.txt | 1 + forge-gui/res/editions/Unstable.txt | 3 +++ forge-gui/src/main/java/forge/sound/EventVisualizer.java | 6 ++++++ forge-gui/src/main/java/forge/sound/SoundEffectType.java | 1 + 4 files changed, 11 insertions(+) diff --git a/forge-gui/MANUAL.txt b/forge-gui/MANUAL.txt index 58a48b12da7..783763bd3d3 100644 --- a/forge-gui/MANUAL.txt +++ b/forge-gui/MANUAL.txt @@ -431,6 +431,7 @@ Regen - regeneration.wav - triggered when a creature is regenerated. RemoveCounter - remove_counter.wav - triggered when a counter is removed from a permanent. Sacrifice - sacrifice.wav - triggered when a permanent is sacrificed. Sorcery [*] - sorcery.wav - triggered when a sorcery is played. +StartOfDuel - start_duel.wav - triggered when a duel starts Shuffle [*] - shuffle.wav - triggered when a player shuffles his deck. Tap [*] - tap.wav - triggered when a permanent is tapped. Token [*] - token.wav - triggered when a token is created. diff --git a/forge-gui/res/editions/Unstable.txt b/forge-gui/res/editions/Unstable.txt index 636eead1d2a..efa45ccd3cb 100644 --- a/forge-gui/res/editions/Unstable.txt +++ b/forge-gui/res/editions/Unstable.txt @@ -20,6 +20,8 @@ w_2_2_cat w_0_1_goat w_1_1_spirit_flying w_1_1_spirit_flying +w_4_4_angel_flying +w_4_4_angel_flying w_1_1_soldier u_1_1_faerie_spy_flying_haste_draw u_8_8_octopus @@ -45,6 +47,7 @@ g_1_1_squirrel g_6_6_wurm gw_x_x_elemental_total_creatures gw_x_x_elemental_total_creatures +wubrg_4_4_dragon c_a_clue_draw c_a_clue_draw c_x_x_a_construct diff --git a/forge-gui/src/main/java/forge/sound/EventVisualizer.java b/forge-gui/src/main/java/forge/sound/EventVisualizer.java index 69ce7a8fba1..734fd807df2 100644 --- a/forge-gui/src/main/java/forge/sound/EventVisualizer.java +++ b/forge-gui/src/main/java/forge/sound/EventVisualizer.java @@ -22,6 +22,7 @@ import forge.game.event.GameEventCardSacrificed; import forge.game.event.GameEventCardTapped; import forge.game.event.GameEventFlipCoin; import forge.game.event.GameEventGameOutcome; +import forge.game.event.GameEventGameStarted; import forge.game.event.GameEventLandPlayed; import forge.game.event.GameEventManaBurn; import forge.game.event.GameEventPlayerLivesChanged; @@ -47,6 +48,11 @@ public class EventVisualizer extends IGameEventVisitor.Base imp this.player = lobbyPlayer; } + @Override + public SoundEffectType visit(GameEventGameStarted event) { + return SoundEffectType.StartDuel; + } + @Override public SoundEffectType visit(final GameEventCardDamaged event) { return SoundEffectType.Damage; } @Override diff --git a/forge-gui/src/main/java/forge/sound/SoundEffectType.java b/forge-gui/src/main/java/forge/sound/SoundEffectType.java index 14ea9684c53..002e93360a0 100644 --- a/forge-gui/src/main/java/forge/sound/SoundEffectType.java +++ b/forge-gui/src/main/java/forge/sound/SoundEffectType.java @@ -83,6 +83,7 @@ public enum SoundEffectType { ScriptedEffect("", false), // Plays the effect defined by SVar:SoundEffect Shuffle("shuffle.wav", false), Sorcery("sorcery.wav", false), + StartDuel("start_duel.wav",false), Tap("tap.wav", false), Token("token.wav", true), Untap("untap.wav", true), From 5c79cb7de63cdbc98b8f69427f4dbbd7fd0af292 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Mon, 6 Apr 2020 05:27:39 +0000 Subject: [PATCH 19/19] Revert "Merge branch 'revert-93f1bb70' into 'master'" This reverts merge request !2668 --- .../ability/effects/CountersPutEffect.java | 13 ++++ .../src/main/java/forge/game/card/Card.java | 63 +++++++++++++++++++ .../java/forge/game/card/CounterType.java | 53 +++++++++++++++- forge-gui/res/cardsfolder/d/dismantle.txt | 14 ++--- forge-gui/res/cardsfolder/f/find_finality.txt | 6 +- .../cardsfolder/h/haphazard_bombardment.txt | 8 +-- .../res/cardsfolder/s/settle_the_score.txt | 5 +- .../res/cardsfolder/t/the_elderspell.txt | 5 +- .../res/cardsfolder/upcoming/fully_grown.txt | 6 ++ 9 files changed, 148 insertions(+), 25 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/fully_grown.txt diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java index 76c8ed3b6a8..63a8536f1ff 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java @@ -150,6 +150,19 @@ public class CountersPutEffect extends SpellAbilityEffect { CardCollection leastToughness = new CardCollection(Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetDefense)); tgtCards.addAll(pc.chooseCardsForEffect(leastToughness, sa, Localizer.getInstance().getMessage("lblChooseACreatureWithLeastToughness"), 1, 1, false)); tgtObjects.addAll(tgtCards); + } else if (sa.hasParam("Choices")) { + ZoneType choiceZone = ZoneType.Battlefield; + if (sa.hasParam("ChoiceZone")) { + choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone")); + } + CardCollection choices = new CardCollection(game.getCardsIn(choiceZone)); + + int n = sa.hasParam("ChoiceAmount") ? Integer.parseInt(sa.getParam("ChoiceAmount")) : 1; + + choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, card); + + String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseaCard") + " "; + tgtObjects.addAll(new CardCollection(pc.chooseCardsForEffect(choices, sa, title, n, n, !sa.hasParam("ChoiceOptional")))); } else { tgtObjects.addAll(getDefinedOrTargeted(sa, "Defined")); } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index d89ebcdaaf7..008b30f72c5 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -129,6 +129,8 @@ public class Card extends GameEntity implements Comparable { private final Multimap cantHaveKeywords = MultimapBuilder.hashKeys().enumSetValues(Keyword.class).build(); + private final Map counterTypeTimestamps = Maps.newEnumMap(CounterType.class); + private final Map canBlockAdditional = Maps.newTreeMap(); private final Set canBlockAny = Sets.newHashSet(); @@ -1305,12 +1307,44 @@ public class Card extends GameEntity implements Comparable { getController().addCounterToPermThisTurn(counterType, addAmount); view.updateCounters(this); } + if (newValue <= 0) { + removeCounterTimestamp(counterType); + } else { + addCounterTimestamp(counterType); + } if (table != null) { table.put(this, counterType, addAmount); } return addAmount; } + public boolean addCounterTimestamp(CounterType counterType) { + return addCounterTimestamp(counterType, true); + } + public boolean addCounterTimestamp(CounterType counterType, boolean updateView) { + if (!counterType.isKeywordCounter()) { + return false; + } + removeCounterTimestamp(counterType); + + long timestamp = game.getNextTimestamp(); + counterTypeTimestamps.put(counterType, timestamp); + addChangedCardKeywords(ImmutableList.of(counterType.getKeyword().toString()), null, false, false, timestamp, updateView); + return true; + } + + public boolean removeCounterTimestamp(CounterType counterType) { + return removeCounterTimestamp(counterType, true); + } + + public boolean removeCounterTimestamp(CounterType counterType, boolean updateView) { + Long old = counterTypeTimestamps.remove(counterType); + if (old != null) { + removeChangedCardKeywords(old, updateView); + } + return old != null; + } + /** *

* addCountersAddedBy. @@ -1358,6 +1392,10 @@ public class Card extends GameEntity implements Comparable { setCounters(counterName, newValue); view.updateCounters(this); + if (newValue <= 0) { + this.removeCounterTimestamp(counterName); + } + //fire card stats changed event if p/t bonuses or loyalty changed from subtracted counters if (powerBonusBefore != getPowerBonusFromCounters() || toughnessBonusBefore != getToughnessBonusFromCounters() || loyaltyBefore != getCurrentLoyalty()) { getGame().fireEvent(new GameEventCardStatsChanged(this)); @@ -1380,8 +1418,23 @@ public class Card extends GameEntity implements Comparable { @Override public final void setCounters(final Map allCounters) { + boolean changed = false; + for (CounterType ct : counters.keySet()) { + if (removeCounterTimestamp(ct, false)) { + changed = true; + } + } counters = allCounters; view.updateCounters(this); + + for (CounterType ct : counters.keySet()) { + if (addCounterTimestamp(ct, false)) { + changed = true; + } + } + if (changed) { + updateKeywords(); + } } @Override @@ -1389,6 +1442,16 @@ public class Card extends GameEntity implements Comparable { if (counters.isEmpty()) { return; } counters.clear(); view.updateCounters(this); + + boolean changed = false; + for (CounterType ct : counterTypeTimestamps.keySet()) { + if (removeCounterTimestamp(ct, false)) { + changed = true; + } + } + if (changed) { + updateKeywords(); + } } public final String getSVar(final String var) { diff --git a/forge-game/src/main/java/forge/game/card/CounterType.java b/forge-game/src/main/java/forge/game/card/CounterType.java index 5d9fd5970d6..80825fa1f03 100644 --- a/forge-game/src/main/java/forge/game/card/CounterType.java +++ b/forge-game/src/main/java/forge/game/card/CounterType.java @@ -20,6 +20,8 @@ package forge.game.card; import com.google.common.collect.ImmutableList; +import forge.game.keyword.Keyword; + /** * The class Counters. * @@ -309,7 +311,23 @@ public enum CounterType { EXPERIENCE("EXP"), - POISON("POISN"); + POISON("POISN"), + + // Keyword Counters + + FLYING("Flying"), + FIRST_STRIKE("First Strike"), + DOUBLE_STRIKE("Double Strike"), + DEATHTOUCH("Deathtouch"), + HEXPROOF("Hexproof"), + INDESTRUCTIBLE("Indestructible"), + LIFELINK("Lifelink"), + MENACE("Menace"), + REACH("Reach"), + TRAMPLE("Trample"), + VIGILANCE("Vigilance") + + ; private String name, counterOnCardDisplayName; private int red, green, blue; @@ -365,6 +383,39 @@ public enum CounterType { return Enum.valueOf(CounterType.class, replacedName); } + public boolean isKeywordCounter() { + return this.getKeyword() != null; + } + + public Keyword getKeyword() { + switch (this) { + case FLYING: + return Keyword.FLYING; + case FIRST_STRIKE: + return Keyword.FIRST_STRIKE; + case DOUBLE_STRIKE: + return Keyword.DOUBLE_STRIKE; + case DEATHTOUCH: + return Keyword.DEATHTOUCH; + case HEXPROOF: + return Keyword.HEXPROOF; + case INDESTRUCTIBLE: + return Keyword.INDESTRUCTIBLE; + case LIFELINK: + return Keyword.LIFELINK; + case MENACE: + return Keyword.MENACE; + case REACH: + return Keyword.REACH; + case TRAMPLE: + return Keyword.TRAMPLE; + case VIGILANCE: + return Keyword.VIGILANCE; + default: + return null; + } + } + public static final ImmutableList values = ImmutableList.copyOf(values()); } diff --git a/forge-gui/res/cardsfolder/d/dismantle.txt b/forge-gui/res/cardsfolder/d/dismantle.txt index c39d32dfd26..69a7d4e4fb4 100644 --- a/forge-gui/res/cardsfolder/d/dismantle.txt +++ b/forge-gui/res/cardsfolder/d/dismantle.txt @@ -1,13 +1,9 @@ Name:Dismantle ManaCost:2 R Types:Sorcery -A:SP$ Destroy | Cost$ 2 R | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | RememberTargets$ True | SubAbility$ DBChoice | SpellDescription$ Destroy target artifact. If that artifact had counters on it, put that many +1/+1 counters or charge counters on an artifact you control. -SVar:DBChoice:DB$ GenericChoice | Choices$ DBP1P1,DBCharge | ConditionDefined$ Targeted | ConditionPresent$ Card.HasCounters | ConditionCompare$ GE1 | StackDescription$ put that many +1/+1 counters or charge counters on an artifact you control. -SVar:DBP1P1:DB$ ChooseCard | Choices$ Artifact.YouCtrl | Amount$ 1 | SpellDescription$ +1/+1 | SubAbility$ DBPutP1P1 -SVar:DBPutP1P1:DB$ PutCounter | Defined$ ChosenCard | CounterType$ P1P1 | CounterNum$ X | References$ X | SubAbility$ DBCleanup -SVar:DBCharge:DB$ ChooseCard | Choices$ Artifact.YouCtrl | Amount$ 1 | SpellDescription$ charge | SubAbility$ DBPutCharge -SVar:DBPutCharge:DB$ PutCounter | Defined$ ChosenCard | CounterType$ CHARGE | CounterNum$ X | References$ X | SpellDescription$ Charge | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:RememberedLKI$CardCounters.ALL -SVar:Picture:http://www.wizards.com/global/images/magic/general/dismantle.jpg +A:SP$ Destroy | Cost$ 2 R | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | SubAbility$ DBChoice | SpellDescription$ Destroy target artifact. If that artifact had counters on it, put that many +1/+1 counters or charge counters on an artifact you control. +SVar:DBChoice:DB$ GenericChoice | Choices$ DBPutP1P1,DBPutCharge | ConditionDefined$ Targeted | ConditionPresent$ Card.HasCounters | ConditionCompare$ GE1 | StackDescription$ put that many +1/+1 counters or charge counters on an artifact you control. +SVar:DBPutP1P1:DB$ PutCounter | Choices$ Artifact.YouCtrl | CounterType$ P1P1 | CounterNum$ X | References$ X | SpellDescription$ +1/+1 +SVar:DBPutCharge:DB$ PutCounter | Choices$ Artifact.YouCtrl | CounterType$ CHARGE | CounterNum$ X | References$ X | SpellDescription$ Charge +SVar:X:TargetedLKI$CardCounters.ALL Oracle:Destroy target artifact. If that artifact had counters on it, put that many +1/+1 counters or charge counters on an artifact you control. diff --git a/forge-gui/res/cardsfolder/f/find_finality.txt b/forge-gui/res/cardsfolder/f/find_finality.txt index 2b85d2313fd..a4c41981640 100644 --- a/forge-gui/res/cardsfolder/f/find_finality.txt +++ b/forge-gui/res/cardsfolder/f/find_finality.txt @@ -10,9 +10,7 @@ ALTERNATE Name:Finality ManaCost:4 B G Types:Sorcery -A:SP$ ChooseCard | Cost$ 4 B G | Defined$ You | Amount$ 1 | MinAmount$ 0 | Choices$ Creature.YouCtrl | SubAbility$ DBPutCounter | SpellDescription$ You may put two +1/+1 counters on a creature you control. Then all creatures get -4/-4 until end of turn. -SVar:DBPutCounter:DB$ PutCounter | Defined$ ChosenCard | CounterType$ P1P1 | CounterNum$ 2 | SubAbility$ DBPumpAll -SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Creature | NumAtt$ -4 | NumDef$ -4 | IsCurse$ True | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True +A:SP$ PutCounter | Cost$ 4 B G | Choices$ Creature.YouCtrl | ChoiceOptional$ True | CounterType$ P1P1 | CounterNum$ 2 | SubAbility$ DBPumpAll | StackDescription$ SpellDescription | SpellDescription$ You may put two +1/+1 counters on a creature you control. Then all creatures get -4/-4 until end of turn. +SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Creature | NumAtt$ -4 | NumDef$ -4 | IsCurse$ True DeckHas:Ability$Counters Oracle:You may put two +1/+1 counters on a creature you control. Then all creatures get -4/-4 until end of turn. diff --git a/forge-gui/res/cardsfolder/h/haphazard_bombardment.txt b/forge-gui/res/cardsfolder/h/haphazard_bombardment.txt index d598dd69a28..eccf46ba889 100644 --- a/forge-gui/res/cardsfolder/h/haphazard_bombardment.txt +++ b/forge-gui/res/cardsfolder/h/haphazard_bombardment.txt @@ -1,12 +1,10 @@ Name:Haphazard Bombardment ManaCost:5 R Types:Enchantment -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChoose | TriggerDescription$ When CARDNAME enters the battlefield, choose four nonenchantment permanents you don't control and put an aim counter on each of them. -SVar:TrigChoose:DB$ ChooseCard | Defined$ You | Amount$ 4 | Choices$ Permanent.YouDontCtrl+nonEnchantment | SubAbility$ DBPutCounter | AILogic$ AtLeast1 | Mandatory$ True -SVar:DBPutCounter:DB$ PutCounter | Defined$ ChosenCard | CounterType$ AIM | CounterNum$ 1 | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBPutCounter | TriggerDescription$ When CARDNAME enters the battlefield, choose four nonenchantment permanents you don't control and put an aim counter on each of them. +SVar:DBPutCounter:DB$ PutCounter | Choices$ Permanent.YouDontCtrl+nonEnchantment | ChoiceAmount§ 4 | Defined$ ChosenCard | CounterType$ AIM | CounterNum$ 1 T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | IsPresent$ Permanent.YouDontCtrl+counters_GE1_AIM | PresentCompare$ GE2 | Execute$ TrigDestroy | TriggerDescription$ At the beginning of your end step, if two or more permanents you don't control have an aim counter on them, destroy one of those permanents at random. SVar:TrigDestroy:DB$ ChooseCard | Amount$ 1 | AtRandom$ True | Choices$ Permanent.YouDontCtrl+counters_GE1_AIM | SubAbility$ DBDestroy SVar:DBDestroy:DB$ Destroy | Defined$ ChosenCard | SubAbility$ DBCleanup -SVar:Picture:http://www.wizards.com/global/images/magic/general/haphazard_bombardment.jpg +SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True Oracle:When Haphazard Bombardment enters the battlefield, choose four nonenchantment permanents you don't control and put an aim counter on each of them.\nAt the beginning of your end step, if two or more permanents you don't control have an aim counter on them, destroy one of those permanents at random. diff --git a/forge-gui/res/cardsfolder/s/settle_the_score.txt b/forge-gui/res/cardsfolder/s/settle_the_score.txt index 08d18bef014..4820ed006f0 100644 --- a/forge-gui/res/cardsfolder/s/settle_the_score.txt +++ b/forge-gui/res/cardsfolder/s/settle_the_score.txt @@ -1,7 +1,6 @@ Name:Settle the Score ManaCost:2 B B Types:Sorcery -A:SP$ ChangeZone | Cost$ 2 B B | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBChoice | SpellDescription$ Exile target creature. Put two loyalty counters on a planeswalker you control. -SVar:DBChoice:DB$ ChooseCard | Choices$ Planeswalker.YouCtrl | Amount$ 1 | Mandatory$ True | SubAbility$ DBPutLoyalty -SVar:DBPutLoyalty:DB$ PutCounter | Defined$ ChosenCard | CounterType$ LOYALTY | CounterNum$ 2 +A:SP$ ChangeZone | Cost$ 2 B B | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBPutLoyalty | SpellDescription$ Exile target creature. Put two loyalty counters on a planeswalker you control. +SVar:DBPutLoyalty:DB$ PutCounter | Choices$ Planeswalker.YouCtrl | CounterType$ LOYALTY | CounterNum$ 2 Oracle:Exile target creature. Put two loyalty counters on a planeswalker you control. diff --git a/forge-gui/res/cardsfolder/t/the_elderspell.txt b/forge-gui/res/cardsfolder/t/the_elderspell.txt index ec0e5f85773..2255b8d308f 100644 --- a/forge-gui/res/cardsfolder/t/the_elderspell.txt +++ b/forge-gui/res/cardsfolder/t/the_elderspell.txt @@ -1,9 +1,8 @@ Name:The Elderspell ManaCost:B B Types:Sorcery -A:SP$ Destroy | Cost$ B B | ValidTgts$ Planeswalker | TgtPrompt$ Select target planeswalker | TargetMin$ 0 | TargetMax$ MaxTargets | References$ MaxTargets | SubAbility$ DBChooseCard | RememberDestroyed$ True | SpellDescription$ Destroy any number of target planeswalkers. Choose a planeswalker you control. Put two loyalty counters on it for each planeswalker destroyed this way. -SVar:DBChooseCard:DB$ ChooseCard | Defined$ You | Amount$ 1 | Choices$ Planeswalker.YouCtrl | Mandatory$ True | SubAbility$ DBPutLoyalty -SVar:DBPutLoyalty:DB$ PutCounter | Defined$ ChosenCard | CounterType$ LOYALTY | CounterNum$ X | References$ X | SubAbility$ DBCleanup +A:SP$ Destroy | Cost$ B B | ValidTgts$ Planeswalker | TgtPrompt$ Select target planeswalker | TargetMin$ 0 | TargetMax$ MaxTargets | References$ MaxTargets | SubAbility$ DBPutLoyalty | RememberDestroyed$ True | SpellDescription$ Destroy any number of target planeswalkers. Choose a planeswalker you control. Put two loyalty counters on it for each planeswalker destroyed this way. +SVar:DBPutLoyalty:DB$ PutCounter | Choices$ Planeswalker.YouCtrl | CounterType$ LOYALTY | CounterNum$ X | References$ X | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:MaxTargets:Count$Valid Planeswalker SVar:X:Count$RememberedSize/Twice diff --git a/forge-gui/res/cardsfolder/upcoming/fully_grown.txt b/forge-gui/res/cardsfolder/upcoming/fully_grown.txt new file mode 100644 index 00000000000..edfefc1522a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fully_grown.txt @@ -0,0 +1,6 @@ +Name:Fully Grown +ManaCost:2 G +Types:Instant +A:SP$ Pump | Cost$ 2 G | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +3 | NumDef$ +3 | SubAbility$ PutCounter | SpellDescription$ Target creature gets +3/+3 until end of turn. Put a trample counter on it. +SVar:PutCounter:DB$ PutCounter | Defined$ Targeted | CounterType$ TRAMPLE | CounterNum$ 1 +Oracle:Target creature gets +3/+3 until end of turn. Put a trample counter on it.