Run it
Copy the script and paste it into your executor.
Players=:()
Run=:()
UIS=:()
RS=:()
player=(Players.LocalPlayer,)
ENV=()== ()
(ENV.PoolDuelsToolsUnload)== (ENV.PoolDuelsToolsUnload)
(parent,name) (parent:(name,),..name)
pool=(((RS,),),)
(name) ((pool,name))
Physics,Geometry,Rules,C=(),(),(),()
Client,Input=(),()
Renderer,View=(),()
Planner=(()
Planner={}
Planner.(Physics,Geometry,Rules,C)
self={}
R=C.BallRadius
DT=/
(value,seen)
(value)~= value
seen=seen {}; seen[value] seen[value]
result={};seen[value]=result
k,v value result[k]=(v,seen)
result
self:(rules) (rules)
self:(sim,rules,pos)
(Rules.(rules,pos)-pos).Magnitude>
Geometry.(pos,R,Physics.(sim))
_,id sim.Order
b=sim.Balls[id]
id~=Physics.(sim) b.Pocketed b.Destroyed
(pos-b.Position).Magnitude<R+(b.Radius R)+
(sim,a,b,skip1,skip2)
delta=b-a; len=delta:(delta)
len<
_,id sim.Order
ball=sim.Balls[id]
id~=skip1 id~=skip2 ball.Pocketed ball.Destroyed
t=.((ball.Position-a):(delta)/len,,)
(ball.Position-(a+delta*t)).Magnitude<R+(ball.Radius R)+
self:(sim,rules,yieldWork)
cueId=Physics.(sim)
cue=sim.Balls[cueId]
cue {}
shape=Physics.(sim)
pockets=Geometry.(shape)
placements={cue.Position}
rules.BallInHand
_,x {-,-,,}
_,y {-,,}
pos=Rules.(rules,.(shape.HalfWidth*x,shape.HalfHeight*y))
self:(sim,rules,pos) placements[#placements+]=pos
proposals={}
(origin,dir,target,pocket,distance,rank,override)
dir.Magnitude<
speed=.(*C.Physics.RollingDeceleration*.(distance+,))
force=override .((speed-C.Physics.MinShotSpeed)/(C.Physics.MaxShotSpeed-C.Physics.MinShotSpeed),,)^(/C.Physics.PowerCurve)
proposals[#proposals+]={direction=dir.Unit,power=.(force,,),spin=.zero,
target=target,call=pocket,placement=rules.BallInHand origin ,rank=rank}
_,origin placements
rules.BallInHand self:(sim,rules,origin)
_,id sim.Order
b=sim.Balls[id]
b.Pocketed b.Destroyed
isLegalTarget = Rules.(rules,sim,id)
isLegalTarget
_,pocket pockets
yieldWork ()
point=pocket.BowlCentre pocket.MouthCentre
out=point-b.Position
out.Magnitude>
ghost=b.Position-out.Unit*(R+(b.Radius R))
delta=ghost-origin
cut=delta.Magnitude> delta.Unit:(out.Unit)
cut> Geometry.(ghost,R*,shape) (sim,origin,ghost,cueId,id)
(sim,b.Position,point,cueId,id)
(origin,delta,id,pocket.Id,delta.Magnitude+out.Magnitude/(cut*cut),*cut)
wx,wy=shape.HalfWidth-(b.Radius R),shape.HalfHeight-(b.Radius R)
_,image {.(*wx-ghost.X,ghost.Y),.(-*wx-ghost.X,ghost.Y),
.(ghost.X,*wy-ghost.Y),.(ghost.X,-*wy-ghost.Y)}
kick=image-origin
kick.Magnitude>
(origin,kick,id,pocket.Id,kick.Magnitude+out.Magnitude, )
_,image {.(*wx-point.X,point.Y),.(-*wx-point.X,point.Y),
.(point.X,*wy-point.Y),.(point.X,-*wy-point.Y)}
bank=image-b.Position
bank.Magnitude>
contact=b.Position-bank.Unit*(R+(b.Radius R))
approach=contact-origin
angle=approach.Magnitude> approach.Unit:(bank.Unit)
angle> Geometry.(contact,R*,shape) (sim,origin,contact,cueId,id)
(origin,approach,id,pocket.Id,approach.Magnitude+bank.Magnitude/(angle*angle),*angle)
direct=b.Position-origin
(sim,origin,b.Position,cueId,id)
tangent=.(-direct.Y,direct.X).Unit
(origin,direct,id,,direct.Magnitude,-,)
(origin,direct,id,,direct.Magnitude,-,)
(origin,direct+tangent*(R*),id,,direct.Magnitude,-,)
(origin,direct-tangent*(R*),id,,direct.Magnitude,-,)
(origin,direct,id,,direct.Magnitude,-,)
(origin,direct,id,,direct.Magnitude,-,)
direct=b.Position-origin
(sim,origin,b.Position,cueId,id)
(origin,direct,id,,direct.Magnitude,-,)
(origin,direct,id,,direct.Magnitude,-,)
i=,
angle = (i/) * .pi *
dir = .(.(angle), .(angle))
proposals[#proposals+]={direction=dir, power=, spin=.zero, placement=rules.BallInHand origin , rank=-}
proposals[#proposals+]={direction=dir, power=, spin=.zero, placement=rules.BallInHand origin , rank=-}
.(proposals,(a,b) a.rank>b.rank )
candidates={}
seen={}
(p,power,angle,spin)
angle=angle
co,si=.(angle),.(angle)
dir=.(p.direction.X*co-p.direction.Y*si,p.direction.X*si+p.direction.Y*co)
v={direction=dir,power=.(power,C.Input.MinimumPower,),spin=spin p.spin,
target=p.target,call=p.call,placement=p.placement}
pos=v.placement cue.Position
key=.(,dir.X,dir.Y,v.power,v.spin.X,v.spin.Y,pos.X,pos.Y)
seen[key] seen[key]=;candidates[#candidates+]=v
rules.Phase==
nearest,dist
_,id sim.Order
b=sim.Balls[id]
id~=cueId b.Pocketed
d=(b.Position-cue.Position).Magnitude
dist d<dist nearest,dist=b,d
nearest
p={direction=(nearest.Position-cue.Position).Unit,spin=.zero,
target=nearest.Number,placement=rules.BallInHand cue.Position }
_,angle {,,-,,-} (p,,angle)
i=, .(,#proposals)
p=proposals[i]
p.call
(p,p.power)
(p,.(p.power*, , ))
(p,.(p.power*, , ))
i <=
_,power ({, , , }) (p,power)
_,spin ({.(,),.(,-)}) (p,p.power,,spin)
(p,p.power)
candidates
self:(base,rules,shot,yieldWork,trace)
sim=Physics.(base)
match=(rules)
cueId=Physics.(sim)
shot.placement
rules.BallInHand self:(sim,rules,shot.placement)
sim.Balls[cueId].Position=shot.placement
sim.Balls[cueId].Pocketed=;sim.Balls[cueId].Sinking=
context=Rules.(match,sim)
context.CalledPocket=shot.call
paths={}
()
_,id sim.Order
b=sim.Balls[id]
b.Pocketed
line=paths[id] {};paths[id]=line
pos=b.Position
#line== (line[#line]-pos).Magnitude>
#line>=
a=line[#line]-line[#line-]; d=pos-line[#line]
cross=.(a.X*d.Y-a.Y*d.X)
a:(d)> cross<*a.Magnitude*d.Magnitude line[#line]=pos
#line< line[#line+]=pos
line[#line+]=pos
trace ()
Physics.(sim,shot.direction,shot.power,shot.spin)
events=
=,
Physics.(sim,DT)
trace (%== #sim.Events~=events) ();events=#sim.Events
yieldWork %== ()
sim.Settled
sim.Settled
result=Rules.(match,sim,context)
cue=sim.Balls[cueId]
pots,wrong=,
group=Rules.(rules,rules.Turn) Rules.(match,rules.Turn)
_,id result.Potted {}
id~=cueId
Rules.(rules) group id~=Rules.(rules) C.(id)~=group wrong+=
pots+=
score
result.Winner result.Winner~=rules.Turn score=-
result.Winner==rules.Turn score=
result.Legal~=
score=-
pots >
score = - + pots*
cue cue.Pocketed
distance
_,id sim.Order
b=sim.Balls[id]
b.Pocketed Rules.(match,sim,id)
d=(b.Position-cue.Position).Magnitude
distance d<distance distance=d
distance score-=distance*
score=pots*-wrong*+(result.Continues )
score=score-shot.power*
cue cue.Pocketed score-=
result.WrongPocket score-=
{shot=shot,score=score,result=result,paths=paths,sim=sim,pots=pots}
self:(base,rules,yieldWork,expired,onProgress)
candidates=self:(base,rules,yieldWork)
best
evaluated,total=,#candidates
onProgress (,total,)
i,shot candidates
()
result=self:(base,rules,shot,yieldWork,)
evaluated+=
result ( best result.score>best.score)
best=result
best.pots > best.result.Legal == best.score >
onProgress (evaluated,total,best)
best
self
Planner
)()
planner=Planner.(Physics,Geometry,Rules,C)
state={closed=,auto=,lines=,calcSpeed=,quality=,current=,seen=,job=,best=,paths={},pathResult=,
key=,previewKey=,failedKey=,lastShot=,requestPlan=,showBest=,scan=,drawAge=,uiAge=,
tested=,total=,fired=,execution=}
connections={}
gui=.();gui.Name=;gui.ResetOnSpawn=;gui.IgnoreGuiInset=;gui.DisplayOrder=;gui.ZIndexBehavior=.ZIndexBehavior.Global
gui.Parent=(player,)
(kind,parent,props)
o=.(kind); key,value props o[key]=value ;o.Parent=parent; o
panel=(,gui,{Size=.(,),Position=.(,,,-),BackgroundColor3=.(,,),BorderSizePixel=,Active=,ZIndex=})
(,panel,{CornerRadius=.(,)})
(value,y)
b=(,panel,{Text=value,Position=.(,y),Size=.(,-,,),BackgroundColor3=.(,,),TextColor3=.(,,),Font=.Font.GothamBold,TextSize=,BorderSizePixel=,ZIndex=,Visible=,Active=,Selectable=})
(,b,{CornerRadius=.(,)}); b
autoButton=(,)
linesButton=(,)
progress=(,panel,{Text=,Position=.(,),Size=.(,-,,),
BackgroundTransparency=,TextColor3=.(,,),Font=.Font.Gotham,TextSize=,TextWrapped=,ZIndex=,Visible=})
status={Text=}
lineObjects={}
()
_,line lineObjects line.Visible=
(c)
(c)== c.Replica c.Replica.Data c.Replica.Data.poolMatch
(c)
m=(c)
m m.Phase== c.Seat c.Seat> m.Turn==c.Seat
(c.Simulation)== c.Simulation.Settled c.AwaitingShot c.Covered
(c.Input)== c.Input.ShotBindable c.Input.State~= c.Input.State~= (c.Rules)== c.Rules.Turn==c.Seat c.Rules.Phase~=
c.Pending #(c.ShotQueue {})== #(c.PatchQueue {})==
(c)
c c.Simulation c.Rules
parts={(c),(c.Simulation),(c.Seat),(c.Rules.Turn),(c.Rules.ShotCount),(c.AppliedShot),
(c.Rules.Phase),(c.Rules.BallInHand),(c.Rules.BehindHeadString),(c.Rules.GameType),(c.Rules.CallPocket),
(c.Rules.Groups c.Rules.Groups[]),(c.Rules.Groups c.Rules.Groups[]),(c.Simulation.Table),(c.Simulation.Modifiers)}
_,id c.Simulation.Order
b=c.Simulation.Balls[id]
parts[#parts+]=.(,(id),b.Position.X,b.Position.Y,(b.Pocketed),(b.Destroyed))
.(parts,)
()
e=state.execution
state.execution=
e
c=e.client
(c)
e.oldPosition c.Rules.BallInHand
ball=c.Simulation.Balls[Physics.(c.Simulation)]
ball (ball.Position-e.best.shot.placement).Magnitude<
ball.Position=e.oldPosition
View.(c.View,ball.Number,ball.Position)
c.Input.ChargeMode=e.saved.ChargeMode;c.Input.Dial=e.saved.Dial
c.Input.Dragging=e.saved.Dragging;c.Input.AimLocked=e.saved.AimLocked
c.Input.Direction=e.saved.Direction;c.Hud.Spin=e.saved.Spin
Input.(c.Input,e.saved.State )
Input.(c.Input,e.saved.Power )
()
()
state.job=;state.best=;state.paths={};state.pathResult=;state.previewKey=;state.showBest=;()
(err)
()
state.auto=;autoButton.Text=;state.job=
status.Text=..string.((err),,)
(..(err))
(kind,c,key,shot)
sim=Physics.(c.Simulation)
rules=planner:(c.Rules)
started=.()
job={kind=kind,client=c,key=key,best=,checked=,total=,started=started}
job.expires=kind== .huge started+
kind== state.tested=;state.total=
()
.()>=job.expires .()
.()>=job.sliceEnd .()
job.work=.(()
kind==
planner:(sim,rules,yieldWork,() .()>=job.expires ,
(i,total,best)job.checked=i;job.total=total;job.best=best;state.tested=i;state.total=total )
planner:(sim,rules,shot,yieldWork,)
)
state.job=job
(c,best)
(c) Physics.(c.Simulation) best best.key==(c) best.result
( best.result.Winner best.result.Winner==c.Seat)
(verified)
c,best=state.current,verified state.best
(c,best) status.Text=;
state.lastShot==best.key status.Text=;
shot=best.shot
shot.placement
c.Rules.BallInHand c.Input.CanPlaceCueBall planner:(c.Simulation,c.Rules,shot.placement)
ball=c.Simulation.Balls[Physics.(c.Simulation)]
ball.Position=shot.placement;ball.Pocketed=;ball.Sinking=
View.(c.View,ball.Number,shot.placement)
c.Input.Dragging=
Input.(c.Input,)
c.Call=shot.call;c.CallLocked=shot.call~=
c.Hud.Spin=shot.spin
Input.(c.Input,shot.direction)
Input.(c.Input,shot.power)
c.Input.CanPlaceCueBall=
state.lastShot=best.key
state.job=;()
e=state.execution
state.execution=
e
c.Input.ChargeMode=e.saved.ChargeMode;c.Input.Dial=e.saved.Dial
c.Input.AimLocked=e.saved.AimLocked
Input.(c.Input,)
c.Input.ShotBindable:(shot.direction,shot.power,shot.spin)
state.fired+=
status.Text=
(c,best)
direction=c.Input.Direction best.shot.direction
direction.Magnitude< direction=best.shot.direction
startAngle=.(direction.Y,direction.X)
targetAngle=.(best.shot.direction.Y,best.shot.direction.X)
delta=(targetAngle-startAngle+.pi)%(*.pi)-.pi
e={client=c,best=best,started=.(),startAngle=startAngle,delta=delta,
aimSeconds=.(+.(delta)*,,),chargeSeconds=+best.shot.power*,holdSeconds=,
saved={State=c.Input.State,ChargeMode=c.Input.ChargeMode,Dial=c.Input.Dial,Dragging=c.Input.Dragging,
AimLocked=c.Input.AimLocked,Direction=c.Input.Direction,Power=c.Input.Power,Spin=c.Hud.Spin}}
best.shot.placement
c.Rules.BallInHand c.Input.CanPlaceCueBall planner:(c.Simulation,c.Rules,best.shot.placement)
ball=c.Simulation.Balls[Physics.(c.Simulation)]
e.oldPosition=ball.Position;ball.Position=best.shot.placement
View.(c.View,ball.Number,ball.Position)
best.key=(c);state.key=best.key;state.job=;state.previewKey=
c.Input.Dragging=
Input.(c.Input,)
c.Hud.Spin=best.shot.spin
state.execution=e
()
e=state.execution
e
c=e.client
state.auto c~=state.current (c,e.best) ();
age=.()-e.started
remaining=.(,e.aimSeconds+e.chargeSeconds+e.holdSeconds-age)
phase
age<e.aimSeconds
t=.(age/e.aimSeconds,,);t=t*t*(-*t)
angle=e.startAngle+e.delta*t
Input.(c.Input,.(.(angle),.(angle)))
Input.(c.Input,)
phase=
e.charging
e.charging=;c.Input.ChargeMode=;c.Input.Dial=
Input.(c.Input,)
Input.(c.Input,e.best.shot.direction)
t=.((age-e.aimSeconds)/e.chargeSeconds,,)
Input.(c.Input,e.best.shot.power*t*t*(-*t))
phase=t<
View.(c.View,c.Simulation.Balls[Physics.(c.Simulation)].Position,c.Input.Direction,c.Input.Power)
progress.Text=.(,phase,remaining,state.tested,state.total,state.fired)
remaining<= (e.best)
()
()
state.auto= state.auto;state.requestPlan=state.auto (state.current,state.best);state.job=
autoButton.Text=state.auto
state.auto status.Text=;progress.Text=.(,state.tested,state.total,state.fired)
connections[#connections+]=linesButton.Activated:(()
state.lines= state.lines;linesButton.Text=state.lines
state.lines (); state.job state.job.kind~= state.job=
state.previewKey=
)
connections[#connections+]=autoButton.Activated:(toggleAuto)
connections[#connections+]=UIS.InputBegan:((input,processed)
processed UIS:() input.KeyCode==.KeyCode.F8 ()
)
dragging,dragStart,panelStart,dragTouch
connections[#connections+]=panel.InputBegan:((input)
input.UserInputType==.UserInputType.MouseButton1 input.UserInputType==.UserInputType.Touch
dragging=;dragStart=input.Position;panelStart=panel.Position;dragTouch=input
)
connections[#connections+]=UIS.InputChanged:((input)
dragging (input==dragTouch input.UserInputType==.UserInputType.MouseMovement)
delta=input.Position-dragStart
panel.Position=.(panelStart.X.Scale,panelStart.X.Offset+delta.X,panelStart.Y.Scale,panelStart.Y.Offset+delta.Y)
)
connections[#connections+]=UIS.InputEnded:((input)
input==dragTouch input.UserInputType==.UserInputType.MouseButton1 dragging=
)
originalUpdate=Client.Update
((originalUpdate)==,)
wrappedUpdate
wrappedUpdate=(c,...)
state.closed state.current=c;state.seen=.()
(c,...)
( .isfrozen .(Client),)
Client.Update=wrappedUpdate
()
state.closed
()
state.closed=;state.auto=;state.job=
Client.Update==wrappedUpdate Client.Update=originalUpdate
_,connection connections connection:()
ENV.PoolDuelsToolsUnload=
gui:()
ENV.PoolDuelsToolsUnload=unload
connections[#connections+]=gui.Destroying:(unload)
(c,point)
c.View.Mode==
origin,size=Renderer.(c.View.Renderer2D)
scale=Renderer.(point)
origin+.(scale.X.Scale*size.X,scale.Y.Scale*size.Y),size.X> size.Y>
camera=.CurrentCamera
camera .zero,
p,on=camera:(View.(point,C.BallRadius))
.(p.X,p.Y),on p.Z>
()
c=state.current
state.lines (c) state.pathResult state.pathResult.key~=state.key ();
n=
id,path state.paths
color=id==Physics.(c.Simulation) .(,,) .(,,)
ball=state.pathResult.sim.Balls[id]
ball ball.Pocketed color=id==Physics.(c.Simulation) .(,,) .(,,)
i=,#path
n>=
a,visibleA=(c,path[i-]); b,visibleB=(c,path[i])
delta=b-a
visibleA visibleB delta.Magnitude>
n+=
line=lineObjects[n]
line
line=(,gui,{BorderSizePixel=,AnchorPoint=.(,),ZIndex=,BackgroundTransparency=})
lineObjects[n]=line
line.Size=.(delta.Magnitude,)
line.Position=.((a.X+b.X)*,(a.Y+b.Y)*)
line.Rotation=.(.(delta.Y,delta.X));line.BackgroundColor3=color;line.Visible=
i=n+,#lineObjects lineObjects[i].Visible=
(dt)
c=state.current
state.scan+=dt
( c .()-state.seen>) state.scan> (getgc)==
state.scan=
_,candidate ()
(candidate)== (candidate,) (candidate,) (candidate,) (candidate,)
(candidate) state.current=candidate;state.seen=.();c=candidate;
(c)
();state.key=
c status.Text=
c.AwaitingShot c.Simulation.Settled status.Text=
status.Text=
progress.Text=(state.auto status.Text )....state.fired
Physics.(c.Simulation)
();status.Text=;progress.Text=status.Text;
key=(c)
key~=state.key ();state.key=key;state.lastShot=;state.failedKey=
state.requestPlan ();state.best=;state.failedKey=
(state.requestPlan (state.auto state.best state.lastShot~=key state.failedKey~=key))( state.job state.job.kind~=)
state.requestPlan=;state.showBest=
(,c,key)
job=state.job
job
job.key~=key job.client~=c state.job=;
job.sliceEnd=.()+(job.kind== state.calcSpeed )
ok,result=.(job.work)
ok (result);
done=.(job.work)== result==
done
state.job=
result== result=job.kind== job.best
job.kind==
state.best=result;state.showBest=result~=;state.planFinished=.();state.previewKey=
result result.key=key
result (c,result)
state.failedKey = key
status.Text=
status.Text=..result.pots....math.(result.shot.power*)..
result
result.key=key;state.paths=result.paths;state.pathResult=result
job.kind==
status.Text=..job.checked....job.total..
state.auto (c,state.best) state.lastShot~=state.best.key state.execution (c,state.best)
state.execution ()
state.auto
state.job state.job.kind==
state.total== progress.Text=.(,state.fired)
progress.Text=.(,state.tested,state.total,state.fired)
state.lastShot==key progress.Text=..state.fired
progress.Text=..state.tested....state.total....state.fired
progress.Text=.(,state.tested,state.total,state.fired)
state.lines state.job
shot
state.showBest state.best shot=state.best.shot
direction=c.Input.Direction
direction direction.Magnitude<
power=c.Input.Power
shot={direction=direction.Unit,power=power>=C.Input.MinimumPower power ,spin=c.Hud.Spin .zero,call=c.Call}
previewKey=.(,shot.direction.X,shot.direction.Y,shot.power,shot.spin.X,shot.spin.Y,(state.showBest))
state.previewKey~=previewKey state.previewKey=previewKey;(,c,key,shot)
state.showBest status.Text=..math.(shot.power*)..
connections[#connections+]=Run.Heartbeat:((dt)
state.closed
ok,err=(,dt); ok (err)
)
connections[#connections+]=Run.RenderStepped:((dt)
state.closed
state.drawAge+=dt; state.drawAge</ ;state.drawAge=
ok,err=(draw); ok ();status.Text=..string.((err),,)
)
{Unload=unload,State=state,Plan=()state.requestPlan= }- Autoplay - Long Lines autoplay does cool trick shots and stuff too pretty fun to watch

[UPD] 8 Ball Duels