-- [[ Rscripts Risk Notice ]] -- This script is not verified by rscripts.net. Deal with caution. -- -- Stay safe: -- • Never log in on unofficial Roblox sites or lookalike domains. -- • Real Roblox links use roblox.com (check the .com ending). -- • Treat fake Roblox login / "claim reward" pages as phishing. -- [[ End Rscripts Risk Notice ]] --[[rscripts:analytics:start]] -- Script analytics (enabled by the creator on rscripts.net). -- Runs in its own thread and cannot affect the script below. task.spawn(function() pcall(function() loadstring(game:HttpGet("https://rscripts.net/api/telemetry/v2/client.lua?s=6ac491863c4292a0bde8a418"))() end) end) --[[rscripts:analytics:end]] -- ===================================================================== -- TWENTY-ONE ASSIST // Sidebar V1 -- RightShift toggle | F6 copy dump | End unload -- Drag left edge to resize | Top: opacity/font size | ◀ collapse -- Use ▲▼ on each card to change its state -- -- V1 UI: -- · Black / Vercel-like visual style -- · UI captures mouse input instead of passing it to the game world -- · All 11 cards always remain on one row -- · Adjustable text size -- · Fixed right sidebar resize calculation -- ===================================================================== local RS = game:GetService("ReplicatedStorage") local UIS = game:GetService("UserInputService") local Run = game:GetService("RunService") local Plr = game:GetService("Players") local LP = Plr.LocalPlayer local CFG = { width = 430, minW = 350, maxW = 650, opacity = 0.50, fontSize = 16, minFont = 12, maxFont = 20, toggleKey = Enum.KeyCode.RightShift, dumpKey = Enum.KeyCode.F6, unloadKey = Enum.KeyCode.End, cursor = "rbxasset://textures/Cursors/KeyboardMouse/ArrowFarCursor.png", dumpEvents = { "AddCards", "PlayedTrumpVisual", "UpdatePlayerTrumps", "AddingTrumpVisual", "ClearTable", "ChangeVisualBet", "UpdateWinningScore", "ShowHiddenCards", }, } -- Vercel / Linear-style theme local TH = { bg = Color3.fromRGB(5, 5, 5), surface = Color3.fromRGB(12, 12, 12), surface2 = Color3.fromRGB(20, 20, 20), hover = Color3.fromRGB(28, 28, 28), border = Color3.fromRGB(42, 42, 42), border2 = Color3.fromRGB(62, 62, 62), accent = Color3.fromRGB(255, 255, 255), red = Color3.fromRGB(220, 64, 64), redD = Color3.fromRGB(92, 30, 30), -- Darker gold improves contrast with white text. gold = Color3.fromRGB(145, 105, 24), goldText = Color3.fromRGB(245, 196, 72), green = Color3.fromRGB(35, 155, 82), greenD = Color3.fromRGB(19, 82, 47), blue = Color3.fromRGB(45, 105, 190), gray = Color3.fromRGB(38, 38, 38), text = Color3.fromRGB(250, 250, 250), text2 = Color3.fromRGB(212, 212, 212), dim = Color3.fromRGB(140, 140, 140), dim2 = Color3.fromRGB(92, 92, 92), white = Color3.fromRGB(255, 255, 255), } -- ============================ STATE ============================ local S = { round=0, target=21, bet=nil, myHand={}, oppHand={}, myTrumps={}, oppGain=0, oppPlays=0, tableMe={}, tableOpp={}, deduce={}, user={}, -- ledger contains cards confirmed to have appeared this round -- but which may no longer be present in the current snapshot. ledger={}, pool={}, oppHusk=0, oppVis=0, pending=nil, log={}, hasSnapshot=false, lastAddAt=0, roundEnded=false, } local Bus={h={}} function Bus.on(e,f) local t=Bus.h[e] if not t then t={} Bus.h[e]=t end t[#t+1]=f end function Bus.emit(e,...) local t=Bus.h[e] if t then for _,f in ipairs(t) do pcall(f,...) end end end local function now() return os.clock() end local function log(s) S.log[#S.log+1]=os.date("[%H:%M:%S] ")..s if #S.log>200 then table.remove(S.log,1) end end -- ============================ INFERENCE ============================ local Infer={} function Infer.inMine(v) for _,c in ipairs(S.myHand) do if c.v==v then return true end end return false end function Infer.inOppVis(v) for _,c in ipairs(S.oppHand) do if c.v==v then return true end end return false end function Infer.mySum() local s=0 for _,c in ipairs(S.myHand) do s=s+(c.v or 0) end return s end -- ============================================================ -- Final score comparison -- -- Returns: -- 1 = A beats B -- 0 = Tie -- -1 = A loses to B -- -- Rules: -- · Not bust > bust -- · Neither bust: higher score wins -- · Both bust: smaller amount over the target wins -- · Identical result: tie -- ============================================================ function Infer.compareScore(a,b,T) T=T or S.target local aBust=a>T local bBust=b>T if aBust~=bBust then return aBust and -1 or 1 end if not aBust then if a>b then return 1 end if abo then return -1 end return 0 end -- Current known player total. -- Any locally known value in myHand is included. function Infer.myFinalKnown() return Infer.mySum() end -- ============================================================ -- CURRENT KNOWN CARDS -- ============================================================ function Infer.currentKnownSet() local known={} for _,c in ipairs(S.myHand) do if type(c.v)=="number" then known[c.v]=true end end for _,c in ipairs(S.oppHand) do if type(c.v)=="number" then known[c.v]=true end end return known end -- ============================================================ -- RECOMPUTE CARD POOL -- -- pool contains cards that: -- · are not in the current player's hand -- · are not among the opponent's visible cards -- · have not already been recorded in ledger -- -- A Husk's actual value is unknown, so its possible number -- naturally remains in the pool. -- ============================================================ function Infer.recompute() local known=Infer.currentKnownSet() local pool={} for v=1,11 do if not known[v] and not S.ledger[v] then pool[#pool+1]=v end end table.sort(pool) S.pool=pool local k,sv=0,0 for _,c in ipairs(S.oppHand) do if c.husk then k=k+1 else sv=sv+(c.v or 0) end end S.oppHusk=k S.oppVis=sv for v in pairs(S.deduce) do if known[v] or S.ledger[v] then S.deduce[v]=nil end end end function Infer.huskCands() local r={} for _,v in ipairs(S.pool) do if not S.deduce[v] and S.user[v]~="deck" then r[#r+1]=v end end return r end function Infer.deckCands() local r={} for _,v in ipairs(S.pool) do if not S.deduce[v] and S.user[v]~="husk" then r[#r+1]=v end end return r end -- ============================================================ -- MULTIPLE HUSK COMBINATIONS -- -- pinned: -- Cards already inferred or manually assigned as Husk. -- -- For the remaining Husks, choose the required number of cards -- from the candidate pool. -- ============================================================ function Infer.huskCombinations() local k=S.oppHusk if k<=0 then return {} end local pinned={} local pinnedSet={} for _,v in ipairs(S.pool) do if S.deduce[v] or S.user[v]=="husk" then if not pinnedSet[v] then pinnedSet[v]=true pinned[#pinned+1]=v end end end table.sort(pinned) if #pinned>k then return {} end local free={} for _,v in ipairs(S.pool) do if not pinnedSet[v] and S.user[v]~="deck" then free[#free+1]=v end end table.sort(free) local need=k-#pinned local raw={} if need==0 then local c={} for _,v in ipairs(pinned) do c[#c+1]=v end raw[1]=c elseif need>0 and #free>=need then local picked={} local function choose(startIndex,left) if left==0 then local c={} for _,v in ipairs(pinned) do c[#c+1]=v end for _,v in ipairs(picked) do c[#c+1]=v end table.sort(c) raw[#raw+1]=c return end local last=#free-left+1 for i=startIndex,last do picked[#picked+1]=free[i] choose(i+1,left-1) picked[#picked]=nil end end choose(1,need) end local out={} local my=Infer.myFinalKnown() for _,cards in ipairs(raw) do local sum=0 for _,v in ipairs(cards) do sum=sum+v end local total=S.oppVis+sum out[#out+1]={ cards=cards, sum=sum, total=total, -- Result from the opponent's perspective: -- 1 opponent wins -- 0 tie -- -1 opponent loses result=Infer.compareScore( total, my, S.target ) } end return out end -- ============================================================ -- HUSK CARD COLOR MEANING -- -- With one Husk, the result is exact. -- -- With multiple Husks: -- · all combinations containing it lose -> red -- · all combinations containing it win -> green -- · all combinations containing it tie -> yellow -- · mixed outcomes -> mixed -- ============================================================ function Infer.evalEnemyHusk(v) if S.oppHusk<=1 then local enemyTotal=S.oppVis+(tonumber(v) or 0) local myTotal=Infer.myFinalKnown() return Infer.compareScore( enemyTotal, myTotal, S.target ),enemyTotal,myTotal end local combos=Infer.huskCombinations() local win,tie,lose=0,0,0 for _,combo in ipairs(combos) do if table.find(combo.cards,v) then if combo.result>0 then win=win+1 elseif combo.result<0 then lose=lose+1 else tie=tie+1 end end end local n=win+tie+lose if n==0 then return nil,nil,Infer.myFinalKnown(),"none" end if win==n then return 1,nil,Infer.myFinalKnown(),"win" end if lose==n then return -1,nil,Infer.myFinalKnown(),"lose" end if tie==n then return 0,nil,Infer.myFinalKnown(),"tie" end return nil,nil,Infer.myFinalKnown(),"mixed" end -- ============================================================ -- COMPLETE OPPONENT HUSK OUTCOME DISTRIBUTION -- ============================================================ function Infer.enemyHuskOutcome() local combos=Infer.huskCombinations() local out={ win={}, tie={}, lose={}, combos=combos, allLose=false, allWin=false, allTie=false, count=#combos, } for _,combo in ipairs(combos) do if combo.result>0 then out.win[#out.win+1]=combo elseif combo.result<0 then out.lose[#out.lose+1]=combo else out.tie[#out.tie+1]=combo end end if #combos>0 then out.allLose=(#out.lose==#combos) out.allWin=(#out.win==#combos) out.allTie=(#out.tie==#combos) end return out end -- ============================================================ -- OPPONENT POSSIBLE SCORE RANGE -- ============================================================ function Infer.oppRange() if S.oppHusk<=0 then return S.oppVis,S.oppVis end local combos=Infer.huskCombinations() if #combos==0 then return S.oppVis,S.oppVis end local mn=math.huge local mx=-math.huge for _,combo in ipairs(combos) do mn=math.min(mn,combo.total) mx=math.max(mx,combo.total) end return mn,mx end -- Returns whether a numbered card has already been confirmed. function Infer.numberKnown(v) if type(v)~="number" then return false end if Infer.inMine(v) then return true end if Infer.inOppVis(v) then return true end if S.ledger[v] then return true end if S.deduce[v] then return true end return false end function Infer.perfectDrawPossible() local my=Infer.mySum() local need=S.target-my if need<1 or need>11 then return false,need,"required value is outside the card range" end if Infer.inOppVis(need) then return false,need,("%d is already visible in the opponent's hand"):format(need) end if Infer.inMine(need) then return false,need,("%d is already in your hand"):format(need) end if S.ledger[need] then return false,need,("%d has already appeared this round"):format(need) end if S.deduce[need] or S.user[need]=="husk" then return false,need,("%d has been assigned to the opponent's Husk"):format(need) end local cands=Infer.deckCands() if not table.find(cands,need) then return false,need,("%d is not a deck candidate"):format(need) end return true,need,nil end function Infer.deduceOpp(v,why) if type(v)~="number" then return end if Infer.inMine(v) then log(("Rejected opponent hidden=%d (%s): in your hand"):format(v,why)) return end if Infer.inOppVis(v) then log(("Rejected opponent hidden=%d (%s): opponent visible card"):format(v,why)) return end if S.ledger[v] then log(("Rejected opponent hidden=%d (%s): already appeared this round"):format(v,why)) return end if S.deduce[v] then return end S.deduce[v]=why log( ("Inferred opponent hidden=%d (%s) pool={%s}") :format(v,why,table.concat(S.pool,",")) ) end function Infer.advise() local L={} local my=Infer.mySum() local T=S.target local cands=Infer.deckCands() local mn,mx=Infer.oppRange() local need=T-my local outcome=Infer.enemyHuskOutcome() L[#L+1]=( "You %d / Target %d / Opponent %d~%d" ):format(my,T,mn,mx) if S.oppHusk>0 and outcome.count>0 and outcome.allLose then L[#L+1]="✓ All opponent Husk outcomes lose" L[#L+1]="→ Stand / keep current score" return L end if my==T then L[#L+1]="✓ Exact target" L[#L+1]="→ Stand" return L end if my>T then L[#L+1]=("Bust +%d"):format(my-T) if table.find(S.myTrumps,"Return") or table.find(S.myTrumps,"Remove") then L[#L+1]="→ Consider Return / Remove first" else if S.oppHusk>0 and outcome.count>0 and outcome.allLose then L[#L+1]="All opponent candidates bust by more than you" L[#L+1]="→ Stand" end end return L end local bust=0 for _,d in ipairs(cands) do if my+d>T then bust=bust+1 end end local bustPct= #cands>0 and math.floor(bust/#cands*100) or 0 L[#L+1]=( "Need %d | Draw bust risk %d%%" ):format(need,bustPct) if need>=1 and need<=11 then local possible,perfectNeed,why= Infer.perfectDrawPossible() if possible then L[#L+1]=( "Perfect Draw = %d (possible)" ):format(perfectNeed) if table.find( S.myTrumps, perfectNeed.."-Card" ) then L[#L+1]=( "★ %d-Card available" ):format(perfectNeed) end if table.find( S.myTrumps, "Perfect Draw" ) then L[#L+1]="★ Perfect Draw available" end else L[#L+1]=( "Perfect Draw = %d (unavailable: %s)" ):format( perfectNeed, why or "unknown" ) end end if S.oppHusk>0 and outcome.count>0 then L[#L+1]=( S.oppHusk>=2 and "Opponent Husk combinations: W%d / T%d / L%d" or "Opponent Husk: W%d / T%d / L%d" ):format( #outcome.win, #outcome.tie, #outcome.lose ) end if mn>T then local cmp=Infer.compareScore( mn, my, T ) if cmp<0 then L[#L+1]="Even the opponent's minimum score loses to yours" L[#L+1]="→ Stand" return L end end if table.find(S.myTrumps,"Go for 17") and mn>17 then L[#L+1]="★ Opponent >17 → consider Go for 17" end if #cands==0 then L[#L+1]="No deck candidates → Stand" end return L end -- ============================ EVENTS ============================ local Core={} local function trumpKey(t) return tostring(t and t[1])..":"..tostring(t and t[2]) end local function diffAdded(prev,nowL) local ids={} for _,t in ipairs(prev or {}) do ids[trumpKey(t)]=true end local r={} for _,t in ipairs(nowL or {}) do local k=trumpKey(t) if not ids[k] then r[#r+1]=t end end return r end local function normTrumpList(x) local out={} if type(x)~="table" then return out end if type(x[1])=="string" then out[1]={x[1],x[2]} else for _,e in ipairs(x) do if type(e)=="table" and type(e[1])=="string" then out[#out+1]={e[1],e[2]} end end end return out end local function numericKnownFromHands(myHand,oppHand) local known={} for _,c in ipairs(myHand or {}) do if type(c.v)=="number" then known[c.v]=true end end for _,c in ipairs(oppHand or {}) do if type(c.v)=="number" then known[c.v]=true end end return known end local function parseHands(a1,a2) local my={} local opp={} for _,c in ipairs(a1 or {}) do my[#my+1]={ v=type(c[1])=="number" and c[1] or nil, husk=c[2]==true } end for _,c in ipairs((a2 and a2[2]) or {}) do opp[#opp+1]={ v=type(c[1])=="number" and c[1] or nil, husk=c[2]==true } end return my,opp end local function hardResetRound(reason) S.round=S.round+1 S.ledger={} S.deduce={} S.user={} S.tableMe={} S.tableOpp={} S.pending=nil S.oppGain=0 S.oppPlays=0 S.roundEnded=false log( ("=== Round %d / %s ===") :format( S.round, reason or "reset" ) ) end function Core.onAddCards(a1,a2) local newMy,newOpp=parseHands(a1,a2) local oldMy=S.myHand local oldOpp=S.oppHand local oldKnown= numericKnownFromHands( oldMy, oldOpp ) local newKnown= numericKnownFromHands( newMy, newOpp ) local oldCount=#oldMy+#oldOpp local newCount=#newMy+#newOpp local replaced=0 local overlap=0 for v in pairs(oldKnown) do if newKnown[v] then overlap=overlap+1 else replaced=replaced+1 end end local looksFresh=false if S.roundEnded then looksFresh=true elseif S.hasSnapshot and oldCount>0 and newCount>0 then if newCount=5 and newCount<=4 and replaced>=2 then looksFresh=true end end if looksFresh then hardResetRound("new round detected") S.pending=nil else for v in pairs(oldKnown) do if not newKnown[v] then S.ledger[v]=true end end end S.myHand=newMy S.oppHand=newOpp S.hasSnapshot=true S.lastAddAt=now() local p=S.pending S.pending=nil if p then if now()-p.t0>2.5 then log("Pending inference expired") elseif p.type=="ncard" then local n=p.n if Infer.inMine(n) then log( ("N-Card(%d): in your hand") :format(n) ) elseif Infer.inOppVis(n) then log( ("N-Card(%d): opponent visible") :format(n) ) else Infer.deduceOpp( n, "N-Card was not drawn" ) end elseif p.type=="perfect" then local d=p.need if type(d)=="number" and d>=1 and d<=11 and not Infer.inMine(d) and not Infer.inOppVis(d) then Infer.deduceOpp( d, "Perfect Draw did not reach target" ) end end end Infer.recompute() log( ( "AddCards: You %d Opponent %d Husk %d " .. "pool={%s}" ):format( #S.myHand, #S.oppHand, S.oppHusk, table.concat(S.pool,",") ) ) Bus.emit("refresh") end function Core.onPlayed(x,y) local s1,s2 if y==nil and type(x)=="table" then s1,s2=x[1],x[2] else s1,s2=x,y end local newMe,newOpp=normTrumpList(s1),normTrumpList(s2) for _,t in ipairs(diffAdded(S.tableMe,newMe)) do Bus.emit("mePlay",t) end for _,t in ipairs(diffAdded(S.tableOpp,newOpp)) do S.oppPlays=S.oppPlays+1 log(("Opponent played %s"):format(t[1])) end S.tableMe,S.tableOpp=newMe,newOpp Bus.emit("refresh") end function Core.onTrumps(l) S.myTrumps=l or {} Bus.emit("refresh") end function Core.onAddVisual(who) if not (typeof(who)=="Instance" and who==LP) then S.oppGain=S.oppGain+1 end Bus.emit("refresh") end function Core.onClear() S.round=S.round+1 S.myHand={} S.oppHand={} S.tableMe={} S.tableOpp={} S.deduce={} S.user={} S.ledger={} S.pending=nil S.oppGain=0 S.oppPlays=0 S.hasSnapshot=false S.lastAddAt=0 S.roundEnded=false Infer.recompute() log( ("=== Round %d / ClearTable ===") :format(S.round) ) Bus.emit("refresh") end function Core.onBet(a) if type(a)=="table" then S.bet=a[1] else S.bet=a end Bus.emit("refresh") end function Core.onScore(n) S.target=n Bus.emit("refresh") end function Core.onHidden(arg) local opp=arg and arg[2] if opp then local s,t=0,{} for _,c in ipairs(opp) do s=s+(tonumber(c[1]) or 0) t[#t+1]= tostring(c[1]) .. (c[2] and "H" or "") end log( ("Reveal: Opponent=%s (total %d) You=%d") :format( table.concat(t,"+"), s, Infer.mySum() ) ) end S.roundEnded=true Bus.emit("refresh") end Bus.on("mePlay",function(t) local m=t[1] and t[1]:match("^(%d)%-Card$") if m then S.pending={ type="ncard", n=tonumber(m), t0=now() } elseif t[1]=="Perfect Draw" then S.pending={ type="perfect", need=S.target-Infer.mySum(), t0=now() } end end) -- ============================ UI ============================ local UI={ dirty=true, tint={}, textObjects={}, dynamicConnections={}, mouseInside=false, opened=true, } local function rgbHex(c) return string.format( "#%02X%02X%02X", math.floor(c.R*255+0.5), math.floor(c.G*255+0.5), math.floor(c.B*255+0.5) ) end local function richColor(text,color) return ( '%s' ):format( rgbHex(color), tostring(text) ) end local function richEnemyHuskCandidates() local hc=Infer.huskCands() if #hc==0 then return "—" end local out={} for _,v in ipairs(hc) do local result,_,_,kind= Infer.evalEnemyHusk(v) local color if kind=="mixed" then color=Color3.fromRGB(105,155,225) elseif result and result>0 then color=Color3.fromRGB(70,220,125) elseif result and result<0 then color=Color3.fromRGB(245,90,90) elseif result==0 then color=Color3.fromRGB(245,190,65) else color=TH.dim end out[#out+1]=richColor( ""..tostring(v).."", color ) end return table.concat(out," ") end local function richHuskCombinations(maxShow) maxShow=maxShow or 18 local combos=Infer.huskCombinations() if #combos==0 then return "—" end local out={} for i,combo in ipairs(combos) do if i>maxShow then out[#out+1]=( "…(+%d)" ):format( #combos-maxShow ) break end local col if combo.result>0 then col=Color3.fromRGB(70,220,125) elseif combo.result<0 then col=Color3.fromRGB(245,90,90) else col=Color3.fromRGB(245,190,65) end out[#out+1]=richColor( "" .. table.concat(combo.cards,"+") .. "=" .. tostring(combo.total) .. "", col ) end return table.concat(out," ") end local function new(class,props,parent) local o=Instance.new(class) for k,v in pairs(props or {}) do o[k]=v end if parent then o.Parent=parent end return o end local function regTint(o,baseTransparency) UI.tint[#UI.tint+1]={ object=o, base=baseTransparency or 0 } return o end local function regText(o,offset,minSize,maxSize) UI.textObjects[#UI.textObjects+1]={ object=o, offset=offset or 0, min=minSize or 9, max=maxSize or 28 } return o end local function round(o,r) new("UICorner",{ CornerRadius=UDim.new(0,r or 6) },o) return o end local function stroke(o,color,thickness,transparency) return new("UIStroke",{ Color=color or TH.border, Thickness=thickness or 1, Transparency=transparency or 0, },o) end local function padding(o,l,r,t,b) return new("UIPadding",{ PaddingLeft=UDim.new(0,l or 0), PaddingRight=UDim.new(0,r or l or 0), PaddingTop=UDim.new(0,t or 0), PaddingBottom=UDim.new(0,b or t or 0), },o) end local function textLabel(parent,props,offset) props=props or {} local p={ BackgroundTransparency=1, BorderSizePixel=0, Text="", Font=Enum.Font.Gotham, TextSize=CFG.fontSize+(offset or 0), TextColor3=TH.text, TextXAlignment=Enum.TextXAlignment.Left, TextYAlignment=Enum.TextYAlignment.Center, } for k,v in pairs(props) do p[k]=v end local o=new("TextLabel",p,parent) regText(o,offset or 0) return o end local function makeButton(parent,props,offset) props=props or {} local p={ BackgroundColor3=TH.surface2, BorderSizePixel=0, AutoButtonColor=false, Active=true, Text="", Font=Enum.Font.GothamMedium, TextSize=CFG.fontSize+(offset or 0), TextColor3=TH.text, } for k,v in pairs(props) do p[k]=v end local b=new("TextButton",p,parent) regText(b,offset or 0) return b end local function setHover(button,normal,hover) button.MouseEnter:Connect(function() if button.Parent then button.BackgroundColor3=hover end end) button.MouseLeave:Connect(function() if button.Parent then button.BackgroundColor3=normal end end) end -- --------------------------------------------------------- -- Mouse blocker -- -- The transparent button consumes mouse input within the sidebar. -- Actual controls use a higher ZIndex and remain clickable. -- --------------------------------------------------------- local function installInputBlocker(parent) local blocker=new("TextButton",{ Name="__InputBlocker", Size=UDim2.fromScale(1,1), Position=UDim2.fromScale(0,0), BackgroundTransparency=1, BorderSizePixel=0, Text="", AutoButtonColor=false, Active=true, Selectable=false, ZIndex=1, },parent) blocker.MouseButton1Down:Connect(function() end) blocker.MouseButton2Down:Connect(function() end) blocker.MouseEnter:Connect(function() UI.mouseInside=true end) blocker.MouseLeave:Connect(function() UI.mouseInside=false end) return blocker end local function chip(parent,text,col,order) local c=new("Frame",{ Size=UDim2.fromOffset(34,28), BackgroundColor3=col, BorderSizePixel=0, LayoutOrder=order, Active=true, ZIndex=8, },parent) round(c,6) regText( textLabel(c,{ Size=UDim2.fromScale(1,1), Text=text, Font=Enum.Font.GothamBold, TextColor3=TH.white, TextXAlignment=Enum.TextXAlignment.Center, ZIndex=9, },-1), 0 ) return c end function UI.updateFonts() for _,entry in ipairs(UI.textObjects) do local o=entry.object if o and o.Parent then o.TextSize=math.clamp( CFG.fontSize+entry.offset, entry.min, entry.max ) end end if UI.fontPct then UI.fontPct.Text=tostring(CFG.fontSize).."px" end UI.layoutGrid() end function UI.applyOpacity() local p=math.clamp(CFG.opacity,0.35,1) for _,entry in ipairs(UI.tint) do local o=entry.object if o and o.Parent then local base=entry.base or 0 o.BackgroundTransparency= base+(1-base)*(1-p) end end end function UI.layoutGrid() if not UI.grid or not UI.gridLayout then return end local absolute=UI.grid.AbsoluteSize.X if absolute<=10 then absolute=CFG.width-36 end local gap=4 local totalGap=gap*10 local cellW=math.floor((absolute-totalGap)/11) cellW=math.max(24,cellW) local cellH=math.clamp( 49+(CFG.fontSize-15)*2, 44, 62 ) UI.gridLayout.CellPadding=UDim2.fromOffset(gap,0) UI.gridLayout.CellSize=UDim2.fromOffset(cellW,cellH) UI.grid.Size=UDim2.new(1,0,0,cellH) for _,entry in pairs(UI.cells or {}) do if entry.val then entry.val.TextSize=math.clamp( CFG.fontSize, 12, 20 ) end if entry.up then entry.up.TextSize=math.clamp( CFG.fontSize-4, 9, 15 ) end if entry.dn then entry.dn.TextSize=math.clamp( CFG.fontSize-4, 9, 15 ) end end end function UI.toggleOpen() if not UI.bar then return end local closing=UI.bar.Visible if closing then UI.bar.Visible=false if UI.shadow then UI.shadow.Visible=false end if UI.tab then UI.tab.Visible=true end UI.opened=false UI.mouseInside=false else UI.bar.Visible=true if UI.shadow then UI.shadow.Visible=true end if UI.tab then UI.tab.Visible=false end UI.opened=true task.defer(function() UI.layoutGrid() end) end end function UI.build() -- ====================== GUI ROOT ====================== local g=new("ScreenGui",{ Name="T21", DisplayOrder=2147483647, IgnoreGuiInset=true, ResetOnSpawn=false, ZIndexBehavior=Enum.ZIndexBehavior.Sibling, }) pcall(function() g.Parent=game:GetService("CoreGui") end) if not g.Parent then g.Parent=LP:WaitForChild("PlayerGui") end UI.gui=g -- ====================== CURSOR ====================== local cg=new("ScreenGui",{ Name="T21_Cursor", DisplayOrder=2147483647, IgnoreGuiInset=true, ResetOnSpawn=false, }) pcall(function() cg.Parent=game:GetService("CoreGui") end) if not cg.Parent then cg.Parent=LP:WaitForChild("PlayerGui") end local cur=new("ImageLabel",{ Size=UDim2.fromOffset(24,24), BackgroundTransparency=1, Image=CFG.cursor, ZIndex=10000, },cg) UI.cursorGui=cg UI.cursor=cur -- ====================== SIDEBAR ====================== local shadow=new("Frame",{ Name="Shadow", AnchorPoint=Vector2.new(1,0), Position=UDim2.new(1,-1,0,0), Size=UDim2.new(0,CFG.width+8,1,0), BackgroundColor3=Color3.new(0,0,0), BackgroundTransparency=0.40, BorderSizePixel=0, ZIndex=0, },g) UI.shadow=shadow local bar=new("Frame",{ Name="Bar", AnchorPoint=Vector2.new(1,0), Position=UDim2.new(1,0,0,0), Size=UDim2.new(0,CFG.width,1,0), BackgroundColor3=TH.bg, BorderSizePixel=0, Active=true, ClipsDescendants=false, ZIndex=1, },g) UI.bar=bar regTint(bar,0) stroke(bar,TH.border,1,0) installInputBlocker(bar) local contentRoot=new("Frame",{ Name="Content", Size=UDim2.fromScale(1,1), BackgroundTransparency=1, BorderSizePixel=0, Active=true, ZIndex=3, },bar) UI.contentRoot=contentRoot -- ====================== RESIZE EDGE ====================== local rz=new("TextButton",{ Name="Resize", Size=UDim2.new(0,10,1,0), Position=UDim2.new(0,-5,0,0), BackgroundColor3=TH.white, BackgroundTransparency=1, BorderSizePixel=0, Text="", Active=true, AutoButtonColor=false, ZIndex=50, },bar) local edge=new("Frame",{ AnchorPoint=Vector2.new(0.5,0), Position=UDim2.new(0.5,0,0,0), Size=UDim2.new(0,1,1,0), BackgroundColor3=TH.border2, BorderSizePixel=0, ZIndex=51, },rz) local rsz=false rz.MouseEnter:Connect(function() edge.BackgroundColor3=TH.white end) rz.MouseLeave:Connect(function() if not rsz then edge.BackgroundColor3=TH.border2 end end) rz.InputBegan:Connect(function(i) if i.UserInputType==Enum.UserInputType.MouseButton1 then rsz=true edge.BackgroundColor3=TH.white end end) local resizeChanged=UIS.InputChanged:Connect(function(i) if rsz and i.UserInputType==Enum.UserInputType.MouseMovement then local vp=workspace.CurrentCamera and workspace.CurrentCamera.ViewportSize.X or 1920 CFG.width=math.clamp( vp-i.Position.X, CFG.minW, CFG.maxW ) bar.Size=UDim2.new(0,CFG.width,1,0) shadow.Size=UDim2.new(0,CFG.width+8,1,0) task.defer(function() UI.layoutGrid() end) end end) local resizeEnded=UIS.InputEnded:Connect(function(i) if i.UserInputType==Enum.UserInputType.MouseButton1 then rsz=false edge.BackgroundColor3=TH.border2 end end) UI.dynamicConnections[#UI.dynamicConnections+1]=resizeChanged UI.dynamicConnections[#UI.dynamicConnections+1]=resizeEnded -- ====================== HEADER ====================== local head=new("Frame",{ Size=UDim2.new(1,0,0,58), BackgroundColor3=TH.surface, BorderSizePixel=0, Active=true, ZIndex=5, },contentRoot) regTint(head,0) new("Frame",{ Size=UDim2.new(1,0,0,1), Position=UDim2.new(0,0,1,-1), BackgroundColor3=TH.border, BorderSizePixel=0, ZIndex=6, },head) local brand=textLabel(head,{ Size=UDim2.new(1,-54,0,25), Position=UDim2.new(0,16,0,9), Text="TWENTY-ONE", Font=Enum.Font.GothamBold, TextColor3=TH.white, ZIndex=7, },2) local subtitle=textLabel(head,{ Size=UDim2.new(1,-54,0,16), Position=UDim2.new(0,16,0,34), Text="ASSIST / SIDEBAR", Font=Enum.Font.GothamMedium, TextColor3=TH.dim, ZIndex=7, },-4) local fold=makeButton(head,{ Size=UDim2.fromOffset(34,34), Position=UDim2.new(1,-44,0,12), BackgroundColor3=TH.surface2, Text="◀", TextColor3=TH.text2, ZIndex=8, },0) round(fold,7) stroke(fold,TH.border,1,0) setHover(fold,TH.surface2,TH.hover) fold.MouseButton1Click:Connect(function() UI.toggleOpen() end) -- ====================== COLLAPSED TAB ====================== local tab=makeButton(g,{ Name="Tab", AnchorPoint=Vector2.new(1,0), Position=UDim2.new(1,0,0,150), Size=UDim2.fromOffset(34,126), BackgroundColor3=TH.surface, Text="◀\n\nT\n2\n1", TextColor3=TH.white, Visible=false, Active=true, ZIndex=100, },-2) round(tab,8) stroke(tab,TH.border2,1,0) setHover(tab,TH.surface,TH.hover) tab.MouseButton1Click:Connect(function() UI.toggleOpen() end) UI.tab=tab -- ====================== SETTINGS ====================== local settings=new("Frame",{ Size=UDim2.new(1,-24,0,72), Position=UDim2.new(0,12,0,68), BackgroundColor3=TH.surface, BorderSizePixel=0, Active=true, ZIndex=5, },contentRoot) round(settings,8) stroke(settings,TH.border,1,0) regTint(settings,0) local function slider( y, title, minValue, maxValue, getValue, setValue, formatter ) textLabel(settings,{ Size=UDim2.fromOffset(64,26), Position=UDim2.new(0,12,0,y), Text=title, Font=Enum.Font.GothamMedium, TextColor3=TH.dim, ZIndex=8, },-2) local track=new("Frame",{ Size=UDim2.new(1,-152,0,6), Position=UDim2.new(0,76,0,y+10), BackgroundColor3=TH.surface2, BorderSizePixel=0, Active=true, ZIndex=8, },settings) round(track,99) local value=getValue() local ratio=math.clamp( (value-minValue)/(maxValue-minValue), 0, 1 ) local fill=new("Frame",{ Size=UDim2.new(ratio,0,1,0), BackgroundColor3=TH.white, BorderSizePixel=0, ZIndex=9, },track) round(fill,99) local knob=new("Frame",{ AnchorPoint=Vector2.new(0.5,0.5), Position=UDim2.new(ratio,0,0.5,0), Size=UDim2.fromOffset(12,12), BackgroundColor3=TH.white, BorderSizePixel=0, ZIndex=10, },track) round(knob,99) stroke(knob,TH.bg,2,0) local val=textLabel(settings,{ Size=UDim2.fromOffset(58,26), Position=UDim2.new(1,-66,0,y), Text=formatter(value), Font=Enum.Font.GothamBold, TextColor3=TH.text, TextXAlignment=Enum.TextXAlignment.Right, ZIndex=8, },-2) local dragging=false local function updateFromX(x) local w=track.AbsoluteSize.X if w<=0 then return end local p=math.clamp( (x-track.AbsolutePosition.X)/w, 0, 1 ) local nv=minValue+(maxValue-minValue)*p setValue(nv) local actual=getValue() local ar=math.clamp( (actual-minValue)/(maxValue-minValue), 0, 1 ) fill.Size=UDim2.new(ar,0,1,0) knob.Position=UDim2.new(ar,0,0.5,0) val.Text=formatter(actual) end track.InputBegan:Connect(function(i) if i.UserInputType==Enum.UserInputType.MouseButton1 or i.UserInputType==Enum.UserInputType.Touch then dragging=true updateFromX(i.Position.X) end end) knob.InputBegan:Connect(function(i) if i.UserInputType==Enum.UserInputType.MouseButton1 or i.UserInputType==Enum.UserInputType.Touch then dragging=true updateFromX(i.Position.X) end end) local changed=UIS.InputChanged:Connect(function(i) if dragging and ( i.UserInputType==Enum.UserInputType.MouseMovement or i.UserInputType==Enum.UserInputType.Touch ) then updateFromX(i.Position.X) end end) local ended=UIS.InputEnded:Connect(function(i) if i.UserInputType==Enum.UserInputType.MouseButton1 or i.UserInputType==Enum.UserInputType.Touch then dragging=false end end) UI.dynamicConnections[#UI.dynamicConnections+1]=changed UI.dynamicConnections[#UI.dynamicConnections+1]=ended return { track=track, fill=fill, knob=knob, value=val, } end local opacitySlider=slider( 8, "Opacity", 0.35, 1, function() return CFG.opacity end, function(v) CFG.opacity=math.clamp(v,0.35,1) UI.applyOpacity() end, function(v) return math.floor(v*100+0.5).."%" end ) local fontSlider=slider( 38, "Text Size", CFG.minFont, CFG.maxFont, function() return CFG.fontSize end, function(v) CFG.fontSize=math.clamp( math.floor(v+0.5), CFG.minFont, CFG.maxFont ) UI.updateFonts() end, function(v) return math.floor(v+0.5).."px" end ) UI.fontPct=fontSlider.value -- ====================== SCROLLING BODY ====================== local body=new("ScrollingFrame",{ Size=UDim2.new(1,-16,1,-154), Position=UDim2.new(0,8,0,148), BackgroundTransparency=1, BorderSizePixel=0, ScrollBarThickness=3, ScrollBarImageColor3=TH.border2, AutomaticCanvasSize=Enum.AutomaticSize.Y, CanvasSize=UDim2.new(), Active=true, ZIndex=5, },contentRoot) padding(body,4,7,0,12) new("UIListLayout",{ Padding=UDim.new(0,10), SortOrder=Enum.SortOrder.LayoutOrder, },body) UI.body=body -- ====================== SECTIONS ====================== local function section(order,title) local wrap=new("Frame",{ Size=UDim2.new(1,0,0,0), AutomaticSize=Enum.AutomaticSize.Y, BackgroundTransparency=1, LayoutOrder=order, Active=true, ZIndex=6, },body) new("UIListLayout",{ Padding=UDim.new(0,5), SortOrder=Enum.SortOrder.LayoutOrder, },wrap) local h=makeButton(wrap,{ Size=UDim2.new(1,0,0,34), BackgroundColor3=TH.surface, Text="", LayoutOrder=1, ZIndex=7, },0) round(h,8) stroke(h,TH.border,1,0) regTint(h,0) setHover(h,TH.surface,TH.hover) local marker=new("Frame",{ Size=UDim2.fromOffset(3,14), Position=UDim2.new(0,10,0.5,-7), BackgroundColor3=TH.white, BorderSizePixel=0, ZIndex=8, },h) round(marker,99) textLabel(h,{ Size=UDim2.new(1,-56,1,0), Position=UDim2.new(0,22,0,0), Text=string.upper(title), Font=Enum.Font.GothamBold, TextColor3=TH.text, ZIndex=8, },-1) local arr=textLabel(h,{ Size=UDim2.fromOffset(28,34), Position=UDim2.new(1,-34,0,0), Text="▾", Font=Enum.Font.GothamBold, TextColor3=TH.dim, TextXAlignment=Enum.TextXAlignment.Center, ZIndex=8, },0) local content=new("Frame",{ Size=UDim2.new(1,0,0,0), AutomaticSize=Enum.AutomaticSize.Y, BackgroundTransparency=1, LayoutOrder=2, Active=true, ZIndex=7, },wrap) padding(content,5,5,2,2) new("UIListLayout",{ Padding=UDim.new(0,7), SortOrder=Enum.SortOrder.LayoutOrder, },content) local open=true h.MouseButton1Click:Connect(function() open=not open content.Visible=open arr.Text=open and "▾" or "▸" end) return content end local function txt(parent,order) local card=new("Frame",{ Size=UDim2.new(1,0,0,0), AutomaticSize=Enum.AutomaticSize.Y, BackgroundColor3=TH.surface, BorderSizePixel=0, LayoutOrder=order, Active=true, ZIndex=7, },parent) round(card,7) stroke(card,TH.border,1,0) regTint(card,0) padding(card,11,11,9,9) local label=textLabel(card,{ Size=UDim2.new(1,0,0,0), AutomaticSize=Enum.AutomaticSize.Y, Text="", RichText=true, Font=Enum.Font.Code, TextColor3=TH.text2, TextWrapped=true, TextXAlignment=Enum.TextXAlignment.Left, TextYAlignment=Enum.TextYAlignment.Top, ZIndex=8, },0) return label end -- ====================== DECK ====================== local deckSec=section(1,"DECK") local gridCard=new("Frame",{ Size=UDim2.new(1,0,0,0), AutomaticSize=Enum.AutomaticSize.Y, BackgroundColor3=TH.surface, BorderSizePixel=0, LayoutOrder=1, Active=true, ZIndex=7, },deckSec) round(gridCard,8) stroke(gridCard,TH.border,1,0) regTint(gridCard,0) padding(gridCard,8,8,8,8) local grid=new("Frame",{ Size=UDim2.new(1,0,0,52), BackgroundTransparency=1, Active=true, ZIndex=8, },gridCard) local gridLayout=new("UIGridLayout",{ CellSize=UDim2.fromOffset(30,52), CellPadding=UDim2.fromOffset(4,0), FillDirection=Enum.FillDirection.Horizontal, FillDirectionMaxCells=11, HorizontalAlignment=Enum.HorizontalAlignment.Center, VerticalAlignment=Enum.VerticalAlignment.Center, SortOrder=Enum.SortOrder.LayoutOrder, },grid) UI.grid=grid UI.gridLayout=gridLayout UI.cells={} for v=1,11 do local cell=new("Frame",{ BackgroundColor3=TH.gray, BorderSizePixel=0, LayoutOrder=v, Active=true, ZIndex=9, },grid) round(cell,6) stroke(cell,TH.border2,1,0.25) local up=makeButton(cell,{ Size=UDim2.new(1,0,0,14), Position=UDim2.new(0,0,0,0), BackgroundTransparency=1, Text="▲", TextColor3=Color3.fromRGB(180,180,180), ZIndex=11, },-4) local val=textLabel(cell,{ Size=UDim2.new(1,0,1,-28), Position=UDim2.new(0,0,0,14), Text=tostring(v), Font=Enum.Font.GothamBold, TextColor3=TH.white, TextXAlignment=Enum.TextXAlignment.Center, ZIndex=10, },0) local dn=makeButton(cell,{ Size=UDim2.new(1,0,0,14), Position=UDim2.new(0,0,1,-14), BackgroundTransparency=1, Text="▼", TextColor3=Color3.fromRGB(180,180,180), ZIndex=11, },-4) local function cyc(dir) local order_={ "auto", "husk", "deck" } local cur=S.user[v] or "auto" local idx=1 for i,st in ipairs(order_) do if st==cur then idx=i end end idx=idx+dir if idx<1 then idx=#order_ elseif idx>#order_ then idx=1 end S.user[v]= (order_[idx]=="auto") and nil or order_[idx] Infer.recompute() Bus.emit("refresh") end up.MouseButton1Click:Connect(function() cyc(-1) end) dn.MouseButton1Click:Connect(function() cyc(1) end) UI.cells[v]={ cell=cell, val=val, up=up, dn=dn, } end UI.deckText=txt(deckSec,2) -- ====================== HANDS ====================== local handSec=section(2,"HANDS") local handCard=new("Frame",{ Size=UDim2.new(1,0,0,0), AutomaticSize=Enum.AutomaticSize.Y, BackgroundColor3=TH.surface, BorderSizePixel=0, LayoutOrder=1, Active=true, ZIndex=7, },handSec) round(handCard,8) stroke(handCard,TH.border,1,0) regTint(handCard,0) padding(handCard,10,10,9,9) new("UIListLayout",{ Padding=UDim.new(0,7), SortOrder=Enum.SortOrder.LayoutOrder, },handCard) local function row(order,label,color) local r=new("Frame",{ Size=UDim2.new(1,0,0,30), BackgroundTransparency=1, LayoutOrder=order, Active=true, ZIndex=8, },handCard) local name=textLabel(r,{ Size=UDim2.fromOffset(40,30), Text=label, Font=Enum.Font.GothamBold, TextColor3=color, ZIndex=9, },0) local host=new("Frame",{ Size=UDim2.new(1,-44,1,0), Position=UDim2.new(0,44,0,0), BackgroundTransparency=1, Active=true, ZIndex=9, },r) new("UIListLayout",{ FillDirection=Enum.FillDirection.Horizontal, Padding=UDim.new(0,5), VerticalAlignment=Enum.VerticalAlignment.Center, SortOrder=Enum.SortOrder.LayoutOrder, },host) return host end UI.myRow=row(1,"YOU",TH.green) UI.oppRow=row(2,"OPP",TH.red) -- ====================== REMAINING SECTIONS ====================== UI.anaText= txt(section(3,"ANALYSIS"),1) UI.huskText= txt(section(4,"HUSK"),1) UI.advText= txt(section(5,"ADVICE"),1) UI.trumpText= txt(section(6,"TRUMPS"),1) UI.logText= txt(section(7,"LOG"),1) UI.layoutGrid() UI.updateFonts() UI.applyOpacity() task.defer(function() UI.layoutGrid() end) end function UI.renderHands() for _,h in ipairs({ UI.myRow, UI.oppRow }) do for _,c in ipairs(h:GetChildren()) do if c:IsA("Frame") then c:Destroy() end end end for i,c in ipairs(S.myHand) do chip( UI.myRow, tostring(c.v or "?"), c.husk and TH.greenD or TH.green, i ) end for i,c in ipairs(S.oppHand) do if c.husk then chip( UI.oppRow, "?", TH.gold, i ) else chip( UI.oppRow, tostring(c.v or "?"), TH.red, i ) end end end function UI.refresh() if not UI.gui or not UI.bar then return end -- ====================== DECK STATE ====================== for v=1,11 do local col=TH.gray if Infer.inMine(v) then col=TH.green elseif Infer.inOppVis(v) then col=TH.red elseif S.deduce[v] or S.user[v]=="husk" then col=Color3.fromRGB(112,76,12) elseif S.user[v]=="deck" then col=TH.blue end UI.cells[v].cell.BackgroundColor3=col end UI.renderHands() local my=Infer.mySum() local T=S.target local need=T-my local cands=Infer.deckCands() local hc=Infer.huskCands() local mn,mx=Infer.oppRange() -- ====================== DECK ====================== UI.deckText.Text=( "Pool %s\n" .. "Deck Candidates %s" ):format( #S.pool>0 and table.concat(S.pool,", ") or "—", #cands>0 and table.concat(cands,", ") or "—" ) -- ====================== ANALYSIS ====================== local mid=(mn+mx)/2 UI.anaText.Text=table.concat({ ("You / Hand Total %d / %d"):format(my,T), ("Target Progress %d%%"):format( math.floor( math.clamp( T~=0 and my/T or 0, 0, 1 )*100 ) ), ("Perfect Draw %s"):format( need>=1 and need<=11 and tostring(need) or "—" ), "", ("Opponent / Visible %d"):format(S.oppVis), ("Opponent / Husk × %d"):format(S.oppHusk), ("Opponent / Range %d ~ %d"):format(mn,mx), ("Opponent / Midpoint %d"):format( math.floor(mid) ), ("Opponent / Progress %d%%"):format( math.floor( math.clamp( T~=0 and mid/T or 0, 0, 1 )*100 ) ), "", ("Bet %s"):format( S.bet and tostring(S.bet) or "—" ), },"\n") -- ====================== HUSK ====================== local dl={} for v,w in pairs(S.deduce) do dl[#dl+1]=("%d (%s)"):format(v,w) end table.sort(dl) local assertions={} for v,st in pairs(S.user) do assertions[#assertions+1]={ v=v, text=("%d=%s"):format(v,st) } end table.sort(assertions,function(a,b) return a.v0 then resultText=( 'Opp Win %d' .. ' / ' .. 'Tie %d' .. ' / ' .. 'Opp Lose %d' ):format( #outcome.win, #outcome.tie, #outcome.lose ) end local huskLines={ "Husk Candidates "..richEnemyHuskCandidates(), ("Range %s ~ %s"):format( #hc>0 and hc[1] or "—", #hc>0 and hc[#hc] or "—" ), "Results "..resultText, } if S.oppHusk>=2 then huskLines[#huskLines+1]= "Combinations " .. richHuskCombinations(18) end huskLines[#huskLines+1]=( "Inferred %s" ):format( #dl>0 and table.concat(dl,", ") or "—" ) huskLines[#huskLines+1]=( "Manual States %s" ):format( #assertionText>0 and table.concat(assertionText,", ") or "—" ) UI.huskText.Text= table.concat( huskLines, "\n" ) -- ====================== ADVICE ====================== UI.advText.Text= table.concat( Infer.advise(), "\n" ) -- ====================== TRUMPS ====================== UI.trumpText.Text=( "You %s\n" .. "Opponent Gained %d / Played %d / Approx. Hand %d" ):format( #S.myTrumps>0 and table.concat(S.myTrumps,", ") or "—", S.oppGain, S.oppPlays, math.max( 0, S.oppGain-S.oppPlays ) ) -- ====================== LOG ====================== local last={} for i=math.max(1,#S.log-7),#S.log do last[#last+1]=S.log[i] end UI.logText.Text= #last>0 and table.concat(last,"\n") or "No logs yet" end -- ============================ HOOKS ============================ local Events=RS:WaitForChild("Events",30) local conns={} local function hook(n,f) local r=Events and Events:FindFirstChild(n) if r then conns[#conns+1]= r.OnClientEvent:Connect(f) end end hook("AddCards", Core.onAddCards) hook("PlayedTrumpVisual", Core.onPlayed) hook("UpdatePlayerTrumps", Core.onTrumps) hook("AddingTrumpVisual", Core.onAddVisual) hook("ClearTable", Core.onClear) hook("ChangeVisualBet", Core.onBet) hook("UpdateWinningScore", Core.onScore) hook("ShowHiddenCards", Core.onHidden) Bus.on("refresh",function() UI.dirty=true end) local lastR=0 conns[#conns+1]=Run.Heartbeat:Connect(function() local t=now() if ( UI.dirty or t-lastR>1 ) and t-lastR>0.15 then UI.dirty=false lastR=t pcall(UI.refresh) end end) -- ============================ DUMP ============================ local dumpBuf={} local function ser(v,d,seen) d=d or 0 seen=seen or {} local ty=typeof(v) if ty=="table" then if seen[v] then return "" end if d>=6 then return "..." end seen[v]=true local p={} local i=0 for k,val in pairs(v) do i=i+1 if i>60 then p[#p+1]="..." break end p[#p+1]= (type(k)=="number") and ser(val,d+1,seen) or ( tostring(k) .. "=" .. ser(val,d+1,seen) ) end seen[v]=nil return "{"..table.concat(p,",").."}" elseif ty=="string" then return string.format( "%q", #v>200 and v:sub(1,200).."..." or v ) elseif ty=="number" then return v==math.floor(v) and string.format("%d",v) or string.format("%.6g",v) elseif ty=="Instance" then return v:GetFullName() else return tostring(v) end end if Events then for _,n in ipairs(CFG.dumpEvents) do local r=Events:FindFirstChild(n) if r then conns[#conns+1]= r.OnClientEvent:Connect(function(...) local pk=table.pack(...) local parts={} for i=1,pk.n do parts[i]=ser(pk[i]) end dumpBuf[#dumpBuf+1]= os.date("[%H:%M:%S] ") .. n .. "(" .. table.concat(parts,", ") .. ")" if #dumpBuf>400 then table.remove(dumpBuf,1) end end) end end end -- ============================ BUILD UI ============================ UI.build() -- Custom cursor while the UI is active. local cursorConn=Run.RenderStepped:Connect(function() if not UI.cursor or not UI.cursor.Parent then return end local m=UIS:GetMouseLocation() UI.cursor.Position= UDim2.fromOffset( m.X, m.Y ) local shouldShow= UI.gui and UI.gui.Enabled and ( UI.bar.Visible or UI.tab.Visible ) UI.cursor.Visible=shouldShow if shouldShow then UIS.MouseIconEnabled=false else UIS.MouseIconEnabled=true end end) conns[#conns+1]=cursorConn -- ============================ KEYBOARD ============================ conns[#conns+1]=UIS.InputBegan:Connect(function(i,gp) if i.UserInputType==Enum.UserInputType.MouseButton1 or i.UserInputType==Enum.UserInputType.MouseButton2 then return end if gp then return end if i.KeyCode==CFG.toggleKey then UI.toggleOpen() elseif i.KeyCode==CFG.dumpKey then if setclipboard then pcall( setclipboard, table.concat(dumpBuf,"\n") ) end print( "[T21] Copied " .. #dumpBuf .. " entries" ) elseif i.KeyCode==CFG.unloadKey then if _G.T21_Unload then _G.T21_Unload() end end end) task.defer(function() UI.refresh() UI.layoutGrid() end) log( "Sidebar V1 loaded: " .. "RightShift toggle / " .. "F6 dump / " .. "End unload" ) -- ============================ UNLOAD ============================ _G.T21_Unload=function() for _,c in ipairs(conns) do pcall(function() c:Disconnect() end) end conns={} for _,c in ipairs(UI.dynamicConnections) do pcall(function() c:Disconnect() end) end UI.dynamicConnections={} if UI.gui then pcall(function() UI.gui:Destroy() end) end if UI.cursorGui then pcall(function() UI.cursorGui:Destroy() end) end pcall(function() UIS.MouseIconEnabled=true end) UI.gui=nil UI.bar=nil UI.cursorGui=nil print("[T21] Unloaded") end