diff --git a/forge-core/src/main/java/forge/card/CardTypeView.java b/forge-core/src/main/java/forge/card/CardTypeView.java index 5a997ea842c..5fc907f15ac 100644 --- a/forge-core/src/main/java/forge/card/CardTypeView.java +++ b/forge-core/src/main/java/forge/card/CardTypeView.java @@ -28,11 +28,11 @@ public interface CardTypeView extends Serializable { boolean hasABasicLandType(); boolean hasANonBasicLandType(); - public boolean sharesCreaturetypeWith(final CardTypeView ctOther); - public boolean sharesLandTypeWith(final CardTypeView ctOther); - public boolean sharesPermanentTypeWith(final CardTypeView ctOther); - public boolean sharesCardTypeWith(final CardTypeView ctOther); - public boolean sharesAllCardTypesWith(final CardTypeView ctOther); + boolean sharesCreaturetypeWith(final CardTypeView ctOther); + boolean sharesLandTypeWith(final CardTypeView ctOther); + boolean sharesPermanentTypeWith(final CardTypeView ctOther); + boolean sharesCardTypeWith(final CardTypeView ctOther); + boolean sharesAllCardTypesWith(final CardTypeView ctOther); boolean isPermanent(); boolean isCreature(); diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index 361b8463a7a..9108e95e758 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -453,12 +453,14 @@ void updateChosenColors(Card c) { set(TrackableProperty.ChosenColors, c.getChosenColors()); flagAsChanged(TrackableProperty.ChosenColors); } + public boolean hasPaperFoil() { return get(TrackableProperty.PaperFoil); } void updatePaperFoil(boolean v) { set(TrackableProperty.PaperFoil, v); } + public ColorSet getMarkedColors() { return get(TrackableProperty.MarkedColors); } @@ -527,15 +529,6 @@ void updateIntensity(Card c) { set(TrackableProperty.Intensity, c.getIntensity(true)); } - public boolean wasDestroyed() { - if (get(TrackableProperty.WasDestroyed) == null) - return false; - return get(TrackableProperty.WasDestroyed); - } - void updateWasDestroyed(boolean value) { - set(TrackableProperty.WasDestroyed, value); - } - public int getClassLevel() { return get(TrackableProperty.ClassLevel); } @@ -600,7 +593,7 @@ void updateRemembered(Card c) { sb.append("\r\nRemembered: \r\n"); for (final Object o : c.getRemembered()) { if (o != null) { - sb.append(o.toString()); + sb.append(o); sb.append("\r\n"); } } @@ -727,7 +720,6 @@ public boolean canBeShownTo(final PlayerView viewer) { public boolean canFaceDownBeShownToAny(final Iterable viewers) { if (viewers == null || Iterables.isEmpty(viewers)) { return true; } - return IterableUtil.any(viewers, this::canFaceDownBeShownTo); } @@ -1011,25 +1003,26 @@ void updateBackSide(String stateName, boolean hasBackSide) { set(TrackableProperty.HasBackSide, hasBackSide); set(TrackableProperty.BackSideName, stateName); } + + public boolean wasDestroyed() { + return get(TrackableProperty.WasDestroyed); + } + void updateWasDestroyed(boolean value) { + set(TrackableProperty.WasDestroyed, value); + } public boolean needsUntapAnimation() { - if (get(TrackableProperty.NeedsUntapAnimation) == null) - return false; return get(TrackableProperty.NeedsUntapAnimation); } public void updateNeedsUntapAnimation(boolean value) { set(TrackableProperty.NeedsUntapAnimation, value); } public boolean needsTapAnimation() { - if (get(TrackableProperty.NeedsTapAnimation) == null) - return false; return get(TrackableProperty.NeedsTapAnimation); } public void updateNeedsTapAnimation(boolean value) { set(TrackableProperty.NeedsTapAnimation, value); } public boolean needsTransformAnimation() { - if (get(TrackableProperty.NeedsTransformAnimation) == null) - return false; return get(TrackableProperty.NeedsTransformAnimation); } public void updateNeedsTransformAnimation(boolean value) { @@ -1070,8 +1063,6 @@ void updateState(Card c) { updateBackSide(c.getAlternateState().getName(), c.isDoubleFaced()); final Card cloner = c.getCloner(); - - //CardStateView cloner = CardView.getState(c, CardStateName.Cloner); set(TrackableProperty.Cloner, cloner == null ? null : cloner.getName() + " (" + cloner.getId() + ")"); CardCollection mergedCollection = new CardCollection(); @@ -1194,7 +1185,6 @@ void updateBlockAdditional(Card c) { public boolean isRingBearer() { return get(TrackableProperty.IsRingBearer); } - void updateRingBearer(Card c) { set(TrackableProperty.IsRingBearer, c.isRingBearer()); } diff --git a/forge-game/src/main/java/forge/game/player/PlayerView.java b/forge-game/src/main/java/forge/game/player/PlayerView.java index daddba99a62..25224deefee 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerView.java +++ b/forge-game/src/main/java/forge/game/player/PlayerView.java @@ -197,17 +197,13 @@ void updateCounters(Player p) { public boolean getIsExtraTurn() { return get(TrackableProperty.IsExtraTurn); } - public void setIsExtraTurn(final boolean val) { set(TrackableProperty.IsExtraTurn, val); } public boolean getHasLost() { - if (get(TrackableProperty.HasLost) == null) - return false; return get(TrackableProperty.HasLost); } - public void setHasLost(final boolean val) { set(TrackableProperty.HasLost, val); } @@ -216,8 +212,6 @@ public int getAvatarLifeDifference() { return (int)get(TrackableProperty.AvatarLifeDifference); } public boolean wasAvatarLifeChanged() { - if ((int)get(TrackableProperty.AvatarLifeDifference) == 0) - return false; return (int)get(TrackableProperty.AvatarLifeDifference) != 0; } public void setAvatarLifeDifference(final int val) { @@ -227,7 +221,6 @@ public void setAvatarLifeDifference(final int val) { public int getExtraTurnCount() { return get(TrackableProperty.ExtraTurnCount); } - public void setExtraTurnCount(final int val) { set(TrackableProperty.ExtraTurnCount, val); } @@ -497,19 +490,8 @@ void updateFlashback(Player p) { set(TrackableProperty.Flashback, CardView.getCollection(p.getCardsIn(ZoneType.Flashback))); } - public int getMana(final int manaAtom) { - return getMana((byte) manaAtom); - } public int getMana(final byte color) { - Integer count = null; - try { - count = getMana().get(color); - } - catch (Exception e) { - e.printStackTrace(); - count = null; - } - return count != null ? count : 0; + return getMana().getOrDefault(color, 0); } private Map getMana() { return get(TrackableProperty.Mana); diff --git a/forge-gui-mobile/src/forge/toolbox/FChoiceList.java b/forge-gui-mobile/src/forge/toolbox/FChoiceList.java index 5cf4acd895c..f42235a123f 100644 --- a/forge-gui-mobile/src/forge/toolbox/FChoiceList.java +++ b/forge-gui-mobile/src/forge/toolbox/FChoiceList.java @@ -604,14 +604,12 @@ public void drawValue(Graphics g, T value, FSkinFont font, FSkinColor foreColor, else CardRenderer.drawCard(g, cv, x, y, VStack.CARD_WIDTH, VStack.CARD_HEIGHT, CardStackPosition.Top, false, showAlternate, true, false); } - } else { - if (cv != null) { - boolean showAlternate = CardRendererUtils.canShowAlternate(cv, value.toString()); - if (!cv.isFaceDown()) - CardRenderer.drawCardWithOverlays(g, cv, x, y, VStack.CARD_WIDTH, VStack.CARD_HEIGHT, CardStackPosition.Top, false, showAlternate, true); - else - CardRenderer.drawCard(g, cv, x, y, VStack.CARD_WIDTH, VStack.CARD_HEIGHT, CardStackPosition.Top, false, showAlternate, true, false); - } + } else if (cv != null) { + boolean showAlternate = CardRendererUtils.canShowAlternate(cv, value.toString()); + if (!cv.isFaceDown()) + CardRenderer.drawCardWithOverlays(g, cv, x, y, VStack.CARD_WIDTH, VStack.CARD_HEIGHT, CardStackPosition.Top, false, showAlternate, true); + else + CardRenderer.drawCard(g, cv, x, y, VStack.CARD_WIDTH, VStack.CARD_HEIGHT, CardStackPosition.Top, false, showAlternate, true, false); } } catch (Exception ignored) { //fixme: java.lang.ClassCastException for cards like Subtlety which should be cancelable instead... diff --git a/forge-gui-mobile/src/forge/util/CardRendererUtils.java b/forge-gui-mobile/src/forge/util/CardRendererUtils.java index 40f695165c8..3ce9cec8353 100644 --- a/forge-gui-mobile/src/forge/util/CardRendererUtils.java +++ b/forge-gui-mobile/src/forge/util/CardRendererUtils.java @@ -64,7 +64,6 @@ public static boolean hasAftermath(final CardView card) { return false; } - public static boolean isPreferenceEnabled(final ForgePreferences.FPref preferenceName) { return FModel.getPreferences().getPrefBoolean(preferenceName); } diff --git a/forge-gui/src/main/java/forge/gamemodes/net/TrackableSerializer.java b/forge-gui/src/main/java/forge/gamemodes/net/TrackableSerializer.java index 54d0cfb4be4..3a817c7e165 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/TrackableSerializer.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/TrackableSerializer.java @@ -63,26 +63,33 @@ static TrackableType trackableTypeFor(byte typeTag) { * ({@code eventMode = true}). When the tracker holds a different object * for the CardView's id (zone-change copy), {@code preserveSnapshot} is * set so the receiver decodes a detached CardView from the carried name - * and image key. When {@code tracker} is null, the snapshot check is - * skipped (used by the client encoder, which has no game-state awareness). + * and image key. + *

+ * In non-event mode, CardViews missing from the tracker pass through + * unchanged so Java serializes the full object inline (covers ephemeral + * choice copies that never enter a tracked zone). */ static Object replace(Object obj, Tracker tracker, boolean eventMode) { if (obj instanceof TrackableObject trackable) { byte tag = typeTagFor(trackable); if (tag < 0) return obj; - if (!eventMode || tag == TYPE_PLAYER_VIEW) { + if (tag == TYPE_PLAYER_VIEW) { return new IdRef(tag, trackable.getId()); } + if (!eventMode) { + if (tracker != null && tracker.getObj(trackableTypeFor(tag), trackable.getId()) != null) { + return new IdRef(tag, trackable.getId()); + } + return obj; + } + boolean preserveSnapshot = false; if (tracker != null) { - TrackableType type = trackableTypeFor(tag); - if (type != null) { - Object tracked = tracker.getObj(type, trackable.getId()); - if (tracked != null && tracked != trackable) { - preserveSnapshot = true; - } + Object tracked = tracker.getObj(trackableTypeFor(tag), trackable.getId()); + if (tracked != null && tracked != trackable) { + preserveSnapshot = true; } } CardView cv = (CardView) trackable; @@ -114,14 +121,11 @@ static Object resolve(Object obj, Tracker tracker) { return detached; } if (obj instanceof IdRef ref) { - TrackableType type = trackableTypeFor(ref.typeTag()); - if (type != null) { - Object resolved = tracker.getObj(type, ref.id()); - if (resolved == null) { - netLog.warn("Could not resolve IdRef(tag={}, id={}) from Tracker", ref.typeTag(), ref.id()); - } - return resolved; + Object resolved = tracker.getObj(trackableTypeFor(ref.typeTag()), ref.id()); + if (resolved == null) { + netLog.warn("Could not resolve IdRef(tag={}, id={}) from Tracker", ref.typeTag(), ref.id()); } + return resolved; } return obj; } diff --git a/forge-gui/src/main/java/forge/gamemodes/net/client/GameClientHandler.java b/forge-gui/src/main/java/forge/gamemodes/net/client/GameClientHandler.java index aea0d0a0b4e..b8d504f138e 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/client/GameClientHandler.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/client/GameClientHandler.java @@ -4,6 +4,7 @@ import forge.game.card.CardView; import forge.game.player.PlayerView; import forge.gamemodes.net.CompatibleObjectDecoder; +import forge.gamemodes.net.CompatibleObjectEncoder; import forge.gamemodes.net.GameProtocolHandler; import forge.gui.GuiBase; import forge.util.IHasForgeLog; @@ -74,12 +75,13 @@ protected void beforeCall(final ChannelHandlerContext ctx, final ProtocolMethod if (args.length > 0 && args[0] instanceof GameView gameView) { if (this.tracker == null) { this.tracker = new Tracker(); - // Set tracker on decoder for IdRef resolution in server messages. - // The client encoder does NOT get a tracker — it uses simple - // IdRef replacement without stale detection. Stale detection - // on the client would create StaleCardRef markers for cards - // updated by delta sync, causing the server to create detached - // CardViews that don't match real game objects. + // Encoder uses the tracker to emit IdRef for client→server CardView args + // (presence check only — stale detection is server-only). + // Ephemerals absent from the tracker serialize as full objects in both directions. + CompatibleObjectEncoder encoder = ctx.pipeline().get(CompatibleObjectEncoder.class); + if (encoder != null) { + encoder.setTracker(this.tracker); + } CompatibleObjectDecoder decoder = ctx.pipeline().get(CompatibleObjectDecoder.class); if (decoder != null) { decoder.setTracker(this.tracker); @@ -128,6 +130,10 @@ protected void beforeCall(final ChannelHandlerContext ctx, final ProtocolMethod * This method is used to recursively update the tracker * references on all objects and their props. * + *

Inline-serialized CardViews are intentionally NOT registered in the + * tracker's id lookup: a tracker miss is the symmetric signal that a + * CardView is ephemeral, mirroring the host's encoder check. + * * @param objs */ private void updateTrackers(final Object[] objs) {