From f8b5c14509a10865ae871206ad72f00b0f244993 Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Tue, 12 May 2026 20:38:12 +0200 Subject: [PATCH] fix(config): deep-clone UsedChannel and TellTarget in Tab.Clone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cherry-pick from ChatTwo upstream f35b7d3 (Infiziert90, 2026-05-12). Tab.Clone() used to assign CurrentChannel = CurrentChannel and run TellTarget.From(TellTarget). The first was a plain reference copy of the UsedChannel — the clone and the source shared the same channel state, so a channel switch or TellTarget update on a PopOut/Temp tab also mutated its origin tab. The second was a static factory call that read like a constructor where every other place uses Clone(). - TellTarget: static From(t) replaced by instance Clone(); only call-site swapped to TellTarget.Clone(). - UsedChannel: new Clone() that copies the scalar fields and runs Clone() on the two TellTarget references (null-safe). - Tab.Clone(): CurrentChannel goes through UsedChannel.Clone(). --- HellionChat/Configuration.cs | 27 +++++++++++++++++-- HellionChat/GameFunctions/Types/TellTarget.cs | 8 +++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/HellionChat/Configuration.cs b/HellionChat/Configuration.cs index c463467..fee30ca 100755 --- a/HellionChat/Configuration.cs +++ b/HellionChat/Configuration.cs @@ -500,7 +500,7 @@ public class Tab Opacity = Opacity, Identifier = Identifier, InputDisabled = InputDisabled, - CurrentChannel = CurrentChannel, + CurrentChannel = CurrentChannel.Clone(), CanMove = CanMove, CanResize = CanResize, IndependentHide = IndependentHide, @@ -512,7 +512,7 @@ public class Tab HideWhenInactive = HideWhenInactive, IsTempTab = IsTempTab, AllSenderMessages = AllSenderMessages, - TellTarget = TellTarget.From(TellTarget), + TellTarget = TellTarget.Clone(), IsGreeted = IsGreeted, }; } @@ -690,6 +690,29 @@ public class UsedChannel { Channel = channel; } + + // --------------------------------------------------------------- + // Cherry-picked from ChatTwo upstream f35b7d3 (Infiziert90, 2026-05-12) + // - Deep-clone the UsedChannel so Tab.Clone() no longer shares + // channel state (incl. TellTarget) with its origin Tab. Previously + // a reference copy: PopOut and Temp tabs mutated each other. + // - Name is intentionally a reference copy (matches upstream); it + // gets reassigned on every channel switch anyway. + // TEST-MIRROR: ../../Hellion Build test/_Helpers/UsedChannelCloneTests.cs + // --------------------------------------------------------------- + public UsedChannel Clone() + { + return new UsedChannel + { + Channel = Channel, + Name = Name, + TellTarget = TellTarget?.Clone(), + + UseTempChannel = UseTempChannel, + TempChannel = TempChannel, + TempTellTarget = TempTellTarget?.Clone(), + }; + } } [Serializable] diff --git a/HellionChat/GameFunctions/Types/TellTarget.cs b/HellionChat/GameFunctions/Types/TellTarget.cs index e792e24..e1654e4 100755 --- a/HellionChat/GameFunctions/Types/TellTarget.cs +++ b/HellionChat/GameFunctions/Types/TellTarget.cs @@ -40,5 +40,11 @@ public class TellTarget public static TellTarget Empty() => new(string.Empty, 0, 0, TellReason.Direct); - public static TellTarget From(TellTarget t) => new(t.Name, t.World, t.ContentId, t.Reason); + // --------------------------------------------------------------- + // Cherry-picked from ChatTwo upstream f35b7d3 (Infiziert90, 2026-05-12) + // - Replaced static From(t) with an instance-style Clone() so call + // sites read like a copy operation, not a factory. + // TEST-MIRROR: ../../../Hellion Build test/_Helpers/TellTargetCloneTests.cs + // --------------------------------------------------------------- + public TellTarget Clone() => new(Name, World, ContentId, Reason); }